initial commit after one day coding agent session

This commit is contained in:
Hans Aschauer 2026-02-07 07:45:57 +01:00
commit 372af75b90
88 changed files with 22694 additions and 0 deletions

View file

@ -0,0 +1,304 @@
"""Tests for UV Package Installer module."""
import pytest
from pathlib import Path
from unittest.mock import Mock, patch, mock_open
import tempfile
import shutil
from mcp_forge.builder.uv_installer import UVInstaller
@pytest.fixture
def cache_path(tmp_path):
"""Temporary cache directory."""
cache_dir = tmp_path / "uv_cache"
return cache_dir
@pytest.fixture
def installer(cache_path):
"""UVInstaller instance."""
return UVInstaller(cache_path)
def test_init_creates_cache_directory(cache_path):
"""Test that __init__ creates cache directory."""
assert not cache_path.exists()
installer = UVInstaller(cache_path)
assert cache_path.exists()
assert cache_path.is_dir()
def test_init_with_existing_cache(cache_path):
"""Test initialization with existing cache directory."""
cache_path.mkdir(parents=True)
installer = UVInstaller(cache_path)
assert cache_path.exists()
def test_generate_requirements_single_package(installer):
"""Test generating requirements.txt with single package."""
packages = ["numpy>=1.24.0"]
requirements = installer.generate_requirements(packages)
assert requirements == "numpy>=1.24.0"
def test_generate_requirements_multiple_packages(installer):
"""Test generating requirements.txt with multiple packages."""
packages = ["numpy>=1.24.0", "pandas==2.0.0", "requests"]
requirements = installer.generate_requirements(packages)
lines = requirements.strip().split('\n')
assert len(lines) == 3
assert "numpy>=1.24.0" in lines
assert "pandas==2.0.0" in lines
assert "requests" in lines
def test_generate_requirements_empty_list(installer):
"""Test generating requirements.txt with empty list."""
packages = []
requirements = installer.generate_requirements(packages)
assert requirements == ""
def test_generate_containerfile_structure(installer):
"""Test Containerfile has correct structure."""
base_image = "docker.io/python:3.11-slim"
packages = ["numpy>=1.24.0", "pandas"]
containerfile = installer.generate_containerfile(base_image, packages)
# Check key sections are present
assert f"FROM {base_image}" in containerfile
assert "pip install" in containerfile and "uv" in containerfile
assert "COPY" in containerfile and "requirements.txt" in containerfile
assert "RUN uv pip install" in containerfile
assert "WORKDIR" in containerfile
def test_generate_containerfile_with_python_version(installer):
"""Test Containerfile generation with specific Python version."""
base_image = "docker.io/python:3.12-slim"
packages = ["numpy"]
containerfile = installer.generate_containerfile(
base_image, packages, python_version="3.12"
)
assert "3.12" in containerfile or "python:3.12" in base_image
def test_generate_containerfile_uses_requirements(installer):
"""Test Containerfile copies and uses requirements.txt."""
base_image = "docker.io/python:3.11-slim"
packages = ["numpy", "pandas"]
containerfile = installer.generate_containerfile(base_image, packages)
# Should copy requirements.txt for layer caching
assert "COPY" in containerfile and "requirements.txt" in containerfile
assert "-r requirements.txt" in containerfile
def test_generate_containerfile_creates_user(installer):
"""Test Containerfile creates non-root user."""
base_image = "docker.io/python:3.11-slim"
packages = ["numpy"]
containerfile = installer.generate_containerfile(base_image, packages)
# Should create user for security
assert "useradd" in containerfile.lower() or "USER" in containerfile
def test_create_build_context_creates_directory(installer):
"""Test that build context directory is created."""
base_image = "docker.io/python:3.11-slim"
packages = ["numpy", "pandas"]
context_path = installer.create_build_context(base_image, packages)
try:
assert context_path.exists()
assert context_path.is_dir()
finally:
if context_path.exists():
shutil.rmtree(context_path)
def test_create_build_context_contains_containerfile(installer):
"""Test that build context contains Containerfile."""
base_image = "docker.io/python:3.11-slim"
packages = ["numpy"]
context_path = installer.create_build_context(base_image, packages)
try:
containerfile_path = context_path / "Containerfile"
assert containerfile_path.exists()
content = containerfile_path.read_text()
assert f"FROM {base_image}" in content
finally:
if context_path.exists():
shutil.rmtree(context_path)
def test_create_build_context_contains_requirements(installer):
"""Test that build context contains requirements.txt."""
base_image = "docker.io/python:3.11-slim"
packages = ["numpy>=1.24.0", "pandas"]
context_path = installer.create_build_context(base_image, packages)
try:
requirements_path = context_path / "requirements.txt"
assert requirements_path.exists()
content = requirements_path.read_text()
assert "numpy>=1.24.0" in content
assert "pandas" in content
finally:
if context_path.exists():
shutil.rmtree(context_path)
def test_create_build_context_returns_temp_directory(installer):
"""Test that build context is in temp directory."""
base_image = "docker.io/python:3.11-slim"
packages = ["numpy"]
context_path = installer.create_build_context(base_image, packages)
try:
# Should be in system temp directory
temp_dir = Path(tempfile.gettempdir())
assert temp_dir in context_path.parents
finally:
if context_path.exists():
shutil.rmtree(context_path)
def test_get_cache_volume_mount_returns_dict(installer):
"""Test cache volume mount returns proper dict."""
mount_config = installer.get_cache_volume_mount()
assert isinstance(mount_config, dict)
assert "bind" in mount_config
assert "mode" in mount_config
assert mount_config["mode"] == "rw"
def test_get_cache_volume_mount_includes_cache_path(installer, cache_path):
"""Test cache volume mount includes cache path."""
mount_config = installer.get_cache_volume_mount()
# The bind target should reference UV cache location
assert "bind" in mount_config
assert "/cache" in mount_config["bind"] or "uv" in mount_config["bind"].lower()
def test_generate_containerfile_security_practices(installer):
"""Test Containerfile follows security best practices."""
base_image = "docker.io/python:3.11-slim"
packages = ["numpy"]
containerfile = installer.generate_containerfile(base_image, packages)
# Should not run as root
assert "USER" in containerfile
# Should set working directory
assert "WORKDIR" in containerfile
def test_generate_requirements_preserves_version_specs(installer):
"""Test that version specifiers are preserved exactly."""
packages = [
"numpy>=1.24.0,<2.0.0",
"pandas==2.0.0",
"requests~=2.28.0",
"scipy!=1.10.0"
]
requirements = installer.generate_requirements(packages)
for package in packages:
assert package in requirements
def test_generate_containerfile_with_extras(installer):
"""Test Containerfile works with package extras."""
base_image = "docker.io/python:3.11-slim"
packages = ["requests[security]>=2.28.0", "pandas[excel]"]
containerfile = installer.generate_containerfile(base_image, packages)
# Should handle extras in requirements
assert "COPY" in containerfile and "requirements.txt" in containerfile
assert "RUN uv pip install" in containerfile
def test_create_build_context_with_custom_python_version(installer):
"""Test build context creation with custom Python version."""
base_image = "docker.io/python:3.12-slim"
packages = ["numpy"]
context_path = installer.create_build_context(
base_image, packages, python_version="3.12"
)
try:
containerfile = (context_path / "Containerfile").read_text()
assert "3.12" in containerfile or "python:3.12" in containerfile
finally:
if context_path.exists():
shutil.rmtree(context_path)
def test_generate_containerfile_optimizes_layer_caching(installer):
"""Test that Containerfile structure optimizes Docker layer caching."""
base_image = "docker.io/python:3.11-slim"
packages = ["numpy", "pandas", "scipy"]
containerfile = installer.generate_containerfile(base_image, packages)
lines = containerfile.split('\n')
# UV install should come before requirements copy
uv_install_idx = None
requirements_idx = None
for i, line in enumerate(lines):
if 'uv' in line.lower() and ('pip install' in line.lower() or 'ADD' in line):
uv_install_idx = i
if 'COPY' in line and 'requirements.txt' in line:
requirements_idx = i
# UV installation should be cached separately
assert uv_install_idx is not None
# Requirements copy should happen for cache busting
assert requirements_idx is not None
def test_empty_packages_list_creates_valid_containerfile(installer):
"""Test that empty package list still creates valid Containerfile."""
base_image = "docker.io/python:3.11-slim"
packages = []
containerfile = installer.generate_containerfile(base_image, packages)
# Should still have base structure
assert f"FROM {base_image}" in containerfile
assert "WORKDIR" in containerfile