"""Tests for the system/config/papers API routes.""" import json import pytest from fastapi.testclient import TestClient from researchlink.api.app import app client = TestClient(app) @pytest.fixture def papers_dir(tmp_path, monkeypatch): """Point the API at a temp papers/ root with one module.""" mod = tmp_path / "2024-x" mod.mkdir() (mod / "metadata.json").write_text(json.dumps({ "slug": "2024-x", "conflicts": [], "fields": {"title": {"value": "Paper X", "status": "verified"}, "year": {"value": 2024}, "authors": {"value": ["A"]}}, }), encoding="utf-8") (mod / "summary.md").write_text("# Summary\n", encoding="utf-8") monkeypatch.setenv("RESEARCHLINK_OUTPUT_DIR", str(tmp_path)) from researchlink import config config.get_settings.__globals__["_settings"] = None yield tmp_path config.get_settings.__globals__["_settings"] = None def test_health(): r = client.get("/api/health") assert r.status_code == 200 and r.json()["backend"] == "ok" def test_config_has_no_keys(): body = client.get("/api/config").text assert "api_key" not in body and "sk-" not in body data = json.loads(body) assert data["mode"] in ("offline", "anthropic", "openai", "openrouter", "ollama", "custom") def test_llm_status_safe(): d = client.get("/api/llm/status").json() assert "tasks" in d and "api_key" not in d def test_papers_list(papers_dir): d = client.get("/api/papers").json() slugs = [p["slug"] for p in d["papers"]] assert "2024-x" in slugs p = next(p for p in d["papers"] if p["slug"] == "2024-x") assert p["title"] == "Paper X" assert p["artifacts"]["summary.md"] == "generated" # exists assert p["artifacts"]["review.md"] == "not-generated" # missing def test_paper_detail(papers_dir): d = client.get("/api/papers/2024-x").json() assert d["metadata"]["slug"] == "2024-x" assert d["artifacts"]["summary.md"]["content"].startswith("# Summary") assert d["artifacts"]["summary.md"]["status"] == "generated" def test_paper_detail_missing_artifact_placeholder(papers_dir): d = client.get("/api/papers/2024-x").json() review = d["artifacts"]["review.md"] assert review["status"] == "not-generated" assert "not-generated" in review["content"] # clean placeholder, never null assert review["exists"] is False def test_raw_artifact_and_traversal(papers_dir): assert client.get("/api/papers/2024-x/raw/summary.md").text.startswith("# Summary") assert client.get("/api/papers/2024-x/raw/nope.md").status_code == 404 assert client.get("/api/papers/2024-x/raw/..%2f..%2fetc").status_code in (400, 404) def test_paper_detail_404(papers_dir): assert client.get("/api/papers/does-not-exist").status_code == 404 def test_paper_traversal_blocked(papers_dir): # encoded traversal should not escape the papers root assert client.get("/api/papers/..%2f..%2fetc").status_code in (400, 404)