Skip to content

Commit f8016f1

Browse files
yasinBursaliclaude
andcommitted
test(dashboard-api): pytest coverage for __DREAM_RESULT__ sentinel emission
Locks in the streaming contract that the SetupWizard frontend parser consumes. Five tests against /api/setup/test: - Script exits 0 → final stream line is "__DREAM_RESULT__:PASS:0". - Script exits 3 → final stream line is "__DREAM_RESULT__:FAIL:3" (the literal subprocess returncode, not a coerced 1). - Script missing on both INSTALL_DIR/scripts and cwd → error_stream() fallback still emits a structurally valid sentinel. - Sentinel matches the exact regex the frontend pins (^__DREAM_RESULT__:(PASS|FAIL):(-?\d+)$) — guards against stray whitespace, prefix drift, or trailing-character regressions on either side of the contract. - Unauthenticated POST returns 401 before any stream is opened. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent dffda00 commit f8016f1

1 file changed

Lines changed: 148 additions & 0 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
"""Tests for the __DREAM_RESULT__ sentinel emission on /api/setup/test.
2+
3+
The frontend SetupWizard parser treats the sentinel as the source of truth
4+
for diagnostic success/failure. Absence falls back to scraping log lines
5+
for "All tests passed!", which is fragile, so the contract is that every
6+
terminal state of the streaming endpoint MUST yield exactly one sentinel
7+
line as its last line.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import os
13+
import stat
14+
from pathlib import Path
15+
16+
import pytest
17+
18+
19+
SENTINEL_PREFIX = "__DREAM_RESULT__:"
20+
21+
22+
def _last_sentinel_line(lines):
23+
"""Return the parsed (status, rc) tuple for the last sentinel found.
24+
25+
Yields a tuple even when several sentinel lines exist in the stream
26+
so we can assert the final terminator wins. ``rc`` is returned as a
27+
string to keep the parse symmetric with the wire format.
28+
"""
29+
sentinel = None
30+
for line in lines:
31+
if line.startswith(SENTINEL_PREFIX):
32+
sentinel = line
33+
assert sentinel is not None, f"no __DREAM_RESULT__ line found in stream: {lines!r}"
34+
payload = sentinel[len(SENTINEL_PREFIX):]
35+
status, _, rc = payload.partition(":")
36+
return status, rc
37+
38+
39+
def _write_test_script(install_root: Path, body: str) -> Path:
40+
"""Write a bash script to install_root/scripts/dream-test-functional.sh."""
41+
scripts = install_root / "scripts"
42+
scripts.mkdir(parents=True, exist_ok=True)
43+
path = scripts / "dream-test-functional.sh"
44+
path.write_text(body, encoding="utf-8")
45+
path.chmod(path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
46+
return path
47+
48+
49+
@pytest.fixture()
50+
def setup_install_dir(tmp_path, monkeypatch):
51+
"""Provide an isolated INSTALL_DIR for the setup router.
52+
53+
The setup endpoint resolves the diagnostic script via
54+
``Path(INSTALL_DIR) / "scripts" / "dream-test-functional.sh"``; if it's
55+
not there it falls back to ``Path(os.getcwd()) / "dream-test-functional.sh"``.
56+
We point INSTALL_DIR at a tmp dir and chdir to another tmp dir so the
57+
cwd fallback is also empty unless the test opts in.
58+
"""
59+
install_root = tmp_path / "dream-server"
60+
install_root.mkdir()
61+
cwd_root = tmp_path / "cwd"
62+
cwd_root.mkdir()
63+
64+
monkeypatch.setattr("routers.setup.INSTALL_DIR", str(install_root))
65+
monkeypatch.chdir(cwd_root)
66+
return install_root
67+
68+
69+
def test_setup_test_emits_pass_sentinel_on_success(test_client, setup_install_dir):
70+
"""A diagnostic script that exits 0 must terminate with PASS:0."""
71+
_write_test_script(setup_install_dir, "#!/bin/bash\necho 'check 1 ok'\necho 'check 2 ok'\nexit 0\n")
72+
73+
with test_client.stream("POST", "/api/setup/test", headers=test_client.auth_headers) as response:
74+
assert response.status_code == 200
75+
lines = [line for line in response.iter_lines() if line]
76+
77+
status, rc = _last_sentinel_line(lines)
78+
assert status == "PASS"
79+
assert rc == "0"
80+
# Sentinel must be the LAST non-empty line on the wire.
81+
assert lines[-1].startswith(SENTINEL_PREFIX), (
82+
f"sentinel was emitted but not last; tail was: {lines[-3:]!r}"
83+
)
84+
85+
86+
def test_setup_test_emits_fail_sentinel_with_returncode_on_failure(test_client, setup_install_dir):
87+
"""A diagnostic script that exits non-zero must terminate with FAIL:<rc>."""
88+
_write_test_script(setup_install_dir, "#!/bin/bash\necho 'check failed'\nexit 3\n")
89+
90+
with test_client.stream("POST", "/api/setup/test", headers=test_client.auth_headers) as response:
91+
assert response.status_code == 200
92+
lines = [line for line in response.iter_lines() if line]
93+
94+
status, rc = _last_sentinel_line(lines)
95+
assert status == "FAIL"
96+
assert rc == "3", f"expected the script's literal exit code, got {rc!r}"
97+
assert lines[-1].startswith(SENTINEL_PREFIX)
98+
99+
100+
def test_setup_test_emits_sentinel_when_script_missing(test_client, setup_install_dir):
101+
"""When neither INSTALL_DIR/scripts nor cwd has the script, the
102+
error_stream() fallback runs aiohttp probes against configured
103+
services and still terminates with a sentinel."""
104+
# No script written — both lookup paths miss; error_stream() runs.
105+
# In the test environment the dashboard SERVICES map is populated but
106+
# nothing is actually listening, so every probe will fail and the
107+
# sentinel should be FAIL:1.
108+
109+
with test_client.stream("POST", "/api/setup/test", headers=test_client.auth_headers) as response:
110+
assert response.status_code == 200
111+
lines = [line for line in response.iter_lines() if line]
112+
113+
status, rc = _last_sentinel_line(lines)
114+
assert status in {"PASS", "FAIL"}, f"unknown sentinel status {status!r}"
115+
# The FAIL path is the realistic outcome — services aren't running in
116+
# the test harness — but assert only the structural contract here so
117+
# this test stays robust against fixture changes.
118+
assert rc.lstrip("-").isdigit(), f"sentinel rc must be numeric, got {rc!r}"
119+
assert lines[-1].startswith(SENTINEL_PREFIX)
120+
121+
122+
def test_setup_test_sentinel_format_is_machine_parseable(test_client, setup_install_dir):
123+
"""The on-the-wire format must match the regex the SetupWizard frontend
124+
pins: ``^__DREAM_RESULT__:(PASS|FAIL):(-?\\d+)$``. This test guards
125+
against accidental whitespace, prefix, or trailing-character drift on
126+
either side of the contract."""
127+
import re
128+
129+
_write_test_script(setup_install_dir, "#!/bin/bash\nexit 0\n")
130+
sentinel_re = re.compile(r"^__DREAM_RESULT__:(PASS|FAIL):(-?\d+)$")
131+
132+
with test_client.stream("POST", "/api/setup/test", headers=test_client.auth_headers) as response:
133+
assert response.status_code == 200
134+
lines = [line for line in response.iter_lines() if line]
135+
136+
sentinel_lines = [line for line in lines if line.startswith(SENTINEL_PREFIX)]
137+
assert len(sentinel_lines) == 1, (
138+
f"expected exactly one sentinel line, got {len(sentinel_lines)}: {sentinel_lines!r}"
139+
)
140+
assert sentinel_re.match(sentinel_lines[0]), (
141+
f"sentinel does not match frontend parser regex: {sentinel_lines[0]!r}"
142+
)
143+
144+
145+
def test_setup_test_requires_auth(test_client):
146+
"""Unauthenticated POST must fail before anything is streamed."""
147+
response = test_client.post("/api/setup/test")
148+
assert response.status_code == 401

0 commit comments

Comments
 (0)