Files
UmbraAudio/tests/test_audio_controller_connect.py
B.D. Murphy 9dfa7915ee Probe suite: T06/T07 now test, T08 names its real blocker
The dry run passes since routes_compute.py:1792 was aligned with read_bundle
(separators + ensure_ascii=False), so three tests had to catch up. All three
carried narratives written when the bundle was rejected, and each kept asserting
a reason that had stopped being true — a BLOCKED test is a finding, so a stale
BLOCKED reason is a finding about the wrong world.

T06 re-runs the dry run and compares responses, proving validation is
deterministic. It says plainly that re-import suppressing stored rows is NOT
covered: that path needs rows that persist, and the import still builds
InMemoryAudioImportStore.

T07 reconciles the controller's counts against the bytes sent. 8884 records in,
8883 accounted for, plus the book record it does not count — so a controller
that silently dropped a record kind now fails here instead of passing quietly.

T08 keeps BLOCKED, but for the reason that is actually true: no Postgres
AudioImportStore adapter exists, so even a successful commit would return a
uuid4 project_id while translation_utterances stayed empty. Its text names
InMemoryAudioImportStore on purpose, so wiring an adapter fails a test and
forces the verdict to be re-decided.

Suite: 9 PASS / 0 FAIL / 1 BLOCKED (T08). Tests 30 -> 40.
2026-10-16 00:27:24 -07:00

677 lines
25 KiB
Python

