STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_summary.py
Go to the documentation of this file.
1"""Unit tests for the pre-index run summary, and the contract that opening a run is cheap.
2
3The point of this module is that selecting a run must not trigger a parquet export, so
4the tests assert the absence of work as much as the presence of numbers.
5
6Run: cd webviewer && .venv/bin/python -m pytest backend/test_summary.py -q
7"""
8
9import os
10
11import pytest
12from fastapi.testclient import TestClient
13
14from . import config, export_cache, summary
15from .main import app
16
17
18@pytest.fixture
19def run_dir(tmp_path, monkeypatch):
20 """A fake run laid out the way elodin-db lays one out: a numeric directory per
21 component, each holding data/index/metadata/schema, plus non-component siblings."""
22 monkeypatch.setattr(config, "ELODIN_DIR", tmp_path)
23 monkeypatch.setattr(config, "CACHE_DIR", tmp_path / "_cache")
24 run = tmp_path / "daq_20260811_120000"
25 run.mkdir()
26 for i, age in enumerate([30.0, 20.0, 0.0]): # newest last: a 30 s span
27 d = run / str(1000 + i)
28 d.mkdir()
29 (d / "data").write_bytes(b"x")
30 os.utime(d / "data", (1_000_000 - age, 1_000_000 - age))
31 (run / "db_state").write_bytes(b"") # not a component
32 (run / "msgs").mkdir() # not a component either
33 return run
34
35
37 s = summary.summarize("daq_20260811_120000")
38 assert s["n_components"] == 3 # db_state and msgs are not components
39
40
42 s = summary.summarize("daq_20260811_120000")
43 assert s["duration_s"] == pytest.approx(30.0)
44 # Never presented as exact: it is when data was written, not when it was sampled.
45 assert s["duration_approx"] is True
46
47
49 s = summary.summarize("daq_20260811_120000")
50 assert s["size_bytes"] and s["size_bytes"] > 0
51 assert s["cached"] is False
52
53
54def test_summarize_does_not_export(run_dir, monkeypatch):
55 # The whole point: opening a run costs a few stats, not a multi-second export.
56 monkeypatch.setattr(
57 export_cache, "ensure_exported",
58 lambda *a, **k: pytest.fail("summarize() must never trigger an export"),
59 )
60 summary.summarize("daq_20260811_120000")
61 assert not (config.CACHE_DIR / "daq_20260811_120000").exists()
62
63
65 # Lets the client skip asking for a config that a pre-snapshot run never had, rather
66 # than taking a 404 on every selection.
67 assert summary.summarize("daq_20260811_120000")["has_config"] is False
68 (run_dir.parent / "daq_20260811_120000.toml").write_text("[network]\n")
69 assert summary.summarize("daq_20260811_120000")["has_config"] is True
70
71
73 monkeypatch.setattr(config, "ELODIN_DIR", tmp_path)
74 monkeypatch.setattr(config, "CACHE_DIR", tmp_path / "_cache")
75 (tmp_path / "daq_20260811_120000").mkdir()
76 s = summary.summarize("daq_20260811_120000")
77 assert s["n_components"] == 0 and s["duration_s"] is None
78
79
80# ── the API contract ─────────────────────────────────────────────────────────
81
82
84 """GET /components used to export on demand, which made merely clicking a run in the
85 list cost tens of seconds. It now refuses; POST /index is the way to pay that."""
86 monkeypatch.setattr(
87 export_cache, "ensure_exported",
88 lambda *a, **k: pytest.fail("GET /components must not trigger an export"),
89 )
90 r = TestClient(app).get("/api/runs/daq_20260811_120000/components")
91 assert r.status_code == 409
92 assert "not indexed" in r.json()["detail"]
93
94
96 client = TestClient(app)
97 # RUN_RE is the path-traversal guard on every {run_id} route.
98 assert client.get("/api/runs/not-a-run/summary").status_code in (400, 404)
99 assert client.get("/api/runs/daq_19990101_000000/summary").status_code == 404
test_reports_component_count_ignoring_non_component_entries(run_dir)
test_reports_size_and_cached_state(run_dir)
test_summarize_does_not_export(run_dir, monkeypatch)
test_components_refuses_an_unindexed_run_rather_than_exporting(run_dir, monkeypatch)
test_duration_is_the_write_span_and_is_flagged_approximate(run_dir)
run_dir(tmp_path, monkeypatch)
test_summary_route_rejects_a_bad_run_id_and_a_missing_run(run_dir)
test_reports_whether_a_config_snapshot_exists(run_dir)
test_a_run_with_no_components_still_summarizes(tmp_path, monkeypatch)