fix(tool): stop defaulting manifest working_dir to /workspace

The /workspace compatibility symlink was removed from the manifest
compiler/entrypoint in 3e59a25. Tool definitions that still set
runtime.working_dir to /workspace therefore start in an empty directory
instead of /home/user/{repo_name}.

- manifest-editor.tsx: default working_dir to empty instead of /workspace;
  update startup-script placeholder to reference /home/alex/.
- Add Alembic migration 2026_06_19_113000 that clears the stale
  runtime.working_dir = /workspace from the built-in pi-agent manifest.
- Add migration import test.

Quality gates: pytest tests/api tests/services/test_terminal_manager_multi.py tests/unit (248 passed), ruff check (clean), npx tsc --noEmit (clean), eslint (clean).
This commit is contained in:
2026-06-19 12:10:42 +02:00
parent 6d7f538a79
commit 10955dfe8e
28 changed files with 235 additions and 123 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/tests/unit
## role
Unit test suite for the API backend, covering configuration, Docker services, Git operations, event bus, file/SSH/terminal utilities, monitoring, notifications, and manifest compilation.
Contains isolated unit tests for individual components of the API application, covering configuration, services, utilities, and core business logic without external dependencies.
## parent
index: apps/api/tests/.pi-map.index.md
map: apps/api/tests/.pi-map.md
+3 -3
View File
@@ -4,10 +4,10 @@ dir: apps/api/tests/unit
index: apps/api/tests/unit/.pi-map.index.md
## role
Unit test suite for the API backend, covering configuration, Docker services, Git operations, event bus, file/SSH/terminal utilities, monitoring, notifications, and manifest compilation.
Contains isolated unit tests for individual components of the API application, covering configuration, services, utilities, and core business logic without external dependencies.
## files
- __init__.py | Empty package initialization file that marks a directory as a Python package.
- test_alembic_migrations.py | Verifies the structure, importability, and metadata of specific Alembic migration files without requiring a database connection. | exp: func:test_home_directory_migration_imports_and_rewrites() → None, call:Path, call:migration_path.exists, call:importlib.util.spec_from_file_location, call:importlib.util.module_from_spec, call:spec.loader.exec_module, call:callable, func:test_merge_migration_resolves_heads() → None, call:Path, call:migration_path.exists, call:importlib.util.spec_from_file_location, call:importlib.util.module_from_spec, call:spec.loader.exec_module, call:callable, func:test_remove_pi_agent_repo_mount_migration_imports() → None, call:Path, call:migration_path.exists, call:importlib.util.spec_from_file_location, call:importlib.util.module_from_spec, call:spec.loader.exec_module, call:callable | dep: importlib.util, pathlib, pytest, pathlib.Path
- test_alembic_migrations.py | Unit tests that verify Alembic database migration files are importable, have correct revision identifiers, and declare expected dependencies without requiring a live database. | exp: func:test_home_directory_migration_imports_and_rewrites() → None, call:Path, call:migration_path.exists, call:importlib.util.spec_from_file_location, call:importlib.util.module_from_spec, call:spec.loader.exec_module, call:callable, func:test_merge_migration_resolves_heads() → None, call:Path, call:migration_path.exists, call:importlib.util.spec_from_file_location, call:importlib.util.module_from_spec, call:spec.loader.exec_module, call:callable, func:test_remove_pi_agent_repo_mount_migration_imports() → None, call:Path, call:migration_path.exists, call:importlib.util.spec_from_file_location, call:importlib.util.module_from_spec, call:spec.loader.exec_module, call:callable, func:test_remove_pi_agent_workspace_symlink_migration_imports() → None, call:Path, call:migration_path.exists, call:importlib.util.spec_from_file_location, call:importlib.util.module_from_spec, call:spec.loader.exec_module, call:callable | dep: importlib.util, pathlib, pytest, pathlib.Path
- test_config.py | Unit tests verifying configuration settings including database URLs, auth defaults, and environment-specific cookie policies. | exp: func:test_settings_default_database_url_uses_asyncpg(monkeypatch) → None, call:monkeypatch.delenv, call:Settings, func:test_build_database_url_uses_explicit_values() → None, call:build_database_url, func:test_settings_prefers_explicit_database_url_env(monkeypatch) → None, call:monkeypatch.setenv, call:Settings, func:test_auth_settings_have_secure_defaults() → None, call:Settings, call:settings.resolved_authentik_authorize_url.endswith, call:settings.resolved_authentik_token_url.endswith, call:settings.resolved_authentik_jwks_url.endswith, func:test_cookie_policy_is_strict_in_production(monkeypatch) → None, call:monkeypatch.setenv, call:Settings, func:test_cookie_policy_is_relaxed_for_local_dev(monkeypatch) → None, call:monkeypatch.setenv, call:Settings | dep: pytest, src.config, src.database, src.config.Settings, src.database.build_database_url
- test_config_profile_resolver.py | Tests config profile resolution logic including merge functions, profile inheritance with includes, cycle detection, and git mount normalization. | exp: class:TestMergeFunctions, method:test_merge_env_vars_basic(self) → None, call:_merge_env_vars, method:test_merge_env_vars_tracks_overrides(self) → None, call:_merge_env_vars, method:test_merge_runtime_hints_basic(self) → None, call:_merge_runtime_hints, method:test_merge_files_basic(self) → None, call:_merge_files, method:test_merge_mounts_basic(self) → None, call:_merge_mounts, method:test_merge_mounts_file_override(self) → None, call:_merge_mounts, call:ResolvedMount, method:test_merge_mounts_mode_conflict(self) → None, call:_merge_mounts, call:ResolvedMount, method:test_merge_git_mounts_basic(self) → None, call:_merge_git_mounts, call:len, method:test_merge_git_mounts_concatenate_same_repo_branch(self) → None, call:_merge_git_mounts, call:len, method:test_merge_git_mounts_dedup_same_mapping(self) → None, call:_merge_git_mounts, call:len, method:test_merge_git_mounts_different_repos(self) → None, call:_merge_git_mounts, call:len, method:test_merge_git_mounts_different_branches(self) → None, call:_merge_git_mounts, call:len, call:m.get, class:TestResolveProfile, class:TestApplyResolvedProfile, method:test_mounts_directory_not_individual_files(self, tmp_path) → None, call:ResolvedProfile, call:uuid.uuid4, call:ResolvedMount, call:apply_resolved_profile, call:str, call:len, call:Path(volumes[0]["source"]).is_dir, call:(Path(volumes[0]["source"]) / "config.json").exists, call:(Path(volumes[0]["source"]) / "nested" / "file.txt").exists, method:test_directory_mount_target(self, tmp_path) → None, call:ResolvedProfile, call:uuid.uuid4, call:ResolvedMount, call:apply_resolved_profile, call:str, call:len, call:Path, call:(Path(volumes[0]["source"]) / "z.json").exists, method:test_empty_mount_produces_no_volumes(self, tmp_path) → None, call:ResolvedProfile, call:uuid.uuid4, call:ResolvedMount, call:apply_resolved_profile, call:str, method:test_home_expansion_in_directory_mount_target(self, tmp_path) → None, call:ResolvedProfile, call:uuid.uuid4, call:ResolvedMount, call:apply_resolved_profile, call:str, call:len, call:(Path(volumes[0]["source"]) / "app.toml").exists, call:Path, method:test_readonly_mount_sets_readonly_flag(self, tmp_path) → None, call:ResolvedProfile, call:uuid.uuid4, call:ResolvedMount, call:apply_resolved_profile, call:str, call:len, call:volumes[0].get, method:test_writable_mount_does_not_set_readonly_flag(self, tmp_path) → None, call:ResolvedProfile, call:uuid.uuid4, call:ResolvedMount, call:apply_resolved_profile, call:str, call:len, call:volumes[0].get, class:TestCheckIncludeCycle | dep: uuid, pathlib, pytest, sqlalchemy.ext.asyncio, src.models.config.config_profile, src.services.config.config_profile_resolver, pathlib.Path, sqlalchemy.ext.asyncio.AsyncSession
- test_docker_build.py | Unit tests for the `build_image` Docker build service function, verifying successful builds, error handling, file writing, and security. | exp: class:TestBuildImage | dep: subprocess, tempfile, pathlib, unittest.mock, pytest, src.services.build.docker_build, pathlib.Path
@@ -32,7 +32,7 @@ Unit test suite for the API backend, covering configuration, Docker services, Gi
- test_terminal_container_user.py | Unit tests for resolving container user from tool type definitions, handling legacy tools, manifest-based tools, base definition inheritance, and missing definitions. | exp: func:test_resolve_container_user_legacy_tool_type(), call:MagicMock, call:AsyncMock, call:_resolve_container_user, call:session.get.assert_awaited_once, func:test_resolve_container_user_manifest_no_manifest_id(), call:MagicMock, call:AsyncMock, call:_resolve_container_user, func:test_resolve_container_user_manifest_with_user(), call:MagicMock, call:AsyncMock, call:_resolve_container_user, func:session_get(model, obj_id), func:test_resolve_container_user_manifest_with_base_definition(), call:MagicMock, call:AsyncMock, call:_resolve_container_user, func:session_get(model, obj_id), func:test_resolve_container_user_missing_manifest_definition(), call:MagicMock, call:AsyncMock, call:_resolve_container_user, func:session_get(model, obj_id) | dep: unittest.mock, pytest, src.api.system.terminal, src.api.system.terminal._resolve_container_user
- test_terminal_session.py | Unit tests verifying TerminalSession passes container_user to docker exec --user flag when set, and omits it when not configured. | exp: func:test_start_passes_container_user_to_docker_exec() → None, call:TerminalSession, call:str, call:uuid.uuid4, call:patch, call:AsyncMock, call:session.start, call:args.index, func:test_start_omits_user_when_not_configured() → None, call:TerminalSession, call:str, call:uuid.uuid4, call:patch, call:AsyncMock, call:session.start | dep: uuid, unittest.mock, pytest, src.services.terminal.terminal_session
## arch
pytest-based unit testing with fixtures for temporary resources (git repos, Docker mocks), heavy use of mocking for external dependencies (Docker, databases), and parameterized tests for cross-platform/git-hosting scenarios.
Standard Python unittest/pytest package structure with test files mirroring the source codebase; uses dependency injection, mocking (likely unittest.mock or pytest-mock), and temporary filesystem/git fixtures to test components in isolation.
## tags
test, url, call:, git, user, call:notification, container, mounts
## symbols
@@ -67,3 +67,23 @@ def test_remove_pi_agent_repo_mount_migration_imports() -> None:
assert module.down_revision == "2026_06_14_182955"
assert callable(module.upgrade)
assert callable(module.downgrade)
@pytest.mark.unit
def test_remove_pi_agent_workspace_symlink_migration_imports() -> None:
migration_path = Path(__file__).parent.parent.parent / (
"alembic/versions/2026_06_19_113000_remove_pi_agent_workspace_symlink.py"
)
assert migration_path.exists()
spec = importlib.util.spec_from_file_location(
"remove_workspace_symlink_migration", migration_path
)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
assert module.revision == "2026_06_19_113000"
assert module.down_revision == "2026_06_15_090500"
assert callable(module.upgrade)
assert callable(module.downgrade)