regenerate_website_pages() ran at step 3.7, before the step-4 publish call. generate_episode_pages.py is driven entirely by the RSS feed, so the episode being published was never in the data it read: the generator exited 0, printed its success line, and silently omitted the new episode. Its page only appeared on the NEXT publish. Social posts link to /episode/<slug>/, so every launch-day link across X, Bluesky, Discord, Instagram, Threads, Facebook, LinkedIn, Mastodon and Nostr 404'd during exactly the window the post was circulating. That held for all 58 published episodes. The existing test only asserted the generator gets invoked with --sitemap, which passed throughout. Added two tests pinning the call between the publish and the wrangler deploy, verified to fail against the previous ordering. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
117 lines
3.9 KiB
Python
117 lines
3.9 KiB
Python
"""Publishing must rebuild the static episode pages and the sitemap.
|
|
|
|
Before static pages existed, publish_episode.py appended one entry to
|
|
sitemap.xml itself. That appender is gone — generate_episode_pages.py owns the
|
|
sitemap now — so the publish has to invoke it, or a newly published episode
|
|
would have no page and never reach the sitemap.
|
|
|
|
A generator failure must never abort a publish: by the time this runs the audio
|
|
is already live on Castopod and the RSS feed has been rebuilt.
|
|
"""
|
|
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
import publish_episode
|
|
|
|
|
|
class _Result:
|
|
def __init__(self, returncode=0, stderr=""):
|
|
self.returncode = returncode
|
|
self.stderr = stderr
|
|
self.stdout = ""
|
|
|
|
|
|
def test_invokes_the_generator_with_sitemap(monkeypatch):
|
|
calls = []
|
|
monkeypatch.setattr(subprocess, "run", lambda cmd, **kw: calls.append(cmd) or _Result())
|
|
|
|
assert publish_episode.regenerate_website_pages() is True
|
|
|
|
assert len(calls) == 1
|
|
cmd = calls[0]
|
|
assert "generate_episode_pages.py" in " ".join(cmd)
|
|
assert "--sitemap" in cmd
|
|
|
|
|
|
def test_uses_the_running_interpreter(monkeypatch):
|
|
"""Must not shell out to a bare 'python' that may not have the venv."""
|
|
calls = []
|
|
monkeypatch.setattr(subprocess, "run", lambda cmd, **kw: calls.append(cmd) or _Result())
|
|
|
|
publish_episode.regenerate_website_pages()
|
|
|
|
assert calls[0][0] == sys.executable
|
|
|
|
|
|
def test_generator_failure_is_not_fatal(monkeypatch):
|
|
monkeypatch.setattr(subprocess, "run",
|
|
lambda cmd, **kw: _Result(returncode=1, stderr="boom"))
|
|
|
|
assert publish_episode.regenerate_website_pages() is False
|
|
|
|
|
|
def test_generator_timeout_is_not_fatal(monkeypatch):
|
|
def _boom(cmd, **kw):
|
|
raise subprocess.TimeoutExpired(cmd, 300)
|
|
|
|
monkeypatch.setattr(subprocess, "run", _boom)
|
|
|
|
assert publish_episode.regenerate_website_pages() is False
|
|
|
|
|
|
def test_missing_generator_is_not_fatal(monkeypatch):
|
|
def _boom(cmd, **kw):
|
|
raise OSError("no such file")
|
|
|
|
monkeypatch.setattr(subprocess, "run", _boom)
|
|
|
|
assert publish_episode.regenerate_website_pages() is False
|
|
|
|
|
|
def test_publish_flow_calls_it_after_copying_the_transcript():
|
|
"""Guards the wiring, not just the helper."""
|
|
source = Path(publish_episode.__file__).read_text()
|
|
copy_at = source.index("Transcript copied to website/transcripts/")
|
|
call_at = source.index("regenerate_website_pages()", copy_at)
|
|
assert call_at > copy_at
|
|
|
|
|
|
def test_generator_runs_after_the_publish_call():
|
|
"""Ordering regression guard.
|
|
|
|
generate_episode_pages.py is driven entirely by the RSS feed, so it only
|
|
sees an episode once Castopod has published it. For 58 episodes the
|
|
regenerate call sat at step 3.7 — before the step-4 publish — so the
|
|
episode being published was silently absent from its own run and its page
|
|
only appeared on the NEXT publish. Social posts link to /episode/<slug>/,
|
|
so every launch-day link 404'd.
|
|
|
|
Asserted on source order because the surrounding publish flow does network
|
|
and filesystem work that isn't practical to drive end to end here.
|
|
"""
|
|
src = Path(publish_episode.__file__).read_text()
|
|
body = src[src.index("def main("):]
|
|
|
|
publish_call = body.index("published = publish_episode(")
|
|
regen_call = body.index("regenerate_website_pages()")
|
|
deploy_call = body.index("wrangler")
|
|
|
|
assert publish_call < regen_call, (
|
|
"regenerate_website_pages() must run AFTER publish_episode(), or the new "
|
|
"episode is missing from the feed the generator reads"
|
|
)
|
|
assert regen_call < deploy_call, (
|
|
"regenerate_website_pages() must run BEFORE the wrangler deploy, or the "
|
|
"freshly built page never ships"
|
|
)
|
|
|
|
|
|
def test_regenerate_is_called_exactly_once_in_main():
|
|
src = Path(publish_episode.__file__).read_text()
|
|
body = src[src.index("def main("):]
|
|
assert body.count("regenerate_website_pages()") == 1
|