"""Tests for the scoreboard schema + CSV snapshot (no live Mongo needed).""" import csv from pathlib import Path from harness import scoreboard from harness.scoreboard import ScoreRow REPO_ROOT = Path(__file__).resolve().parent.parent def test_score_row_field_order(): names = ScoreRow.field_names() assert names[:9] == [ "video_id", "upload_date", "variant_id", "genome_hash", "parent_genome", "APV", "VSA", "fitness", "channel_status", ] # context fields exist and come after the objective for ctx in ["views", "likes", "comments", "shares", "avg_view_duration_sec", "subscribers_gained"]: assert ctx in names def test_committed_metrics_csv_header_matches_schema(): with (REPO_ROOT / "metrics.csv").open(newline="", encoding="utf-8") as fh: header = next(csv.reader(fh)) assert header == ScoreRow.field_names() def test_from_doc_ignores_unknown_keys(): row = ScoreRow.from_doc({ "video_id": "v", "upload_date": "2026-06-10", "variant_id": "A", "genome_hash": "h", "parent_genome": "seed", "APV": 50.0, "VSA": 0.5, "fitness": 5.0, "_id": "should-be-ignored", "junk": 123, }) assert row.video_id == "v" and row.fitness == 5.0 def test_score_row_holds_no_secret_fields(): # Defense-in-depth: the schema must not contain anything that could carry a credential. forbidden = {"token", "secret", "password", "api_key", "mongo_url", "credentials", "oauth"} for name in ScoreRow.field_names(): assert not any(bad in name.lower() for bad in forbidden) def test_snapshot_to_csv(monkeypatch, tmp_path): rows = [ ScoreRow("b", "2026-06-11", "A", "h", "seed", 50, 0.5, 5.0), ScoreRow("a", "2026-06-10", "A", "h", "seed", 80, 0.5, 6.8, views=10, likes=2), ] monkeypatch.setattr(scoreboard, "read_all", lambda readonly=True: rows) out = tmp_path / "snap.csv" n = scoreboard.snapshot_to_csv(out, readonly=True) assert n == 2 with out.open(newline="", encoding="utf-8") as fh: reader = list(csv.DictReader(fh)) # sorted by (upload_date, variant_id) → 'a' (06-10) before 'b' (06-11) assert [r["video_id"] for r in reader] == ["a", "b"] assert reader[0]["likes"] == "2"