Files
shd.build/tools/game-data-capture/tests/integration/test_capture_labeling.py
T
jb f48cc878aa
Validate and deploy production / validate (push) Successful in 24s
Validate and deploy staging / validate (push) Successful in 44s
Validate and deploy production / deploy (push) Failing after 29s
Validate and deploy staging / deploy (push) Failing after 6s
feat: classify gear and weapon mods
2026-07-31 21:54:47 -04:00

238 lines
7.8 KiB
Python

from __future__ import annotations
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from pathlib import Path
from fastapi.testclient import TestClient
from game_data_capture.config import ServerConfig
from game_data_capture.web.app import (
CaptureRecord,
CaptureSessionStatus,
ControlOutcome,
MediaDescriptor,
MutationOutcome,
WebConfig,
create_app,
)
from game_data_capture.web.security import new_bootstrap_token
_ID = "capture-00000000000000000000000000000002"
@dataclass
class CaptureControl:
armed: bool = False
def status(self) -> CaptureSessionStatus:
return CaptureSessionStatus(self.armed, 1, 64)
def start_session(self) -> ControlOutcome:
self.armed = True
return "started"
def stop_session(self) -> ControlOutcome:
self.armed = False
return "stopped"
class Corpus:
def __init__(self) -> None:
self.record = CaptureRecord(_ID, "stored", "2026-07-31T00:00:00Z", None)
self.rejected = False
self.deleted = False
def list_captures(self, *, limit: int) -> Sequence[CaptureRecord]:
return (self.record,)
def get_capture(self, capture_id: str) -> CaptureRecord | None:
return self.record if capture_id == _ID else None
def open_media(self, capture_id: str) -> MediaDescriptor | None:
return MediaDescriptor("image/png", 3, [b"png"]) if capture_id == _ID else None
def label(self, capture_id: str, classification: Mapping[str, object]) -> MutationOutcome:
if capture_id != _ID:
return "not_found"
self.record = CaptureRecord(_ID, "stored", "2026-07-31T00:00:00Z", dict(classification))
return "updated"
def reject_preserving_evidence(self, capture_id: str) -> MutationOutcome:
if capture_id != _ID:
return "not_found"
self.rejected = True
return "updated"
def delete_with_retention(self, capture_id: str) -> MutationOutcome:
if capture_id != _ID:
return "not_found"
self.deleted = True
return "updated"
def test_private_capture_labeling_flow_preserves_rejection_before_retention_delete() -> None:
token = new_bootstrap_token()
control = CaptureControl()
corpus = Corpus()
client = TestClient(
create_app(
config=WebConfig(ServerConfig()),
capture_control=control,
corpus=corpus,
bootstrap_token=token,
),
base_url="http://127.0.0.1:8765",
)
origin = "http://127.0.0.1:8765"
csrf = str(
client.post("/api/bootstrap", headers={"Origin": origin}, json={"token": token}).json()[
"csrf_token"
]
)
label = {
"family": "gear",
"view": "detail",
"gear_slot": "Mask",
"record_types": ["gear", "brand_set"],
"confidence": 1.0,
"abstention_reason": None,
}
response = client.post(
f"/api/captures/{_ID}/label", headers={"Origin": origin, "X-CSRF-Token": csrf}, json=label
)
assert response.status_code == 200
csrf = str(response.json()["csrf_token"])
assert client.get(f"/api/captures/{_ID}").json()["capture"]["classification"] == label
response = client.post(
f"/api/captures/{_ID}/reject",
headers={"Origin": origin, "X-CSRF-Token": csrf},
json={"confirmation": "REJECT"},
)
assert response.status_code == 200
csrf = str(response.json()["csrf_token"])
assert corpus.rejected is True
response = client.post(
f"/api/captures/{_ID}/delete",
headers={"Origin": origin, "X-CSRF-Token": csrf},
json={"confirmation": "DELETE"},
)
assert response.status_code == 200
assert corpus.deleted is True
def test_mod_labels_round_trip_through_the_reviewed_label_route() -> None:
token = new_bootstrap_token()
client = TestClient(
create_app(
config=WebConfig(ServerConfig()),
capture_control=CaptureControl(),
corpus=Corpus(),
bootstrap_token=token,
),
base_url="http://127.0.0.1:8765",
)
origin = "http://127.0.0.1:8765"
csrf = str(
client.post("/api/bootstrap", headers={"Origin": origin}, json={"token": token}).json()[
"csrf_token"
]
)
for family, record_type in (("weapon_mod", "weapon_mod"), ("gear_mod", "gear_mod")):
label = {
"family": family,
"view": "detail",
"gear_slot": None,
"record_types": [record_type],
"confidence": 1.0,
"abstention_reason": None,
}
response = client.post(
f"/api/captures/{_ID}/label",
headers={"Origin": origin, "X-CSRF-Token": csrf},
json=label,
)
assert response.status_code == 200
csrf = str(response.json()["csrf_token"])
assert client.get(f"/api/captures/{_ID}").json()["capture"]["classification"] == label
def test_review_ui_uses_local_external_assets_and_keeps_guardrails_in_static_source() -> None:
web_root = Path(__file__).parents[2] / "src" / "game_data_capture" / "web"
template = (web_root / "templates" / "index.html").read_text(encoding="utf-8")
script = (web_root / "static" / "review.js").read_text(encoding="utf-8")
stylesheet = (web_root / "static" / "review.css").read_text(encoding="utf-8")
assert 'href="/static/review.css"' in template
assert 'src="/static/review.js"' in template
assert "<style" not in template
assert "<script>" not in template
assert "http://" not in template + script + stylesheet
assert "https://" not in template + script + stylesheet
assert "innerHTML" not in script
assert "Origin" not in script
assert (
'history.replaceState(null, "", `${window.location.pathname}${window.location.search}`)'
in script
)
assert '"/api/session/start"' in script
assert "JSON.stringify({ confirmation })" in script
assert "confidence: 1.0" in script
assert 'credentials: "same-origin"' in script
assert "localStorage" not in script
assert "sessionStorage" not in script
assert "document.cookie" not in script
for expected in (
"weapon",
"gear",
"skill_chip",
"os_protocol",
"specialization",
"weapon_mod",
"gear_mod",
"unknown",
"Mask",
"Gloves",
"Backpack",
"Body Armor",
"Holster",
"Knee Pads",
"prefers-reduced-motion",
):
assert expected in script + stylesheet
def test_review_page_and_local_static_assets_serve_with_security_headers() -> None:
client = TestClient(
create_app(
config=WebConfig(ServerConfig()),
capture_control=CaptureControl(),
corpus=Corpus(),
bootstrap_token=new_bootstrap_token(),
),
base_url="http://127.0.0.1:8765",
)
page = client.get("/")
stylesheet = client.get("/static/review.css")
script = client.get("/static/review.js")
for response in (page, stylesheet, script):
assert response.status_code == 200
assert response.headers["content-security-policy"].startswith("default-src 'self'")
assert response.headers["referrer-policy"] == "no-referrer"
assert response.headers["x-content-type-options"] == "nosniff"
assert response.headers["x-frame-options"] == "DENY"
assert "http://" not in response.text
assert "https://" not in response.text
assert 'href="/static/review.css"' in page.text
assert 'src="/static/review.js"' in page.text
assert stylesheet.headers["content-type"].startswith("text/css")
assert "csrfPattern" in script.text
assert "setCheckedMembers" in script.text
assert "announce: false" in script.text
assert 'id="hide-reviewed"' in page.text