scripts/test_prepare_review_round.py
scripts/test_prepare_review_round.pyBrowse 10 files
1,603 tokens
7,466 bytes
Token encoding: o200k_base
Snapshot 506f736
← Back to SKILL.md
1#!/usr/bin/env python32 3from __future__ import annotations4 5import json6import subprocess7import sys8import tempfile9import unittest10from pathlib import Path11 12sys.path.insert(0, str(Path(__file__).parent))13 14from prepare_review_round import prepare_review_round15from review_state import review_state16 17 18class PrepareReviewRoundTest(unittest.TestCase):19 def setUp(self) -> None:20 self.temporary_directory = tempfile.TemporaryDirectory()21 self.review_directory = tempfile.TemporaryDirectory()22 self.repo = Path(self.temporary_directory.name)23 self.review_root = Path(self.review_directory.name)24 self._git("init", "-q")25 self._git("config", "user.email", "review-round@example.test")26 self._git("config", "user.name", "Review Round Test")27 (self.repo / "src").mkdir()28 (self.repo / "providers").mkdir()29 (self.repo / "src" / "core.ts").write_text("export const core = 1;\n")30 (self.repo / "providers" / "adapter.ts").write_text(31 "export const adapter = 1;\n"32 )33 self._git("add", ".")34 self._git("commit", "-qm", "initial")35 self.base = self._git("rev-parse", "HEAD").strip()36 37 (self.repo / "src" / "core.ts").write_text("export const core = 2;\n")38 (self.repo / "providers" / "adapter.ts").write_text(39 "export const adapter = 2;\n"40 )41 (self.repo / "src" / "new.ts").write_text("export const newValue = 1;\n")42 self.task_paths = self.repo / "task.paths"43 self.core_paths = self.repo / "core.paths"44 self.provider_paths = self.repo / "providers.paths"45 self.task_paths.write_text("src\nproviders\n")46 self.core_paths.write_text("src\n")47 self.provider_paths.write_text("providers\n")48 self.base_packet = self.repo / "base.md"49 self.round_delta = self.repo / "delta.md"50 self.base_packet.write_text("Required behavior: preserve review assurance.\n")51 self.round_delta.write_text("Round 2 changes only provider wiring.\n")52 53 def tearDown(self) -> None:54 self.review_directory.cleanup()55 self.temporary_directory.cleanup()56 57 def _git(self, *args: str) -> str:58 return subprocess.check_output(("git", "-C", str(self.repo), *args), text=True)59 60 def _prepare(61 self, output_dir: Path, prior_clean_state: Path | None = None62 ) -> dict[str, object]:63 return prepare_review_round(64 repo=self.repo,65 base=self.base,66 pathspec_file=self.task_paths,67 component_pathspec_files=[68 f"core={self.core_paths}",69 f"provider-adapters={self.provider_paths}",70 ],71 base_packet=self.base_packet,72 round_delta=self.round_delta,73 prior_clean_state=prior_clean_state,74 output_dir=output_dir,75 )76 77 def test_generates_deterministic_current_round_bundle(self) -> None:78 output_dir = self.review_root / "review-output"79 80 first = self._prepare(output_dir)81 first_packet = (output_dir / "review-packet.md").read_text()82 first_state = (output_dir / "review-state.json").read_text()83 second = self._prepare(output_dir)84 85 self.assertEqual(first, second)86 self.assertEqual(first_packet, (output_dir / "review-packet.md").read_text())87 self.assertEqual(first_state, (output_dir / "review-state.json").read_text())88 self.assertIn("## Stable Base Evidence", first_packet)89 self.assertIn("## Current Round Delta", first_packet)90 self.assertIn("Historical round transcripts", first_packet)91 self.assertIn("## Reviewer Contract", first_packet)92 self.assertIn(93 "Exact fingerprint revalidation command: `PYTHONDONTWRITEBYTECODE=1 python3 ",94 first_packet,95 )96 self.assertIn("Return exactly one JSON object", first_packet)97 self.assertIn('"reviewed_fingerprints"', first_packet)98 self.assertTrue((output_dir / "task.diff").read_text())99 self.assertTrue((output_dir / "task-context.diff").read_text())100 untracked = json.loads((output_dir / "task-untracked.json").read_text())101 self.assertEqual(untracked[0]["path"], "src/new.ts")102 self.assertEqual(untracked[0]["encoding"], "utf-8")103 self.assertIn("newValue", untracked[0]["content"])104 105 def test_marks_only_byte_identical_prior_clean_components_as_candidates(106 self,107 ) -> None:108 components = {109 "core": ("src",),110 "provider-adapters": ("providers",),111 }112 before = review_state(self.repo, self.base, ("src", "providers"), components)113 prior = self.repo / "prior.json"114 prior.write_text(115 json.dumps(116 {117 "clean_components": {118 name: component["content_fingerprint"]119 for name, component in before["components"].items()120 }121 }122 )123 )124 (self.repo / "providers" / "adapter.ts").write_text(125 "export const adapter = 3;\n"126 )127 128 output_dir = self.review_root / "review-output"129 state = self._prepare(output_dir, prior)130 131 self.assertEqual(state["prior_clean_candidates"], ["core"])132 self.assertEqual(133 state["invalidated_prior_clean_components"], ["provider-adapters"]134 )135 packet = (output_dir / "review-packet.md").read_text()136 self.assertIn("not automatically reusable clean credit", packet)137 138 def test_malformed_prior_clean_state_fails_closed(self) -> None:139 prior = self.repo / "prior.json"140 prior.write_text(json.dumps({"clean_components": {"unknown": "0" * 64}}))141 142 with self.assertRaisesRegex(ValueError, "unknown components"):143 self._prepare(self.repo / "review-output", prior)144 145 def test_component_partition_errors_remain_fail_closed(self) -> None:146 self.provider_paths.write_text("src\nproviders\n")147 148 with self.assertRaisesRegex(ValueError, "overlapping"):149 self._prepare(self.repo / "review-output")150 151 def test_output_directory_inside_review_scope_fails_closed(self) -> None:152 with self.assertRaisesRegex(ValueError, "output directory is excluded"):153 self._prepare(self.repo / "src" / "review-output")154 155 def test_output_directory_inside_git_directory_fails_closed(self) -> None:156 with self.assertRaisesRegex(ValueError, "must not be inside .git"):157 self._prepare(self.repo / ".git" / "review-output")158 159 def test_reviewer_brief_without_instructions_fails_closed(self) -> None:160 malformed_brief = self.repo / "malformed-reviewer-brief.md"161 malformed_brief.write_text("# Missing required section\n")162 163 with self.assertRaisesRegex(ValueError, "Reviewer instructions"):164 prepare_review_round(165 repo=self.repo,166 base=self.base,167 pathspec_file=self.task_paths,168 component_pathspec_files=[169 f"core={self.core_paths}",170 f"provider-adapters={self.provider_paths}",171 ],172 base_packet=self.base_packet,173 round_delta=self.round_delta,174 reviewer_brief=malformed_brief,175 output_dir=self.repo / "review-output",176 )177 178 179if __name__ == "__main__":180 unittest.main()181