davideisinger.com

My personal website
Log | Files | Refs | README

check-dispatch-hook (4939B)


      1 #!/usr/bin/env python3
      2 """Test the publication guard using disposable repositories and local pushes."""
      3 
      4 from datetime import datetime, timedelta, timezone
      5 from pathlib import Path
      6 import os
      7 import shutil
      8 import subprocess
      9 import tempfile
     10 
     11 
     12 repo = Path(__file__).resolve().parent.parent
     13 env = dict(os.environ, GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_GLOBAL=os.devnull)
     14 
     15 with tempfile.TemporaryDirectory(prefix="dispatch-hook-") as directory:
     16     root = Path(directory)
     17     work = root / "work"
     18     work.mkdir()
     19 
     20     def git(*args):
     21         result = subprocess.run(["git", "-C", str(work), *args], env=env,
     22                                 text=True, capture_output=True)
     23         assert result.returncode == 0, result.stderr
     24         return result.stdout.strip()
     25 
     26     def post(name, hours=192, draft=False, date=True):
     27         path = work / f"content/journal/dispatch-{name}/index.md"
     28         path.parent.mkdir(parents=True, exist_ok=True)
     29         timestamp = (datetime.now(timezone.utc) - timedelta(hours=hours)).isoformat()
     30         field = f"date: {timestamp}\n" if date else ""
     31         path.write_text(f"---\ntitle: Test\n{field}draft: {str(draft).lower()}\n"
     32                         "references:\n- date: 2020-01-01\n---\n\nBody.\ndate: Leave this alone.\n")
     33         return path
     34 
     35     def commit():
     36         git("add", ".")
     37         git("commit", "-m", "Test change")
     38 
     39     def push(allowed, remote="origin", ref="main", message=None):
     40         before = git("--git-dir", str(root / "origin.git"), "rev-parse", "main")
     41         result = subprocess.run(["git", "-C", str(work), "push", remote, ref],
     42                                 env=env, text=True, capture_output=True)
     43         assert (result.returncode == 0) == allowed, result.stderr
     44         if message:
     45             assert message in result.stderr, result.stderr
     46         if not allowed:
     47             assert git("--git-dir", str(root / "origin.git"), "rev-parse", "main") == before
     48 
     49     git("init", "-b", "main")
     50     git("config", "user.name", "Hook Test")
     51     git("config", "user.email", "[email protected]")
     52     git("config", "commit.gpgsign", "false")
     53     for remote in ("origin", "mirror"):
     54         git("init", "--bare", str(root / f"{remote}.git"))
     55         git("remote", "add", remote, str(root / f"{remote}.git"))
     56     for file in (".githooks/pre-push", "bin/check-dispatch-dates", "bin/timestamp"):
     57         target = work / file
     58         target.parent.mkdir(parents=True, exist_ok=True)
     59         shutil.copy2(repo / file, target)
     60     published = post("published")
     61     post("draft", draft=True)
     62     commit()
     63     git("push", "origin", "main")
     64     git("config", "core.hooksPath", ".githooks")
     65 
     66     # Old posts remain editable and renameable without changing their dates.
     67     published.write_text(published.read_text() + "Correction.\n")
     68     commit()
     69     push(True)
     70     git("mv", "content/journal/dispatch-published", "content/journal/dispatch-renamed")
     71     commit()
     72     push(True)
     73 
     74     # Drafts may be old; publishing them must use a recent committed timestamp.
     75     post("new-draft", draft=True)
     76     commit()
     77     push(True)
     78     draft = post("draft", draft=False)
     79     commit()
     80     push(False, message="publication date is")
     81     original = draft.read_text()
     82     result = subprocess.run([str(work / "bin/timestamp"), str(draft)],
     83                             env=env, text=True, capture_output=True)
     84     assert result.returncode == 0, result.stderr
     85     assert draft.read_text() != original
     86     assert "- date: 2020-01-01" in draft.read_text()
     87     assert "date: Leave this alone." in draft.read_text()
     88     push(False, message="publication date is")  # Working-tree changes don't count.
     89     commit()
     90     push(True)
     91 
     92     # Accept under 24 hours; block over 24 hours and missing dates.
     93     post("recent", hours=23)
     94     commit()
     95     push(True)
     96     post("stale", hours=25)
     97     commit()
     98     push(False, message="maximum: 24 hours")
     99     push(True, ref="main:refs/heads/writing")
    100     push(True, remote="mirror")
    101     git("reset", "--hard", "origin/main")
    102     post("missing-date", date=False)
    103     commit()
    104     push(False, message="publication date is missing or invalid")
    105     git("reset", "--hard", "origin/main")
    106 
    107     # A differently named local branch still gets checked when publishing main.
    108     git("checkout", "-b", "publishing")
    109     post("from-branch")
    110     commit()
    111     push(False, ref="publishing:main", message="publication date is")
    112 
    113     # No known remote baseline must fail clearly, rather than checking the archive.
    114     sha = git("rev-parse", "HEAD")
    115     result = subprocess.run([str(work / "bin/check-dispatch-dates"), "origin"],
    116                             cwd=work, env=env, text=True, capture_output=True,
    117                             input=f"refs/heads/main {sha} refs/heads/main {'0' * 40}\n")
    118     assert result.returncode != 0 and "does not exist yet" in result.stderr
    119 
    120 print("Dispatch hook checks passed: real local pushes, draft publication, timestamps, renames, and remote/branch scope.")