#!/usr/bin/env bats # Seam: shared/homepage/services.json — the service config the checker reads # and the pages render. It must be a JSON list; every entry carries name, # category, section, and a host:port probe. category is user or admin; # section must be one the page's SECTION_ORDER renders. method, when # present, is tcp or http. wip, when set, is "open" or "closed". load helpers @test "services.json: valid schema" { python3 - "$REPO_ROOT/shared/homepage/services.json" <<'PY' import json, sys path = sys.argv[1] try: data = json.load(open(path)) except FileNotFoundError: sys.exit("services.json not found") assert isinstance(data, list), "top level must be a list" assert data, "the list must not be empty" for svc in data: for field in ("name", "category", "section", "probe"): assert field in svc, f"{svc.get('name', '?')} is missing {field}" assert svc["category"] in ("user", "admin"), svc["category"] if "method" in svc: assert svc["method"] in ("tcp", "http"), svc["method"] wip = svc.get("wip") assert wip is False or wip in ("open", "closed"), f"{svc['name']} wip must be false, 'open' or 'closed'" host, _, port = svc["probe"].rpartition(":") assert host and port.isdigit(), f"bad probe {svc['probe']}" PY } @test "services.json: every section is one the page renders" { # A service whose section is not in app.js's SECTION_ORDER for its # category is silently dropped from the page. Pin the contract. python3 - "$REPO_ROOT/shared/homepage/services.json" <<'PY' import json, sys data = json.load(open(sys.argv[1])) allowed = { "user": {"AI", "Web Apps", "Media Library Apps"}, "admin": {"Media Library Apps", "Observability", "AI", "Infrastructure"}, } for svc in data: assert svc.get("section") in allowed[svc["category"]], ( f"{svc['name']} has unrenderable section {svc.get('section')!r} for {svc['category']}") PY } @test "services.json: every user service has an Open link, status-only backends do not" { python3 - "$REPO_ROOT/shared/homepage/services.json" <<'PY' import json, sys data = json.load(open(sys.argv[1])) for svc in data: show = svc.get("showLink", True) if show: assert svc.get("url"), f"{svc['name']} shows a link but has no url" else: assert not svc.get("url"), f"{svc['name']} is status-only but has a url" PY } @test "the LLM status cards track the fleet, not retired service names" { # This drifted twice: the card probing Dankserver's :7770 was still named # frank-small long after frank-medium took that endpoint, and the endpoint # itself has since been frank-medium (radiance), frank-primary (lucebox) # and frank-primary (llama.cpp). The probe target is what matters; the # name has to follow it. python3 - "$REPO_ROOT/shared/homepage/services.json" <<'PY' import json, sys services = json.load(open(sys.argv[1])) by_probe = {s["probe"]: s["name"] for s in services} primary = by_probe.get("192.168.0.235:7770") assert primary == "frank-primary", f"Dankserver :7770 card is named {primary!r}" # Dankclaw serves two endpoints on two ports: frank-large (unsloth llama.cpp # on the iGPU, 7770) and frank-small (llama.cpp on the RTX 3090, 7771). assert by_probe.get("127.0.0.1:7770") == "frank-large", by_probe.get("127.0.0.1:7770") assert by_probe.get("127.0.0.1:7771") == "frank-small", by_probe.get("127.0.0.1:7771") PY }