-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtest_api_routes.py
More file actions
145 lines (102 loc) · 5.06 KB
/
Copy pathtest_api_routes.py
File metadata and controls
145 lines (102 loc) · 5.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""HTTP route matrix — full create_app coverage beyond integration smoke tests."""
from __future__ import annotations
import shutil
from pathlib import Path
from app import create_app
from tests.conftest import assert_error_response
FIXTURES = Path(__file__).parent / "fixtures"
def test_index_returns_html(client):
resp = client.get("/")
assert resp.status_code == 200
assert b"html" in resp.data.lower() or (resp.content_type and "html" in resp.content_type)
def test_session_stats_happy_path(client):
resp = client.get("/api/sessions/test-project/session_abc123/stats")
assert resp.status_code == 200
stats = resp.get_json()
assert "conversation_turns" in stats
assert "cost_estimate_usd" in stats
def test_session_stats_not_found(client):
resp = client.get("/api/sessions/test-project/nonexistent/stats")
assert resp.status_code == 404
assert_error_response(resp, expected_code="SESSION_NOT_FOUND")
def test_session_stats_excluded_session_returns_404(tmp_path, export_state_file):
project_dir = tmp_path / "test-project"
project_dir.mkdir(parents=True)
shutil.copy(FIXTURES / "session_minimal.jsonl", project_dir / "session_abc123.jsonl")
rules_path = tmp_path / "exclusion-rules.txt"
rules_path.write_text("integration fixture\n", encoding="utf-8")
app = create_app(base_dir=str(tmp_path), exclusion_rules_path=str(rules_path))
app.config["TESTING"] = True
excluded_client = app.test_client()
resp = excluded_client.get("/api/sessions/test-project/session_abc123/stats")
assert resp.status_code == 404
assert_error_response(resp, expected_code="SESSION_NOT_FOUND")
def test_session_stats_invalid_path(client):
resp = client.get("/api/sessions/../../etc/passwd/session_abc123/stats")
assert resp.status_code == 400
assert_error_response(resp, expected_code="INVALID_PATH")
def test_session_detail_invalid_path(client):
resp = client.get("/api/sessions/../../etc/passwd/session_abc123")
assert resp.status_code == 400
assert_error_response(resp, expected_code="INVALID_PATH")
def test_session_detail_parse_failure_returns_500_without_leak(client, monkeypatch):
"""Parser failures must return generic PARSE_ERROR, not exception internals (#25)."""
def _boom(*_args, **_kwargs):
raise KeyError("internal_secret_field_id")
monkeypatch.setattr("api.sessions.get_cached_session", _boom)
resp = client.get("/api/sessions/test-project/session_abc123")
assert resp.status_code == 500
body_text = resp.get_data(as_text=True)
assert "internal_secret_field_id" not in body_text
assert "KeyError" not in body_text
assert_error_response(resp, expected_code="PARSE_ERROR")
def test_search_limit_capped_at_max(client):
resp = client.get("/api/search?q=Hello&limit=9999")
assert resp.status_code == 200
results = resp.get_json()
assert isinstance(results, list)
assert len(results) <= 500
def test_project_sessions_invalid_path_returns_invalid_path(client):
resp = client.get("/api/projects/../../outside/sessions")
assert_error_response(resp, expected_code="INVALID_PATH")
def test_export_state_defaults(client_empty):
resp = client_empty.get("/api/export/state")
assert resp.status_code == 200
body = resp.get_json()
assert "last_export_session_count" in body
assert "export_count" not in body
def test_bulk_export_empty_projects_returns_422(client_empty):
resp = client_empty.post("/api/export", json={"since": "all"})
assert resp.status_code == 422
assert_error_response(resp, expected_code="EXPORT_NOTHING_TO_EXPORT")
assert resp.get_json()["since"] == "all"
def test_bulk_export_invalid_since(client):
resp = client.post("/api/export", json={"since": "yesterday"})
assert resp.status_code == 400
assert_error_response(resp, expected_code="INVALID_SINCE_MODE")
assert resp.get_json()["since"] == "yesterday"
def test_bulk_export_non_object_json_returns_400(client):
resp = client.post(
"/api/export",
data='["all"]',
content_type="application/json",
)
assert resp.status_code == 400
assert_error_response(resp, expected_code="INVALID_REQUEST_BODY")
def test_export_session_markdown_attachment(client):
resp = client.get("/api/export/session/test-project/session_abc123")
assert resp.status_code == 200
disposition = resp.headers.get("Content-Disposition") or ""
assert "attachment" in disposition.lower()
def test_export_session_json_format(client):
resp = client.get("/api/export/session/test-project/session_abc123?format=json")
assert resp.status_code == 200
assert resp.mimetype == "application/json"
def test_export_session_not_found(client):
resp = client.get("/api/export/session/test-project/nonexistent")
assert resp.status_code == 404
assert_error_response(resp, expected_code="SESSION_NOT_FOUND")
def test_export_session_invalid_path(client):
resp = client.get("/api/export/session/../../etc/passwd/session_abc123")
assert resp.status_code == 400
assert_error_response(resp, expected_code="INVALID_PATH")