63 lines
2.4 KiB
Python
63 lines
2.4 KiB
Python
"""Story-to-test traceability.
|
|
|
|
Every mapped story must map to test files that exist, every test file must be
|
|
claimed by a story (so a new test can't go unexercised), and all Phase A stories
|
|
US01-01..US01-07 must be present. Whether those tests *pass* is proven by running
|
|
the suite; this guards the mapping's completeness.
|
|
"""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
REPO = Path(__file__).resolve().parents[2]
|
|
MAP = json.loads((REPO / "tests" / "story_traceability.json").read_text())["stories"]
|
|
PHASE_A_STORIES = {f"US01-0{n}" for n in range(1, 8)}
|
|
PHASE_D_STORIES = {f"US04-0{n}" for n in range(1, 7)}
|
|
PHASE_E_STORIES = {f"US05-0{n}" for n in range(1, 7)}
|
|
PHASE_F_STORIES = {f"US06-0{n}" for n in range(1, 7)}
|
|
|
|
|
|
def test_all_phase_a_stories_are_mapped():
|
|
assert PHASE_A_STORIES <= set(MAP)
|
|
|
|
|
|
def test_all_phase_d_stories_are_mapped():
|
|
"""US04-06 acceptance: every guarded-rename story, plan through browser, is tied
|
|
to automated tests — renaming is the first thing that mutates the real library."""
|
|
assert PHASE_D_STORIES <= set(MAP)
|
|
|
|
|
|
def test_all_phase_e_stories_are_mapped():
|
|
"""US05-06 acceptance: every upload story, preflight through browser, is tied to
|
|
automated tests — upload is the one stage the app cannot take back."""
|
|
assert PHASE_E_STORIES <= set(MAP)
|
|
|
|
|
|
def test_all_phase_f_stories_are_mapped():
|
|
"""US06-06 acceptance: every archive-lifecycle story, destination through
|
|
restore, is tied to automated tests — archiving is the only stage that removes
|
|
an original."""
|
|
assert PHASE_F_STORIES <= set(MAP)
|
|
|
|
|
|
def test_every_mapped_test_file_exists_and_is_nonempty():
|
|
for story, files in MAP.items():
|
|
assert files, f"{story} maps to no tests"
|
|
for rel in files:
|
|
path = REPO / rel
|
|
assert path.is_file(), f"{story}: missing {rel}"
|
|
assert path.stat().st_size > 0, f"{story}: empty {rel}"
|
|
assert "def test_" in path.read_text(), f"{story}: no tests in {rel}"
|
|
|
|
|
|
def test_no_unexercised_phase_a_test_files():
|
|
"""Every test_*.py under tests/ is claimed by a story (no orphan coverage)."""
|
|
mapped = {rel for files in MAP.values() for rel in files}
|
|
on_disk = {
|
|
str(p.relative_to(REPO))
|
|
for p in (REPO / "tests").rglob("test_*.py")
|
|
if "__pycache__" not in p.parts
|
|
}
|
|
unmapped = on_disk - mapped
|
|
assert not unmapped, f"test files not tied to any story: {sorted(unmapped)}"
|