[ADD] website_sale_aplicoop: restore the test coverage lost with the dead code
The dead-code cleanup dropped 11 test files that were never wired into tests/__init__.py. Reviewing what they covered turned up real holes, so the worthwhile ones come back, rewritten against the current schema. Blacklists were the serious gap: product, supplier and category exclusions have absolute priority over product discovery and nothing exercised them. The old file had two separate defects. Its supplier fixtures wrote `main_seller_id` directly, but product_main_seller computes that field from `variant_seller_ids`, so the compute reset it to False and the blacklist had nothing to exclude; they now create real supplierinfo records. Worse, four whole classes asserted against `group_order.product_ids` -- the m2m *input* -- instead of the discovery result, so they set `category_ids` and then checked a field they never touched. Those go through `_get_products_for_group_order` now, and three tests that had no assertions at all got some. The remaining three failures were test bugs too, all Odoo 17->18 leftovers: * Date cases assumed `pickup_date` derives from `start_date`. The chain is cutoff -> pickup -> delivery, and a recurring order whose start date has passed rolls forward to the current cycle, so a 2024 order has no 2024 pickup. The new file anchors on future dates and finds the next 29 February dynamically, with a class documenting the roll-forward itself. * `/eskaera/labels` is `type="json"`; the old test hit it with a plain GET and read the resulting 400 as a bug. It is called over JSON-RPC now, and a test pins the 400 so nobody repeats it. Also `uom.uom.categ` -> `uom.category`. * `price_include` is computed in 18.0, so fixtures must set `price_include_override`. On top of that `_get_price` filters taxes by company and defaults to `env.company`, not the fixture's, which left the tax list empty -- `tax_included` was False for the wrong reason. Two of the portal tests were passing for the wrong reason as well: the access guard bounced them to /eskaera, which also answers 200. Membership has to be set from the member side with `is_group`, and a new test checks the final URL rather than the status alone. Each fixture that can silently build the wrong thing now carries a guard test. Left out on purpose: three files were unimplemented placeholders, and test_draft_persistence still deserves recovering (see the notes file). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
3eb79e2431
commit
3e4bd5e5db
6 changed files with 1817 additions and 0 deletions
207
website_sale_aplicoop/tests/test_portal_routes.py
Normal file
207
website_sale_aplicoop/tests/test_portal_routes.py
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
# Copyright 2025 Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
"""Smoke tests for the Eskaera pages as seen by a plain portal user.
|
||||
|
||||
Covers that the main pages answer 200 and that reading a product's UoM for
|
||||
display does not raise an AccessError for a portal user.
|
||||
|
||||
`/eskaera/labels` and `/eskaera/i18n` are `type="json"` routes: a bare GET is
|
||||
answered with 400 by design, so they are exercised through a JSON-RPC call.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import HttpCase
|
||||
|
||||
|
||||
class PortalRoutesCommon:
|
||||
"""Build a portal user that belongs to an open group order."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Portal Routes Group",
|
||||
"is_company": True,
|
||||
"is_group": True,
|
||||
"email": "routes-group@test.com",
|
||||
}
|
||||
)
|
||||
# The shop guard reads `partner_id.group_ids`, so the membership has
|
||||
# to be set from the member side to be visible right away.
|
||||
self.member_partner = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Routes Member",
|
||||
"email": "routes-member@test.com",
|
||||
"group_ids": [(6, 0, [self.group.id])],
|
||||
}
|
||||
)
|
||||
|
||||
# HttpCase.authenticate() wants the password, so reuse the login.
|
||||
self.portal_login = "portal.routes@test.com"
|
||||
self.portal_user = self.env["res.users"].create(
|
||||
{
|
||||
"name": "Portal Routes User",
|
||||
"login": self.portal_login,
|
||||
"password": self.portal_login,
|
||||
"partner_id": self.member_partner.id,
|
||||
"groups_id": [(4, self.env.ref("base.group_portal").id)],
|
||||
}
|
||||
)
|
||||
|
||||
start_date = datetime.now().date()
|
||||
self.group_order = self.env["group.order"].create(
|
||||
{
|
||||
"name": "Routes Test Order",
|
||||
"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",
|
||||
}
|
||||
)
|
||||
self.group_order.action_open()
|
||||
|
||||
def _login_portal(self):
|
||||
self.authenticate(self.portal_login, self.portal_login)
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestPortalGetRoutes(PortalRoutesCommon, HttpCase):
|
||||
"""The main GET pages answer 200 for a portal user."""
|
||||
|
||||
def test_portal_get_routes_return_200(self):
|
||||
"""Every public Eskaera page renders for a portal user."""
|
||||
self._login_portal()
|
||||
|
||||
routes = [
|
||||
"/eskaera",
|
||||
f"/eskaera/{self.group_order.id}",
|
||||
f"/eskaera/{self.group_order.id}/checkout",
|
||||
f"/eskaera/{self.group_order.id}/load-page?page=1",
|
||||
]
|
||||
|
||||
for route in routes:
|
||||
response = self.url_open(route, allow_redirects=True)
|
||||
self.assertEqual(
|
||||
response.status_code, 200, msg=f"Route {route} returned an error"
|
||||
)
|
||||
|
||||
def test_shop_page_is_not_bounced_to_the_list(self):
|
||||
"""A member reaches the shop itself, not the "/eskaera" fallback.
|
||||
|
||||
The access guard redirects non-members to the list page, which also
|
||||
answers 200 -- so a plain status check would pass even when the member
|
||||
never got in.
|
||||
"""
|
||||
self._login_portal()
|
||||
|
||||
response = self.url_open(
|
||||
f"/eskaera/{self.group_order.id}", allow_redirects=True
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue(
|
||||
response.url.endswith(f"/eskaera/{self.group_order.slug}"),
|
||||
msg=f"Bounced to {response.url} instead of the shop page",
|
||||
)
|
||||
|
||||
def test_slug_urls_answer_for_portal_user(self):
|
||||
"""The canonical slug URLs answer too, not just the numeric ones."""
|
||||
self._login_portal()
|
||||
|
||||
for suffix in ("", "/checkout"):
|
||||
route = f"/eskaera/{self.group_order.slug}{suffix}"
|
||||
response = self.url_open(route, allow_redirects=True)
|
||||
self.assertEqual(
|
||||
response.status_code, 200, msg=f"Route {route} returned an error"
|
||||
)
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestPortalLabelsEndpoint(PortalRoutesCommon, HttpCase):
|
||||
"""`/eskaera/labels` is a JSON-RPC endpoint, not a plain GET page."""
|
||||
|
||||
def test_labels_endpoint_returns_translations(self):
|
||||
"""A JSON-RPC call returns the label dictionary."""
|
||||
self._login_portal()
|
||||
|
||||
labels = self.make_jsonrpc_request("/eskaera/labels")
|
||||
|
||||
self.assertIsInstance(labels, dict)
|
||||
# A few keys the checkout summary relies on.
|
||||
for key in ("product", "quantity", "price", "subtotal", "total"):
|
||||
self.assertIn(key, labels)
|
||||
|
||||
def test_i18n_alias_returns_the_same_payload(self):
|
||||
"""`/eskaera/i18n` is an alias of `/eskaera/labels`."""
|
||||
self._login_portal()
|
||||
|
||||
labels = self.make_jsonrpc_request("/eskaera/labels")
|
||||
alias = self.make_jsonrpc_request("/eskaera/i18n")
|
||||
|
||||
self.assertEqual(labels, alias)
|
||||
|
||||
def test_labels_endpoint_is_public(self):
|
||||
"""The endpoint answers without logging in (auth="public")."""
|
||||
labels = self.make_jsonrpc_request("/eskaera/labels")
|
||||
|
||||
self.assertIsInstance(labels, dict)
|
||||
self.assertTrue(labels)
|
||||
|
||||
def test_plain_get_is_rejected(self):
|
||||
"""A bare GET is not a valid call for a JSON route.
|
||||
|
||||
Guards the mistake this test file used to make: asserting 200 on a
|
||||
plain GET against `type="json"`, which Odoo answers with 400.
|
||||
"""
|
||||
self._login_portal()
|
||||
|
||||
response = self.url_open("/eskaera/labels", allow_redirects=True)
|
||||
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestPortalProductUoMAccess(PortalRoutesCommon, HttpCase):
|
||||
"""Rendering the shop must not need UoM read rights beyond the portal's."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
uom_category = self.env["uom.category"].create({"name": "Test UoM Cat"})
|
||||
self.uom = self.env["uom.uom"].create(
|
||||
{
|
||||
"name": "Test UoM",
|
||||
"uom_type": "reference",
|
||||
"factor": 1.0,
|
||||
"category_id": uom_category.id,
|
||||
}
|
||||
)
|
||||
self.product = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Portal UoM Product",
|
||||
"type": "consu",
|
||||
"list_price": 10.0,
|
||||
"is_published": True,
|
||||
"sale_ok": True,
|
||||
"uom_id": self.uom.id,
|
||||
"uom_po_id": self.uom.id,
|
||||
}
|
||||
)
|
||||
self.group_order.product_ids = [(4, self.product.id)]
|
||||
|
||||
def test_portal_user_can_view_shop_with_uom(self):
|
||||
"""The shop page renders for a portal user with a custom UoM."""
|
||||
self._login_portal()
|
||||
|
||||
response = self.url_open(
|
||||
f"/eskaera/{self.group_order.id}", allow_redirects=True
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Portal UoM Product", response.text)
|
||||
Loading…
Add table
Add a link
Reference in a new issue