84 lines
2.7 KiB
Python
84 lines
2.7 KiB
Python
from __future__ import annotations
|
|
|
|
import subprocess # noqa: S404
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from control_plane.host_commands import HostCommandError
|
|
from control_plane.host_commands import HostCommandResult
|
|
from control_plane.host_commands import build_host_command_env
|
|
from control_plane.host_commands import run_host_command
|
|
|
|
|
|
def test_run_host_command_rejects_env_without_allowlist() -> None:
|
|
"""Environment overrides must use an explicit allowlist."""
|
|
with pytest.raises(ValueError, match="allowed_env_keys"):
|
|
run_host_command(command=("true",), env_overrides={"SECRET": "value"})
|
|
|
|
|
|
def test_run_host_command_returns_captured_output() -> None:
|
|
"""Successful host commands should preserve stdout and stderr."""
|
|
completed = subprocess.CompletedProcess(
|
|
args=("echo", "ok"),
|
|
returncode=0,
|
|
stdout="ok\n",
|
|
stderr="",
|
|
)
|
|
with patch(
|
|
"control_plane.host_commands.subprocess.run",
|
|
return_value=completed,
|
|
) as mock_run:
|
|
result = run_host_command(
|
|
command=("echo", "ok"),
|
|
cwd=Path.cwd(),
|
|
env_overrides={"UV_PROJECT_ENVIRONMENT": "test"},
|
|
allowed_env_keys=frozenset({"UV_PROJECT_ENVIRONMENT"}),
|
|
)
|
|
|
|
assert result == HostCommandResult(
|
|
args=("echo", "ok"),
|
|
returncode=0,
|
|
stdout="ok\n",
|
|
stderr="",
|
|
)
|
|
forwarded_env = mock_run.call_args.kwargs["env"]
|
|
assert "DJANGO_SETTINGS_MODULE" not in forwarded_env
|
|
assert forwarded_env["UV_PROJECT_ENVIRONMENT"] == "test"
|
|
mock_run.assert_called_once()
|
|
|
|
|
|
def test_build_host_command_env_strips_platform_django_settings(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Tenant child processes must not inherit platform Django settings."""
|
|
monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "config.settings")
|
|
monkeypatch.setenv("PATH", "/usr/bin")
|
|
|
|
environment = build_host_command_env()
|
|
|
|
assert environment["PATH"] == "/usr/bin"
|
|
assert "DJANGO_SETTINGS_MODULE" not in environment
|
|
|
|
|
|
def test_run_host_command_wraps_called_process_errors() -> None:
|
|
"""Failing host commands should raise a typed exception with captured output."""
|
|
error = subprocess.CalledProcessError(
|
|
returncode=17,
|
|
cmd=("podman", "run"),
|
|
output="",
|
|
stderr="boom",
|
|
)
|
|
with (
|
|
patch("control_plane.host_commands.subprocess.run", side_effect=error),
|
|
pytest.raises(
|
|
HostCommandError,
|
|
match="Host command failed",
|
|
) as exc_info,
|
|
):
|
|
run_host_command(command=("podman", "run"))
|
|
|
|
assert exc_info.value.command_args == ("podman", "run")
|
|
assert exc_info.value.returncode == 17
|
|
assert exc_info.value.stderr == "boom"
|