addons-cm/website_sale_aplicoop/tests/test_group_order_slug.py
GitHub Copilot 157b344d4b [FIX] website_sale_aplicoop: three defects in the group order slug
- create(): slugs were only checked against the database, so records created
  in the same batch (duplicating several orders from the list view, importing
  rows sharing a name) collided on group_order_slug_uniq. _generate_unique_slug
  now also skips the slugs already handed out in the batch.
- _redirect_to_slug_url(): `post` is passed as a dict instead of splatted, so a
  query parameter named `suffix` no longer binds to the keyword argument of the
  same name (HTTP 500 on /eskaera/<id>/checkout?suffix=x, and a corrupted path
  on the shop route).
- post-migrate: the backfill runs with tracking_disable, `slug` is tracked and
  the upgrade posted a chatter message on every pre-existing order.

Tests for the two reachable cases: batch create and query parameters kept
across the legacy redirect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 22:53:09 +02:00

193 lines
7.4 KiB
Python

# Copyright 2026 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from datetime import datetime
from datetime import timedelta
from odoo.exceptions import ValidationError
from odoo.tests import tagged
from odoo.tests.common import HttpCase
from odoo.tests.common import TransactionCase
class GroupOrderSlugCommon:
"""Helpers to build consumer group orders with the mandatory fields."""
def _group_order_vals(self, name, **values):
start_date = datetime.now().date()
vals = {
"name": name,
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
vals.update(values)
return vals
def _create_group_order(self, name, **values):
return self.env["group.order"].create(self._group_order_vals(name, **values))
class TestGroupOrderSlug(GroupOrderSlugCommon, TransactionCase):
"""URL slug generation, normalization and validation."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Slug Test Group",
"is_company": True,
"is_group": True,
"email": "slug-group@test.com",
}
)
def test_slug_generated_from_name(self):
order = self._create_group_order("Escola Fructuós Gelabert")
self.assertEqual(order.slug, "escola-fructuos-gelabert")
def test_slug_is_unique(self):
first = self._create_group_order("Weekly Order")
second = self._create_group_order("Weekly Order")
self.assertEqual(first.slug, "weekly-order")
self.assertEqual(second.slug, "weekly-order-2")
def test_slug_is_unique_within_a_single_create(self):
"""Records created in one batch cannot see each other in the database."""
orders = self.env["group.order"].create(
[self._group_order_vals("Weekly Order") for _ in range(3)]
)
self.assertEqual(
orders.mapped("slug"),
["weekly-order", "weekly-order-2", "weekly-order-3"],
)
def test_generated_slug_avoids_an_explicit_one_in_the_same_create(self):
orders = self.env["group.order"].create(
[
self._group_order_vals("Something Else", slug="weekly-order"),
self._group_order_vals("Weekly Order"),
]
)
self.assertEqual(orders.mapped("slug"), ["weekly-order", "weekly-order-2"])
def test_explicit_slug_is_normalized(self):
order = self._create_group_order("Weekly Order", slug="Fructuós Gelabert")
self.assertEqual(order.slug, "fructuos-gelabert")
def test_slug_kept_when_name_changes(self):
order = self._create_group_order("Weekly Order")
order.name = "Renamed Order"
self.assertEqual(order.slug, "weekly-order")
def test_emptying_slug_regenerates_it(self):
order = self._create_group_order("Weekly Order")
order.write({"name": "Renamed Order", "slug": False})
self.assertEqual(order.slug, "renamed-order")
def test_reserved_slug_is_avoided_on_generation(self):
order = self._create_group_order("Labels")
self.assertEqual(order.slug, "labels-eskaera")
def test_reserved_slug_is_rejected(self):
with self.assertRaises(ValidationError):
self._create_group_order("Weekly Order", slug="save-order")
def test_numeric_slug_is_rejected(self):
with self.assertRaises(ValidationError):
self._create_group_order("Weekly Order", slug="42")
def test_name_without_slug_characters_falls_back(self):
order = self._create_group_order("!!!")
self.assertEqual(order.slug, "eskaera")
@tagged("post_install", "-at_install")
class TestGroupOrderSlugRoutes(GroupOrderSlugCommon, HttpCase):
"""The shop pages answer on the slug URL, the numeric one redirects."""
def setUp(self):
super().setUp()
# is_group matters: both `res.partner.group_ids` and
# `group.order.group_ids` filter on it when they are read.
self.group = self.env["res.partner"].create(
{
"name": "Slug Routes Group",
"is_company": True,
"is_group": True,
"email": "slug-routes-group@test.com",
}
)
member = self.env["res.partner"].create(
{"name": "Slug Routes Member", "email": "slug-routes-member@test.com"}
)
self.group.member_ids = [(4, member.id)]
login = "portal.slug.routes@test.com"
self.env["res.users"].create(
{
"name": "Slug Routes User",
"login": login,
"password": login,
"partner_id": member.id,
"groups_id": [(4, self.env.ref("base.group_portal").id)],
}
)
self.portal_login = login
self.group_order = self._create_group_order("Slug Routes Order")
self.group_order.action_open()
# The HTTP requests below share this transaction but not the ORM cache:
# the many2many rows (group members, order groups) have to be in the
# database before the controller reads them.
self.env.flush_all()
def test_slug_urls_answer(self):
self.authenticate(self.portal_login, self.portal_login)
for url in (
f"/eskaera/{self.group_order.slug}",
f"/eskaera/{self.group_order.slug}/checkout",
):
response = self.url_open(url, allow_redirects=False)
self.assertEqual(response.status_code, 200, url)
def test_numeric_urls_redirect_to_slug(self):
self.authenticate(self.portal_login, self.portal_login)
for url, expected in (
(f"/eskaera/{self.group_order.id}", f"/eskaera/{self.group_order.slug}"),
(
f"/eskaera/{self.group_order.id}/checkout",
f"/eskaera/{self.group_order.slug}/checkout",
),
):
response = self.url_open(url, allow_redirects=False)
self.assertIn(response.status_code, (301, 302, 303), url)
self.assertTrue(
response.headers.get("Location", "").endswith(expected),
f"{url} should redirect to {expected}, "
f"got {response.headers.get('Location')}",
)
def test_numeric_url_keeps_query_parameters(self):
"""A `suffix` parameter must stay in the query string, not in the path."""
self.authenticate(self.portal_login, self.portal_login)
response = self.url_open(
f"/eskaera/{self.group_order.id}/checkout?suffix=x&search=apple",
allow_redirects=False,
)
self.assertIn(response.status_code, (301, 302, 303))
location = response.headers.get("Location", "")
self.assertIn(f"/eskaera/{self.group_order.slug}/checkout?", location)
self.assertIn("suffix=x", location)
self.assertIn("search=apple", location)
def test_unknown_slug_falls_back_to_the_list(self):
self.authenticate(self.portal_login, self.portal_login)
response = self.url_open("/eskaera/does-not-exist", allow_redirects=False)
self.assertIn(response.status_code, (301, 302, 303))
self.assertTrue(response.headers.get("Location", "").endswith("/eskaera"))