test_review_idempotency.py
file-level
1
files
1
commits
0
hotspots
0
🧊 dead
0
💥 blast risk
| 1 | """Data-integrity tests for review idempotency (§K5.12).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from pathlib import Path |
| 6 | from unittest.mock import patch |
| 7 | |
| 8 | import pytest |
| 9 | |
| 10 | from cli.atomic import WriteFailure |
| 11 | from cli.kit_root import kit_root |
| 12 | from tests.support import FIXTURES, git_status_runner, pass_provider_factory, run_cli, write_config |
| 13 | from tools.freeze_reviewer.artifact import parse_artifact |
| 14 | from tools.freeze_reviewer.stamp import reference_digest, write_stamp |
| 15 | from tools.freeze_reviewer.types import ReviewerSettings |
| 16 | |
| 17 | |
| 18 | def _review_pass(path: Path, repo: Path) -> None: |
| 19 | rel = path.relative_to(repo).as_posix() |
| 20 | run_cli( |
| 21 | ["review", "--freeze", rel], |
| 22 | cwd=repo, |
| 23 | runner=git_status_runner(), |
| 24 | kit=kit_root(), |
| 25 | review_provider_factory=pass_provider_factory(), |
| 26 | ) |
| 27 | |
| 28 | |
| 29 | @pytest.mark.parametrize( |
| 30 | "fixture_name", |
| 31 | ["freeze-artifact.md", "freeze-artifact.yaml"], |
| 32 | ) |
| 33 | def test_restamp_digest_stable(tmp_path: Path, fixture_name: str) -> None: |
| 34 | write_config(tmp_path, "config-git-only.yaml") |
| 35 | if fixture_name.endswith(".md"): |
| 36 | target = tmp_path / "docs" / "freeze.md" |
| 37 | target.parent.mkdir(parents=True) |
| 38 | else: |
| 39 | target = tmp_path / "freeze.yaml" |
| 40 | target.write_text((FIXTURES / fixture_name).read_text(encoding="utf-8"), encoding="utf-8") |
| 41 | _review_pass(target, tmp_path) |
| 42 | after_first = target.read_bytes() |
| 43 | _review_pass(target, tmp_path) |
| 44 | assert target.read_bytes() == after_first |
| 45 | |
| 46 | |
| 47 | def test_operator_forced_md_restamp_stable(tmp_path: Path) -> None: |
| 48 | write_config(tmp_path, "config-git-only.yaml") |
| 49 | target = tmp_path / "notes.md" |
| 50 | target.write_text("# operator forced\n", encoding="utf-8") |
| 51 | _review_pass(target, tmp_path) |
| 52 | first = target.read_bytes() |
| 53 | _review_pass(target, tmp_path) |
| 54 | assert target.read_bytes() == first |
| 55 | |
| 56 | |
| 57 | def test_whitespace_invariant_digest(tmp_path: Path) -> None: |
| 58 | loose = tmp_path / "loose.yaml" |
| 59 | loose.write_text( |
| 60 | "phase: K5b-test\noutputs:\n - id: a\n path: docs/a.md\n frozen: true\n", |
| 61 | encoding="utf-8", |
| 62 | ) |
| 63 | parsed_loose = parse_artifact(loose, rel_path="loose.yaml") |
| 64 | tight = tmp_path / "tight.yaml" |
| 65 | tight.write_text( |
| 66 | 'phase: "K5b-test"\noutputs:\n - {id: a, path: docs/a.md, frozen: true}\n', |
| 67 | encoding="utf-8", |
| 68 | ) |
| 69 | parsed_tight = parse_artifact(tight, rel_path="tight.yaml") |
| 70 | assert reference_digest(parsed_loose) == reference_digest(parsed_tight) |
| 71 | |
| 72 | |
| 73 | def test_dry_run_and_no_stamp_write_zero(tmp_path: Path) -> None: |
| 74 | write_config(tmp_path, "config-git-only.yaml") |
| 75 | target = tmp_path / "freeze.yaml" |
| 76 | target.write_text((FIXTURES / "freeze-artifact.yaml").read_text(encoding="utf-8"), encoding="utf-8") |
| 77 | rel = target.relative_to(tmp_path).as_posix() |
| 78 | before = target.read_bytes() |
| 79 | assert ( |
| 80 | run_cli( |
| 81 | ["review", "--freeze", rel, "--dry-run"], |
| 82 | cwd=tmp_path, |
| 83 | runner=git_status_runner(), |
| 84 | kit=kit_root(), |
| 85 | review_provider_factory=pass_provider_factory(), |
| 86 | ) |
| 87 | == 0 |
| 88 | ) |
| 89 | assert target.read_bytes() == before |
| 90 | assert ( |
| 91 | run_cli( |
| 92 | ["review", "--freeze", rel, "--no-stamp"], |
| 93 | cwd=tmp_path, |
| 94 | runner=git_status_runner(), |
| 95 | kit=kit_root(), |
| 96 | review_provider_factory=pass_provider_factory(), |
| 97 | ) |
| 98 | == 0 |
| 99 | ) |
| 100 | assert target.read_bytes() == before |