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["VISIBILITY"] = "public" env["SITE_URL"] = "https://blog.example.com" env["HASHTAGS"] = "#throwback" env["THROWNBACK_PREFIX"] = "Throwback:" env["MAX_RETRIES"] = "3" 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["VISIBILITY"] = "public" env["SITE_URL"] = "https://blog.example.com" env["HASHTAGS"] = "#throwback" env["THROWNBACK_PREFIX"] = "Throwback:" env["MAX_RETRIES"] = "3" 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