The test assumed bib.sqlite always has items when present. In CI, a stale or empty bib.sqlite could exist without data, causing the export to produce an empty library.json. Now validates the database has items before asserting on the export output.
157 lines
5.3 KiB
Python
157 lines
5.3 KiB
Python
"""Integration tests for the docs build pipeline."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
_ROOT = Path(__file__).resolve().parents[2]
|
|
BIB_DB = _ROOT / "data/bib.sqlite"
|
|
EXPORT_SCRIPT = _ROOT / "docs/scripts/export_library.py"
|
|
LIBRARY_JSON = _ROOT / "docs/static/library.json"
|
|
DOCS_DIR = _ROOT / "docs/docs"
|
|
INTRO_MD = DOCS_DIR / "intro.md"
|
|
|
|
|
|
class TestExportLibrary:
|
|
"""Tests for docs/scripts/export_library.py."""
|
|
|
|
def test_export_runs_without_error(self):
|
|
result = subprocess.run(
|
|
[sys.executable, str(EXPORT_SCRIPT)],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
assert result.returncode == 0, result.stderr
|
|
|
|
def test_library_json_has_items(self):
|
|
"""library.json should have >0 items when bib.sqlite has data."""
|
|
if not BIB_DB.exists():
|
|
pytest.skip("bib.sqlite not available")
|
|
# Verify bib.sqlite is a real database with items
|
|
import sqlite3
|
|
|
|
try:
|
|
con = sqlite3.connect(str(BIB_DB))
|
|
count = con.execute("SELECT count(*) FROM items").fetchone()[0]
|
|
con.close()
|
|
except (sqlite3.OperationalError, sqlite3.DatabaseError):
|
|
pytest.skip("bib.sqlite is not a valid database")
|
|
if count == 0:
|
|
pytest.skip("bib.sqlite has no items")
|
|
# Run the export
|
|
subprocess.run([sys.executable, str(EXPORT_SCRIPT)], check=True)
|
|
data = json.loads(LIBRARY_JSON.read_text())
|
|
assert len(data["items"]) > 0, "library.json has no items"
|
|
assert len(data["tags"]) > 0, "library.json has no tags"
|
|
|
|
def test_item_has_required_fields(self):
|
|
"""Each item must have all required fields."""
|
|
if not LIBRARY_JSON.exists():
|
|
import pytest
|
|
|
|
pytest.skip("library.json not generated")
|
|
data = json.loads(LIBRARY_JSON.read_text())
|
|
if not data["items"]:
|
|
import pytest
|
|
|
|
pytest.skip("no items in library.json")
|
|
required = {
|
|
"key",
|
|
"item_type",
|
|
"title",
|
|
"url",
|
|
"abstract",
|
|
"tags",
|
|
"collections",
|
|
"creators",
|
|
}
|
|
for item in data["items"][:10]: # spot check first 10
|
|
missing = required - set(item.keys())
|
|
assert not missing, f"Item {item.get('key', '?')} missing fields: {missing}"
|
|
|
|
def test_abstracts_not_truncated(self):
|
|
"""Abstracts should not be truncated to 300 chars."""
|
|
if not LIBRARY_JSON.exists():
|
|
import pytest
|
|
|
|
pytest.skip("library.json not generated")
|
|
data = json.loads(LIBRARY_JSON.read_text())
|
|
# Find items with long abstracts
|
|
truncated = [
|
|
i["key"]
|
|
for i in data["items"]
|
|
if i["abstract"].endswith("...") and len(i["abstract"]) == 300
|
|
]
|
|
assert not truncated, f"Found truncated abstracts: {truncated[:5]}"
|
|
|
|
def test_tags_in_items_match_tag_list(self):
|
|
"""Every tag referenced by items should appear in the tags list."""
|
|
if not LIBRARY_JSON.exists():
|
|
import pytest
|
|
|
|
pytest.skip("library.json not generated")
|
|
data = json.loads(LIBRARY_JSON.read_text())
|
|
tag_names = {t["name"] for t in data["tags"]}
|
|
item_tags = set()
|
|
for item in data["items"]:
|
|
item_tags.update(item["tags"])
|
|
orphans = item_tags - tag_names
|
|
assert not orphans, f"Tags on items but not in tags list: {orphans}"
|
|
|
|
|
|
class TestIntroLinks:
|
|
"""Verify intro.md links resolve to real pages."""
|
|
|
|
def test_intro_exists(self):
|
|
assert INTRO_MD.exists(), "intro.md not found"
|
|
|
|
def test_no_broken_relative_links(self):
|
|
"""All relative links in intro.md should point to existing files."""
|
|
if not INTRO_MD.exists():
|
|
import pytest
|
|
|
|
pytest.skip("intro.md not found")
|
|
content = INTRO_MD.read_text()
|
|
# Find markdown links like [text](path)
|
|
links = re.findall(r"\[.*?\]\(([^)]+)\)", content)
|
|
for link in links:
|
|
if link.startswith("http") or link.startswith("#"):
|
|
continue
|
|
# Relative link — check it resolves
|
|
target = DOCS_DIR / link.lstrip("/")
|
|
# Could be a directory with index.md or a .md file
|
|
exists = (
|
|
target.exists()
|
|
or target.with_suffix(".md").exists()
|
|
or (target / "index.md").exists()
|
|
or target.parent.exists() # directory exists
|
|
)
|
|
assert exists, f"Broken link in intro.md: {link}"
|
|
|
|
|
|
class TestExtractDocs:
|
|
"""Verify the API doc extraction produces output."""
|
|
|
|
def test_api_docs_directory_exists(self):
|
|
api_dir = DOCS_DIR / "api"
|
|
if not api_dir.exists():
|
|
import pytest
|
|
|
|
pytest.skip("API docs not generated yet")
|
|
assert api_dir.is_dir()
|
|
|
|
def test_api_has_packages(self):
|
|
api_dir = DOCS_DIR / "api"
|
|
if not api_dir.exists():
|
|
pytest.skip("API docs not generated yet")
|
|
packages = [d.name for d in api_dir.iterdir() if d.is_dir()]
|
|
if not packages:
|
|
pytest.skip("API docs not generated yet (griffe not run)")
|
|
assert len(packages) > 0
|