"""Unit tests for the tool instance service.""" from unittest.mock import MagicMock, AsyncMock import pytest from src.services.tool.instance_service import modify_compose_file, prepare_manifest_instance @pytest.mark.unit class TestModifyComposeFile: """Tests for modify_compose_file home-directory expansion.""" def test_extra_volumes_expand_home_dir(self, tmp_path): compose_path = tmp_path / "docker-compose.yml" compose_path.write_text( 'services:\n app:\n image: test:latest\n volumes: []\n' ) modify_compose_file( str(compose_path), extra_volumes=[ {"source": "/host/config", "target": "~/.config", "type": "bind"}, {"source": "/host/code", "target": "$HOME/code", "type": "bind"}, ], home_dir="/home/user", ) content = compose_path.read_text() assert "/host/config:/home/user/.config" in content assert "/host/code:/home/user/code" in content def test_working_directory_expands_home_dir(self, tmp_path): compose_path = tmp_path / "docker-compose.yml" compose_path.write_text( 'services:\n app:\n image: test:latest\n' ) modify_compose_file( str(compose_path), working_directory="~/workspace", home_dir="/home/user", ) content = compose_path.read_text() assert "working_dir: /home/user/workspace" in content @pytest.mark.unit async def test_prepare_manifest_instance_uses_repo_name_not_workspace_dir(): """WORKSPACE_NAME must be the repository name, not the workspace path basename.""" repo = MagicMock() repo.name = "my-cool-repo" repo.path = "/data/repos/main" tool_type = MagicMock() tool_type.name = "pi-agent" tool_type.manifest_id = "manifest-uuid" manifest_def = MagicMock() manifest_def.id = "manifest-uuid" manifest_def.manifest = { "base_image": "ubuntu:24.04", "interface_type": "terminal", "user": {"name": "user", "uid": 1001, "gid": 1001}, "mounts": [{"name": "workspace", "target": "~/{{WORKSPACE_NAME}}", "source_type": "repo"}], } manifest_def.base_definition_id = None instance = MagicMock() instance.id = "instance-uuid" instance.name = "pi-agent-my-cool-repo-abc123" instance.repository_id = "repo-uuid" instance.tool_type_id = "tooltype-uuid" instance.port = 0 instance.selected_config_profile_id = None instance.workspace_id = None session = AsyncMock() async def session_get(model, obj_id): if model.__name__ == "ToolType": return tool_type if model.__name__ == "ToolDefinitionManifest": return manifest_def if model.__name__ == "GitRepository": return repo return None session.get.side_effect = session_get # Patch docker images check to report the image already exists so we skip # the actual Docker build. import subprocess from src.services.tool import instance_service original_run = subprocess.run def fake_run(cmd, **kwargs): class Result: returncode = 0 stdout = "image-id" stderr = "" return Result() instance_service.subprocess.run = fake_run try: image_tag, compose_content, manifest, home_dir = await prepare_manifest_instance( session=session, instance=instance, instance_dir="/tmp/instance", repo_path="/data/repos/main", env_vars={}, extra_volumes=[], working_directory=None, ) finally: instance_service.subprocess.run = original_run assert "WORKSPACE_NAME: my-cool-repo" in compose_content assert "/data/repos/main:/home/user/my-cool-repo" in compose_content