refactor: move strix/viewer under strix/interface

This commit is contained in:
Ahmed Allam
2026-07-26 13:11:14 -07:00
committed by Ahmed Allam
parent 8157ccba27
commit d1e8225d5f
88 changed files with 87 additions and 81 deletions
+1 -1
View File
@@ -4,13 +4,13 @@ from __future__ import annotations
from pygments.lexers import BashLexer, PythonLexer
from strix.interface.viewer.report_pdf import _strip_code_fence
from strix.report.writer import (
guess_language_name,
parse_fenced_code,
resolve_lexer,
safe_fence,
)
from strix.viewer.report_pdf import _strip_code_fence
def test_parse_fenced_code_extracts_language_and_body() -> None:
+2 -2
View File
@@ -10,7 +10,7 @@ import pytest
from pypdf import PdfReader
from pypdf.errors import WrongPasswordError
from strix.viewer.report_pdf import (
from strix.interface.viewer.report_pdf import (
build_encrypted_report,
encrypt_pdf,
generate_password,
@@ -91,7 +91,7 @@ def test_wrong_password_is_rejected(tmp_path: Path) -> None:
run_dir = _make_run(tmp_path)
encrypted = encrypt_pdf(generate_report_pdf(run_dir), "correct-horse-battery")
with pytest.raises(WrongPasswordError):
PdfReader(BytesIO(encrypted), password="not-the-password")
PdfReader(BytesIO(encrypted), password="not-the-password") # nosec B106
def test_build_encrypted_report(tmp_path: Path) -> None:
+36 -30
View File
@@ -1,4 +1,4 @@
"""Tests for the local run viewer (strix.viewer) and its path helpers."""
"""Tests for the local run viewer (strix.interface.viewer) and its path helpers."""
from __future__ import annotations
@@ -9,8 +9,8 @@ import urllib.request
from typing import TYPE_CHECKING
from strix.core.paths import latest_run_dir, runs_base_dir
from strix.viewer.server import serve
from strix.viewer.transcript import (
from strix.interface.viewer.server import serve
from strix.interface.viewer.transcript import (
build_run_state,
read_report_markdown,
read_run_summary,
@@ -89,7 +89,7 @@ def test_build_run_state_from_agents_json(tmp_path: Path) -> None:
def _get(url: str, *, cookie: str | None = None) -> tuple[int, str, bytes]:
headers = {"Cookie": cookie} if cookie else {}
req = urllib.request.Request(url, headers=headers) # noqa: S310 - localhost test server
with urllib.request.urlopen(req) as resp: # noqa: S310 - localhost test server
with urllib.request.urlopen(req) as resp: # noqa: S310 - localhost test server # nosec B310
return resp.status, resp.headers.get("Content-Type", ""), resp.read()
@@ -100,7 +100,7 @@ def test_server_serves_api_and_static(tmp_path: Path, monkeypatch: pytest.Monkey
(assets / "assets").mkdir(parents=True)
(assets / "index.html").write_text("<!doctype html><div id=root></div>", encoding="utf-8")
(assets / "assets" / "app.js").write_text("console.log(1)", encoding="utf-8")
monkeypatch.setattr("strix.viewer.server.bundle_dir", lambda: assets)
monkeypatch.setattr("strix.interface.viewer.server.bundle_dir", lambda: assets)
httpd, url, _ = serve(run_dir, open_browser=False)
try:
@@ -132,7 +132,7 @@ def test_server_event_endpoint_forwards_cta(
assets = tmp_path / "bundle"
assets.mkdir()
(assets / "index.html").write_text("x", encoding="utf-8")
monkeypatch.setattr("strix.viewer.server.bundle_dir", lambda: assets)
monkeypatch.setattr("strix.interface.viewer.server.bundle_dir", lambda: assets)
seen: list[tuple[str, str | None]] = []
monkeypatch.setattr(
@@ -148,7 +148,7 @@ def test_server_event_endpoint_forwards_cta(
req = urllib.request.Request( # noqa: S310 - localhost test server
f"{url}/api/event", data=body, headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req) as resp: # noqa: S310
with urllib.request.urlopen(req) as resp: # noqa: S310 # nosec B310
assert resp.status == 204
assert seen == [("PR reviews", "sidebar_nav")]
finally:
@@ -163,7 +163,7 @@ def test_server_event_endpoint_forwards_email_funnel(
assets = tmp_path / "bundle"
assets.mkdir()
(assets / "index.html").write_text("x", encoding="utf-8")
monkeypatch.setattr("strix.viewer.server.bundle_dir", lambda: assets)
monkeypatch.setattr("strix.interface.viewer.server.bundle_dir", lambda: assets)
seen: list[tuple[str, str | None]] = []
monkeypatch.setattr(
@@ -183,7 +183,7 @@ def test_server_event_endpoint_forwards_email_funnel(
data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as resp: # noqa: S310
with urllib.request.urlopen(req) as resp: # noqa: S310 # nosec B310
assert resp.status == 204
assert seen == expected
finally:
@@ -207,7 +207,7 @@ def test_server_event_endpoint_forwards_agent_steered(
data=json.dumps({"event": "agent_steered"}).encode(),
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as resp: # noqa: S310
with urllib.request.urlopen(req) as resp: # noqa: S310 # nosec B310
assert resp.status == 204
assert seen == [True]
finally:
@@ -222,7 +222,7 @@ def test_feedback_records_telemetry_on_success(
_bundle(tmp_path, monkeypatch)
sent: list[bool] = []
monkeypatch.setattr("strix.viewer.auth.feedback_submit", lambda *_a: None)
monkeypatch.setattr("strix.interface.viewer.auth.feedback_submit", lambda *_a: None)
monkeypatch.setattr(
"strix.telemetry.posthog.viewer_feedback_submitted", lambda: sent.append(True)
)
@@ -257,7 +257,7 @@ def _post(
url + path, data=json.dumps(payload).encode(), headers=headers, method="POST"
)
try:
with urllib.request.urlopen(req) as resp: # noqa: S310
with urllib.request.urlopen(req) as resp: # noqa: S310 # nosec B310
return resp.status, resp.read()
except urllib.error.HTTPError as exc:
return exc.code, exc.read()
@@ -266,7 +266,7 @@ def _post(
def _session_cookie(url: str, token: str) -> str:
"""Bootstrap a session via the tokened URL and return its ``name=value`` cookie."""
bootstrap = f"{url}/?token={token}"
with urllib.request.urlopen(bootstrap) as resp: # noqa: S310 - localhost test server
with urllib.request.urlopen(bootstrap) as resp: # noqa: S310 - localhost test server # nosec B310
raw = str(resp.headers.get("Set-Cookie", ""))
return raw.split(";", 1)[0]
@@ -275,7 +275,7 @@ def _get_status(url: str, *, cookie: str | None = None) -> int:
headers = {"Cookie": cookie} if cookie else {}
req = urllib.request.Request(url, headers=headers) # noqa: S310 - localhost test server
try:
with urllib.request.urlopen(req) as resp: # noqa: S310
with urllib.request.urlopen(req) as resp: # noqa: S310 # nosec B310
return int(resp.status)
except urllib.error.HTTPError as exc:
return int(exc.code)
@@ -285,7 +285,7 @@ def _bundle(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
assets = tmp_path / "bundle"
assets.mkdir()
(assets / "index.html").write_text("<!doctype html><div id=root></div>", encoding="utf-8")
monkeypatch.setattr("strix.viewer.server.bundle_dir", lambda: assets)
monkeypatch.setattr("strix.interface.viewer.server.bundle_dir", lambda: assets)
def test_capability_issued_only_for_tokened_bootstrap(
@@ -296,26 +296,26 @@ def test_capability_issued_only_for_tokened_bootstrap(
(assets / "assets").mkdir(parents=True)
(assets / "index.html").write_text("<!doctype html>index", encoding="utf-8")
(assets / "assets" / "app.js").write_text("1", encoding="utf-8")
monkeypatch.setattr("strix.viewer.server.bundle_dir", lambda: assets)
monkeypatch.setattr("strix.interface.viewer.server.bundle_dir", lambda: assets)
httpd, url, token = serve(run_dir, open_browser=False)
try:
# A bare index load -- all a reachable client can do -- hands out nothing.
with urllib.request.urlopen(url + "/") as resp: # noqa: S310
with urllib.request.urlopen(url + "/") as resp: # noqa: S310 # nosec B310
assert resp.headers.get("Set-Cookie") is None
# A wrong token is likewise refused the capability.
with urllib.request.urlopen(f"{url}/?token=wrong") as resp: # noqa: S310
with urllib.request.urlopen(f"{url}/?token=wrong") as resp: # noqa: S310 # nosec B310
assert resp.headers.get("Set-Cookie") is None
# Only the correct bootstrap token mints the session cookie.
with urllib.request.urlopen(f"{url}/?token={token}") as resp: # noqa: S310
with urllib.request.urlopen(f"{url}/?token={token}") as resp: # noqa: S310 # nosec B310
cookie = str(resp.headers.get("Set-Cookie", ""))
assert "strix_viewer_session=" in cookie
assert "HttpOnly" in cookie and "SameSite=Strict" in cookie
# Static assets never carry it.
with urllib.request.urlopen(url + "/assets/app.js") as resp: # noqa: S310
with urllib.request.urlopen(url + "/assets/app.js") as resp: # noqa: S310 # nosec B310
assert resp.headers.get("Set-Cookie") is None
finally:
httpd.shutdown()
@@ -338,7 +338,7 @@ def test_unauthorized_client_cannot_acquire_capability(
try:
# A direct network client can reach the page but is handed no capability,
# so replaying an empty/guessed cookie cannot steer a live scan.
with urllib.request.urlopen(url + "/") as resp: # noqa: S310
with urllib.request.urlopen(url + "/") as resp: # noqa: S310 # nosec B310
assert resp.headers.get("Set-Cookie") is None
status, _ = _post(
url,
@@ -356,9 +356,11 @@ def test_unauthorized_client_cannot_acquire_capability(
def test_auth_status_reflects_expiry(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
run_dir = _make_run(tmp_path, "status", status="running", end_time=None)
_bundle(tmp_path, monkeypatch)
monkeypatch.setattr("strix.viewer.auth.read_auth", lambda: {"email": "a@b.com", "token": "t"})
monkeypatch.setattr(
"strix.interface.viewer.auth.read_auth", lambda: {"email": "a@b.com", "token": "t"}
)
verified = {"value": True}
monkeypatch.setattr("strix.viewer.auth.is_verified", lambda: verified["value"])
monkeypatch.setattr("strix.interface.viewer.auth.is_verified", lambda: verified["value"])
httpd, url, token = serve(run_dir, open_browser=False)
try:
@@ -384,7 +386,7 @@ def test_auth_mutations_require_session(tmp_path: Path, monkeypatch: pytest.Monk
run_dir = _make_run(tmp_path, "authmut", status="running", end_time=None)
_bundle(tmp_path, monkeypatch)
forgotten = {"value": False}
monkeypatch.setattr("strix.viewer.auth.forget", lambda: forgotten.update(value=True))
monkeypatch.setattr("strix.interface.viewer.auth.forget", lambda: forgotten.update(value=True))
httpd, url, _ = serve(run_dir, open_browser=False)
try:
@@ -432,7 +434,9 @@ def test_report_send_requires_session_cookie(
_bundle(tmp_path, monkeypatch)
# A verified machine token exists, but that alone must not authorize a caller.
monkeypatch.setattr("strix.viewer.auth.read_auth", lambda: {"email": "a@b.com", "token": "t"})
monkeypatch.setattr(
"strix.interface.viewer.auth.read_auth", lambda: {"email": "a@b.com", "token": "t"}
)
httpd, url, token = serve(run_dir, open_browser=False)
try:
@@ -456,7 +460,9 @@ def test_report_send_rejects_live_run(tmp_path: Path, monkeypatch: pytest.Monkey
# fail closed even for a verified, session-holding caller.
run_dir = _make_run(tmp_path, "live", status="running", end_time=None)
_bundle(tmp_path, monkeypatch)
monkeypatch.setattr("strix.viewer.auth.read_auth", lambda: {"email": "a@b.com", "token": "t"})
monkeypatch.setattr(
"strix.interface.viewer.auth.read_auth", lambda: {"email": "a@b.com", "token": "t"}
)
httpd, url, token = serve(run_dir, open_browser=False)
try:
@@ -475,7 +481,7 @@ def test_historical_run_data_requires_verification(
_bundle(tmp_path, monkeypatch)
verified = {"value": False}
monkeypatch.setattr("strix.viewer.auth.is_verified", lambda: verified["value"])
monkeypatch.setattr("strix.interface.viewer.auth.is_verified", lambda: verified["value"])
httpd, url, token = serve(launched, open_browser=False)
try:
@@ -509,12 +515,12 @@ def test_runs_list_requires_session_and_verification(
_make_run(tmp_path, "other", status="completed", end_time="2026-01-01T00:00:00Z")
_bundle(tmp_path, monkeypatch)
monkeypatch.setattr("strix.viewer.auth.is_verified", lambda: True)
monkeypatch.setattr("strix.interface.viewer.auth.is_verified", lambda: True)
def _runs(cookie: str | None) -> dict[str, object]:
headers = {"Cookie": cookie} if cookie else {}
req = urllib.request.Request(f"{url}/api/runs", headers=headers) # noqa: S310
with urllib.request.urlopen(req) as resp: # noqa: S310 - localhost test server
with urllib.request.urlopen(req) as resp: # noqa: S310 - localhost test server # nosec B310
return dict(json.loads(resp.read()))
httpd, url, token = serve(launched, open_browser=False)
@@ -543,7 +549,7 @@ def test_server_rejects_path_traversal(tmp_path: Path, monkeypatch: pytest.Monke
assets = tmp_path / "bundle"
assets.mkdir()
(assets / "index.html").write_text("<!doctype html>index", encoding="utf-8")
monkeypatch.setattr("strix.viewer.server.bundle_dir", lambda: assets)
monkeypatch.setattr("strix.interface.viewer.server.bundle_dir", lambda: assets)
httpd, url, _ = serve(run_dir, open_browser=False)
try:
+9 -9
View File
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any
import pytest
from strix.viewer import auth
from strix.interface.viewer import auth
def _iso(delta: timedelta) -> str:
@@ -30,7 +30,7 @@ def test_write_read_forget_roundtrip() -> None:
assert auth.read_auth() is None
assert auth.is_verified() is False
auth.write_auth(email="user@example.com", token="tok-123", verified_at=_iso(timedelta(days=30)))
auth.write_auth(email="user@example.com", token="tok-123", verified_at=_iso(timedelta(days=30))) # nosec B106
record = auth.read_auth()
assert record is not None
@@ -47,23 +47,23 @@ def test_write_read_forget_roundtrip() -> None:
def test_is_verified_enforces_expiry() -> None:
# An expired record still reads back, but no longer unlocks history.
auth.write_auth(email="a@b.com", token="t", verified_at=_iso(timedelta(hours=-1)))
auth.write_auth(email="a@b.com", token="t", verified_at=_iso(timedelta(hours=-1))) # nosec B106
assert auth.read_auth() is not None
assert auth.is_verified() is False
# A future expiry unlocks it.
auth.write_auth(email="a@b.com", token="t", verified_at=_iso(timedelta(hours=1)))
auth.write_auth(email="a@b.com", token="t", verified_at=_iso(timedelta(hours=1))) # nosec B106
assert auth.is_verified() is True
def test_is_verified_fails_closed_when_expiry_absent_or_unparseable() -> None:
# No/blank expiry: fail closed rather than unlocking history forever.
auth.write_auth(email="a@b.com", token="t", verified_at="")
auth.write_auth(email="a@b.com", token="t", verified_at="") # nosec B106
assert auth.read_auth() is not None
assert auth.is_verified() is False
# Garbage expiry likewise requires re-verification.
auth.write_auth(email="a@b.com", token="t", verified_at="not-a-date")
auth.write_auth(email="a@b.com", token="t", verified_at="not-a-date") # nosec B106
assert auth.is_verified() is False
@@ -73,9 +73,9 @@ def test_is_verified_accepts_epoch_expiry() -> None:
past = (datetime.now(UTC) - timedelta(hours=1)).timestamp()
# As a numeric string (how write_auth persists it).
auth.write_auth(email="a@b.com", token="t", verified_at=str(future))
auth.write_auth(email="a@b.com", token="t", verified_at=str(future)) # nosec B106
assert auth.is_verified() is True
auth.write_auth(email="a@b.com", token="t", verified_at=str(past))
auth.write_auth(email="a@b.com", token="t", verified_at=str(past)) # nosec B106
assert auth.is_verified() is False
# As a raw JSON number, if a record is written that way.
@@ -87,7 +87,7 @@ def test_is_verified_accepts_epoch_expiry() -> None:
def test_write_auth_is_0600() -> None:
auth.write_auth(email="a@b.com", token="t", verified_at="")
auth.write_auth(email="a@b.com", token="t", verified_at="") # nosec B106
mode = stat.S_IMODE(auth.AUTH_PATH.stat().st_mode)
assert mode == 0o600
+1 -1
View File
@@ -5,7 +5,7 @@ from __future__ import annotations
import json
from typing import TYPE_CHECKING
from strix.viewer.server import build_runs_payload, resolve_run_dir
from strix.interface.viewer.server import build_runs_payload, resolve_run_dir
if TYPE_CHECKING: