AI Implementation feature(1082): Project Scaffold and Docker Packaging (#1)

This commit was merged in pull request #1.
This commit is contained in:
2026-08-04 17:40:24 +00:00
parent d0eed251af
commit 33c6c76ce9
27 changed files with 1098 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
from __future__ import annotations
import re
import shutil
import subprocess
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[1]
ENTRYPOINT = REPO_ROOT / "entrypoint.sh"
def _has_bash() -> bool:
return shutil.which("bash") is not None
def _minimal_path() -> str:
parts = ["/usr/bin", "/bin", "/usr/sbin", "/sbin"]
for p in parts:
if Path(p).exists():
return ":".join(parts)
return os.environ.get("PATH", "")
def _clean_env() -> dict[str, str]:
env = {
"PATH": _minimal_path(),
"DEBIAN_FRONTEND": "noninteractive",
"HOME": "/tmp",
"LANG": "C.UTF-8",
}
return env
@pytest.mark.skipif(not _has_bash(), reason="bash not available")
def test_entrypoint_rejects_bad_run_at(tmp_path: Path, monkeypatch) -> None:
"""The entrypoint must exit non-zero with a clear message on a bad RUN_AT."""
env = _clean_env()
env["MASTODON_BASE_URL"] = "https://mastodon.example"
env["MASTODON_ACCESS_TOKEN"] = "x"
env["SITE_URL"] = "https://blog.example.com"
env["HASHTAGS"] = "#throwback"
env["RUN_AT"] = "25:99"
env["TZ"] = "Europe/Berlin"
proc = subprocess.run(
["/bin/bash", str(ENTRYPOINT)],
capture_output=True,
text=True,
env=env,
timeout=10,
check=False,
)
assert proc.returncode != 0
assert "RUN_AT" in (proc.stdout + proc.stderr)
@pytest.mark.skipif(not _has_bash(), reason="bash not available")
def test_entrypoint_fails_fast_when_token_missing(tmp_path: Path, monkeypatch) -> None:
"""A missing required var must abort before cron is started."""
env = _clean_env()
env["MASTODON_BASE_URL"] = "https://mastodon.example"
env["SITE_URL"] = "https://blog.example.com"
env["HASHTAGS"] = "#throwback"
env["RUN_AT"] = "09:00"
env["TZ"] = "Europe/Berlin"
env.pop("MASTODON_ACCESS_TOKEN", None)
proc = subprocess.run(
["/bin/bash", str(ENTRYPOINT)],
capture_output=True,
text=True,
env=env,
timeout=10,
check=False,
)
assert proc.returncode != 0
combined = (proc.stdout + proc.stderr).lower()
assert "missing" in combined
assert "mastodon_access_token" in combined.lower()
def test_entrypoint_references_cron_in_foreground() -> None:
text = ENTRYPOINT.read_text()
assert re.search(r"exec\s+cron\s+-f", text), "entrypoint must exec cron in the foreground"
cron = (REPO_ROOT / "crontab" / "tenbackward.cron").read_text()
assert "SHELL=/bin/bash" in cron