mirror of
https://github.com/usestrix/strix.git
synced 2026-08-23 19:32:37 +02:00
refactor: move strix/viewer under strix/interface
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user