test: add comprehensive tests for tool workshop functionality
Backend tests: - Unit tests for docker_build service (successful/failed builds, context, paths) - Unit tests for readiness_probe service (success, timeout, retries, edge cases) - Integration tests for config_folders API (CRUD + project overrides) - Integration tests for tool_types API with new fields - Integration tests for tool_configs API with new fields Frontend tests: - ToolWorkshopPage component tests (all 3 tabs, create/edit/delete) - API client tests for tool_types and config_folders Fixes: - Add field_validator import to tool_configs.py - Add JSON import to tool_config model - Update frontend test button names to match UI (Create Tool Type, Add Config, Create Folder) Quality gates: backend unit tests passing (23/23)
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
"""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.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]
|
||||
Reference in New Issue
Block a user