from __future__ import annotations from pathlib import Path import hashlib import json import sys import tempfile import unittest RUNTIME = Path(__file__).resolve().parents[1] / "runtime" sys.path.insert(0, str(RUNTIME)) import moc_indexer # noqa: E402 class MocIndexerTest(unittest.TestCase): def _cutover(self, root: Path, paths: list[Path]) -> dict[Path, Path]: mapping: dict[Path, Path] = {} entries = [] for legacy in paths: relative = legacy.relative_to(root).as_posix() category = legacy.parent.name canonical_rel = f"vault/10-projects/proj/{category}/{legacy.name}" canonical = root / canonical_rel canonical.parent.mkdir(parents=True, exist_ok=True) content = legacy.read_bytes() canonical.write_bytes(content) legacy.write_text(f"---\ncanonical_path: {canonical_rel}\n---\n", encoding="utf-8") entries.append({ "legacy_path": relative, "canonical_path": canonical_rel, "sha256": hashlib.sha256(content).hexdigest(), }) mapping[legacy] = canonical manifest = root / "harness/source/vault-layout.json" manifest.parent.mkdir(parents=True) manifest.write_text(json.dumps({ "schema_version": "vault-layout/v1", "mode": "canonical", "migration_manifest": {"schema_version": "vault-migration/v1", "entries": entries}, }), encoding="utf-8") return mapping def test_canonical_edges_sort_children_and_preserve_manual_cluster(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) project = root / "raw/project-notes/proj.md" project.parent.mkdir(parents=True) project.write_text("# P\n## Cluster / 묶음\n- [[raw/errors/manual]]\n", encoding="utf-8") branch_dir = root / "raw/branch-notes" branch_dir.mkdir(parents=True) for slug in ("feature-zeta-child-contract-runtime", "feature-alpha-child-contract-runtime"): (branch_dir / f"{slug}.md").write_text( f"---\nid: {slug}\nproject: proj\nparent_branch:\ncontract_packet: 1\n---\n# B\n", encoding="utf-8", ) updates, stats = moc_indexer.build_updates(root) self.assertEqual(stats["structured_children"], 2) project.write_text(updates[project], encoding="utf-8") text = project.read_text(encoding="utf-8") self.assertLess(text.index("feature-alpha"), text.index("feature-zeta")) self.assertIn("[[raw/errors/manual]]", text) self.assertEqual(moc_indexer.build_updates(root)[0], {}) def test_relation_config_generates_multiple_reverse_views_and_multiline_daily_edges(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) project = root / "raw/project-notes/proj.md" project.parent.mkdir(parents=True) project.write_text("# Project\n## Cluster / 묶음\nproject manual\n", encoding="utf-8") branch = root / "raw/branch-notes/feature-sample-parent-contract.md" branch.parent.mkdir(parents=True) branch.write_text( "---\nproject: proj\nparent_branch:\ncontract_packet: 1\n---\n# Branch\n## Cluster / 묶음\nbranch manual\n", encoding="utf-8", ) official = root / "raw/official-docs/source.md" official.parent.mkdir(parents=True) official.write_text("---\nrelated_branches: [feature-sample-parent-contract]\n---\n# Source\n", encoding="utf-8") error = root / "raw/errors/error.md" error.parent.mkdir(parents=True) error.write_text("---\nrelated_branches: [feature-sample-parent-contract]\n---\n# Error\n", encoding="utf-8") daily = root / "raw/daily-notes/2026-07-20.md" daily.parent.mkdir(parents=True) daily.write_text("---\nbranches: [\n feature-sample-parent-contract\n]\n---\n# Daily\n", encoding="utf-8") updates, stats = moc_indexer.build_updates(root) for path, text in updates.items(): path.write_text(text, encoding="utf-8") project_text = project.read_text(encoding="utf-8") branch_text = branch.read_text(encoding="utf-8") self.assertIn("", project_text) self.assertIn("[[raw/branch-notes/feature-sample-parent-contract]]", project_text) self.assertIn("", branch_text) self.assertIn("[[raw/official-docs/source]]", branch_text) self.assertIn("", branch_text) self.assertIn("[[raw/errors/error]]", branch_text) self.assertIn("", branch_text) self.assertIn("[[raw/daily-notes/2026-07-20]]", branch_text) self.assertIn("project manual", project_text) self.assertIn("branch manual", branch_text) self.assertEqual(stats["canonical_edges"], 4) self.assertEqual(moc_indexer.build_updates(root)[0], {}) def test_canonical_mode_indexes_manifest_paths_and_excludes_stubs(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) project = root / "raw/project-notes/proj.md" project.parent.mkdir(parents=True) project.write_text("# P\n## Cluster / 묶음\n", encoding="utf-8") branch = root / "raw/branch-notes/feature-sample-contract.md" branch.parent.mkdir(parents=True) branch.write_text( "---\nproject: proj\nparent_branch:\ncontract_packet: 1\n---\n# B\n", encoding="utf-8", ) mapping = self._cutover(root, [project, branch]) updates, stats = moc_indexer.build_updates(root) canonical_project = mapping[project] self.assertEqual(set(updates), {canonical_project}) self.assertIn( "[[raw/branch-notes/feature-sample-contract]]", updates[canonical_project], ) self.assertEqual(stats["mode"], "canonical") self.assertEqual(stats["namespace"], "canonical") canonical_project.write_text(updates[canonical_project], encoding="utf-8") self.assertEqual(moc_indexer.build_updates(root)[0], {}) def test_invalid_or_duplicate_generated_markers_fail_closed(self) -> None: with self.assertRaises(moc_indexer.MocError) as raised: moc_indexer._replace_or_insert_marker( "\n\n\n", set(), "sources", "hub.md", ) self.assertEqual(raised.exception.code, "INVALID_GENERATED_BLOCK") def test_all_named_raw_relations_support_project_and_multi_parent_views(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) project = root / "raw/project-notes/proj.md" project.parent.mkdir(parents=True) project.write_text("# Project\n## Cluster / 묶음\nmanual project\n", encoding="utf-8") branches = [] for slug in ("feature-parent-one", "feature-parent-two"): branch = root / f"raw/branch-notes/{slug}.md" branch.parent.mkdir(parents=True, exist_ok=True) branch.write_text("# Branch\n## Cluster / 묶음\nmanual branch\n", encoding="utf-8") branches.append(branch) fixtures = [ ("raw/official-docs/source.md", "related_branches: [feature-parent-one, feature-parent-two]\nrelated_projects: [proj]"), ("raw/interviews/question.md", "related_branches: [feature-parent-one]\nrelated_projects: [proj]"), ("raw/lectures/course.md", "related_branches: [feature-parent-one]\nrelated_projects: [proj]"), ("raw/job-postings/job.md", "related_branches: [feature-parent-one]\nrelated_projects: [proj]"), ("raw/blog-topics/topic.md", "related_branches: [feature-parent-one]\nrelated_projects: [proj]"), ] for relative, fields in fixtures: path = root / relative path.parent.mkdir(parents=True, exist_ok=True) path.write_text(f"---\n{fields}\n---\n# Child\n", encoding="utf-8") updates, stats = moc_indexer.build_updates(root) for path, text in updates.items(): path.write_text(text, encoding="utf-8") project_text = project.read_text(encoding="utf-8") first_text = branches[0].read_text(encoding="utf-8") second_text = branches[1].read_text(encoding="utf-8") for marker in ("sources", "interviews", "lectures", "job-postings", "blog-topics"): self.assertIn(f"", project_text) self.assertIn(f"", first_text) self.assertIn("[[raw/official-docs/source]]", second_text) self.assertIn("manual project", project_text) self.assertGreaterEqual(stats["canonical_edges"], 11) self.assertEqual(moc_indexer.build_updates(root)[0], {}) def test_deleted_last_child_clears_generated_view_and_preserves_manual_content(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) branch = root / "raw/branch-notes/feature-parent.md" branch.parent.mkdir(parents=True) branch.write_text("# Branch\n## Cluster / 묶음\nmanual\n", encoding="utf-8") source = root / "raw/official-docs/source.md" source.parent.mkdir(parents=True) source.write_text("---\nrelated_branches: [feature-parent]\n---\n# Source\n", encoding="utf-8") for path, text in moc_indexer.build_updates(root)[0].items(): path.write_text(text, encoding="utf-8") source.unlink() updates, _stats = moc_indexer.build_updates(root) rendered = updates[branch] self.assertIn("\n", rendered) self.assertNotIn("raw/official-docs/source", rendered) self.assertIn("manual", rendered) def test_canonical_derived_relations_support_nested_and_multiple_parents(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) concept = root / "wiki/concepts/concept.md" project = root / "wiki/projects/proj/design.md" concept.parent.mkdir(parents=True) project.parent.mkdir(parents=True) concept.write_text("# Concept\nmanual concept\n", encoding="utf-8") project.write_text("# Project\nmanual project\n", encoding="utf-8") children = [ ("wiki/interview/interview.md", "derived-interviews"), ("wiki/portfolio/portfolio.md", "derived-portfolios"), ("wiki/blog/blog.md", "derived-blogs"), ] for relative, _marker in children: child = root / relative child.parent.mkdir(parents=True, exist_ok=True) child.write_text( "---\ncanonical_sources: [wiki/concepts/concept, wiki/projects/proj/design]\n---\n# Derived\n", encoding="utf-8", ) updates, stats = moc_indexer.build_updates(root) concept_text = updates[concept] project_text = updates[project] for relative, marker in children: link = Path(relative).with_suffix("").as_posix() self.assertIn(f"", concept_text) self.assertIn(f"[[{link}]]", concept_text) self.assertIn(f"[[{link}]]", project_text) self.assertEqual(stats["canonical_edges"], 6) if __name__ == "__main__": unittest.main()