feat(I6): FE/BE enum contract test — pin JS mirrors to backend canon
The plain-JS frontend re-declares two backend enum value-sets by hand: download-event statuses (downloadStatus.js) and platform keys (platformColor.js). With no TS codegen, drift is silent — the same class as the last_verified_at field mismatch. tests/test_fe_be_contract.py parses both JS mirrors and asserts they equal the backend canon (downloads._ALLOWED_STATUSES, platforms.known_platform_keys()) so a change on either side fails CI on whichever moved. Backend is the source of truth. Documented the invariant in both JS file headers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
// Single source of truth for the download-event status enum -> display
|
// Single source of truth for the download-event status enum -> display
|
||||||
// metadata (label/color/icon). Mirrors the backend ENUM
|
// metadata (label/color/icon). Mirrors the backend ENUM
|
||||||
// (pending|running|ok|error|skipped). Used by the stat chips, the filter
|
// (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 = [
|
export const DOWNLOAD_STATUSES = [
|
||||||
{ value: 'pending', label: 'Queued', color: 'grey', icon: 'mdi-clock-outline' },
|
{ value: 'pending', label: 'Queued', color: 'grey', icon: 'mdi-clock-outline' },
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
// Single source of truth for platform → color + icon mapping. Used by
|
// Single source of truth for platform → color + icon mapping. Used by
|
||||||
// PlatformChip and any other GS-style platform-tagged surface. The six
|
// PlatformChip and any other GS-style platform-tagged surface. The six
|
||||||
// platforms FC supports map 1:1 to the GS palette; unknown platforms fall
|
// 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 = {
|
const ICONS = {
|
||||||
patreon: 'mdi-patreon',
|
patreon: 'mdi-patreon',
|
||||||
|
|||||||
@@ -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()"
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user