"""Unit tests for the manifest compiler.""" import pytest from src.services.build.manifest_compiler import ( compile_compose, compile_dockerfile, compile_entrypoint, get_manifest_home_dir, ) @pytest.mark.unit def test_compile_dockerfile_creates_config_dirs_for_user() -> None: """The Dockerfile must pre-create ~/.config and chown it to the container user.""" manifest = { "base_image": "ubuntu:24.04", "interface_type": "terminal", "user": {"name": "dev", "uid": 1000, "gid": 1000}, } dockerfile = compile_dockerfile(manifest) assert "groupadd -g 1000 dev" in dockerfile assert "useradd -u 1000 -g 1000 -m -s /bin/bash dev" in dockerfile assert "mkdir -p /home/dev && chown -R dev:dev /home/dev" in dockerfile assert "mkdir -p /home/dev/.config && chown -R dev:dev /home/dev/.config" in dockerfile assert "mkdir -p /home/dev/.local/share && chown -R dev:dev /home/dev/.local/share" in dockerfile assert "mkdir -p /home/dev/.cache && chown -R dev:dev /home/dev/.cache" in dockerfile @pytest.mark.unit def test_compile_dockerfile_no_user_does_not_create_home() -> None: """Without a user config, no home/user setup should be emitted.""" manifest = { "base_image": "ubuntu:24.04", "interface_type": "terminal", } dockerfile = compile_dockerfile(manifest) assert "useradd" not in dockerfile assert "/home/" not in dockerfile @pytest.mark.unit class TestGetManifestHomeDir: """Tests for get_manifest_home_dir precedence.""" def test_home_directory_in_manifest_wins(self) -> None: manifest = { "home_directory": "/home/custom", "user": {"name": "dev"}, } assert get_manifest_home_dir(manifest) == "/home/custom" def test_user_name_derives_home(self) -> None: manifest = {"user": {"name": "dev", "uid": 1000, "gid": 1000}} assert get_manifest_home_dir(manifest) == "/home/dev" def test_root_fallback(self) -> None: manifest = {"base_image": "ubuntu:24.04"} assert get_manifest_home_dir(manifest) == "/root" def test_empty_home_directory_falls_back(self) -> None: manifest = {"home_directory": "", "user": {"name": "dev"}} assert get_manifest_home_dir(manifest) == "/home/dev" @pytest.mark.unit class TestCompileDockerfileHomeDirectory: """Tests that compile_dockerfile honors manifest.home_directory.""" def test_env_home_and_workdir_use_home_directory(self) -> None: manifest = { "base_image": "ubuntu:24.04", "interface_type": "terminal", "home_directory": "/home/custom", "user": {"name": "dev", "uid": 1000, "gid": 1000}, } dockerfile = compile_dockerfile(manifest) assert "ENV HOME=/home/custom" in dockerfile assert "WORKDIR /home/custom" in dockerfile def test_workspace_symlink_created(self) -> None: manifest = { "base_image": "ubuntu:24.04", "interface_type": "terminal", "home_directory": "/home/custom", "user": {"name": "dev", "uid": 1000, "gid": 1000}, } dockerfile = compile_dockerfile(manifest) assert "ln -sfn /home/custom/{{WORKSPACE_NAME}} /workspace" in dockerfile def test_runtime_working_dir_overrides_home_workdir(self) -> None: manifest = { "base_image": "ubuntu:24.04", "interface_type": "terminal", "home_directory": "/home/custom", "user": {"name": "dev", "uid": 1000, "gid": 1000}, "runtime": {"working_dir": "/app/code"}, } dockerfile = compile_dockerfile(manifest) assert "WORKDIR /app/code" in dockerfile assert "WORKDIR /home/custom" not in dockerfile def test_working_dir_expands_tilde(self) -> None: manifest = { "base_image": "ubuntu:24.04", "interface_type": "terminal", "home_directory": "/home/custom", "user": {"name": "dev", "uid": 1000, "gid": 1000}, "runtime": {"working_dir": "~/code"}, } dockerfile = compile_dockerfile(manifest) assert "WORKDIR /home/custom/code" in dockerfile @pytest.mark.unit class TestCompileComposeHomeDirectory: """Tests that compile_compose uses home_directory for volumes and working_dir.""" def test_default_repo_mount_synthesized(self) -> None: manifest = { "base_image": "ubuntu:24.04", "interface_type": "terminal", "home_directory": "/home/custom", "user": {"name": "dev", "uid": 1000, "gid": 1000}, } variables = { "IMAGE_TAG": "test:latest", "INSTANCE_NAME": "test-instance", "REPO_PATH": "/host/repos/my-app", "WORKSPACE_NAME": "my-app", "TOOL_PORT": 0, "EXTRA_ENV": {}, "EXTRA_VOLUMES": [], } compose = compile_compose(manifest, variables) assert "/host/repos/my-app:/home/custom/my-app" in compose def test_explicit_repo_mount_preserved(self) -> None: manifest = { "base_image": "ubuntu:24.04", "interface_type": "terminal", "home_directory": "/home/custom", "user": {"name": "dev", "uid": 1000, "gid": 1000}, "mounts": [ {"source_type": "repo", "target": "/opt/code", "readonly": True} ], } variables = { "IMAGE_TAG": "test:latest", "INSTANCE_NAME": "test-instance", "REPO_PATH": "/host/repos/my-app", "WORKSPACE_NAME": "my-app", "TOOL_PORT": 0, "EXTRA_ENV": {}, "EXTRA_VOLUMES": [], } compose = compile_compose(manifest, variables) assert "/host/repos/my-app:/opt/code:ro" in compose assert "/home/custom/my-app" not in compose def test_working_dir_expands_home(self) -> None: manifest = { "base_image": "ubuntu:24.04", "interface_type": "terminal", "home_directory": "/home/custom", "user": {"name": "dev", "uid": 1000, "gid": 1000}, "runtime": {"working_dir": "$HOME/code"}, } variables = { "IMAGE_TAG": "test:latest", "INSTANCE_NAME": "test-instance", "REPO_PATH": "/host/repos/my-app", "WORKSPACE_NAME": "my-app", "TOOL_PORT": 0, "EXTRA_ENV": {}, "EXTRA_VOLUMES": [], } compose = compile_compose(manifest, variables) assert "working_dir: /home/custom/code" in compose @pytest.mark.unit class TestCompileEntrypoint: """Tests for the generated permission-fixing entrypoint.""" def test_entrypoint_creates_home_and_workspace(self) -> None: manifest = { "base_image": "ubuntu:24.04", "interface_type": "terminal", "home_directory": "/home/custom", "user": {"name": "dev", "uid": 1000, "gid": 1000}, } entrypoint = compile_entrypoint(manifest) assert 'mkdir -p "$HOME_DIR"' in entrypoint assert 'mkdir -p "$WORKSPACE_TARGET"' in entrypoint assert 'ln -sfn "$WORKSPACE_TARGET" /workspace' in entrypoint def test_entrypoint_fixes_mount_owners(self) -> None: manifest = { "base_image": "ubuntu:24.04", "interface_type": "terminal", "home_directory": "/home/custom", "user": {"name": "dev", "uid": 1000, "gid": 1000}, "mounts": [ {"target": "~/.config", "readonly": False}, {"target": "/opt/readonly", "readonly": True}, ], } entrypoint = compile_entrypoint(manifest) assert 'fix_owner "/home/custom/.config"' in entrypoint assert 'fix_owner "/opt/readonly"' not in entrypoint