-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtest_export_api_bulk.py
More file actions
158 lines (127 loc) · 5.11 KB
/
Copy pathtest_export_api_bulk.py
File metadata and controls
158 lines (127 loc) · 5.11 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
146
147
148
149
150
151
152
153
154
155
156
157
158
"""Tests for bulk export HTTP behavior (empty export / state JSON)."""
from __future__ import annotations
import io
import json
import sys
import zipfile
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT))
from flask import Flask
from api.export_api import export_bp
from utils.jsonl_parser import parse_session
@pytest.fixture
def isolated_state(tmp_path, monkeypatch):
path = tmp_path / "export_state.json"
monkeypatch.setattr("api.export_api._STATE_FILE", str(path))
return path
def test_bulk_export_invalid_since_returns_400(isolated_state, tmp_path):
app = Flask(__name__)
app.config["TESTING"] = True
app.config["CLAUDE_PROJECTS_DIR"] = str(tmp_path)
app.register_blueprint(export_bp)
client = app.test_client()
resp = client.post("/api/export", json={"since": "lst"})
assert resp.status_code == 400
body = resp.get_json()
assert body["error"] == "Invalid since mode"
assert body["code"] == "INVALID_SINCE_MODE"
assert body["since"] == "lst"
def test_bulk_export_non_object_json_returns_400(isolated_state, tmp_path):
app = Flask(__name__)
app.config["TESTING"] = True
app.config["CLAUDE_PROJECTS_DIR"] = str(tmp_path)
app.register_blueprint(export_bp)
client = app.test_client()
resp = client.post(
"/api/export",
data=json.dumps(["all"]),
content_type="application/json",
)
assert resp.status_code == 400
body = resp.get_json()
assert body["error"] == "Invalid request body"
assert body["code"] == "INVALID_REQUEST_BODY"
def test_bulk_export_empty_returns_422_json(isolated_state, tmp_path):
app = Flask(__name__)
app.config["TESTING"] = True
app.config["CLAUDE_PROJECTS_DIR"] = str(tmp_path)
app.register_blueprint(export_bp)
client = app.test_client()
resp = client.post("/api/export", json={"since": "all"})
assert resp.status_code == 422
body = resp.get_json()
assert body["error"] == "Nothing to export"
assert body["code"] == "EXPORT_NOTHING_TO_EXPORT"
assert body["since"] == "all"
def test_bulk_export_all_succeed_no_warnings_header(client):
resp = client.post("/api/export", json={"since": "all"})
assert resp.status_code == 200
assert resp.content_type.startswith("application/zip")
assert "X-Export-Warnings" not in resp.headers
zf = zipfile.ZipFile(io.BytesIO(resp.data))
md_files = [name for name in zf.namelist() if name.endswith(".md")]
assert len(md_files) == 2
def test_bulk_export_partial_fail_returns_warning_header(client, monkeypatch):
real_parse = parse_session
def flaky_parse(path: str):
if path.endswith("session_def456.jsonl"):
raise json.JSONDecodeError("bad", "doc", 0)
return real_parse(path)
monkeypatch.setattr("utils.export_engine.parse_session", flaky_parse)
resp = client.post("/api/export", json={"since": "all"})
assert resp.status_code == 200
assert "X-Export-Warnings" in resp.headers
warnings = json.loads(resp.headers["X-Export-Warnings"])
assert len(warnings) == 1
assert warnings[0]["session_id"] == "session_def456"
assert warnings[0]["code"] == "PARSE_ERROR"
zf = zipfile.ZipFile(io.BytesIO(resp.data))
assert len([name for name in zf.namelist() if name.endswith(".md")]) == 1
def test_bulk_export_all_fail_returns_422(client, monkeypatch):
def always_fail(path: str):
raise json.JSONDecodeError("bad", "doc", 0)
monkeypatch.setattr("utils.export_engine.parse_session", always_fail)
resp = client.post("/api/export", json={"since": "all"})
assert resp.status_code == 422
body = resp.get_json()
assert body["code"] == "EXPORT_ALL_FAILED"
assert body["since"] == "all"
assert len(body["failures"]) == 2
assert {item["code"] for item in body["failures"]} == {"PARSE_ERROR"}
def test_bulk_export_partial_fail_excludes_failed_from_state(
client, monkeypatch, export_state_file
):
real_parse = parse_session
def flaky_parse(path: str):
if path.endswith("session_def456.jsonl"):
raise json.JSONDecodeError("bad", "doc", 0)
return real_parse(path)
monkeypatch.setattr("utils.export_engine.parse_session", flaky_parse)
resp = client.post("/api/export", json={"since": "all"})
assert resp.status_code == 200
state = json.loads(export_state_file.read_text(encoding="utf-8"))
sessions = state.get("sessions", {})
assert "session_abc123" in sessions
assert "session_def456" not in sessions
def test_export_state_json_fields(isolated_state):
isolated_state.write_text(
json.dumps(
{
"lastExportTime": "2026-01-01T12:00:00",
"exportedCount": 5,
"sessions": {},
}
),
encoding="utf-8",
)
app = Flask(__name__)
app.config["TESTING"] = True
app.register_blueprint(export_bp)
client = app.test_client()
resp = client.get("/api/export/state")
assert resp.status_code == 200
body = resp.get_json()
assert body["last_export_session_count"] == 5
assert "export_count" not in body