diff --git a/frontend/src/utils/downloadStatus.js b/frontend/src/utils/downloadStatus.js index 62821f5..f622790 100644 --- a/frontend/src/utils/downloadStatus.js +++ b/frontend/src/utils/downloadStatus.js @@ -1,7 +1,8 @@ // Single source of truth for the download-event status enum -> display // metadata (label/color/icon). Mirrors the backend ENUM // (pending|running|ok|error|skipped). Used by the stat chips, the filter -// dropdown, and the event rows so they never drift apart. +// dropdown, and the event rows so they never drift apart. The value set is +// pinned against backend _ALLOWED_STATUSES by tests/test_fe_be_contract.py. export const DOWNLOAD_STATUSES = [ { value: 'pending', label: 'Queued', color: 'grey', icon: 'mdi-clock-outline' }, diff --git a/frontend/src/utils/platformColor.js b/frontend/src/utils/platformColor.js index dab041e..654619d 100644 --- a/frontend/src/utils/platformColor.js +++ b/frontend/src/utils/platformColor.js @@ -1,7 +1,9 @@ // Single source of truth for platform → color + icon mapping. Used by // PlatformChip and any other GS-style platform-tagged surface. The six // platforms FC supports map 1:1 to the GS palette; unknown platforms fall -// back to grey + mdi-web. Operator-confirmed scope 2026-05-27. +// back to grey + mdi-web. Operator-confirmed scope 2026-05-27. The ICONS key +// set is pinned against backend known_platform_keys() by +// tests/test_fe_be_contract.py. const ICONS = { patreon: 'mdi-patreon', diff --git a/tests/test_fe_be_contract.py b/tests/test_fe_be_contract.py new file mode 100644 index 0000000..6fe9fda --- /dev/null +++ b/tests/test_fe_be_contract.py @@ -0,0 +1,44 @@ +"""FE/BE enum contract — guards against the field/enum drift class. + +FabledCurator's frontend is plain JS (no TS codegen), so the frontend +re-declares a few backend enum value-sets by hand: + + - download-event statuses → frontend/src/utils/downloadStatus.js + - platform keys → frontend/src/utils/platformColor.js + +If either side edits its list without the other, the UI silently +mislabels rows or drops a platform (the `last_verified_at`-style drift). +These tests parse the JS mirrors and assert they equal the backend's +canonical definitions, so any divergence fails CI on whichever side moved. +The backend definitions are the source of truth; update the JS to match. +""" + +import re +from pathlib import Path + +from backend.app.api.downloads import _ALLOWED_STATUSES +from backend.app.services.platforms import known_platform_keys + +_FRONTEND = Path(__file__).resolve().parents[1] / "frontend" / "src" / "utils" + + +def _read(name: str) -> str: + return (_FRONTEND / name).read_text(encoding="utf-8") + + +def test_download_status_values_match_backend() -> None: + # downloadStatus.js: each entry is `{ value: 'pending', ... }` + fe_values = set(re.findall(r"value:\s*'([a-z]+)'", _read("downloadStatus.js"))) + assert fe_values == set(_ALLOWED_STATUSES), ( + "downloadStatus.js DOWNLOAD_STATUSES is out of sync with backend " + "downloads._ALLOWED_STATUSES" + ) + + +def test_platform_keys_match_backend() -> None: + # platformColor.js ICONS: each entry is `patreon: 'mdi-patreon',` + fe_keys = set(re.findall(r"^\s*(\w+):\s*'mdi-", _read("platformColor.js"), re.M)) + assert fe_keys == set(known_platform_keys()), ( + "platformColor.js ICONS is out of sync with backend " + "platforms.known_platform_keys()" + )