Tussilago/tests/test_runtime_plans.py
2026-04-27 20:43:26 +02:00

219 lines
8.3 KiB
Python

from __future__ import annotations
from pathlib import Path
import pytest
from control_plane.runtime_plans import DjangoApplicationLaunchConfig
from control_plane.runtime_plans import DjangoContainerImageBuildConfig
from control_plane.runtime_plans import DjangoContainerRuntimeConfig
from control_plane.runtime_plans import PostgresContainerConfig
from control_plane.runtime_plans import RedisContainerConfig
from control_plane.runtime_plans import build_django_container_image_command
from control_plane.runtime_plans import build_django_container_run_command
from control_plane.runtime_plans import build_django_migrate_command
from control_plane.runtime_plans import build_django_server_command
from control_plane.runtime_plans import build_postgres_container_command
from control_plane.runtime_plans import build_redis_container_command
def test_build_postgres_container_command_uses_hardening_flags() -> None:
"""PostgreSQL container plan should include core isolation and auth settings."""
command = build_postgres_container_command(
PostgresContainerConfig(
container_name="postgres-tenant-site",
network_name="tenant-net",
hostname="postgres.internal",
username="site",
database_name="site",
data_directory=Path("/var/lib/tussilago/postgres/site"),
password_file=Path("/run/tussilago/secrets/postgres-password"),
),
)
assert "--cap-drop=all" in command
assert "--cap-add=CHOWN" in command
assert "--cap-add=FOWNER" in command
assert "--cap-add=SETUID" in command
assert "--cap-add=SETGID" in command
assert "--cap-add=DAC_OVERRIDE" in command
assert "--security-opt=no-new-privileges" in command
assert "POSTGRES_PASSWORD_FILE=/run/secrets/postgres-password" in command
assert "password_encryption=scram-sha-256" in command
assert command[-1] == "password_encryption=scram-sha-256"
def test_build_redis_container_command_requires_password() -> None:
"""Redis container plan should enforce password auth and readonly base image."""
command = build_redis_container_command(
RedisContainerConfig(
container_name="redis-tenant-site",
network_name="tenant-net",
hostname="redis.internal",
data_directory=Path("/var/lib/tussilago/redis/site"),
password_file=Path("/run/tussilago/secrets/redis-password"),
),
)
assert "--read-only" in command
assert "/run/secrets/redis-password" in command[-1]
assert command[-1].startswith("redis_password=$(cat /run/secrets/redis-password)")
def test_build_django_server_command_supports_project_reuse() -> None:
"""Gunicorn command should optionally reuse the repo-managed uv project."""
command = build_django_server_command(
DjangoApplicationLaunchConfig(
wsgi_module="tenant_site.wsgi:application",
bind_host="127.0.0.1",
port=9000,
workers=1,
uv_project_path=Path("/workspace/Tussilago"),
),
)
assert command == (
"uv",
"run",
"--project",
"/workspace/Tussilago",
"gunicorn",
"--bind",
"127.0.0.1:9000",
"--workers",
"1",
"--access-logfile",
"-",
"--error-logfile",
"-",
"--capture-output",
"--graceful-timeout",
"30",
"--timeout",
"60",
"tenant_site.wsgi:application",
)
def test_build_django_migrate_command_supports_repo_reuse() -> None:
"""Migration command should optionally reuse repo-managed uv dependencies."""
assert build_django_migrate_command(Path("/workspace/Tussilago")) == (
"uv",
"run",
"--project",
"/workspace/Tussilago",
"python",
"manage.py",
"migrate",
"--noinput",
)
def test_build_django_migrate_command_supports_explicit_python_executable() -> None:
"""Migration command should also support container-local Python execution."""
assert build_django_migrate_command(python_executable=Path("/usr/local/bin/python")) == (
"/usr/local/bin/python",
"manage.py",
"migrate",
"--noinput",
)
def test_build_django_container_image_command_targets_checked_in_containerfile() -> None:
"""Image build plan should reference an explicit Containerfile and context."""
command = build_django_container_image_command(
DjangoContainerImageBuildConfig(
image_reference="localhost/tussilago-test-django:latest",
containerfile_path=Path("/workspace/Tussilago/control_plane/container_assets/test_django/Containerfile"),
context_directory=Path("/workspace/Tussilago/control_plane/container_assets/test_django"),
),
)
assert command == (
"podman",
"build",
"--pull=missing",
"--tag",
"localhost/tussilago-test-django:latest",
"--file",
"/workspace/Tussilago/control_plane/container_assets/test_django/Containerfile",
"/workspace/Tussilago/control_plane/container_assets/test_django",
)
def test_build_django_container_run_command_uses_publish_and_secret_mounts() -> None:
"""Django runtime plan should publish localhost only and mount generated assets read-only."""
command = build_django_container_run_command(
DjangoContainerRuntimeConfig(
container_name="django-tenant-site",
network_name="tenant-net",
hostname="django.internal",
image_reference="localhost/tussilago-test-django:latest",
application_directory=Path("/var/lib/tussilago/test-app"),
host_port=19000,
environment=(
("DJANGO_SECRET_KEY", "secret"),
("DJANGO_SETTINGS_MODULE", "tenant_site.settings"),
),
secret_mounts=((Path("/var/lib/tussilago/postgres-secret"), "/run/postgres-secret"),),
labels=(("tussilago.deployment-id", "deployment-1"),),
),
command=("/usr/local/bin/python", "-m", "gunicorn"),
detach=True,
)
assert "--read-only" in command
assert "--publish" in command
assert "127.0.0.1:19000:8000" in command
assert "/var/lib/tussilago/test-app:/srv/test-app:Z,ro" in command
assert "/var/lib/tussilago/postgres-secret:/run/postgres-secret:Z,ro" in command
assert "DJANGO_SETTINGS_MODULE=tenant_site.settings" in command
assert "tussilago.deployment-id=deployment-1" in command
def test_build_django_container_run_command_supports_pod_membership() -> None:
"""Django runtime plan should join a Podman pod instead of publishing ports when requested."""
command = build_django_container_run_command(
DjangoContainerRuntimeConfig(
container_name="django-tenant-site",
network_name="ignored-net",
hostname="django.internal",
image_reference="localhost/tussilago-test-django:latest",
application_directory=Path("/var/lib/tussilago/test-app"),
pod_name="pod-tenant-site",
host_port=19000,
),
command=("/usr/local/bin/python", "-m", "gunicorn"),
detach=True,
)
assert "--pod" in command
assert "pod-tenant-site" in command
assert "--publish" not in command
def test_build_django_server_command_supports_explicit_python_executable() -> None:
"""Local smoke flows may launch Gunicorn via an explicit Python executable."""
command = build_django_server_command(
DjangoApplicationLaunchConfig(
wsgi_module="tenant_site.wsgi:application",
bind_host="127.0.0.1",
port=9000,
workers=1,
python_executable=Path("/venv/bin/python"),
),
)
assert command[:3] == ("/venv/bin/python", "-m", "gunicorn")
def test_build_django_server_command_rejects_conflicting_python_modes() -> None:
"""Server command builder should reject mixed uv and direct-python execution modes."""
with pytest.raises(ValueError, match="mutually exclusive"):
build_django_server_command(
DjangoApplicationLaunchConfig(
wsgi_module="tenant_site.wsgi:application",
python_executable=Path("/venv/bin/python"),
uv_project_path=Path("/workspace/Tussilago"),
),
)