metube/app/tests/test_entrypoint.py
Jesus 917671c220 Refactor app bootstrap and harden container configuration
Extract environment-backed settings into a dedicated module and switch the aiohttp app to a factory-based bootstrap.

Move runtime dependencies into typed app state, tighten CORS defaults, reject sensitive inline yt-dlp options, and harden the container entrypoint validation and ownership flow.

Update tests to cover settings validation, app factory behavior, CORS policy, and entrypoint safety checks.
2026-03-30 18:43:12 -06:00

145 lines
3.6 KiB
Python

"""Tests for the hardened container entrypoint."""
from __future__ import annotations
import os
import subprocess
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[2]
ENTRYPOINT = REPO_ROOT / "docker-entrypoint.sh"
def _write_executable(path: Path, content: str) -> None:
path.write_text(content, encoding="utf-8")
path.chmod(0o755)
def _prepare_stub_bin(tmp_path: Path) -> Path:
stub_bin = tmp_path / "bin"
stub_bin.mkdir()
_write_executable(
stub_bin / "id",
"""#!/bin/sh
case "$1" in
-u) echo "${TEST_ID_U:-1000}" ;;
-g) echo "${TEST_ID_G:-1000}" ;;
*) exit 1 ;;
esac
""",
)
_write_executable(
stub_bin / "gosu",
"""#!/bin/sh
shift
"$@"
""",
)
_write_executable(
stub_bin / "chown",
"""#!/bin/sh
echo "$@" >> "${CHOWN_LOG}"
exit 0
""",
)
_write_executable(
stub_bin / "python3",
"""#!/bin/sh
echo "$@" >> "${PYTHON_LOG}"
exit 0
""",
)
_write_executable(
stub_bin / "bgutil-pot",
"""#!/bin/sh
echo "$@" >> "${BGUTIL_LOG}"
exit 0
""",
)
return stub_bin
def _base_env(tmp_path: Path) -> dict[str, str]:
download_dir = tmp_path / "downloads"
state_dir = tmp_path / "state"
temp_dir = tmp_path / "temp"
download_dir.mkdir(exist_ok=True)
state_dir.mkdir(exist_ok=True)
temp_dir.mkdir(exist_ok=True)
return {
"DOWNLOAD_DIR": str(download_dir),
"STATE_DIR": str(state_dir),
"TEMP_DIR": str(temp_dir),
"PUID": "1000",
"PGID": "1000",
"UMASK": "022",
"CHOWN_DIRS": "true",
}
def _run_entrypoint(tmp_path: Path, **overrides: str) -> subprocess.CompletedProcess[str]:
stub_bin = _prepare_stub_bin(tmp_path)
env = os.environ.copy()
env.update(_base_env(tmp_path))
env.update(
{
"PATH": f"{stub_bin}:{env['PATH']}",
"CHOWN_LOG": str(tmp_path / "chown.log"),
"PYTHON_LOG": str(tmp_path / "python.log"),
"BGUTIL_LOG": str(tmp_path / "bgutil.log"),
"TEST_ID_U": "0",
"TEST_ID_G": "0",
"UID": "",
"GID": "",
}
)
env.update(overrides)
return subprocess.run(
["sh", str(ENTRYPOINT)],
cwd=REPO_ROOT,
env=env,
text=True,
capture_output=True,
check=False,
)
@pytest.mark.parametrize(
("key", "value", "expected"),
[
("PUID", "abc", "PUID must be numeric"),
("PGID", "abc", "PGID must be numeric"),
("UMASK", "89", "UMASK must be a 3 or 4 digit octal value"),
],
)
def test_entrypoint_rejects_invalid_identity_inputs(tmp_path, key, value, expected):
result = _run_entrypoint(tmp_path, **{key: value})
assert result.returncode != 0
assert expected in result.stderr
def test_entrypoint_chowns_only_data_directories(tmp_path):
result = _run_entrypoint(tmp_path)
assert result.returncode == 0
chown_log = (tmp_path / "chown.log").read_text(encoding="utf-8")
assert "/app" not in chown_log
assert str(tmp_path / "downloads") in chown_log
assert str(tmp_path / "state") in chown_log
assert str(tmp_path / "temp") in chown_log
def test_entrypoint_fails_when_directories_are_not_accessible(tmp_path):
protected_dir = tmp_path / "state"
protected_dir.mkdir(exist_ok=True)
protected_dir.chmod(0)
try:
result = _run_entrypoint(tmp_path, CHOWN_DIRS="false")
finally:
protected_dir.chmod(0o700)
assert result.returncode != 0
assert "Configured directories are not accessible" in result.stderr