Skip to content

Commit 33085a4

Browse files
author
ebembi-crdb
committed
Add CI smoke test for create_full_archive.py
- Add test_full_archive_smoke.py: builds a tiny _site/docs fixture and runs FullArchiveCreator.build() with network calls stubbed out, then verifies: output dir + index.html created, no internal /stable/ paths remain in HTML, nav assets present, and zip is created when requested - Add .github/workflows/test-full-archive.yml CI job that runs test_full_archive_smoke.py on every push/PR touching create_full_archive.py
1 parent b2d83df commit 33085a4

2 files changed

Lines changed: 211 additions & 0 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Smoke test full archive script
2+
3+
on:
4+
push:
5+
branches:
6+
- feat/offline-archive-scripts
7+
paths:
8+
- "src/current/create_full_archive.py"
9+
- "src/current/test_full_archive_smoke.py"
10+
pull_request:
11+
paths:
12+
- "src/current/create_full_archive.py"
13+
- "src/current/test_full_archive_smoke.py"
14+
15+
jobs:
16+
smoke-test:
17+
runs-on: ubuntu-latest
18+
defaults:
19+
run:
20+
working-directory: src/current
21+
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- uses: actions/setup-python@v5
26+
with:
27+
python-version: "3.11"
28+
29+
- name: Install dependencies
30+
run: pip install beautifulsoup4 requests
31+
32+
- name: Run smoke tests
33+
# Stubs out network calls (Google Fonts, nav asset downloads) so the
34+
# test works fully offline using a tiny _site/docs fixture.
35+
run: python3 test_full_archive_smoke.py
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Smoke test for create_full_archive.py.
4+
5+
Creates a minimal _site/docs fixture, runs FullArchiveCreator.build(), and
6+
verifies key invariants without requiring a full Jekyll build or network access.
7+
8+
Run from src/current/:
9+
python3 test_full_archive_smoke.py
10+
"""
11+
import re
12+
import shutil
13+
import sys
14+
import tempfile
15+
from pathlib import Path
16+
from unittest.mock import patch
17+
18+
SCRIPT_DIR = Path(__file__).parent
19+
STABLE_VERSION = "v26.1"
20+
21+
# Minimal HTML page with a /docs/-prefixed href and a Google Fonts link
22+
FIXTURE_HTML = """\
23+
<!DOCTYPE html>
24+
<html>
25+
<head>
26+
<title>Test Page</title>
27+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Poppins&display=swap">
28+
<link rel="stylesheet" href="/docs/css/styles.css">
29+
</head>
30+
<body>
31+
<a href="/docs/stable/overview.html">Overview</a>
32+
<a href="/docs/cockroachcloud/index.html">CockroachCloud</a>
33+
<script>
34+
var baseUrl = "/docs";
35+
</script>
36+
</body>
37+
</html>"""
38+
39+
FIXTURE_CSS = "body { font-family: Arial; }"
40+
41+
42+
def _build_fixture(site_dir: Path):
43+
"""Populate a minimal _site/docs tree."""
44+
version_dir = site_dir / STABLE_VERSION
45+
version_dir.mkdir(parents=True)
46+
(version_dir / "index.html").write_text(FIXTURE_HTML, encoding="utf-8")
47+
48+
css_dir = site_dir / "css"
49+
css_dir.mkdir()
50+
(css_dir / "styles.css").write_text(FIXTURE_CSS, encoding="utf-8")
51+
52+
js_dir = site_dir / "js"
53+
js_dir.mkdir()
54+
(js_dir / "jquery.min.js").write_text("/* jquery stub */", encoding="utf-8")
55+
(js_dir / "jquery.cookie.min.js").write_text("/* cookie stub */", encoding="utf-8")
56+
(js_dir / "jquery.navgoco.min.js").write_text("/* navgoco stub */", encoding="utf-8")
57+
58+
css_navgoco = site_dir / "css" / "jquery.navgoco.css"
59+
css_navgoco.write_text("/* navgoco css stub */", encoding="utf-8")
60+
61+
(site_dir / "index.html").write_text(FIXTURE_HTML, encoding="utf-8")
62+
63+
64+
def _make_creator(site_dir: Path, output_dir: Path):
65+
sys.path.insert(0, str(SCRIPT_DIR))
66+
from create_full_archive import FullArchiveCreator
67+
sys.path.pop(0)
68+
return FullArchiveCreator(
69+
site_dir=str(site_dir),
70+
output_dir=str(output_dir),
71+
stable_version=STABLE_VERSION,
72+
)
73+
74+
75+
def test_output_dir_created(site_dir, output_dir):
76+
"""Output directory is created and index.html is present."""
77+
creator = _make_creator(site_dir, output_dir)
78+
79+
# Stub out network calls so the test works offline
80+
with patch.object(creator, "download_google_fonts", return_value=None), \
81+
patch.object(creator, "ensure_nav_assets", return_value=None):
82+
creator.build(create_zip=False)
83+
84+
assert output_dir.exists(), "FAIL: output directory was not created"
85+
assert (output_dir / "index.html").exists(), "FAIL: index.html not in output"
86+
print(" PASS: output directory created and index.html present")
87+
88+
89+
def test_no_stable_artifacts(site_dir, output_dir):
90+
"""No internal /stable/ path remains in processed HTML (external https:// links are allowed)."""
91+
creator = _make_creator(site_dir, output_dir)
92+
93+
with patch.object(creator, "download_google_fonts", return_value=None), \
94+
patch.object(creator, "ensure_nav_assets", return_value=None):
95+
creator.build(create_zip=False)
96+
97+
# Match href/src that start with a relative or absolute *internal* path containing /stable/
98+
# External URLs (https://) are intentional (e.g. the archived banner link) and excluded.
99+
stable_pattern = re.compile(r'(?:href|src)=["\'](?!https?://)[^"\']*?/stable/')
100+
for html_file in output_dir.rglob("*.html"):
101+
content = html_file.read_text(encoding="utf-8", errors="replace")
102+
match = stable_pattern.search(content)
103+
assert not match, (
104+
f"FAIL: internal /stable/ artifact found in {html_file}: {match.group()}"
105+
)
106+
print(" PASS: no internal /stable/ artifacts in processed HTML")
107+
108+
109+
def test_nav_assets_present(site_dir, output_dir):
110+
"""Navigation JS assets exist in the output directory."""
111+
creator = _make_creator(site_dir, output_dir)
112+
113+
with patch.object(creator, "download_google_fonts", return_value=None), \
114+
patch.object(creator, "ensure_nav_assets", return_value=None):
115+
creator.build(create_zip=False)
116+
117+
# ensure_nav_assets is stubbed, but assets were copied from fixture
118+
assert (output_dir / "js" / "jquery.min.js").exists(), (
119+
"FAIL: jquery.min.js missing from output"
120+
)
121+
print(" PASS: nav assets present in output")
122+
123+
124+
def test_zip_created(site_dir, output_dir):
125+
"""ZIP archive is created when --zip flag is used."""
126+
creator = _make_creator(site_dir, output_dir)
127+
128+
with patch.object(creator, "download_google_fonts", return_value=None), \
129+
patch.object(creator, "ensure_nav_assets", return_value=None):
130+
creator.build(create_zip=True)
131+
132+
zip_path = output_dir.with_suffix(".zip")
133+
assert zip_path.exists(), f"FAIL: expected zip at {zip_path}"
134+
assert zip_path.stat().st_size > 0, "FAIL: zip file is empty"
135+
print(" PASS: complete_archive.zip created and non-empty")
136+
137+
138+
def main():
139+
print("Running full archive smoke tests...")
140+
failures = []
141+
142+
with tempfile.TemporaryDirectory() as tmp:
143+
tmp_path = Path(tmp)
144+
site_dir = tmp_path / "_site" / "docs"
145+
_build_fixture(site_dir)
146+
147+
tests = [
148+
("output dir created + index.html present",
149+
lambda: test_output_dir_created(site_dir, tmp_path / "out1")),
150+
("no /stable/ artifacts in output HTML",
151+
lambda: test_no_stable_artifacts(site_dir, tmp_path / "out2")),
152+
("nav assets present",
153+
lambda: test_nav_assets_present(site_dir, tmp_path / "out3")),
154+
("zip created",
155+
lambda: test_zip_created(site_dir, tmp_path / "out4")),
156+
]
157+
158+
for name, fn in tests:
159+
try:
160+
fn()
161+
except AssertionError as e:
162+
print(f" {e}")
163+
failures.append(name)
164+
except Exception as e:
165+
print(f" ERROR in '{name}': {e}")
166+
failures.append(name)
167+
168+
if failures:
169+
print(f"\nFAILED: {len(failures)} test(s): {', '.join(failures)}")
170+
sys.exit(1)
171+
else:
172+
print(f"\nAll {len(tests)} smoke tests passed.")
173+
174+
175+
if __name__ == "__main__":
176+
main()

0 commit comments

Comments
 (0)