55from dataclasses import asdict , dataclass
66from pathlib import Path
77
8+ from fastapi .testclient import TestClient
9+
810ROOT = Path (__file__ ).resolve ().parents [1 ]
911if str (ROOT ) not in sys .path :
1012 sys .path .insert (0 , str (ROOT ))
1113
1214from frontend .local_server import (
15+ app ,
1316 configure_frontend ,
1417 decode_image_data_url ,
1518 save_uploaded_images ,
19+ _register_file_workspace ,
1620 _resolve_existing_workspace ,
1721 _resolve_workspace_file_path ,
22+ _unregister_file_workspace ,
1823 _workspace_directory_payload ,
1924)
2025
@@ -123,6 +128,28 @@ def test_frontend_workspace_file_paths_are_scoped_to_workspace(tmp_path: Path) -
123128 raise AssertionError ("expected non-image file to be rejected" )
124129
125130
131+ def test_frontend_workspace_file_endpoint_serves_only_scoped_images (tmp_path : Path ) -> None :
132+ image_path = tmp_path / "outputs" / "demo image.png"
133+ image_path .parent .mkdir ()
134+ image_path .write_bytes (b"png-bytes" )
135+ outside = tmp_path .parent / f"outside-{ tmp_path .name } .png"
136+ outside .write_bytes (b"outside" )
137+ token = _register_file_workspace (tmp_path )
138+ client = TestClient (app )
139+ try :
140+ for path in ("outputs/demo image.png" , "outputs/demo%20image.png" , str (image_path )):
141+ response = client .get ("/api/workspace-file" , params = {"token" : token , "path" : path })
142+ assert response .status_code == 200
143+ assert response .content == b"png-bytes"
144+ outside_response = client .get ("/api/workspace-file" , params = {"token" : token , "path" : str (outside )})
145+ assert outside_response .status_code == 403
146+ missing_token_response = client .get ("/api/workspace-file" , params = {"token" : "missing" , "path" : "outputs/demo image.png" })
147+ assert missing_token_response .status_code == 404
148+ finally :
149+ _unregister_file_workspace (token )
150+ outside .unlink (missing_ok = True )
151+
152+
126153def test_frontend_configures_trace_dir (tmp_path : Path ) -> None :
127154 trace_dir = tmp_path / "frontend-traces"
128155 configure_frontend (role_prompt = "Extra role guidance." , trace_dir = str (trace_dir ))
@@ -199,6 +226,12 @@ def test_frontend_static_interaction_contract() -> None:
199226 assert 'addMessage("user", answer, [])' in js
200227 assert "function renderMarkdown(text)" in js
201228 assert "rewriteWorkspaceImageSources" in js
229+ assert "unwrapFullMarkdownFence" in js
230+ assert "markdown|md|gfm" in js
231+ assert "renderMermaidInMarkdown" in js
232+ assert "language-mermaid" in js
233+ assert "securityLevel: \" strict\" " in js
234+ assert "renderMermaidInMarkdown(node)" in js
202235 assert "/api/workspace-file" in js
203236 assert '"file_token"' in server
204237 assert "window.marked.parse" in js
@@ -214,6 +247,7 @@ def test_frontend_static_interaction_contract() -> None:
214247 assert 'class="space-links"' in html
215248 assert "marked@15.0.12/marked.min.js" in html
216249 assert "dompurify@3.2.6/dist/purify.min.js" in html
250+ assert "mermaid@11.12.0/dist/mermaid.min.js" in html
217251 assert 'eventNode.classList.add("collapsed")' not in js
218252 assert 'node.classList.contains("latest")' in js
219253 assert "event.isComposing" in js
@@ -240,6 +274,7 @@ def test_frontend_static_interaction_contract() -> None:
240274 assert ".event.collapsed .event-body-inner::after" not in css
241275 assert ".markdown-body" in css
242276 assert ".markdown-body table" in css
277+ assert ".mermaid-chart" in css
243278 assert ".event.can-collapse" in css
244279 assert ".event:not(.can-collapse) .event-toggle" in css
245280 assert ".space-links" in css
@@ -269,6 +304,9 @@ def main() -> int:
269304 with tempfile .TemporaryDirectory () as tmp :
270305 test_frontend_workspace_file_paths_are_scoped_to_workspace (Path (tmp ))
271306 outputs .append ("workspace image path scoping: ok" )
307+ with tempfile .TemporaryDirectory () as tmp :
308+ test_frontend_workspace_file_endpoint_serves_only_scoped_images (Path (tmp ))
309+ outputs .append ("workspace image endpoint: ok" )
272310 with tempfile .TemporaryDirectory () as tmp :
273311 test_frontend_configures_trace_dir (Path (tmp ))
274312 outputs .append ("frontend trace-dir config: ok" )
0 commit comments