fix: use repository name for workspace mount target
WORKSPACE_NAME was computed from os.path.basename(repo_path), so when a
workspace path ended in a directory like 'main', the container mount target
became /home/user/main instead of /home/user/{repo-name}.
- Use GitRepository.name for WORKSPACE_NAME/REPO_NAME in manifest and
legacy dockerfile flows
- Add unit test verifying prepare_manifest_instance uses repo.name even
when the workspace path basename differs
Quality gates:
- pytest tests/unit: 213 passed
- ruff: clean on changed files
- mypy: clean on changed files
This commit is contained in:
+1
-1
@@ -16,7 +16,7 @@ dir: .
|
||||
Trust boundary: index routes, map orients, source decides.
|
||||
|
||||
## role
|
||||
Infrastructure and deployment configuration for a self-hosted project management platform with containerized services, reverse proxy, and OAuth2 authentication.
|
||||
Infrastructure and deployment configuration for a self-hosted project management platform with backend API, web frontend, PostgreSQL, Redis, and Traefik reverse proxy integration.
|
||||
## parent
|
||||
-
|
||||
## children
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@ index: ./.pi-map.index.md
|
||||
Trust boundary: index routes, map orients, source decides.
|
||||
|
||||
## role
|
||||
Infrastructure and deployment configuration for a self-hosted project management platform with containerized services, reverse proxy, and OAuth2 authentication.
|
||||
Infrastructure and deployment configuration for a self-hosted project management platform with backend API, web frontend, PostgreSQL, Redis, and Traefik reverse proxy integration.
|
||||
## files
|
||||
- .env.example | Provides a template of environment variables for configuring a Headquarter application with PostgreSQL, Redis, Authentik SSO, and Docker/Traefik deployment
|
||||
- .gitignore | Specifies files and directories for Git to ignore across a multi-language project with Python, Node, and custom tooling | dep: Git
|
||||
@@ -31,7 +31,7 @@ Infrastructure and deployment configuration for a self-hosted project management
|
||||
- progress.md | Tracks completed and remaining tasks for a backend-frontend code refactoring project organized in 7 phases
|
||||
- swap-pane | Empty file with no functionality
|
||||
## arch
|
||||
Docker Compose microservices with PostgreSQL/Redis persistence, Traefik edge routing, environment-driven configuration, and multi-phase frontend/backend refactoring.
|
||||
Containerized microservices architecture using Docker Compose with environment-driven configuration, externalized secrets via .env files, and reverse proxy pattern for TLS-terminated multi-domain deployment.
|
||||
## tags
|
||||
docker, redis, git, application, postgresql, compose, traefik, project
|
||||
## symbols
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: apps/api
|
||||
|
||||
## role
|
||||
FastAPI backend API that manages projects, git repositories, and development tools via Docker instances with PostgreSQL persistence.
|
||||
Self-hosted FastAPI backend API that manages projects, git repositories, and development tools via Docker instances
|
||||
## parent
|
||||
index: apps/.pi-map.index.md
|
||||
map: apps/.pi-map.md
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ dir: apps/api
|
||||
index: apps/api/.pi-map.index.md
|
||||
|
||||
## role
|
||||
FastAPI backend API that manages projects, git repositories, and development tools via Docker instances with PostgreSQL persistence.
|
||||
Self-hosted FastAPI backend API that manages projects, git repositories, and development tools via Docker instances
|
||||
## files
|
||||
- .dockerignore | Specifies files and directories to exclude from Docker build context to reduce image size and avoid copying unnecessary files into containers. | dep: Docker
|
||||
- Dockerfile | Multi-stage Docker build for a Python application with Docker socket access, Cloudflare tunneling, and database dependency waiting | dep: python:3.11-slim, gcc, libpq-dev, docker-ce-cli, docker-compose-plugin, cloudflared, uvicorn, pyproject.toml dependencies
|
||||
@@ -14,7 +14,7 @@ FastAPI backend API that manages projects, git repositories, and development too
|
||||
- uv.lock | Lock file for the uv Python package manager that pins exact dependency versions and their artifact hashes for reproducible installations | dep: uv, Python 3.11+, aiosqlite, alembic, annotated-doc, annotated-types, anyio, ast-serialize, asyncpg, and many other PyPI packages
|
||||
- wait-for-db.sh | Wait for a PostgreSQL database to become available before executing a command, with configurable retry logic. | dep: nc (netcat), sh (POSIX shell), sleep
|
||||
## arch
|
||||
Async Python FastAPI with SQLAlchemy/Alembic migrations, multi-stage Docker containerization, Cloudflare tunnel integration, and uv-based dependency management.
|
||||
Async Python backend with FastAPI, PostgreSQL via Alembic migrations, Docker socket integration, Cloudflare tunneling, and multi-stage containerized deployment with uv dependency management
|
||||
## tags
|
||||
docker, alembic, python, database, fastapi, postgresql, asyncpg, uvicorn
|
||||
## symbols
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: apps/api/src
|
||||
|
||||
## role
|
||||
Core API server package for the "Headquarter" backend, handling configuration, database connectivity, logging, and FastAPI application initialization.
|
||||
Core application package for the Headquarter API, providing configuration, database connectivity, structured logging, and FastAPI application initialization.
|
||||
## parent
|
||||
index: apps/api/.pi-map.index.md
|
||||
map: apps/api/.pi-map.md
|
||||
|
||||
@@ -4,7 +4,7 @@ dir: apps/api/src
|
||||
index: apps/api/src/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Core API server package for the "Headquarter" backend, handling configuration, database connectivity, logging, and FastAPI application initialization.
|
||||
Core application package for the Headquarter API, providing configuration, database connectivity, structured logging, and FastAPI application initialization.
|
||||
## files
|
||||
- __init__.py | Marks the directory as a Python package for the Headquarter API.
|
||||
- config.py | Defines application configuration settings with environment-based overrides using Pydantic, including database URLs, service domains, OAuth/Authentik integration, JWT/session settings, and computed properties for environment-specific behavior. | exp: class:Settings, func:build_database_url(user: str, password: str, host: str, port: int, database: str) → str | dep: pydantic, pydantic_settings
|
||||
@@ -12,7 +12,7 @@ Core API server package for the "Headquarter" backend, handling configuration, d
|
||||
- logging_config.py | Configures structured JSON logging with correlation ID injection, custom formatters, and HTTP request/exception middleware for a FastAPI application. | exp: class:CorrelationIdFilter, method:filter(self, record: logging.LogRecord) → bool, call:get_correlation_id, class:JSONFormatter, method:format(self, record: logging.LogRecord) → str, call:self.formatTime, call:record.getMessage, call:getattr, call:self.formatException, call:json.dumps, method:formatTime(self, record: logging.LogRecord, datefmt) → str, call:time.strftime, call:time.gmtime, class:RequestLoggingMiddleware, method:dispatch(self, request: Request, call_next: Callable) → Response, call:time.time, call:logger.info, call:call_next, call:int, call:logger.error, call:type, call:traceback.format_exc, class:ExceptionLoggingMiddleware, method:dispatch(self, request: Request, call_next: Callable) → Response, call:call_next, call:logger.critical, call:traceback.format_exc, func:configure_logging(level) → None, call:JSONFormatter, call:logging.StreamHandler, call:console_handler.setFormatter, call:console_handler.addFilter, call:CorrelationIdFilter, call:root_logger.setLevel, call:logging.getLogger("uvicorn").setLevel, call:logging.getLogger("uvicorn.access").setLevel, call:logging.getLogger("sqlalchemy.engine").setLevel, call:logger.info, call:logging.getLevelName | dep: json, logging, sys, time, traceback, collections.abc, fastapi, starlette.middleware.base, src.services.shared.correlation
|
||||
- main.py | Initializes and configures a FastAPI application for the "Headquarter API" with database setup, middleware, routing, and background services. | exp: func:_sanitize_validation_errors(errors), call:error.get, call:str, call:ctx.items, call:isinstance, call:type, call:sanitized.append, func:validation_exception_handler(request: Request, exc: RequestValidationError), call:exc.errors, call:logger.warning, call:_sanitize_validation_errors, call:JSONResponse, func:on_startup(), call:logger.info, call:init_database, call:logger.error, call:sys.exit, call:_health_monitor.start, call:seed_builtin_tool_types, func:on_shutdown(), call:logger.info, call:_health_monitor.stop | dep: logging, os, fastapi, fastapi.exceptions, fastapi.middleware.cors, fastapi.responses, fastapi.staticfiles, src.api.config, src.api.project, src.api.system, src.api.tool, src.api.user, src.api.workspace, src.config, src.models, src.database, src.logging_config, src.seeds.builtin_tool_types, src.services.instance, src.services.shared, sys, src.api.*
|
||||
## arch
|
||||
Layered async architecture using FastAPI with Pydantic settings management, SQLAlchemy async ORM with Alembic migrations, structured JSON logging with correlation ID tracking, and environment-driven configuration with OAuth/Authentik integration.
|
||||
Layered architecture with environment-based Pydantic configuration, async SQLAlchemy with retry resilience, structured JSON logging with correlation ID tracking, and modular FastAPI composition with middleware and background services.
|
||||
## tags
|
||||
src, database, logging, call:logger.info, api, middleware, fastapi, filter
|
||||
## symbols
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: apps/api/src/services
|
||||
|
||||
## role
|
||||
Marker package for the services layer in the API application
|
||||
Service layer for business logic encapsulation in the API application
|
||||
## parent
|
||||
index: apps/api/src/.pi-map.index.md
|
||||
map: apps/api/src/.pi-map.md
|
||||
|
||||
@@ -4,11 +4,11 @@ dir: apps/api/src/services
|
||||
index: apps/api/src/services/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Marker package for the services layer in the API application
|
||||
Service layer for business logic encapsulation in the API application
|
||||
## files
|
||||
- __init__.py | Empty file with no functionality
|
||||
## arch
|
||||
Standard Python package structure using __init__.py for namespace declaration
|
||||
Minimal/placeholder package following Python package convention with empty __init__.py, awaiting future service module implementations
|
||||
## tags
|
||||
init, empty, functionality
|
||||
## symbols
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: apps/api/src/services/tool
|
||||
|
||||
## role
|
||||
Provides Docker container lifecycle management for tool instances with git repository mounting, configuration resolution, and SSH tunnel connectivity.
|
||||
Manages lifecycle and runtime environment for Docker-based tool execution instances including repository access, configuration, and network connectivity.
|
||||
## parent
|
||||
index: apps/api/src/services/.pi-map.index.md
|
||||
map: apps/api/src/services/.pi-map.md
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -887,7 +887,10 @@ async def prepare_manifest_instance(
|
||||
# The actual resolution happens in resolve_git_mounts; we store placeholder
|
||||
git_mount_vars[f"GIT_MOUNT_{ref}"] = ""
|
||||
|
||||
repo_name = os.path.basename(os.path.normpath(repo_path))
|
||||
# Use the repository name for the workspace/repo mount target, not the
|
||||
# directory name of a workspace/clone path (which may be "main" or similar).
|
||||
repo = await session.get(GitRepository, instance.repository_id)
|
||||
repo_name = repo.name if repo else os.path.basename(os.path.normpath(repo_path))
|
||||
variables = {
|
||||
"IMAGE_TAG": image_tag,
|
||||
"INSTANCE_NAME": instance.name.lower(),
|
||||
@@ -1049,8 +1052,7 @@ async def create_tool_instance(
|
||||
)
|
||||
|
||||
home_dir = tool_type.home_directory or "/home/user"
|
||||
repo_name = os.path.basename(os.path.normpath(repo_path))
|
||||
workspace_target = f"{home_dir}/{repo_name}"
|
||||
workspace_target = f"{home_dir}/{repo.name}"
|
||||
|
||||
compose_content = f"""version: "3.8"\nservices:
|
||||
app:
|
||||
@@ -1086,15 +1088,14 @@ async def create_tool_instance(
|
||||
|
||||
# Manifest templates use WORKSPACE_PATH; REPO_PATH is retained as a
|
||||
# deprecated alias for backward compatibility with older templates.
|
||||
repo_name = os.path.basename(os.path.normpath(repo_path))
|
||||
variables = {
|
||||
"IMAGE_TAG": image_tag,
|
||||
"INSTANCE_NAME": instance_name.lower(),
|
||||
"INSTANCE_DIR": instance_dir,
|
||||
"WORKSPACE_PATH": repo_path,
|
||||
"REPO_PATH": repo_path,
|
||||
"REPO_NAME": repo_name,
|
||||
"WORKSPACE_NAME": repo_name,
|
||||
"REPO_NAME": repo.name,
|
||||
"WORKSPACE_NAME": repo.name,
|
||||
"SSH_PATH": "",
|
||||
"TOOL_PORT": tool_port,
|
||||
"EXTRA_ENV": {},
|
||||
@@ -1115,7 +1116,7 @@ async def create_tool_instance(
|
||||
"TOOL_PORT": tool_port,
|
||||
"USER_ID": str(user_id),
|
||||
"PROJECT_ID": str(project_id),
|
||||
"WORKSPACE_NAME": os.path.basename(os.path.normpath(repo_path)),
|
||||
"WORKSPACE_NAME": repo.name,
|
||||
"HOME_DIRECTORY": tool_type.home_directory or "/home/user",
|
||||
}
|
||||
compose_content = render_compose_template(tool_type.compose_template, variables)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: apps/api/tests
|
||||
|
||||
## role
|
||||
Shared test infrastructure and fixtures for the API application's test suite.
|
||||
Provides shared testing infrastructure and fixtures for the API application's test suite.
|
||||
## parent
|
||||
index: apps/api/.pi-map.index.md
|
||||
map: apps/api/.pi-map.md
|
||||
|
||||
@@ -4,11 +4,11 @@ dir: apps/api/tests
|
||||
index: apps/api/tests/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Shared test infrastructure and fixtures for the API application's test suite.
|
||||
Provides shared testing infrastructure and fixtures for the API application's test suite.
|
||||
## files
|
||||
- conftest.py | Provides shared pytest fixtures for testing a FastAPI application with async SQLite database, authenticated clients, and test data setup. | exp: func:test_client() → Generator[TestClient, None, None], call:create_async_engine, call:engine.begin, call:conn.run_sync, call:asyncio.run, call:init_db, call:async_sessionmaker, call:patch, call:TestClient, call:app.dependency_overrides.pop, call:engine.dispose, func:init_db(), call:engine.begin, call:conn.run_sync, func:override_get_db_session() → AsyncGenerator[AsyncSession, None], call:async_sessionmaker, func:db_session(test_client) → AsyncGenerator[AsyncSession, None], call:app.dependency_overrides.get, call:override_fn, call:gen.asend, call:gen.aclose, call:create_async_engine, call:engine.begin, call:conn.run_sync, call:async_sessionmaker, call:engine.dispose, func:authenticated_client(test_client) → Generator[TestClient, None, None], call:str, call:uuid.uuid4, call:Settings, call:app.dependency_overrides.get, call:override_fn, call:gen.asend, call:User, call:uuid.UUID, call:session.add, call:session.commit, call:gen.aclose, call:asyncio.run, call:create_test_user, call:create_session_cookie, call:test_client.cookies.set, func:create_test_user(), call:app.dependency_overrides.get, call:override_fn, call:gen.asend, call:User, call:uuid.UUID, call:session.add, call:session.commit, call:gen.aclose, func:test_project_and_repo(authenticated_client) → tuple[str, str], call:uuid.uuid4, call:Settings, call:authenticated_client.cookies.get, call:decode_session_cookie, call:uuid.UUID, call:asyncio.run, call:get_user_id, call:app.dependency_overrides.get, call:override_fn, call:gen.asend, call:Project, call:session.add, call:GitRepository, call:session.commit, call:gen.aclose, call:create_project_and_repo, call:str, raise:RuntimeError, func:get_user_id(), call:Settings, call:authenticated_client.cookies.get, call:decode_session_cookie, call:uuid.UUID, func:create_project_and_repo(), call:app.dependency_overrides.get, call:override_fn, call:gen.asend, call:Project, call:session.add, call:GitRepository, call:session.commit, call:gen.aclose, func:admin_client(test_client) → Generator[TestClient, None, None], call:str, call:uuid.uuid4, call:Settings, call:app.dependency_overrides.get, call:override_fn, call:gen.asend, call:User, call:uuid.UUID, call:session.add, call:session.commit, call:gen.aclose, call:asyncio.run, call:create_admin_user, call:create_session_cookie, call:test_client.cookies.set, func:create_admin_user(), call:app.dependency_overrides.get, call:override_fn, call:gen.asend, call:User, call:uuid.UUID, call:session.add, call:session.commit, call:gen.aclose | dep: asyncio, os, typing, unittest.mock, pytest, pytest_asyncio, fastapi.testclient, sqlalchemy.ext.asyncio, src.config, src.models.base, src.main, src.auth.dependencies, uuid, src.auth.session, src.models.user.user, src.models.project.project, src.models.project.git_repository, fastapi, sqlalchemy, aiosqlite, src.models, src.auth
|
||||
## arch
|
||||
Pytest plugin architecture with dependency-injected fixtures providing async database sessions, authenticated HTTP clients, and reusable test data factories.
|
||||
Pytest fixture-based testing architecture with async SQLite test database, dependency injection overrides for authentication, and modular helper utilities for common test scenarios.
|
||||
## tags
|
||||
call:app.dependency, call:create, overrides.get, call:override, fn, call:gen.asend, call:gen.aclose, user
|
||||
## symbols
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: apps/api/tests/unit
|
||||
|
||||
## role
|
||||
Unit test suite for the API application, covering database migrations, configuration, Docker services, Git operations, event handling, file management, health monitoring, manifest compilation, notifications, and SSH key management.
|
||||
Unit test suite for the API application, covering database migrations, configuration, Docker services, Git operations, file handling, health monitoring, manifest compilation, and notification systems.
|
||||
## parent
|
||||
index: apps/api/tests/.pi-map.index.md
|
||||
map: apps/api/tests/.pi-map.md
|
||||
|
||||
@@ -4,7 +4,7 @@ dir: apps/api/tests/unit
|
||||
index: apps/api/tests/unit/.pi-map.index.md
|
||||
|
||||
## role
|
||||
Unit test suite for the API application, covering database migrations, configuration, Docker services, Git operations, event handling, file management, health monitoring, manifest compilation, notifications, and SSH key management.
|
||||
Unit test suite for the API application, covering database migrations, configuration, Docker services, Git operations, file handling, health monitoring, manifest compilation, and notification systems.
|
||||
## files
|
||||
- __init__.py | Empty file with no functionality
|
||||
- test_alembic_migrations.py | Unit tests that verify Alembic database migrations 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 | dep: importlib.util, pathlib, pytest, importlib
|
||||
@@ -19,7 +19,7 @@ Unit test suite for the API application, covering database migrations, configura
|
||||
- test_git_url_parser.py | Tests for git URL parsing utilities that extract base repository URLs, validate clone URLs, and parse various git URL formats across GitHub, GitLab, and Bitbucket. | exp: class:TestExtractBaseRepoUrl, method:test_github_tree_url(self), call:extract_base_repo_url, method:test_github_blob_url(self), call:extract_base_repo_url, method:test_github_pull_url(self), call:extract_base_repo_url, method:test_github_issues_url(self), call:extract_base_repo_url, method:test_github_valid_url(self), call:extract_base_repo_url, method:test_github_url_with_query_params(self), call:extract_base_repo_url, method:test_gitlab_tree_url(self), call:extract_base_repo_url, method:test_gitlab_blob_url(self), call:extract_base_repo_url, method:test_gitlab_merge_request_url(self), call:extract_base_repo_url, method:test_gitlab_valid_url(self), call:extract_base_repo_url, method:test_bitbucket_src_url(self), call:extract_base_repo_url, method:test_bitbucket_valid_url(self), call:extract_base_repo_url, method:test_ssh_url(self), call:extract_base_repo_url, method:test_ssh_url_without_git_suffix(self), call:extract_base_repo_url, method:test_invalid_url(self), call:extract_base_repo_url, method:test_empty_url(self), call:extract_base_repo_url, class:TestIsValidCloneUrl, method:test_valid_ssh_url(self), call:is_valid_clone_url, method:test_valid_https_url(self), call:is_valid_clone_url, method:test_browser_url(self), call:is_valid_clone_url, method:test_url_without_git_suffix(self), call:is_valid_clone_url, method:test_invalid_url(self), call:is_valid_clone_url, class:TestParseGitUrl, method:test_valid_git_url(self), call:parse_git_url, method:test_browser_url(self), call:parse_git_url, method:test_invalid_url(self), call:parse_git_url, method:test_empty_url(self), call:parse_git_url, method:test_ssh_url(self), call:parse_git_url | dep: src.utils.git_url_parser, pytest
|
||||
- test_health_monitor.py | Unit tests for HealthMonitor state-transition logic covering container crash detection, tunnel failure detection, recovery detection, write deduplication, exception resilience, and start/stop lifecycle. | exp: func:event_bus() → InstanceEventBus, call:InstanceEventBus, call:bus._reset_for_testing, func:health_monitor(event_bus: InstanceEventBus) → HealthMonitor, call:HealthMonitor, func:_create_running_instance(db_session) → ToolInstance, call:User, call:uuid.uuid4, call:db_session.add, call:db_session.commit, call:ToolInstance, func:test_detects_container_crash(db_session, event_bus: InstanceEventBus, health_monitor: HealthMonitor) → None, call:_create_running_instance, call:events_captured.append, call:event_bus.subscribe, call:patch, call:health_monitor._check_instance, call:db_session.refresh, call:len, call:db_session.execute, call:select(HealthCheck).where, call:result.scalar_one, func:capture_event(payload: InstanceEventPayload) → None, call:events_captured.append, func:test_detects_tunnel_failure(db_session, event_bus: InstanceEventBus, health_monitor: HealthMonitor) → None, call:_create_running_instance, call:events_captured.append, call:event_bus.subscribe, call:patch, call:health_monitor._check_instance, call:db_session.refresh, call:len, call:db_session.execute, call:select(HealthCheck).where, call:result.scalar_one, func:capture_event(payload: InstanceEventPayload) → None, call:events_captured.append, func:test_detects_recovery(db_session, event_bus: InstanceEventBus, health_monitor: HealthMonitor) → None, call:_create_running_instance, call:db_session.commit, call:HealthSnapshot, call:events_captured.append, call:event_bus.subscribe, call:patch, call:health_monitor._check_instance, call:db_session.refresh, call:len, call:db_session.execute, call:select(HealthCheck).where, call:result.scalar_one, func:capture_event(payload: InstanceEventPayload) → None, call:events_captured.append, func:test_skips_writes_when_no_state_change(db_session, event_bus: InstanceEventBus, health_monitor: HealthMonitor) → None, call:_create_running_instance, call:HealthSnapshot, call:patch, call:health_monitor._check_instance, call:db_session.execute, call:select(HealthCheck).where, call:len, call:result.scalars().all, func:test_docker_exception_resilience(db_session, event_bus: InstanceEventBus, health_monitor: HealthMonitor) → None, call:_create_running_instance, call:events_captured.append, call:event_bus.subscribe, call:patch, call:RuntimeError, call:health_monitor._check_instance, call:db_session.execute, call:select(HealthCheck).where, call:result.scalar_one_or_none, func:capture_event(payload: InstanceEventPayload) → None, call:events_captured.append, func:test_monitor_start_stop(health_monitor: HealthMonitor) → None, call:health_monitor.start, call:task.done, call:health_monitor.stop, call:suppress, call:task.cancelled | dep: asyncio, uuid, contextlib, unittest.mock, pytest, sqlalchemy, src.models.system.health_check, src.models.tool.tool_instance, src.models.user.user, src.services.instance.event_bus, src.services.instance.health_monitor
|
||||
- test_home_path_expansion.py | Unit tests for home directory path expansion (~ and $HOME) in container paths and manifest home directory resolution. | exp: class:TestExpandContainerPath, method:test_tilde_slash_expands(self) → None, call:expand_container_path, method:test_tilde_alone_expands(self) → None, call:expand_container_path, method:test_dollar_home_slash_expands(self) → None, call:expand_container_path, method:test_dollar_home_alone_expands(self) → None, call:expand_container_path, method:test_absolute_path_unchanged(self) → None, call:expand_container_path, method:test_relative_path_unchanged(self) → None, call:expand_container_path, method:test_tilde_in_middle_unchanged(self) → None, call:expand_container_path, method:test_dollar_home_in_middle_unchanged(self) → None, call:expand_container_path, method:test_root_home(self) → None, call:expand_container_path, class:TestGetManifestHomeDir, method:test_with_user_block(self) → None, call:get_manifest_home_dir, method:test_without_user_block(self) → None, call:get_manifest_home_dir, method:test_with_empty_user_name(self) → None, call:get_manifest_home_dir, method:test_with_none_user_name(self) → None, call:get_manifest_home_dir | dep: pytest, src.services.config.config_profile_resolver, src.services.build.manifest_compiler
|
||||
- test_instance_service.py | Unit tests for home directory expansion in docker-compose file modification via instance_service | exp: class:TestModifyComposeFile, method:test_extra_volumes_expand_home_dir(self, tmp_path), call:compose_path.write_text, call:modify_compose_file, call:str, call:compose_path.read_text, method:test_working_directory_expands_home_dir(self, tmp_path), call:compose_path.write_text, call:modify_compose_file, call:str, call:compose_path.read_text | dep: pytest, src.services.tool.instance_service
|
||||
- test_instance_service.py | Unit tests for tool instance service functions that modify docker-compose files and prepare manifest instances with home directory expansion and workspace name resolution. | exp: class:TestModifyComposeFile, method:test_extra_volumes_expand_home_dir(self, tmp_path), call:compose_path.write_text, call:modify_compose_file, call:str, call:compose_path.read_text, method:test_working_directory_expands_home_dir(self, tmp_path), call:compose_path.write_text, call:modify_compose_file, call:str, call:compose_path.read_text, class:Result, func:test_prepare_manifest_instance_uses_repo_name_not_workspace_dir(), call:MagicMock, call:AsyncMock, call:Result, call:prepare_manifest_instance, func:session_get(model, obj_id), func:fake_run(cmd), call:Result | dep: unittest.mock, pytest, src.services.tool.instance_service, subprocess, src.services.tool
|
||||
- test_lifecycle_hooks.py | Unit tests for lifecycle hook helper functions that derive notification titles and determine whether events should trigger notifications. | exp: class:TestDeriveTitle, method:test_known_event_types(self) → None, call:_derive_title, method:test_unknown_event_type(self) → None, call:_derive_title, class:TestShouldNotify, method:test_error_events_are_notified(self) → None, call:_should_notify, method:test_health_changed_running_is_notified(self) → None, call:_should_notify, method:test_created_started_stopped_restarted_deleted_filtered(self) → None, call:_should_notify, method:test_health_changed_non_running_filtered(self) → None, call:_should_notify | dep: pytest, src.services.instance.lifecycle_hooks
|
||||
- test_manifest_compiler.py | Unit tests for a manifest compiler that generates Dockerfiles, docker-compose files, and entrypoint scripts from manifest configurations. | exp: class:TestGetManifestHomeDir, method:test_home_directory_in_manifest_wins(self) → None, call:get_manifest_home_dir, method:test_user_name_derives_home(self) → None, call:get_manifest_home_dir, method:test_root_fallback(self) → None, call:get_manifest_home_dir, method:test_empty_home_directory_falls_back(self) → None, call:get_manifest_home_dir, class:TestCompileDockerfileHomeDirectory, method:test_env_home_and_workdir_use_home_directory(self) → None, call:compile_dockerfile, method:test_workspace_symlink_created(self) → None, call:compile_dockerfile, method:test_runtime_workspace_not_baked_into_image(self) → None, call:compile_dockerfile, method:test_runtime_working_dir_overrides_home_workdir(self) → None, call:compile_dockerfile, method:test_working_dir_expands_tilde(self) → None, call:compile_dockerfile, class:TestCompileComposeHomeDirectory, method:test_default_repo_mount_synthesized(self) → None, call:compile_compose, method:test_explicit_repo_mount_preserved(self) → None, call:compile_compose, method:test_workspace_name_substituted_in_mount_target(self) → None, call:compile_compose, method:test_working_dir_expands_home(self) → None, call:compile_compose, class:TestCompileEntrypoint, method:test_entrypoint_creates_home_and_workspace(self) → None, call:compile_entrypoint, method:test_entrypoint_removes_stale_placeholder_directory(self) → None, call:compile_entrypoint, method:test_entrypoint_uses_root_then_sudo_for_workspace_symlink(self) → None, call:compile_entrypoint, call:entrypoint.find, method:test_entrypoint_fixes_mount_owners(self) → None, call:compile_entrypoint, func:test_compile_dockerfile_creates_config_dirs_for_user() → None, call:compile_dockerfile, func:test_compile_dockerfile_no_user_does_not_create_home() → None, call:compile_dockerfile, func:test_compile_dockerfile_uses_user_npm_prefix() → None, call:compile_dockerfile, func:test_compile_dockerfile_starts_as_root_and_drops_privileges() → None, call:compile_dockerfile, call:compile_entrypoint, func:test_compile_compose_runs_as_root() → None, call:compile_compose | dep: pytest, src.services.build.manifest_compiler
|
||||
- test_migration_metadata.py | Tests Alembic database migration files for correct table definitions and revision chain metadata | exp: func:test_initial_migration_defines_all_core_tables() → None, call:Path(__file__).resolve, call:spec_from_file_location, call:module_from_spec, call:spec.loader.exec_module, func:test_refresh_tokens_migration_has_expected_revision_chain() → None, call:Path(__file__).resolve, call:spec_from_file_location, call:module_from_spec, call:spec.loader.exec_module | dep: pytest, importlib.util, pathlib, pathlib.Path
|
||||
@@ -30,7 +30,7 @@ Unit test suite for the API application, covering database migrations, configura
|
||||
- test_readiness_probe.py | Unit tests for a Docker container readiness probe service that executes commands via docker exec with retry logic. | exp: class:TestExecuteProbe, class:TestIntegrationScenarios | dep: unittest.mock, src.services.shared.readiness_probe, subprocess
|
||||
- test_ssh_keys.py | Unit tests for SSH key preparation functionality including file creation, permissions, ownership, and error handling | exp: class:TestPrepareSshKeyFiles | dep: os, pathlib, unittest.mock, pytest, src.services.shared.ssh_keys
|
||||
## arch
|
||||
Standard Python unittest/pytest pattern with heavy use of mocking for external dependencies (Docker, Git, subprocess, database) to enable fast, isolated tests without requiring live infrastructure.
|
||||
Standard Python unittest/pytest layout with test modules mirroring production code structure, using mocking for external dependencies (Docker, Git, subprocess, database) and parameterized tests for URL parsing and edge cases.
|
||||
## tags
|
||||
test, url, call:notification, git, home, call:, merge, call:db
|
||||
## symbols
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
"""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
|
||||
from src.services.tool.instance_service import modify_compose_file, prepare_manifest_instance
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@@ -42,3 +44,77 @@ class TestModifyComposeFile:
|
||||
|
||||
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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: openspec
|
||||
|
||||
## role
|
||||
Defines the OpenSpec methodology and configuration for managing living software requirements documentation within the project repository.
|
||||
Defines the OpenSpec methodology and configuration for managing software requirements, specifications, and task tracking as living documentation within a project repository.
|
||||
## parent
|
||||
index: ./.pi-map.index.md
|
||||
map: ./.pi-map.md
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
dir: openspec/changes
|
||||
|
||||
## role
|
||||
Manages change tracking, versioning, and history for OpenAPI specification modifications
|
||||
Manages change tracking and history for OpenAPI specification modifications
|
||||
## parent
|
||||
index: openspec/.pi-map.index.md
|
||||
map: openspec/.pi-map.md
|
||||
|
||||
Reference in New Issue
Block a user