"""Payments retention (PR #2754) — "12 месяцев" text sync guard. WHY: mera-public/content.ts declares itself as the ONE place product promises live (docstring at the top of that file: "ни одного утверждения, которого не делает код"). The public retention promise ("оплаченный отчёт хранится N месяцев") has THREE places it could quietly drift: the backend setting (`settings.trade_in_paid_retention_days`), the frontend constant (`PAID_REPORT_RETENTION_MONTHS` in content.ts), and any page that renders it (today: privacy/page.tsx). Deep-review finding 2026-08-06 MEDIUM on PR #2754 caught exactly this: a comment claimed the number "reads from the same setting" while the page actually hardcoded a `12 месяцев` literal -- a comment cannot fail CI, same lesson as test_consent_text_frontend_sync.py's _CONSENT_TEXT_SNAPSHOT guard (which this file mirrors). WHAT: 1. privacy/page.tsx imports PAID_REPORT_RETENTION_MONTHS from content.ts and does NOT hardcode a "N месяцев" literal of its own. 2. The frontend months constant and the backend days setting stay within a sane calendar tolerance of each other (28-31 days per month) -- this does NOT enforce byte-identity (days and months are different units by design, see content.ts docstring), only that nobody silently changes one without the other drifting out of "still honestly ~12 months". """ from __future__ import annotations import os import re from pathlib import Path os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test") _FRONTEND_CONTENT = ( Path(__file__).resolve().parents[2] / "frontend" / "src" / "app" / "mera-public" / "content.ts" ) _FRONTEND_PRIVACY_PAGE = ( Path(__file__).resolve().parents[2] / "frontend" / "src" / "app" / "mera-public" / "privacy" / "page.tsx" ) _MONTHS_CONST_RE = re.compile(r"PAID_REPORT_RETENTION_MONTHS\s*=\s*(\d+)\s*;") _LITERAL_MONTHS_RE = re.compile(r'"?\d+ месяцев"?') def _extract_months_constant(content_ts_source: str) -> int: match = _MONTHS_CONST_RE.search(content_ts_source) assert match is not None, ( "PAID_REPORT_RETENTION_MONTHS not found in content.ts -- " "constant renamed/removed without updating this test" ) return int(match.group(1)) def test_frontend_files_exist() -> None: assert _FRONTEND_CONTENT.is_file(), f"missing frontend file: {_FRONTEND_CONTENT}" assert _FRONTEND_PRIVACY_PAGE.is_file(), f"missing frontend file: {_FRONTEND_PRIVACY_PAGE}" def test_privacy_page_imports_retention_constant_not_hardcoded() -> None: """The whole point: FAILS if privacy/page.tsx stops importing the shared constant and goes back to a hardcoded '12 месяцев' literal (exactly the drift the deep-review finding caught -- comment said 'reads from content.ts', code said otherwise).""" src = _FRONTEND_PRIVACY_PAGE.read_text(encoding="utf-8") assert "PAID_REPORT_RETENTION_MONTHS" in src, ( "privacy/page.tsx no longer references PAID_REPORT_RETENTION_MONTHS -- " "the retention promise must be rendered from the shared content.ts " "constant, not written out again by hand" ) assert re.search(r'from\s+"\.\./content"', src), ( "privacy/page.tsx must import from '../content' (content.ts), where " "PAID_REPORT_RETENTION_MONTHS is declared" ) literal_hits = _LITERAL_MONTHS_RE.findall(src) assert not literal_hits, ( "privacy/page.tsx contains a hardcoded 'N месяцев' literal -- render the " "PAID_REPORT_RETENTION_MONTHS constant instead: " f"{literal_hits!r}" ) def test_backend_days_setting_matches_frontend_months_within_calendar_tolerance() -> None: """Not byte-identity (days vs months are different units, deliberately -- see content.ts docstring on PAID_REPORT_RETENTION_MONTHS): just a sanity bound that `trade_in_paid_retention_days` still honestly rounds to the number of months the public page promises (28-31 days/month, generous).""" from app.core.config import settings days = settings.trade_in_paid_retention_days months = _extract_months_constant(_FRONTEND_CONTENT.read_text(encoding="utf-8")) assert 28 * months <= days <= 31 * months, ( f"settings.trade_in_paid_retention_days={days} no longer honestly rounds to " f"content.ts PAID_REPORT_RETENTION_MONTHS={months} -- update both together " "(and the offer text, when it exists) so the public promise stays true" )