diff --git a/docker/scripts/odoo_website_bootstrap.py b/docker/scripts/odoo_website_bootstrap.py index 74d0c1a..409968c 100644 --- a/docker/scripts/odoo_website_bootstrap.py +++ b/docker/scripts/odoo_website_bootstrap.py @@ -196,6 +196,29 @@ def _canonical_host(canonical_url: str) -> str: return urlparse(canonical_url).netloc or canonical_url +def _canonical_domain_matches(actual_value: object, canonical_url: str) -> bool: + actual_domain = str(actual_value or "").strip().rstrip("/") + return bool(actual_domain) and actual_domain in _canonical_domain_candidates(canonical_url) + + +def _canonical_domain_candidates(canonical_url: str) -> tuple[str, ...]: + canonical_domain = _canonical_host(canonical_url).strip().rstrip("/") + canonical_value = canonical_url.strip() + canonical_value_without_slash = canonical_value.rstrip("/") + return tuple(value for value in dict.fromkeys((canonical_domain, canonical_value_without_slash, canonical_value)) if value) + + +def _assert_canonical_domain(record: Any, field_name: str, canonical_url: str) -> None: + if field_name not in record._fields: + raise RuntimeError(f"Website bootstrap cannot verify canonical domain; missing field: {field_name}.") + actual_value = _field_value(record, field_name) + if not _canonical_domain_matches(actual_value, canonical_url): + raise RuntimeError( + "Website bootstrap failed to persist canonical domain: " + f"expected {_canonical_host(canonical_url)!r} or {canonical_url!r}, got {actual_value!r}." + ) + + def _marker_bool(value: bool) -> str: return "true" if value else "false" @@ -227,7 +250,7 @@ def _print_bootstrap_readback( print(f"website_bootstrap_website_id={getattr(website, 'id', '')}") print(f"website_bootstrap_domain_set={_marker_bool(bool(website_domain))}") - print(f"website_bootstrap_domain_matches_canonical={_marker_bool(bool(canonical_host) and website_domain == canonical_host)}") + print(f"website_bootstrap_domain_matches_canonical={_marker_bool(_canonical_domain_matches(website_domain, canonical_url))}") print(f"website_bootstrap_web_base_url_matches={_marker_bool(not canonical_url or web_base_url_matches)}") print(f"website_bootstrap_homepage_url_set={_marker_bool(bool(actual_homepage_url))}") print(f"website_bootstrap_homepage_url_matches={_marker_bool(bool(homepage_url) and actual_homepage_url == homepage_url)}") @@ -260,7 +283,7 @@ def _select_website( return page_website.sudo() canonical_host = _canonical_host(canonical_url) if canonical_host and "domain" in website_model._fields: - website = website_model.search([("domain", "in", (canonical_host, canonical_url))], order="id", limit=1) + website = website_model.search([("domain", "in", _canonical_domain_candidates(canonical_url))], order="id", limit=1) if website: return website if default_website: @@ -275,7 +298,7 @@ def _clear_duplicate_canonical_domains(website_model: Any, *, website: Any, cano duplicates = website_model.search( [ ("id", "!=", website.id), - ("domain", "in", (canonical_host, canonical_url)), + ("domain", "in", _canonical_domain_candidates(canonical_url)), ], order="id", ) @@ -471,7 +494,7 @@ def apply_website_bootstrap(env: Any, parsed_payload: dict[str, object] | None) if website_name: _assert_field_value(website, "name", website_name, label="website name") if canonical_url: - _assert_field_value(website, "domain", _canonical_host(canonical_url), label="canonical domain") + _assert_canonical_domain(website, "domain", canonical_url) if logo_expected: _assert_binary_field_value(website, "logo", logo_value, label="website logo") diff --git a/tests/test_odoo_website_bootstrap.py b/tests/test_odoo_website_bootstrap.py index 88d15e8..5aae695 100644 --- a/tests/test_odoo_website_bootstrap.py +++ b/tests/test_odoo_website_bootstrap.py @@ -41,6 +41,7 @@ def __init__( self.truthy = truthy self.persist_writes = True self.ignored_write_fields: set[str] = set() + self.normalized_write_values: dict[str, object] = {} for field_name in self._fields: setattr(self, field_name, None) for field_name, value in (values or {}).items(): @@ -63,6 +64,7 @@ def write(self, values: dict[str, object]) -> None: for field_name, value in values.items(): if field_name in self.ignored_write_fields: continue + value = self.normalized_write_values.get(field_name, value) setattr(self, field_name, value) @@ -241,6 +243,62 @@ def test_controller_homepage_route_persists_homepage_url_and_clears_stale_page_h self.assertEqual(env.config_parameter.values["web.base.url.freeze"], "True") self.assertIn({"name": "OPW", "domain": "opw-testing.example.com"}, env.website.writes) + def test_accepts_odoo_normalized_full_url_canonical_domain(self) -> None: + env = FakeEnv() + env.website.normalized_write_values["domain"] = "https://opw-testing.example.com" + payload = { + "website_bootstrap": { + "name": "OPW", + "canonical_url": "https://opw-testing.example.com", + } + } + output = io.StringIO() + + with redirect_stdout(output): + website_bootstrap.apply_website_bootstrap(env, payload) + + self.assertIn({"name": "OPW", "domain": "opw-testing.example.com"}, env.website.writes) + self.assertEqual(env.website.domain, "https://opw-testing.example.com") + self.assertIn("website_bootstrap_domain_matches_canonical=true", output.getvalue()) + self.assertIn("website_bootstrap_applied=true", output.getvalue()) + + def test_selects_odoo_normalized_full_url_domain_when_canonical_has_trailing_slash(self) -> None: + env = FakeEnv() + existing_website = FakeRecord( + record_id=2, + fields=("name", "domain", "homepage_id", "homepage_url", "logo"), + values={"domain": "https://opw-testing.example.com/"}, + ) + env.website_model = FakeModel(record=existing_website, fields=("name", "domain", "homepage_id", "homepage_url", "logo")) + payload = { + "website_bootstrap": { + "name": "OPW", + "canonical_url": "https://opw-testing.example.com/", + } + } + + website_bootstrap.apply_website_bootstrap(env, payload) + + self.assertEqual(existing_website.name, "OPW") + self.assertIn({"name": "OPW", "domain": "opw-testing.example.com"}, existing_website.writes) + self.assertEqual( + env.website_model.searches[0][0], + ( + [ + ( + "domain", + "in", + ( + "opw-testing.example.com", + "https://opw-testing.example.com", + "https://opw-testing.example.com/", + ), + ) + ], + ), + ) + self.assertEqual(env.website_model.searches[0][1], {"order": "id", "limit": 1}) + def test_config_parameter_web_base_url_supplies_canonical_when_bootstrap_payload_omits_it(self) -> None: env = FakeEnv() payload = {