130 lines
5.3 KiB
Python
130 lines
5.3 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
RUNTIME = Path(__file__).resolve().parents[1] / "runtime"
|
|
if str(RUNTIME) not in sys.path:
|
|
sys.path.insert(0, str(RUNTIME))
|
|
|
|
import semantic_surface_extractor as extractor # noqa: E402
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
class SemanticSurfaceExtractorTest(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.tempdir = tempfile.TemporaryDirectory()
|
|
self.root = Path(self.tempdir.name)
|
|
(self.root / "harness/source").mkdir(parents=True)
|
|
shutil.copyfile(REPO_ROOT / extractor.DEFAULT_POLICY, self.root / extractor.DEFAULT_POLICY)
|
|
(self.root / "raw/branch-notes").mkdir(parents=True)
|
|
|
|
def tearDown(self) -> None:
|
|
self.tempdir.cleanup()
|
|
|
|
def _write(self, body: str, *, exclusions: list[str] | None = None) -> Path:
|
|
excluded = "\n".join(f" - {item}" for item in exclusions or [])
|
|
path = self.root / "raw/branch-notes/feature-semantic-fixture.md"
|
|
path.write_text(
|
|
"---\n"
|
|
"title: 의미 fixture\n"
|
|
"source_type: branch-note\n"
|
|
"status: verified\n"
|
|
+ (f"semantic_surface_exclusions:\n{excluded}\n" if excluded else "")
|
|
+ "---\n\n"
|
|
+ body,
|
|
encoding="utf-8",
|
|
)
|
|
return path
|
|
|
|
def test_alias_is_one_logical_surface_and_exclusions_close_coverage(self) -> None:
|
|
path = self._write(
|
|
"<!-- section-id: branch-scope -->\n## 범위\n포함한다.\n",
|
|
exclusions=[
|
|
"branch-contract-packet|fixture scope",
|
|
"decision-evidence|fixture scope",
|
|
"implementation|fixture scope",
|
|
"edge-failure-dependency|fixture scope",
|
|
"claims-to-verify|fixture scope",
|
|
],
|
|
)
|
|
result = extractor.check(self.root, paths=[path])
|
|
self.assertEqual(result["status"], "PASS")
|
|
self.assertEqual(result["coverage"], {
|
|
"eligible_surface_blocks": 6,
|
|
"extracted_surface_blocks": 1,
|
|
"explicitly_excluded_blocks": 5,
|
|
"uncovered_surface_blocks": 0,
|
|
})
|
|
self.assertEqual(result["documents"][0]["surfaces"][0]["section_id"], "scope")
|
|
self.assertNotIn("LEGACY_SEMANTIC_SURFACE", {item["code"] for item in result["findings"]})
|
|
|
|
def test_legacy_heading_warns_but_does_not_fail(self) -> None:
|
|
path = self._write(
|
|
"## 구현 가이드\n구현한다.\n",
|
|
exclusions=[
|
|
"branch-contract-packet|legacy fixture",
|
|
"scope|legacy fixture",
|
|
"decision-evidence|legacy fixture",
|
|
"edge-failure-dependency|legacy fixture",
|
|
"claims-to-verify|legacy fixture",
|
|
],
|
|
)
|
|
result = extractor.check(self.root, paths=[path])
|
|
self.assertEqual(result["status"], "PASS")
|
|
self.assertIn("LEGACY_SEMANTIC_SURFACE", {item["code"] for item in result["findings"]})
|
|
|
|
def test_silent_drop_fails_closed(self) -> None:
|
|
path = self._write("<!-- section-id: scope -->\n## 범위\n내용\n")
|
|
result = extractor.check(self.root, paths=[path])
|
|
self.assertEqual(result["status"], "FAIL")
|
|
self.assertEqual(result["coverage"]["uncovered_surface_blocks"], 5)
|
|
self.assertEqual(
|
|
result["coverage"]["eligible_surface_blocks"],
|
|
result["coverage"]["extracted_surface_blocks"]
|
|
+ result["coverage"]["explicitly_excluded_blocks"]
|
|
+ result["coverage"]["uncovered_surface_blocks"],
|
|
)
|
|
|
|
def test_project_is_always_required_and_canonical_layout_uses_vault_only(self) -> None:
|
|
policy = extractor.load_policy(self.root)
|
|
self.assertTrue(extractor.is_required({"source_type": "project-note", "status": "raw"}, policy))
|
|
(self.root / "harness/source/vault-layout.json").write_text(
|
|
json.dumps({"schema_version": "vault-layout/v1", "mode": "canonical", "vault_root": "vault"}),
|
|
encoding="utf-8",
|
|
)
|
|
legacy = self._write(
|
|
"<!-- section-id: scope -->\n## 범위\nlegacy\n",
|
|
exclusions=[
|
|
"branch-contract-packet|fixture",
|
|
"decision-evidence|fixture",
|
|
"implementation|fixture",
|
|
"edge-failure-dependency|fixture",
|
|
"claims-to-verify|fixture",
|
|
],
|
|
)
|
|
canonical = self.root / "vault/10-projects/sample/branch-notes/feature-canonical.md"
|
|
canonical.parent.mkdir(parents=True)
|
|
canonical.write_bytes(legacy.read_bytes())
|
|
selected = extractor.eligible_documents(self.root, policy, required_only=True, mode="local")
|
|
self.assertEqual(selected, (canonical.resolve(),))
|
|
result = extractor.check(self.root, paths=selected)
|
|
self.assertEqual(result["status"], "PASS")
|
|
self.assertEqual(result["documents"][0]["path"], "vault/10-projects/sample/branch-notes/feature-canonical.md")
|
|
|
|
legacy.unlink()
|
|
legacy.symlink_to(Path("../../") / canonical.relative_to(self.root))
|
|
replay = extractor.extract_document(self.root, legacy, policy)
|
|
self.assertEqual(replay["path"], "raw/branch-notes/feature-semantic-fixture.md")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|