123 lines
4.4 KiB
Python
123 lines
4.4 KiB
Python
"""Route-level owner-scope tests for persisted research reports."""
|
|
|
|
import asyncio
|
|
import json
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
|
|
from routes.research_routes import setup_research_routes
|
|
|
|
|
|
def _request(user: str):
|
|
return SimpleNamespace(state=SimpleNamespace(current_user=user))
|
|
|
|
|
|
def _route(router, path: str, method: str):
|
|
for route in router.routes:
|
|
if getattr(route, "path", "") != path:
|
|
continue
|
|
if method in getattr(route, "methods", set()):
|
|
return route.endpoint
|
|
raise AssertionError(f"{method} {path} route not registered")
|
|
|
|
|
|
def _write_research(data_dir, session_id: str, **data):
|
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
path = data_dir / f"{session_id}.json"
|
|
path.write_text(json.dumps(data), encoding="utf-8")
|
|
return path
|
|
|
|
|
|
def _research_handler():
|
|
handler = MagicMock()
|
|
handler._active_tasks = {}
|
|
return handler
|
|
|
|
|
|
def test_library_returns_only_caller_owned_unarchived_reports(tmp_path, monkeypatch):
|
|
monkeypatch.chdir(tmp_path)
|
|
data_dir = tmp_path / "data" / "deep_research"
|
|
_write_research(data_dir, "alice-live", owner="alice", query="Alice", completed_at=30)
|
|
_write_research(data_dir, "alice-archived", owner="alice", query="Archived", archived=True)
|
|
_write_research(data_dir, "bob-live", owner="bob", query="Bob", completed_at=40)
|
|
_write_research(data_dir, "legacy-null", query="Legacy", completed_at=50)
|
|
|
|
router = setup_research_routes(_research_handler())
|
|
target = _route(router, "/api/research/library", "GET")
|
|
|
|
out = asyncio.run(target(
|
|
request=_request("alice"),
|
|
search=None,
|
|
sort="recent",
|
|
limit=50,
|
|
archived=False,
|
|
))
|
|
|
|
assert [item["id"] for item in out["research"]] == ["alice-live"]
|
|
assert out["total"] == 1
|
|
|
|
|
|
def test_detail_rejects_cross_owner_and_null_owner_reports(tmp_path, monkeypatch):
|
|
monkeypatch.chdir(tmp_path)
|
|
data_dir = tmp_path / "data" / "deep_research"
|
|
_write_research(data_dir, "bob-report", owner="bob", result="bob secret")
|
|
_write_research(data_dir, "legacy-report", result="legacy secret")
|
|
|
|
router = setup_research_routes(_research_handler())
|
|
target = _route(router, "/api/research/detail/{session_id}", "GET")
|
|
|
|
for session_id in ("bob-report", "legacy-report"):
|
|
with pytest.raises(HTTPException) as exc:
|
|
asyncio.run(target(session_id=session_id, request=_request("alice")))
|
|
assert exc.value.status_code == 404
|
|
|
|
|
|
def test_report_rejects_null_owner_before_generating_html(tmp_path, monkeypatch):
|
|
monkeypatch.chdir(tmp_path)
|
|
data_dir = tmp_path / "data" / "deep_research"
|
|
_write_research(data_dir, "legacy-report", result="legacy secret")
|
|
|
|
handler = _research_handler()
|
|
router = setup_research_routes(handler)
|
|
target = _route(router, "/api/research/report/{session_id}", "GET")
|
|
|
|
with pytest.raises(HTTPException) as exc:
|
|
asyncio.run(target(session_id="legacy-report", request=_request("alice")))
|
|
|
|
assert exc.value.status_code == 404
|
|
handler.get_report_html.assert_not_called()
|
|
|
|
|
|
def test_archive_rejects_cross_owner_without_mutating_report(tmp_path, monkeypatch):
|
|
monkeypatch.chdir(tmp_path)
|
|
data_dir = tmp_path / "data" / "deep_research"
|
|
path = _write_research(data_dir, "bob-report", owner="bob", archived=False)
|
|
|
|
router = setup_research_routes(_research_handler())
|
|
target = _route(router, "/api/research/{session_id}/archive", "POST")
|
|
|
|
with pytest.raises(HTTPException) as exc:
|
|
asyncio.run(target(session_id="bob-report", request=_request("alice"), archived=True))
|
|
|
|
assert exc.value.status_code == 404
|
|
assert json.loads(path.read_text(encoding="utf-8"))["archived"] is False
|
|
|
|
|
|
def test_delete_rejects_cross_owner_without_unlinking_report(tmp_path, monkeypatch):
|
|
monkeypatch.chdir(tmp_path)
|
|
data_dir = tmp_path / "data" / "deep_research"
|
|
path = _write_research(data_dir, "bob-report", owner="bob", result="bob secret")
|
|
|
|
router = setup_research_routes(_research_handler())
|
|
target = _route(router, "/api/research/{session_id}", "DELETE")
|
|
|
|
with pytest.raises(HTTPException) as exc:
|
|
asyncio.run(target(session_id="bob-report", request=_request("alice")))
|
|
|
|
assert exc.value.status_code == 404
|
|
assert path.exists()
|
|
assert json.loads(path.read_text(encoding="utf-8"))["result"] == "bob secret"
|