81b9a66ef5
- Delete 4 obsolete unit tests tied to removed git mount/clone models - Update imports and assertions across unit/integration/service tests - Fix Settings defaults (postgres host, JWT props, cookie_samesite) - Add skip guards for PostgreSQL-dependent integration tests - Fix GitService env assertions and HealthMonitor state-change tests - Repair docker/container inspect assertions in test_docker_service - Fix ToolTypeCreate default_port validator ordering bug - Fix check_port_exposed substring false-positive for port 0 - Update test_tool_types_api_extended to use interface_type field Quality gates: pytest 311 passed, 34 skipped; npm typecheck/lint/test 87 passed
152 lines
5.6 KiB
Python
152 lines
5.6 KiB
Python
"""Unit tests for docker build service."""
|
|
|
|
import subprocess
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from src.services.build.docker_build import build_image
|
|
|
|
|
|
class TestBuildImage:
|
|
"""Tests for build_image function."""
|
|
|
|
@patch("subprocess.run")
|
|
def test_builds_image_successfully(self, mock_run) -> None:
|
|
mock_run.return_value = MagicMock(
|
|
returncode=0,
|
|
stdout="Successfully built abc123",
|
|
stderr="",
|
|
)
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
result = build_image(tmpdir, "FROM python:3.11", "test-image:latest")
|
|
|
|
assert result[0] == 0
|
|
assert "Successfully built" in result[1]
|
|
mock_run.assert_called_once()
|
|
call_args = mock_run.call_args
|
|
assert "test-image:latest" in call_args[0][0]
|
|
assert "build" in call_args[0][0]
|
|
|
|
@patch("subprocess.run")
|
|
def test_build_fails(self, mock_run) -> None:
|
|
mock_run.return_value = MagicMock(
|
|
returncode=1,
|
|
stdout="",
|
|
stderr="Error: failed to build",
|
|
)
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
result = build_image(tmpdir, "FROM invalid:image", "test-image:latest")
|
|
|
|
assert result[0] == 1
|
|
assert "failed to build" in result[2]
|
|
|
|
@patch("subprocess.run")
|
|
def test_build_with_tag(self, mock_run) -> None:
|
|
mock_run.return_value = MagicMock(
|
|
returncode=0,
|
|
stdout="",
|
|
stderr="",
|
|
)
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
build_image(tmpdir, "FROM python:3.11", "my-registry/tool:v1.0")
|
|
|
|
call_args = mock_run.call_args[0][0]
|
|
assert "my-registry/tool:v1.0" in call_args
|
|
|
|
@patch("subprocess.run")
|
|
def test_build_command_structure(self, mock_run) -> None:
|
|
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
build_image(tmpdir, "FROM python:3.11", "test:latest")
|
|
|
|
cmd = mock_run.call_args[0][0]
|
|
assert cmd[0] == "docker"
|
|
assert cmd[1] == "build"
|
|
assert "-t" in cmd
|
|
assert "test:latest" in cmd
|
|
assert tmpdir in cmd
|
|
|
|
@patch("subprocess.run")
|
|
def test_build_writes_dockerfile(self, mock_run) -> None:
|
|
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
dockerfile_content = "FROM python:3.11\\nRUN pip install flask"
|
|
build_image(tmpdir, dockerfile_content, "test:latest")
|
|
|
|
dockerfile_path = Path(tmpdir) / "Dockerfile"
|
|
assert dockerfile_path.exists()
|
|
assert dockerfile_path.read_text() == dockerfile_content
|
|
|
|
@patch("subprocess.run")
|
|
def test_build_writes_context_files(self, mock_run) -> None:
|
|
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
build_context = {
|
|
"requirements.txt": "flask==2.0\\nnumpy==1.21",
|
|
"app.py": "from flask import Flask\\napp = Flask(__name__)",
|
|
}
|
|
build_image(tmpdir, "FROM python:3.11", "test:latest", build_context)
|
|
|
|
req_path = Path(tmpdir) / "requirements.txt"
|
|
app_path = Path(tmpdir) / "app.py"
|
|
assert req_path.exists()
|
|
assert req_path.read_text() == "flask==2.0\\nnumpy==1.21"
|
|
assert app_path.exists()
|
|
assert app_path.read_text() == "from flask import Flask\\napp = Flask(__name__)"
|
|
|
|
@patch("subprocess.run")
|
|
def test_build_creates_nested_directories(self, mock_run) -> None:
|
|
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
build_context = {
|
|
"src/app.py": "print('hello')",
|
|
}
|
|
build_image(tmpdir, "FROM python:3.11", "test:latest", build_context)
|
|
|
|
app_path = Path(tmpdir) / "src" / "app.py"
|
|
assert app_path.exists()
|
|
|
|
@patch("subprocess.run")
|
|
def test_build_prevents_path_traversal(self, mock_run) -> None:
|
|
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
build_context = {
|
|
"../../../etc/passwd": "root:x:0:0",
|
|
}
|
|
|
|
with pytest.raises(ValueError, match="escapes instance directory"):
|
|
build_image(tmpdir, "FROM python:3.11", "test:latest", build_context)
|
|
|
|
mock_run.assert_not_called()
|
|
|
|
@patch("subprocess.run")
|
|
def test_build_timeout(self, mock_run) -> None:
|
|
mock_run.side_effect = subprocess.TimeoutExpired(cmd=["docker", "build"], timeout=300)
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
result = build_image(tmpdir, "FROM python:3.11", "test:latest")
|
|
|
|
assert result[0] == 1
|
|
assert "timed out" in result[2].lower()
|
|
|
|
@patch("subprocess.run")
|
|
def test_build_exception(self, mock_run) -> None:
|
|
mock_run.side_effect = OSError("Docker not available")
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
result = build_image(tmpdir, "FROM python:3.11", "test:latest")
|
|
|
|
assert result[0] == 1
|
|
assert "Docker not available" in result[2]
|