import copy import json import os import subprocess import sys import tempfile import unittest from pathlib import Path from harness.tests._support.paths import ROOT from harness.tests._support.spec import MODPATH, mod, publication_fixture, result, verify_fixture class CodecTests(unittest.TestCase): def test_validate_and_render_roundtrip(self): r = result() self.assertEqual(mod.validate_result(r, {"src/a.py", "tests/test_a.py"}), r) body = mod.render_review(r, {"src/a.py", "tests/test_a.py"}, declaration={"mode": "none"}) parsed = mod.parse_review(body, {"src/a.py", "tests/test_a.py"}) self.assertEqual(parsed, r) self.assertEqual(mod.render_review(parsed, {"src/a.py", "tests/test_a.py"}, declaration={"mode": "none"}), body) def test_validation_rejects_bool_path_traversal_bad_verdict(self): r = result() r["target"]["issue"] = True with self.assertRaises(mod.ValidationError): mod.validate_result(r, {"src/a.py"}) r = result(); r["criteria"][0]["implementation_paths"] = ["../x"] with self.assertRaises(mod.ValidationError): mod.validate_result(r, {"../x"}) r = result("request_changes") with self.assertRaises(mod.ValidationError): mod.validate_result(r, {"src/a.py", "tests/test_a.py"}) def test_collect_pages_requires_identity_and_reads_all_pages(self): pages = [[{"id": 1}, {"id": 2}], [{"id": 3}]] def fetch(n): return {"items": pages[n - 1], "total_count": 3, "has_more": n == 1} self.assertEqual(mod.collect_pages(fetch, "id", page_size=2), pages[0] + pages[1]) def bad(n): return {"items": [{"id": 1}], "total_count": 2, "has_more": True} with self.assertRaises(mod.ValidationError): mod.collect_pages(bad, "id", max_pages=1) def test_verify_unchanged(self): self.assertTrue(mod.verify_unchanged({"a": [1]}, {"a": [1]})) with self.assertRaises(mod.ValidationError): mod.verify_unchanged({"a": 1}, {"a": 2}) def test_cli_check_pages_and_unchanged(self): with tempfile.TemporaryDirectory(dir="/opt/data/cache/scratch") as td: td = Path(td); env = dict(os.environ, PYTHONDONTWRITEBYTECODE="1") pages = td / "pages.json"; pages.write_text(json.dumps([ {"items": [{"id": 1}], "total_count": 1, "has_more": False}],), encoding="utf-8") base = [sys.executable, str(MODPATH)] p = subprocess.run(base + ["check-pages", "--pages", str(pages), "--key", "id"], capture_output=True, text=True, env=env) self.assertEqual(p.returncode, 0, p.stderr) b = td / "before.json"; a = td / "after.json" b.write_text('{"x":1}', encoding="utf-8"); a.write_text('{"x":1}', encoding="utf-8") p = subprocess.run(base + ["check-unchanged", "--before", str(b), "--after", str(a)], capture_output=True, text=True, env=env) self.assertEqual(p.returncode, 0, p.stderr); self.assertIn('"unchanged": true', p.stdout) def test_cli_validate_render_parse(self): with tempfile.TemporaryDirectory(dir="/opt/data/cache/scratch") as td: td = Path(td); rf = td / "r.json"; af = td / "a.json"; bf = td / "b.md" rf.write_text(json.dumps(result(), ensure_ascii=False), encoding="utf-8") af.write_text(json.dumps(["src/a.py", "tests/test_a.py"]), encoding="utf-8") env = dict(os.environ, PYTHONDONTWRITEBYTECODE="1") base = [sys.executable, str(MODPATH)] p = subprocess.run(base + ["validate-result", "--result", str(rf), "--allowed-paths", str(af)], text=True, capture_output=True, env=env) self.assertEqual(p.returncode, 0, p.stderr) p = subprocess.run(base + ["render", "--no-delegated-evidence", "--result", str(rf), "--allowed-paths", str(af)], text=True, capture_output=True, env=env) self.assertEqual(p.returncode, 0, p.stderr); bf.write_text(p.stdout, encoding="utf-8") p = subprocess.run(base + ["parse", "--body", str(bf), "--allowed-paths", str(af)], text=True, capture_output=True, env=env) self.assertEqual(p.returncode, 0, p.stderr) self.assertEqual(json.loads(p.stdout), result()) def test_result_codec_preserves_existing_native_format(self): r = result() paths = {"src/a.py", "tests/test_a.py"} body = mod.render_review(r, paths, declaration={"mode": "none"}) self.assertIn(" - 구현 근거: src/a.py; 증거\n", body) self.assertIn("모든 기준을 충족했다.\n\n