Skip to content

Commit d3b101f

Browse files
Validate export API JSON body and use tempfile paths in engine tests
1 parent 45c36ac commit d3b101f

3 files changed

Lines changed: 33 additions & 8 deletions

File tree

api/export_api.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ def export_chats() -> tuple[Response, int] | Response:
9393
exclusion rules file.
9494
"""
9595
try:
96-
body = request.get_json(silent=True) or {}
96+
body = request.get_json(silent=True)
97+
if not isinstance(body, dict):
98+
return json_response({"error": "request body must be a JSON object"}, 400)
9799
since: Literal["all", "last"] = (
98100
"last" if body.get("since") == "last" else "all"
99101
)

tests/test_api_export.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,15 @@ def test_post_returns_zip_with_markdown_entry(self, client, export_state_dir):
9191

9292

9393
class TestExportErrorResponses:
94+
def test_non_dict_json_body_returns_400(self, client, export_state_dir):
95+
response = client.post(
96+
"/api/export",
97+
json=["not", "an", "object"],
98+
content_type="application/json",
99+
)
100+
assert response.status_code == 400
101+
assert response.get_json().get("error") == "request body must be a JSON object"
102+
94103
def test_missing_global_storage_returns_404(self, empty_workspace_client):
95104
response = _post_export(empty_workspace_client)
96105
assert response.status_code == 404

tests/test_export_engine.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import os
66
import sys
7+
import tempfile
78
import unittest
89
from unittest.mock import MagicMock, patch
910

@@ -18,7 +19,17 @@
1819
)
1920

2021

21-
class TestCollectExportEntriesNocache(unittest.TestCase):
22+
class _TempExportPathsMixin:
23+
def setUp(self):
24+
self._tmp = tempfile.TemporaryDirectory()
25+
self.addCleanup(self._tmp.cleanup)
26+
self.tmp_ws = os.path.join(self._tmp.name, "ws")
27+
self.tmp_out = os.path.join(self._tmp.name, "out")
28+
os.makedirs(self.tmp_ws, exist_ok=True)
29+
os.makedirs(self.tmp_out, exist_ok=True)
30+
31+
32+
class TestCollectExportEntriesNocache(_TempExportPathsMixin, unittest.TestCase):
2233
def test_nocache_env_passed_to_prepare_workspace_orchestration(self):
2334
with patch.dict(os.environ, {"CURSOR_CHAT_BROWSER_NOCACHE": "1"}):
2435
with patch(
@@ -30,27 +41,30 @@ def test_nocache_env_passed_to_prepare_workspace_orchestration(self):
3041
return_value=None,
3142
):
3243
collect_export_entries(
33-
workspace_path="/tmp/ws",
44+
workspace_path=self.tmp_ws,
3445
exclusion_rules=[],
3546
since="all",
3647
last_export_ms=0,
37-
out_dir="/tmp/out",
48+
out_dir=self.tmp_out,
3849
include_composer=False,
3950
include_cli=False,
4051
)
4152
mock_prepare.assert_called_once()
4253
self.assertTrue(mock_prepare.call_args.kwargs["nocache"])
4354

4455

45-
class TestCollectExportEntriesCorruptComposer(unittest.TestCase):
56+
class TestCollectExportEntriesCorruptComposer(
57+
_TempExportPathsMixin,
58+
unittest.TestCase,
59+
):
4660
def test_non_dict_composer_row_is_skipped(self):
4761
ctx = MagicMock()
4862
ctx.project_name_to_workspace_id = {}
4963
ctx.workspace_path_to_id = {}
5064
ctx.composer_id_to_workspace_id = {}
5165
ctx.invalid_workspace_ids = set()
5266
orch = WorkspaceOrchestration(
53-
workspace_path="/tmp/ws",
67+
workspace_path=self.tmp_ws,
5468
workspace_entries=[],
5569
fingerprint={},
5670
ctx=ctx,
@@ -80,11 +94,11 @@ def __getitem__(self, key: str) -> str:
8094
return_value=db_data,
8195
):
8296
exported = collect_export_entries(
83-
workspace_path="/tmp/ws",
97+
workspace_path=self.tmp_ws,
8498
exclusion_rules=[],
8599
since="all",
86100
last_export_ms=0,
87-
out_dir="/tmp/out",
101+
out_dir=self.tmp_out,
88102
include_cli=False,
89103
)
90104
self.assertEqual(exported, [])

0 commit comments

Comments
 (0)