from __future__ import annotations import asyncio import json import pytest from investibook.core.run9_workspace_storage import Run9WorkspaceStorage class FakeClient: def __init__(self) -> None: self.closed = False async def aclose(self) -> None: self.closed = True def make_storage() -> Run9WorkspaceStorage: return Run9WorkspaceStorage( client=FakeClient(), # type: ignore[arg-type] box_id="box-1", workspace_root="/workspace", visible_paths=("evidences", "artifacts"), ) def test_remote_paths_are_limited_to_visible_roots(): storage = make_storage() assert storage._remote_path("evidences/report.json") == ( "/workspace/evidences/report.json" ) with pytest.raises(PermissionError): storage._remote_path(".env") with pytest.raises(ValueError): storage._remote_path("../evidences/report.json") with pytest.raises(ValueError): storage._remote_path("/workspace/evidences/report.json") @pytest.mark.asyncio async def test_list_files_returns_only_scanner_metadata(monkeypatch): metadata = [ { "name": "evidences", "path": "evidences", "type": "directory", "children": [ { "name": "report.json", "path": "evidences/report.json", "type": "file", "size": "2 B", "sizeBytes": 2, } ], } ] class FakeProc: def __init__(self) -> None: self.stdout = asyncio.StreamReader() self.stdout.feed_data(json.dumps(metadata).encode("utf-8")) self.stdout.feed_eof() self.closed = False async def wait(self) -> int: return 0 async def aclose(self) -> None: self.closed = True proc = FakeProc() async def fake_start(**kwargs): assert kwargs["close_client_on_close"] is False assert kwargs["command"][0:2] == ["python3", "-c"] return proc monkeypatch.setattr( "investibook.core.run9_workspace_storage.Run9ProcessTransport.start", fake_start, ) storage = make_storage() assert await storage.list_files() == metadata assert proc.closed is True assert storage.client.closed is False # type: ignore[attr-defined] @pytest.mark.asyncio async def test_require_visible_file_rejects_unlisted_file(monkeypatch): storage = make_storage() monkeypatch.setattr(storage, "list_files", lambda: _async_value([])) with pytest.raises(FileNotFoundError): await storage.require_visible_file("evidences/missing.json") async def _async_value(value): return value