85 lines
3.1 KiB
Python
85 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def test_dockerignore_blocks_secrets_and_data() -> None:
|
|
dockerignore = (REPO_ROOT / ".dockerignore").read_text().splitlines()
|
|
normalized = {line.strip() for line in dockerignore if line.strip() and not line.startswith("#")}
|
|
|
|
for required in (".env", "data/", ".git/", "tests/", ".kilo/", ".venv/"):
|
|
assert required in normalized, f"missing {required} from .dockerignore"
|
|
|
|
|
|
def test_gitignore_blocks_dotenv_and_data() -> None:
|
|
gitignore = (REPO_ROOT / ".gitignore").read_text().splitlines()
|
|
normalized = {line.strip() for line in gitignore if line.strip() and not line.startswith("#")}
|
|
assert ".env" in normalized
|
|
assert "data/" in normalized
|
|
assert ".kilo/" in normalized
|
|
|
|
|
|
def test_env_example_has_no_real_tokens() -> None:
|
|
text = (REPO_ROOT / ".env.example").read_text()
|
|
assert "MASTODON_ACCESS_TOKEN=replace-me" in text
|
|
|
|
for token in ("ghp_", "gho_", "xoxb-", "Bearer ey", "AKIA"):
|
|
assert token not in text, f"found suspicious token prefix {token!r} in .env.example"
|
|
|
|
mastodon_line = next(
|
|
line for line in text.splitlines() if line.startswith("MASTODON_ACCESS_TOKEN=")
|
|
)
|
|
value = mastodon_line.split("=", 1)[1].strip()
|
|
assert not re.fullmatch(r"[A-Za-z0-9_\-]{40,}", value), (
|
|
"MASTODON_ACCESS_TOKEN placeholder should not look like a real token"
|
|
)
|
|
|
|
|
|
def test_dockerfile_does_not_copy_env_or_data() -> None:
|
|
dockerfile = (REPO_ROOT / "Dockerfile").read_text()
|
|
for blocked in (".env", "data/", "./data"):
|
|
for line in dockerfile.splitlines():
|
|
if line.lstrip().upper().startswith("COPY"):
|
|
assert blocked not in line, f"Dockerfile COPY must not reference {blocked}: {line!r}"
|
|
|
|
|
|
def test_compose_mounts_data_and_env_readonly() -> None:
|
|
compose = (REPO_ROOT / "docker-compose.yml").read_text()
|
|
assert "env_file:" in compose
|
|
assert "- .env" in compose
|
|
assert "./data:/app/data" in compose
|
|
assert "build:" in compose
|
|
|
|
|
|
def test_dockerfile_runs_entrypoint_as_root() -> None:
|
|
dockerfile = (REPO_ROOT / "Dockerfile").read_text()
|
|
lines = [line.strip() for line in dockerfile.splitlines()]
|
|
entrypoint_idx = next(
|
|
(i for i, line in enumerate(lines) if line.upper().startswith("ENTRYPOINT")),
|
|
None,
|
|
)
|
|
assert entrypoint_idx is not None, "Dockerfile must declare ENTRYPOINT"
|
|
for line in lines[entrypoint_idx:]:
|
|
if line.upper().startswith("USER "):
|
|
pytest.fail(
|
|
"Dockerfile must not switch USER after ENTRYPOINT — entrypoint.sh "
|
|
"needs root to install /etc/cron.d/tenbackward and exec cron -f"
|
|
)
|
|
|
|
|
|
def test_run_bot_wrapper_drops_privileges() -> None:
|
|
wrapper = (REPO_ROOT / "run-bot.sh").read_text()
|
|
assert "setpriv" in wrapper or "su -s" in wrapper
|
|
assert "tenbackward" in wrapper
|
|
|
|
|
|
def test_cron_template_has_env_header() -> None:
|
|
cron = (REPO_ROOT / "crontab" / "tenbackward.cron").read_text()
|
|
assert "SHELL=/bin/bash" in cron
|
|
assert "PATH=" in cron
|