Files
firmware-lowlevel-development/skills/firmware-lowlevel-psp-development/scripts/psp/test_psp_project_guard.py
T

201 lines
8.0 KiB
Python

#!/usr/bin/env python3
"""Behavioral tests for psp_project_guard.py."""
import json
import subprocess
import tempfile
from pathlib import Path
import psp_project_guard
def git(path: Path, *args: str) -> str:
result = subprocess.run(
["git", "-C", str(path), *args],
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
)
return result.stdout.strip()
def create_empty_project(psp_root: Path, name: str) -> Path:
project = psp_root / name
project.mkdir()
git(project, "init", "-q")
git(project, "remote", "add", "origin", f"ssh://git.example/Psp/{name}.git")
return project
def expect_guard_error(action, message: str) -> None:
try:
action()
except psp_project_guard.GuardError:
return
raise AssertionError(message)
def main() -> int:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
psp_root = root / "Psp"
psp_root.mkdir()
reference = psp_root / "REF"
reference.mkdir()
(reference / "configs").mkdir()
(reference / "configs" / "board.json").write_text(
json.dumps({"project": "REF"}), encoding="utf-8"
)
(reference / "psp.json").write_text(
json.dumps({"project": "REF"}), encoding="utf-8"
)
(reference / "build").mkdir()
(reference / "build" / "generated.bin").write_bytes(b"generated")
git(reference, "init", "-q")
git(reference, "add", "configs/board.json", "psp.json")
git(
reference,
"-c",
"user.name=PSP Test",
"-c",
"user.email=psp-test@example.invalid",
"commit",
"-qm",
"reference",
)
target = create_empty_project(psp_root, "LY-SM-YT42-AI")
original_branch = git(target, "symbolic-ref", "--short", "HEAD")
original_remote = git(target, "remote", "get-url", "origin")
inspected = psp_project_guard.inspect_project(target, psp_root)
assert inspected["mode"] == "precreated-empty-repository"
assert inspected["project"] == "LY-SM-YT42-AI"
manifest = root / "evidence" / "clone-manifest.json"
prepared = psp_project_guard.prepare(
reference, target, manifest, ["REF"], include_dirty=False, psp_root=psp_root
)
staging = Path(prepared["staging"])
assert prepared["target_mode"] == "precreated-empty-repository"
assert prepared["psp_root"] == str(psp_root)
assert not (staging / ".git").exists()
assert not (staging / "build").exists()
(staging / "psp.json").write_text(
json.dumps({"project": "NEW"}), encoding="utf-8"
)
failing = psp_project_guard.verify(manifest, ["psp.json"], [], None)
assert failing["passed"] is False
assert failing["residues"]
(staging / "configs" / "board.json").write_text(
json.dumps({"project": "NEW"}), encoding="utf-8"
)
report_path = root / "evidence" / "verify.json"
verified = psp_project_guard.verify(
manifest, ["psp.json", "configs/board.json"], [], report_path
)
assert verified["passed"] is True, verified
finalized = psp_project_guard.finalize(manifest, report_path)
assert finalized["target"] == str(target)
assert finalized["repository_preserved"] is True
assert not staging.exists()
assert (target / ".git").is_dir()
assert git(target, "symbolic-ref", "--short", "HEAD") == original_branch
assert git(target, "remote", "get-url", "origin") == original_remote
assert not (target / ".git" / "refs" / "heads" / original_branch).exists()
audit_path = root / "evidence" / "final-audit.json"
audited = psp_project_guard.audit_final(
manifest, ["psp.json", "configs/board.json"], [], audit_path
)
assert audited["passed"] is True, audited
assert audited["repository_unchanged"] is True
no_reference = create_empty_project(psp_root, "NO-REFERENCE")
empty_manifest = root / "evidence" / "empty-manifest.json"
empty_prepared = psp_project_guard.prepare(
None, no_reference, empty_manifest, [], False, psp_root
)
empty_staging = Path(empty_prepared["staging"])
assert list(empty_staging.iterdir()) == []
assert json.loads(empty_manifest.read_text(encoding="utf-8"))["reference"] is None
(empty_staging / "psp.json").write_text('{"project":"NO-REFERENCE"}', encoding="utf-8")
empty_report = root / "evidence" / "empty-verify.json"
assert psp_project_guard.verify(empty_manifest, ["psp.json"], [], empty_report)["passed"]
psp_project_guard.finalize(empty_manifest, empty_report)
assert psp_project_guard.audit_final(empty_manifest, ["psp.json"], [], None)["passed"]
assert (no_reference / ".git").is_dir()
assert (no_reference / "psp.json").is_file()
missing = psp_root / "MISSING"
expect_guard_error(
lambda: psp_project_guard.prepare(
reference, missing, root / "missing-manifest.json", [], False, psp_root
),
"missing target must be rejected",
)
outside = create_empty_project(root, "OUTSIDE")
expect_guard_error(
lambda: psp_project_guard.prepare(
reference, outside, root / "outside-manifest.json", [], False, psp_root
),
"target outside the fixed PSP root must be rejected",
)
populated = create_empty_project(psp_root, "POPULATED")
(populated / "README.md").write_text("existing\n", encoding="utf-8")
assert psp_project_guard.inspect_project(populated, psp_root)["mode"] == "existing-project"
expect_guard_error(
lambda: psp_project_guard.prepare(
reference, populated, root / "populated-manifest.json", [], False, psp_root
),
"populated target must use the existing-project update flow",
)
changed = create_empty_project(psp_root, "CHANGED")
changed_manifest = root / "evidence" / "changed-manifest.json"
changed_prepared = psp_project_guard.prepare(
reference, changed, changed_manifest, [], False, psp_root
)
changed_report = root / "evidence" / "changed-verify.json"
changed_verified = psp_project_guard.verify(changed_manifest, [], [], changed_report)
assert changed_verified["passed"] is True
git(changed, "remote", "set-url", "origin", "ssh://git.example/changed.git")
expect_guard_error(
lambda: psp_project_guard.finalize(changed_manifest, changed_report),
"Git metadata drift after prepare must block finalization",
)
assert Path(changed_prepared["staging"]).is_dir()
assert not (changed / "psp.json").exists()
(reference / "psp.json").write_text(
json.dumps({"project": "UNCOMMITTED"}), encoding="utf-8"
)
head_target = create_empty_project(psp_root, "FROM-HEAD")
head_manifest = root / "evidence" / "head-manifest.json"
from_head = psp_project_guard.prepare(
reference, head_target, head_manifest, ["REF"], False, psp_root
)
assert from_head["warnings"]
assert json.loads((Path(from_head["staging"]) / "psp.json").read_text())["project"] == "REF"
unsafe = root / "unsafe"
unsafe.mkdir()
(unsafe / "psp.json").write_text("{}", encoding="utf-8")
(unsafe / ".env").write_text("TOKEN=secret", encoding="utf-8")
unsafe_target = create_empty_project(psp_root, "UNSAFE")
expect_guard_error(
lambda: psp_project_guard.prepare(
unsafe, unsafe_target, root / "unsafe-manifest.json", [], False, psp_root
),
"credential-like files must be rejected",
)
print("PSP project guard tests passed")
return 0
if __name__ == "__main__":
raise SystemExit(main())