mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-26 09:16:22 +02:00
feat(filesystem): add local folder backend and verification coverage
This commit is contained in:
parent
15a9e8b085
commit
42d2d2222e
4 changed files with 476 additions and 0 deletions
|
|
@ -0,0 +1,37 @@
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from app.agents.new_chat.filesystem_backends import build_backend_resolver
|
||||
from app.agents.new_chat.filesystem_selection import (
|
||||
ClientPlatform,
|
||||
FilesystemMode,
|
||||
FilesystemSelection,
|
||||
)
|
||||
from app.agents.new_chat.middleware.local_folder_backend import LocalFolderBackend
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
class _RuntimeStub:
|
||||
state = {"files": {}}
|
||||
|
||||
|
||||
def test_backend_resolver_returns_local_backend_for_local_mode(tmp_path: Path):
|
||||
selection = FilesystemSelection(
|
||||
mode=FilesystemMode.DESKTOP_LOCAL_FOLDER,
|
||||
client_platform=ClientPlatform.DESKTOP,
|
||||
local_root_path=str(tmp_path),
|
||||
)
|
||||
resolver = build_backend_resolver(selection)
|
||||
|
||||
backend = resolver(_RuntimeStub())
|
||||
assert isinstance(backend, LocalFolderBackend)
|
||||
|
||||
|
||||
def test_backend_resolver_uses_cloud_mode_by_default():
|
||||
resolver = build_backend_resolver(FilesystemSelection())
|
||||
backend = resolver(_RuntimeStub())
|
||||
# StateBackend class name check keeps this test decoupled
|
||||
# from internal deepagents runtime class identity.
|
||||
assert backend.__class__.__name__ == "StateBackend"
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
import pytest
|
||||
|
||||
from app.agents.new_chat.middleware.filesystem import SurfSenseFilesystemMiddleware
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
class _BackendWithRawRead:
|
||||
def __init__(self, content: str) -> None:
|
||||
self._content = content
|
||||
|
||||
def read(self, file_path: str, offset: int = 0, limit: int = 200000) -> str:
|
||||
del file_path, offset, limit
|
||||
return " 1\tline1\n 2\tline2"
|
||||
|
||||
async def aread(self, file_path: str, offset: int = 0, limit: int = 200000) -> str:
|
||||
return self.read(file_path, offset, limit)
|
||||
|
||||
def read_raw(self, file_path: str) -> str:
|
||||
del file_path
|
||||
return self._content
|
||||
|
||||
async def aread_raw(self, file_path: str) -> str:
|
||||
return self.read_raw(file_path)
|
||||
|
||||
|
||||
class _RuntimeNoSuggestedPath:
|
||||
state = {"file_operation_contract": {}}
|
||||
|
||||
|
||||
def test_verify_written_content_prefers_raw_sync() -> None:
|
||||
middleware = SurfSenseFilesystemMiddleware.__new__(SurfSenseFilesystemMiddleware)
|
||||
expected = "line1\nline2"
|
||||
backend = _BackendWithRawRead(expected)
|
||||
|
||||
verify_error = middleware._verify_written_content_sync(
|
||||
backend=backend,
|
||||
path="/note.md",
|
||||
expected_content=expected,
|
||||
)
|
||||
|
||||
assert verify_error is None
|
||||
|
||||
|
||||
def test_contract_suggested_path_falls_back_to_notes_md() -> None:
|
||||
suggested = SurfSenseFilesystemMiddleware._get_contract_suggested_path(
|
||||
_RuntimeNoSuggestedPath()
|
||||
)
|
||||
assert suggested == "/notes.md"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_verify_written_content_prefers_raw_async() -> None:
|
||||
middleware = SurfSenseFilesystemMiddleware.__new__(SurfSenseFilesystemMiddleware)
|
||||
expected = "line1\nline2"
|
||||
backend = _BackendWithRawRead(expected)
|
||||
|
||||
verify_error = await middleware._verify_written_content_async(
|
||||
backend=backend,
|
||||
path="/note.md",
|
||||
expected_content=expected,
|
||||
)
|
||||
|
||||
assert verify_error is None
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from app.agents.new_chat.middleware.local_folder_backend import LocalFolderBackend
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
def test_local_backend_write_read_edit_roundtrip(tmp_path: Path):
|
||||
backend = LocalFolderBackend(str(tmp_path))
|
||||
|
||||
write = backend.write("/notes/test.md", "line1\nline2")
|
||||
assert write.error is None
|
||||
assert write.path == "/notes/test.md"
|
||||
|
||||
read = backend.read("/notes/test.md", offset=0, limit=20)
|
||||
assert "line1" in read
|
||||
assert "line2" in read
|
||||
|
||||
edit = backend.edit("/notes/test.md", "line2", "updated")
|
||||
assert edit.error is None
|
||||
assert edit.occurrences == 1
|
||||
|
||||
read_after = backend.read("/notes/test.md", offset=0, limit=20)
|
||||
assert "updated" in read_after
|
||||
|
||||
|
||||
def test_local_backend_blocks_path_escape(tmp_path: Path):
|
||||
backend = LocalFolderBackend(str(tmp_path))
|
||||
|
||||
result = backend.write("/../../etc/passwd", "bad")
|
||||
assert result.error is not None
|
||||
assert "Invalid path" in result.error
|
||||
|
||||
|
||||
def test_local_backend_glob_and_grep(tmp_path: Path):
|
||||
backend = LocalFolderBackend(str(tmp_path))
|
||||
(tmp_path / "docs").mkdir()
|
||||
(tmp_path / "docs" / "a.txt").write_text("hello world\n")
|
||||
(tmp_path / "docs" / "b.md").write_text("hello markdown\n")
|
||||
|
||||
infos = backend.glob_info("**/*.txt", "/docs")
|
||||
paths = {info["path"] for info in infos}
|
||||
assert "/docs/a.txt" in paths
|
||||
|
||||
grep = backend.grep_raw("hello", "/docs", "*.md")
|
||||
assert isinstance(grep, list)
|
||||
assert any(match["path"] == "/docs/b.md" for match in grep)
|
||||
|
||||
|
||||
def test_local_backend_read_raw_returns_exact_content(tmp_path: Path):
|
||||
backend = LocalFolderBackend(str(tmp_path))
|
||||
expected = "# Title\n\nline 1\nline 2\n"
|
||||
write = backend.write("/notes/raw.md", expected)
|
||||
assert write.error is None
|
||||
|
||||
raw = backend.read_raw("/notes/raw.md")
|
||||
assert raw == expected
|
||||
Loading…
Add table
Add a link
Reference in a new issue