e7587ca9f5
- Add FileService for workspace-scoped file operations
- Add GitOperations service for workspace-scoped git commands
- Add workspace_files API: GET/POST /workspaces/{id}/files
- Add workspace_git API: status, branches, commit, push, pull, fetch, checkout, history
- Add workspace_instances API: list instances per workspace
- Add top-level POST /workspaces/ (accepts repo_id directly)
- Enrich GET /projects/ with nested repositories and workspaces
- Register all new routers in main.py
- 23 tests passing (17 existing + 6 new)
Quality gates: ruff clean
85 lines
3.1 KiB
Python
85 lines
3.1 KiB
Python
"""Unit tests for FileService."""
|
|
|
|
import os
|
|
import tempfile
|
|
|
|
import pytest
|
|
|
|
from src.models.workspace import Workspace
|
|
from src.services.file_service import FileService
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_workspace():
|
|
"""Create a temporary workspace directory."""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
ws = Workspace(
|
|
id="00000000-0000-0000-0000-000000000001",
|
|
name="test-ws",
|
|
repo_id="00000000-0000-0000-0000-000000000002",
|
|
user_id="00000000-0000-0000-0000-000000000003",
|
|
branch="main",
|
|
path=tmpdir,
|
|
)
|
|
yield ws
|
|
|
|
|
|
class TestFileService:
|
|
"""Tests for FileService."""
|
|
|
|
def test_list_directory_empty(self, temp_workspace: Workspace):
|
|
"""Returns empty list for empty directory."""
|
|
service = FileService()
|
|
entries = service.list_directory(temp_workspace)
|
|
assert entries == []
|
|
|
|
def test_list_directory_with_files(self, temp_workspace: Workspace):
|
|
"""Returns entries sorted (dirs first, then files)."""
|
|
# Create files and dirs
|
|
os.makedirs(os.path.join(temp_workspace.path, "src"))
|
|
with open(os.path.join(temp_workspace.path, "README.md"), "w") as f:
|
|
f.write("# Test")
|
|
with open(os.path.join(temp_workspace.path, "main.py"), "w") as f:
|
|
f.write("print('hello')")
|
|
|
|
service = FileService()
|
|
entries = service.list_directory(temp_workspace)
|
|
|
|
assert len(entries) == 3
|
|
assert entries[0].name == "src" and entries[0].type == "directory"
|
|
assert entries[1].name == "main.py" and entries[1].type == "file"
|
|
assert entries[2].name == "README.md" and entries[2].type == "file"
|
|
|
|
def test_read_file(self, temp_workspace: Workspace):
|
|
"""Reads text file content."""
|
|
with open(os.path.join(temp_workspace.path, "test.txt"), "w") as f:
|
|
f.write("hello world")
|
|
|
|
service = FileService()
|
|
content = service.read_file(temp_workspace, "test.txt")
|
|
assert content == "hello world"
|
|
|
|
def test_read_binary_file_rejected(self, temp_workspace: Workspace):
|
|
"""Rejects binary files."""
|
|
with open(os.path.join(temp_workspace.path, "binary.bin"), "wb") as f:
|
|
f.write(b"\x00\x01\x02")
|
|
|
|
service = FileService()
|
|
with pytest.raises(ValueError, match="Binary"):
|
|
service.read_file(temp_workspace, "binary.bin")
|
|
|
|
def test_write_file(self, temp_workspace: Workspace):
|
|
"""Writes file to workspace."""
|
|
service = FileService()
|
|
service.write_file(temp_workspace, "nested/file.txt", "content")
|
|
|
|
assert os.path.exists(os.path.join(temp_workspace.path, "nested", "file.txt"))
|
|
with open(os.path.join(temp_workspace.path, "nested", "file.txt")) as f:
|
|
assert f.read() == "content"
|
|
|
|
def test_path_escapes_workspace(self, temp_workspace: Workspace):
|
|
"""Rejects paths that escape workspace directory."""
|
|
service = FileService()
|
|
with pytest.raises(ValueError, match="escapes"):
|
|
service.list_directory(temp_workspace, "../outside")
|