"""Tests for the controller connectivity probe.
These exist because three consecutive runs of this tool reported the ingest
endpoint wrongly, each time with confidence and each time in the same
direction — describing the day the check was written rather than the state it
was reading. None of the faults were subtle:
1. Six guessed paths were probed while the controller's route table was one SSH
command away, and 404 on a guess was read as absence.
2. Probing used GET only. The route is POST-only, so it answered GET with 404
and looked missing for a whole session.
3. After the route came up and answered 401, that was read as a dead end rather
than as a live route refusing an unauthenticated request.
Each is pinned here. The fake client scripts exact status codes so the
assertions are about the *interpretation* rule — which is where the faults
were — and not about a live controller.
"""
from __future__ import annotations
import importlib.util
import json
import sys
from pathlib import Path
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
import pytest
REPO = Path(__file__).resolve().parent.parent
TOOL_PATH = REPO / "scripts" / "audio_controller_connect.py"
NODE_IMPORT = "/api/node/audio/import"
def _load_tool() -> Any:
spec = importlib.util.spec_from_file_location("audio_controller_connect", TOOL_PATH)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
tool = _load_tool()
class FakeClient:
"""Stands in for the HTTP client, returning scripted status codes.
Keyed by (method, path). Any path not scripted answers 404, which is what a
real controller does for an unrouted path, and is the case that must be
distinguished from every routed one.
"""
def __init__(self, scripted: dict[tuple[str, str], int],
bodies: dict[tuple[str, str], Any] | None = None) -> None:
self.scripted = scripted
self.bodies = bodies or {}
self.calls: list[tuple[str, str, dict[str, str]]] = []
def request(self, method: str, path: str, *, body: Any = None,
headers: dict[str, str] | None = None,
phase: str = "probe") -> tuple[int, Any, int]:
self.calls.append((method, path, dict(headers or {})))
status = self.scripted.get((method, path), 404)
return status, self.bodies.get((method, path)), 1.0
# ---------------------------------------------------------------------------
# Finding an endpoint
# ---------------------------------------------------------------------------
def test_everything_404_is_reported_as_unconfirmed_not_missing() -> None:
"""Bug 1: a guessed path that 404s is evidence about the guess."""
result = tool.t04_ingest_exists(FakeClient({}))
assert result.status == "FAIL"
assert result.evidence["finding"] == "UNCONFIRMED_INGEST_ENDPOINT"
assert "route table" in result.detail
assert "guessed" in result.detail
def test_get_only_404_with_post_401_is_routed() -> None:
"""Bug 2: a POST-only route answers GET with 404. That is not absence."""
result = tool.t04_ingest_exists(FakeClient({
("GET", NODE_IMPORT): 404,
("POST", NODE_IMPORT): 401,
}))
assert result.status == "PASS"
assert result.evidence["routed"] == f"POST {NODE_IMPORT}"
assert result.evidence["auth_gated"] is True
assert result.evidence["reachable"] is False
def test_405_counts_as_routed() -> None:
"""The original signal: path in the table, wrong method.
405 is routing, not authentication — it says the path matched a route
registered for another method, so it must not be reported as an auth gate.
"""
result = tool.t04_ingest_exists(FakeClient({
("GET", NODE_IMPORT): 404,
("POST", NODE_IMPORT): 405,
}))
assert result.status == "PASS"
assert result.evidence["routed"] == f"POST {NODE_IMPORT}"
assert result.evidence["auth_gated"] is False
assert result.evidence["handler_error"] is False
assert result.evidence["reachable"] is False
assert "another method" in result.detail
def test_post_401_on_a_node_path_is_not_a_dead_end() -> None:
"""Bug 3: require_node runs inside the route, so 401 proves it resolved."""
result = tool.t04_ingest_exists(FakeClient({("POST", NODE_IMPORT): 401}))
assert result.status == "PASS"
assert result.evidence["auth_gated"] is True
assert "credential doing its job" in result.detail
assert "not the path going missing" in result.detail
def test_csrf_403_on_a_non_node_path_is_not_routed() -> None:
"""A 403 from CSRF middleware runs before routing and proves nothing.
The controller checks CSRF in middleware (app.py:246-248) and exempts only
``/api/node`` (app.py:89). So POST /api/compute/audio/import answers 403
whether or not a route exists — there is in fact no such route. Counting it
would report a live ingest path that does not exist.
"""
client = FakeClient({
("POST", "/api/compute/audio/import"): 403,
("GET", "/api/compute/audio/import"): 404,
("GET", NODE_IMPORT): 404,
("POST", NODE_IMPORT): 404,
})
result = tool.t04_ingest_exists(client)
assert result.status == "FAIL", "a pre-routing 403 must not count as routed"
assert result.evidence["finding"] == "UNCONFIRMED_INGEST_ENDPOINT"
# The discounted probe must survive into the report, otherwise the next
# reader sees only 404s and re-litigates the same false positive.
masked = result.evidence["csrf_masked_probe"]
assert "middleware that runs before routing" in masked
assert "/api/compute/audio/import" in masked
per_path = result.evidence["probed"]["/api/compute/audio/import"]
assert "csrf_token_missing" in per_path["POST_note"]
assert per_path["POST"] == 403
assert per_path["GET"] == 404
def test_403_on_a_node_path_is_routed_because_that_prefix_is_csrf_exempt() -> None:
result = tool.t04_ingest_exists(FakeClient({("POST", NODE_IMPORT): 403}))
assert result.status == "PASS"
assert result.evidence["auth_gated"] is True
def test_handler_error_is_distinguished_from_absent() -> None:
result = tool.t04_ingest_exists(FakeClient({("POST", NODE_IMPORT): 500}))
assert result.status == "PASS"
assert result.evidence["handler_error"] is True
assert result.evidence["auth_gated"] is False
assert "handler is reachable" in result.detail
def test_credential_is_forwarded_to_the_probe() -> None:
"""Probing without the credential made a live route look permanently gated."""
client = FakeClient({("POST", NODE_IMPORT): 401})
tool.t04_ingest_exists(client, {"Authorization": "Bearer <key>"})
assert client.calls, "no request was made"
assert all(h.get("Authorization") for _, _, h in client.calls)
def test_probe_without_credential_omits_headers_cleanly() -> None:
client = FakeClient({("POST", NODE_IMPORT): 401})
result = tool.t04_ingest_exists(client)
assert result.status == "PASS"
assert client.calls[0][2] == {}
def test_first_routed_path_in_candidate_order_wins() -> None:
"""The real route is first in INGEST_CANDIDATES and must be the one named."""
client = FakeClient({
("POST", NODE_IMPORT): 401,
("POST", "/api/compute/audio/import"): 403,
("GET", "/api/compute/audio/import"): 404,
})
result = tool.t04_ingest_exists(client)
assert result.evidence["routed"] == f"POST {NODE_IMPORT}"
# ---------------------------------------------------------------------------
# Readiness verdicts
# ---------------------------------------------------------------------------
def _readiness(evidence: dict[str, Any] | None, **kwargs: Any) -> tuple[str, list[str]]:
"""Build a routed result from a bare evidence dict and ask for a verdict."""
routed = None
if evidence is not None:
routed = tool.Result("T04", "Ingest endpoint exists", "PASS", "detail", evidence)
kwargs.setdefault("missing_ingest", evidence is None)
return tool._readiness(routed, **kwargs)
def test_readiness_names_unconfirmed_path_without_claiming_absence() -> None:
verdict, rationale = _readiness(None)
assert verdict == "INGEST_PATH_NOT_CONFIRMED"
assert any("UNCONFIRMED" in line for line in rationale)
def test_readiness_auth_required_is_separate_from_needs_restart() -> None:
verdict, rationale = _readiness({
"routed": f"POST {NODE_IMPORT}", "status": 401, "reachable": False,
"auth_gated": True, "handler_error": False,
})
assert verdict == "INGEST_ROUTED_AUTH_REQUIRED"
assert any("401/403" in line for line in rationale)
def test_readiness_handler_error_is_not_a_restart_request() -> None:
"""After the restart happened, the verdict must stop asking for one."""
verdict, _ = _readiness({
"routed": f"POST {NODE_IMPORT}", "status": 500, "reachable": False,
"auth_gated": False, "handler_error": True,
})
assert verdict == "INGEST_ROUTED_HANDLER_ERROR"
assert "RESTART" not in verdict
def test_readiness_ready_only_when_serving() -> None:
verdict, _ = _readiness({
"routed": f"POST {NODE_IMPORT}", "status": 200, "reachable": True,
"auth_gated": False, "handler_error": False,
})
assert verdict == "READY_FOR_PUSH"
def test_readiness_routed_but_not_serving_is_named() -> None:
verdict, _ = _readiness({
"routed": f"POST {NODE_IMPORT}", "status": 405, "reachable": False,
"auth_gated": False, "handler_error": False,
})
assert verdict == "INGEST_ROUTED_NOT_SERVING"
def test_readiness_422_means_parsing_not_broken() -> None:
"""A 422 against an empty probe body is the schema working, not a fault."""
verdict, rationale = _readiness({
"routed": f"POST {NODE_IMPORT}", "status": 422, "reachable": False,
"auth_gated": False, "handler_error": False, "schema_rejected": True,
})
assert verdict == "INGEST_ROUTED_AWAITING_BUNDLE"
assert any("parsing" in line for line in rationale)
def test_a_real_dry_run_failure_outranks_transport_observations() -> None:
"""Once T05 has run, connectivity is settled and the data is the story."""
t05 = tool.Result("T05", "Dry-run validation", "FAIL",
"dry run rejected (500); content_hash mismatch", {})
verdict, rationale = _readiness(
{"routed": f"POST {NODE_IMPORT}", "status": 422, "reachable": False,
"auth_gated": False, "handler_error": False, "schema_rejected": True},
missing_ingest=False, t05=t05)
assert verdict == "DRY_RUN_REJECTED"
assert any("content_hash mismatch" in line for line in rationale)
assert any("Nothing was pushed" in line for line in rationale)
def test_a_passing_dry_run_still_requires_confirmation() -> None:
t05 = tool.Result("T05", "Dry-run validation", "PASS",
"controller accepted the bundle", {"status": "dry_run"})
verdict, rationale = _readiness(
{"routed": f"POST {NODE_IMPORT}", "status": 200, "reachable": True,
"auth_gated": False, "handler_error": False},
missing_ingest=False, t05=t05)
assert verdict == "DRY_RUN_OK_CONFIRMATION_REQUIRED"
assert any("not an import" in line for line in rationale)
assert any("Nothing has been pushed" in line for line in rationale)
def test_dry_run_blocked_does_not_override_transport_state() -> None:
"""A bundle that was never supplied must not be reported as a rejection."""
t05 = tool.Result("T05", "Dry-run validation", "BLOCKED",
"no --bundle given", {"ran": False})
verdict, _ = _readiness(
{"routed": f"POST {NODE_IMPORT}", "status": 401, "reachable": False,
"auth_gated": True, "handler_error": False},
missing_ingest=False, t05=t05)
assert verdict == "INGEST_ROUTED_AUTH_REQUIRED"
def test_no_verdict_still_claims_a_restart_after_the_restart_happened() -> None:
"""INGEST_ROUTED_NEEDS_RESTART survived the restart it was describing."""
seen = set()
for evidence in (
None,
{"routed": f"POST {NODE_IMPORT}", "status": 401, "reachable": False,
"auth_gated": True, "handler_error": False},
{"routed": f"POST {NODE_IMPORT}", "status": 500, "reachable": False,
"auth_gated": False, "handler_error": True},
{"routed": f"POST {NODE_IMPORT}", "status": 405, "reachable": False,
"auth_gated": False, "handler_error": False},
{"routed": f"POST {NODE_IMPORT}", "status": 200, "reachable": True,
"auth_gated": False, "handler_error": False},
):
verdict, _ = _readiness(evidence)
seen.add(verdict)
assert "INGEST_ROUTED_NEEDS_RESTART" not in seen
def test_every_readiness_branch_carries_the_standing_facts() -> None:
"""The bundle story and the sequenced tests are true in every state."""
for evidence in (
None,
{"routed": f"POST {NODE_IMPORT}", "status": 401, "reachable": False,
"auth_gated": True, "handler_error": False},
{"routed": f"POST {NODE_IMPORT}", "status": 500, "reachable": False,
"auth_gated": False, "handler_error": True},
{"routed": f"POST {NODE_IMPORT}", "status": 200, "reachable": True,
"auth_gated": False, "handler_error": False},
):
_, rationale = _readiness(evidence)
assert any("metadata bundle" in line for line in rationale)
assert any("T05-T08 are sequenced" in line for line in rationale)
def test_every_non_ready_verdict_says_nothing_was_pushed() -> None:
"""Only READY_FOR_PUSH may omit it; every other state must state it plainly."""
non_ready = (
None,
{"routed": f"POST {NODE_IMPORT}", "status": 401, "reachable": False,
"auth_gated": True, "handler_error": False},
{"routed": f"POST {NODE_IMPORT}", "status": 500, "reachable": False,
"auth_gated": False, "handler_error": True},
{"routed": f"POST {NODE_IMPORT}", "status": 405, "reachable": False,
"auth_gated": False, "handler_error": False},
)
for evidence in non_ready:
verdict, rationale = _readiness(evidence)
assert verdict != "READY_FOR_PUSH"
assert any("pushed" in line for line in rationale), (
f"{verdict} does not say whether anything was pushed"
)
verdict, rationale = _readiness({
"routed": f"POST {NODE_IMPORT}", "status": 200, "reachable": True,
"auth_gated": False, "handler_error": False,
})
assert verdict == "READY_FOR_PUSH"
assert not any("Nothing was pushed" in line for line in rationale)
# ---------------------------------------------------------------------------
# Credential handling
# ---------------------------------------------------------------------------
def test_node_key_is_sent_as_bearer_not_x_node_key(monkeypatch: pytest.MonkeyPatch,
tmp_path: Path) -> None:
"""require_node parses only 'bearer ' (routes_compute.py:477).
Sending X-Node-Key meant a perfectly valid key came back rejected.
"""
monkeypatch.setenv("UMBRA_AUDIO_NODE_KEY", "<secret>")
monkeypatch.delenv("UMBRA_AUDIO_API_TOKEN", raising=False)
log = tool.SyncLog(tmp_path / "log.jsonl")
client = tool.Client.__new__(tool.Client)
headers, how = client.authenticate(log)
assert how == "node_bearer"
assert "X-Node-Key" not in headers
assert headers["Authorization"] == "Bearer <secret>"
def test_service_token_falls_back_when_no_node_key(monkeypatch: pytest.MonkeyPatch,
tmp_path: Path) -> None:
monkeypatch.delenv("UMBRA_AUDIO_NODE_KEY", raising=False)
monkeypatch.setenv("UMBRA_AUDIO_API_TOKEN", "<token>")
log = tool.SyncLog(tmp_path / "log.jsonl")
client = tool.Client.__new__(tool.Client)
headers, how = client.authenticate(log)
assert how == "service_token_bearer"
assert headers["Authorization"] == "Bearer <token>"
def test_no_credential_configured_returns_empty_headers(monkeypatch: pytest.MonkeyPatch,
tmp_path: Path) -> None:
monkeypatch.delenv("UMBRA_AUDIO_NODE_KEY", raising=False)
monkeypatch.delenv("UMBRA_AUDIO_API_TOKEN", raising=False)
log = tool.SyncLog(tmp_path / "log.jsonl")
client = tool.Client.__new__(tool.Client)
headers, how = client.authenticate(log)
assert headers == {}
assert how == "none"
def test_auth_log_records_a_mechanism_but_never_the_key(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
"""The key goes in an env var and out in a header, never into a log line."""
secret = "YMcrD3QlREDACTEDTESTKEY"
monkeypatch.setenv("UMBRA_AUDIO_NODE_KEY", secret)
monkeypatch.delenv("UMBRA_AUDIO_API_TOKEN", raising=False)
path = tmp_path / "log.jsonl"
log = tool.SyncLog(path)
client = tool.Client.__new__(tool.Client)
client.authenticate(log)
log.write("discovery", "example", endpoint="/api/node/agent")
text = path.read_text(encoding="utf-8")
assert secret not in text
entries = [json.loads(line) for line in text.splitlines() if line.strip()]
assert any(e.get("action") == "try_node_key" for e in entries)
# ---------------------------------------------------------------------------
# Dry-run safety
# ---------------------------------------------------------------------------
def test_dry_run_is_blocked_without_a_bundle(tmp_path: Path) -> None:
result = tool.t05_dry_run(FakeClient({}), None, {})
assert result.status == "BLOCKED"
assert result.evidence["ran"] is False
def test_dry_run_is_blocked_on_an_incomplete_bundle(tmp_path: Path) -> None:
(tmp_path / "bundle.json").write_text("{}", encoding="utf-8")
result = tool.t05_dry_run(FakeClient({}), str(tmp_path), {})
assert result.status == "BLOCKED"
assert "manifest.jsonl" in result.detail
def test_dry_run_never_claims_to_have_written(tmp_path: Path) -> None:
"""Whatever happens, a dry run must not report a write."""
(tmp_path / "bundle.json").write_text(
json.dumps({"bundle_format_version": 1,
"content_hash": {"value": "0" * 64}}), encoding="utf-8")
(tmp_path / "manifest.jsonl").write_text(
json.dumps({"id": "b-1", "kind": "book"}) + "\n", encoding="utf-8")
for status in (200, 400, 422, 500):
client = FakeClient({("POST", NODE_IMPORT): status},
bodies={("POST", NODE_IMPORT): {"status": "dry_run"}})
result = tool.t05_dry_run(client, str(tmp_path), {})
assert result.evidence["dry_run"] is True
assert result.evidence["wrote"] is False
assert result.evidence["confirm"] is None
assert result.evidence["records_sent"] == 1
def test_dry_run_passes_only_on_a_2xx(tmp_path: Path) -> None:
(tmp_path / "bundle.json").write_text(
json.dumps({"bundle_format_version": 1,
"content_hash": {"value": "0" * 64}}), encoding="utf-8")
(tmp_path / "manifest.jsonl").write_text(
json.dumps({"id": "b-1", "kind": "book"}) + "\n", encoding="utf-8")
passed = tool.t05_dry_run(
FakeClient({("POST", NODE_IMPORT): 200},
bodies={("POST", NODE_IMPORT): {"status": "dry_run",
"content_hash": "a" * 64}}),
str(tmp_path), {})
failed = tool.t05_dry_run(FakeClient({("POST", NODE_IMPORT): 500}), str(tmp_path), {})
assert passed.status == "PASS"
assert passed.evidence["content_hash"] == "a" * 64
assert failed.status == "FAIL"
assert failed.evidence["status_code"] == 500
# ---------------------------------------------------------------------------
# What T06/T07/T08 claim, once a dry run can actually pass
# ---------------------------------------------------------------------------
#
# These tests exist because all three carried narratives written for the era
# when the bundle was rejected, and each kept asserting a reason that had
# stopped being true: T06 said "T05 does not get [a content_hash] while the
# bundle is rejected", T07 said the controller "has not yet issued a manifest
# format", T08 said there was no ingest endpoint. Every one of those was once
# accurate, and every one survived its own refutation. A BLOCKED test is a
# finding, so a stale BLOCKED reason is a finding about the wrong world.
def _write_bundle(tmp_path: Path, records: int = 1) -> None:
(tmp_path / "bundle.json").write_text(
json.dumps({"bundle_format_version": 1,
"content_hash": {"value": "0" * 64}}), encoding="utf-8")
(tmp_path / "manifest.jsonl").write_text(
"".join(json.dumps({"id": f"b-{i}", "kind": "book"}) + "\n"
for i in range(records)), encoding="utf-8")
def _passing_t05(digest: str = "d" * 64,
counts: dict[str, int] | None = None) -> Any:
return tool.Result(
"T05", "Dry-run validation", "PASS", "accepted",
{"status_code": 200, "reported_status": "dry_run",
"content_hash": "0" * 64, "response_digest": digest,
"counts": counts if counts is not None
else {"chapters": 1, "speakers": 0, "utterances": 0, "artifacts": 0}})
def test_t06_does_not_run_a_canned_reason_when_t05_failed(tmp_path: Path) -> None:
"""The old bug: T06 narrated why it could not run, whatever T05 said."""
_write_bundle(tmp_path)
rejected = tool.Result("T05", "Dry-run validation", "FAIL", "500", {})
result = tool.t06_idempotency(FakeClient({}), str(tmp_path), {}, rejected)
assert result.status == "BLOCKED"
assert result.evidence.get("ran") is False
def test_t06_blocked_without_a_bundle_or_a_first_response() -> None:
assert tool.t06_idempotency(FakeClient({}), None, {}, None).evidence["ran"] is False
assert tool.t06_idempotency(
FakeClient({}), None, {}, _passing_t05()).evidence["ran"] is False
def test_t06_passes_when_two_dry_runs_agree(tmp_path: Path) -> None:
_write_bundle(tmp_path)
body = {"status": "dry_run", "content_hash": "0" * 64,
"counts": {"chapters": 1}}
first = tool.t05_dry_run(
FakeClient({("POST", NODE_IMPORT): 200},
bodies={("POST", NODE_IMPORT): body}), str(tmp_path), {})
second = tool.t06_idempotency(
FakeClient({("POST", NODE_IMPORT): 200},
bodies={("POST", NODE_IMPORT): body}),
str(tmp_path), {}, first)
assert first.status == "PASS"
assert second.status == "PASS"
assert second.evidence["first_digest"] == second.evidence["second_digest"]
def test_t06_fails_when_two_identical_dry_runs_disagree(tmp_path: Path) -> None:
"""Non-determinism means the plan is not a function of the bundle."""
_write_bundle(tmp_path)
first = _passing_t05(digest="d" * 64)
drifted = tool.t06_idempotency(
FakeClient({("POST", NODE_IMPORT): 200},
bodies={("POST", NODE_IMPORT): {"status": "dry_run",
"content_hash": "1" * 64}}),
str(tmp_path), {}, first)
assert drifted.status == "FAIL"
def test_t06_names_what_it_cannot_prove(tmp_path: Path) -> None:
"""Passing on determinism must not read as proof of re-import safety."""
_write_bundle(tmp_path)
body = {"status": "dry_run", "content_hash": "0" * 64}
first = tool.t05_dry_run(
FakeClient({("POST", NODE_IMPORT): 200},
bodies={("POST", NODE_IMPORT): body}), str(tmp_path), {})
result = tool.t06_idempotency(
FakeClient({("POST", NODE_IMPORT): 200},
bodies={("POST", NODE_IMPORT): body}),
str(tmp_path), {}, first)
assert result.status == "PASS"
assert "no_change" not in result.detail.split(".")[0]
assert "NOT covered" in result.detail
assert "in-memory store" in result.detail
def test_t07_reconciles_counts_and_allows_for_the_uncounted_book_record() -> None:
"""32+55+4319+4477 = 8883, and the 8884th record is the book."""
result = tool.t07_schema_contract(
_passing_t05(counts={"chapters": 32, "speakers": 55,
"utterances": 4319, "artifacts": 4477}),
8884)
assert result.status == "PASS"
assert result.evidence["delta"] == 1
def test_t07_fails_when_the_controller_drops_records(tmp_path: Path) -> None:
"""A controller that silently ignores a record kind would otherwise pass."""
result = tool.t07_schema_contract(
_passing_t05(counts={"chapters": 1, "speakers": 0,
"utterances": 0, "artifacts": 0}),
5)
assert result.status == "FAIL"
assert result.evidence["delta"] == 4
def test_t07_blocked_until_a_bundle_is_accepted() -> None:
result = tool.t07_schema_contract(
tool.Result("T05", "Dry-run validation", "FAIL", "500", {}), 1)
assert result.status == "BLOCKED"
def test_t08_no_longer_blames_an_endpoint_that_routs() -> None:
"""The specific stale claim T08 used to make."""
result = tool.t08_audit_round_trip(FakeClient({}))
assert result.status == "BLOCKED"
assert "without an ingest endpoint" not in result.detail
assert "depends on T04" not in result.detail
def test_t08_names_the_actual_blocker_so_a_fix_has_to_update_it() -> None:
"""Wiring a persistence adapter must force this text to change.
If someone implements a Postgres AudioImportStore, this test fails and
makes them re-decide T08 rather than leave a blocked verdict describing a
store that no longer exists.
"""
result = tool.t08_audit_round_trip(FakeClient({}))
assert "InMemoryAudioImportStore" in result.detail
assert "uuid4" in result.detail