From 453da11aa923a4aaeaf0a2bdb90051e848b847b0 Mon Sep 17 00:00:00 2001 From: Chris Busillo Date: Sun, 10 May 2026 12:54:10 -0400 Subject: [PATCH] Persist controller route homepage bootstrap --- docker/scripts/odoo_website_bootstrap.py | 21 +++- tests/test_odoo_website_bootstrap.py | 122 +++++++++++++++++++++++ 2 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 tests/test_odoo_website_bootstrap.py diff --git a/docker/scripts/odoo_website_bootstrap.py b/docker/scripts/odoo_website_bootstrap.py index e93e9d3..156b4b9 100644 --- a/docker/scripts/odoo_website_bootstrap.py +++ b/docker/scripts/odoo_website_bootstrap.py @@ -38,6 +38,17 @@ def _write_existing_fields(record: Any, values: dict[str, object]) -> None: record.sudo().write(filtered_values) +def _homepage_values(website: Any, *, homepage_url: str, homepage_page: Any | None) -> dict[str, object]: + values: dict[str, object] = {} + if homepage_page and "homepage_id" in website._fields: + values["homepage_id"] = homepage_page.id + if homepage_url and "homepage_url" in website._fields: + values["homepage_url"] = homepage_url + if homepage_url and homepage_page is None and "homepage_id" in website._fields: + values["homepage_id"] = False + return values + + def _module_is_installed(env: Any, module_name: object) -> bool: normalized_module_name = str(module_name or "").strip() if not normalized_module_name: @@ -156,21 +167,23 @@ def apply_website_bootstrap(env: Any, parsed_payload: dict[str, object] | None) if "website_id" in homepage_page._fields: page_values["website_id"] = website.id _write_existing_fields(homepage_page, page_values) - _write_existing_fields(website, {"homepage_id": homepage_page.id}) elif primary_page_xmlid: raise RuntimeError(f"Website bootstrap primary page XML ID not found: {primary_page_xmlid}") + _write_existing_fields(website, _homepage_values(website, homepage_url=homepage_url, homepage_page=homepage_page)) raw_routes_source = website_payload.get("routes_source") routes_source = raw_routes_source if isinstance(raw_routes_source, dict) else {} fallback_module = str(routes_source.get("module") or "").strip() if homepage_url and not homepage_page: - _verify_route( + route_page = _verify_route( env, website, {"url": homepage_url, "module": fallback_module, "published": True}, fallback_module=fallback_module ) + _write_existing_fields(website, _homepage_values(website, homepage_url=homepage_url, homepage_page=route_page)) for route_payload in website_payload.get("routes") or []: if isinstance(route_payload, dict): route_page = _verify_route(env, website, route_payload, fallback_module=fallback_module) - if route_page and bool(route_payload.get("homepage")): - _write_existing_fields(website, {"homepage_id": route_page.id}) + if bool(route_payload.get("homepage")): + route_url = str(route_payload.get("url") or "").strip() + _write_existing_fields(website, _homepage_values(website, homepage_url=route_url, homepage_page=route_page)) print("website_bootstrap_applied=true") diff --git a/tests/test_odoo_website_bootstrap.py b/tests/test_odoo_website_bootstrap.py new file mode 100644 index 0000000..a2f3f6d --- /dev/null +++ b/tests/test_odoo_website_bootstrap.py @@ -0,0 +1,122 @@ +from __future__ import annotations + +import importlib.util +import sys +import types +import unittest +from pathlib import Path +from typing import Any + + +def _load_bootstrap_module() -> types.ModuleType: + module_path = Path(__file__).resolve().parents[1] / "docker" / "scripts" / "odoo_website_bootstrap.py" + spec = importlib.util.spec_from_file_location("odoo_devkit_website_bootstrap_test_module", module_path) + if spec is None or spec.loader is None: + raise RuntimeError(f"Unable to load module from {module_path}") + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +website_bootstrap = _load_bootstrap_module() + + +class FakeRecord: + def __init__(self, *, record_id: int = 1, fields: tuple[str, ...] = (), truthy: bool = True) -> None: + self.id = record_id + self._fields = set(fields) + self.writes: list[dict[str, object]] = [] + self.truthy = truthy + + def __bool__(self) -> bool: + return self.truthy + + def sudo(self) -> FakeRecord: + return self + + def write(self, values: dict[str, object]) -> None: + self.writes.append(values) + + +class FakeModel: + def __init__(self, *, record: FakeRecord | None = None, fields: tuple[str, ...] = ()) -> None: + self.record = record if record is not None else FakeRecord(truthy=False) + self._fields = set(fields) + + def sudo(self) -> FakeModel: + return self + + def search(self, *unused_args: object, **unused_kwargs: object) -> FakeRecord: + return self.record + + def create(self, values: dict[str, object]) -> FakeRecord: + self.record = FakeRecord(fields=tuple(self._fields)) + self.record.write(values) + return self.record + + +class FakeConfigParameter: + def __init__(self) -> None: + self.values: dict[str, str] = {} + + def sudo(self) -> FakeConfigParameter: + return self + + def set_param(self, key: str, value: str) -> None: + self.values[key] = value + + +class FakeEnv: + def __init__(self) -> None: + self.website = FakeRecord(fields=("name", "domain", "homepage_id", "homepage_url")) + self.config_parameter = FakeConfigParameter() + self.modules = FakeModel(record=FakeRecord(fields=(), truthy=True)) + self.pages = FakeModel(record=FakeRecord(fields=(), truthy=False)) + self.langs = FakeModel(record=FakeRecord(fields=(), truthy=False)) + self.registry = {"website": object()} + + def __getitem__(self, model_name: str) -> Any: + return { + "website": FakeModel(record=self.website, fields=("name", "domain", "homepage_id", "homepage_url")), + "ir.config_parameter": self.config_parameter, + "ir.module.module": self.modules, + "website.page": self.pages, + "res.lang": self.langs, + "ir.http": FakeModel(record=FakeRecord()), + }[model_name] + + @staticmethod + def ref(*unused_args: object, **unused_kwargs: object) -> None: + return None + + +class WebsiteBootstrapHelperTests(unittest.TestCase): + def test_controller_homepage_route_persists_homepage_url_and_clears_stale_page_homepage(self) -> None: + env = FakeEnv() + payload = { + "website_bootstrap": { + "name": "OPW", + "canonical_url": "https://opw-testing.example.com", + "homepage_url": "/shop", + "routes_source": {"module": "website_sale"}, + "routes": [ + { + "name": "Shop", + "url": "/shop", + "module": "website_sale", + "published": True, + "homepage": True, + } + ], + } + } + + website_bootstrap.apply_website_bootstrap(env, payload) + + self.assertIn({"homepage_url": "/shop", "homepage_id": False}, env.website.writes) + self.assertEqual(env.config_parameter.values["web.base.url"], "https://opw-testing.example.com") + + +if __name__ == "__main__": + unittest.main()