feat(okf): add okf_validate; harden YAML parser for block lists + folded scalars

Verified conformant against Google reference bundles (crypto_bitcoin, ga4, stackoverflow).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 19:38:31 +09:00
parent 9762ee97ab
commit 7b239dda8f
4 changed files with 223 additions and 4 deletions

View File

@@ -24,6 +24,21 @@ class TestFrontmatter(unittest.TestCase):
def test_empty_list(self):
self.assertEqual(parse_yaml_subset("tags: []")["tags"], [])
def test_block_list(self):
raw = "type: BigQuery Dataset\ntags:\n- cryptocurrency\n- bitcoin\n- public data\ntimestamp: '2026-05-28T22:44:47+00:00'"
meta = parse_yaml_subset(raw)
self.assertEqual(meta["tags"], ["cryptocurrency", "bitcoin", "public data"])
self.assertEqual(meta["timestamp"], "2026-05-28T22:44:47+00:00")
def test_folded_multiline_scalar(self):
raw = "description: This dataset contains a complete history of the Bitcoin\n blockchain and updates every 10 minutes.\ntype: BigQuery Dataset"
meta = parse_yaml_subset(raw)
self.assertEqual(
meta["description"],
"This dataset contains a complete history of the Bitcoin blockchain and updates every 10 minutes.",
)
self.assertEqual(meta["type"], "BigQuery Dataset")
def test_no_frontmatter(self):
meta, body = parse_frontmatter("# Just markdown\n")
self.assertIsNone(meta)

View File

@@ -0,0 +1,64 @@
import sys, tempfile, unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from okf_validate import validate_bundle
FIX = Path(__file__).resolve().parent / "fixtures" / "mini_bundle"
GOOGLE = Path.home() / "Documents/reference-library/open-knowledge-format/okf/bundles"
def write_bundle(root, files):
for rel, content in files.items():
p = Path(root) / rel
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(content, encoding="utf-8")
class TestValidate(unittest.TestCase):
def test_mini_fixture_conformant(self):
report = validate_bundle(FIX)
self.assertTrue(report["conformant"], report["errors"])
self.assertGreaterEqual(report["concepts"], 3)
def test_missing_type_is_error(self):
with tempfile.TemporaryDirectory() as d:
write_bundle(d, {"tables/x.md": "---\ntitle: X\n---\nbody"})
report = validate_bundle(d)
self.assertFalse(report["conformant"])
self.assertTrue(any(e["rule"] == "type" for e in report["errors"]))
def test_missing_frontmatter_is_error(self):
with tempfile.TemporaryDirectory() as d:
write_bundle(d, {"tables/x.md": "# no frontmatter\n"})
self.assertFalse(validate_bundle(d)["conformant"])
def test_reserved_files_not_required_to_have_type(self):
with tempfile.TemporaryDirectory() as d:
write_bundle(d, {"index.md": "# Index\n* [x](/tables/x.md)\n",
"tables/x.md": "---\ntype: T\n---\nb"})
self.assertTrue(validate_bundle(d)["conformant"])
def test_broken_link_is_warning_not_error(self):
with tempfile.TemporaryDirectory() as d:
write_bundle(d, {"tables/x.md": "---\ntype: T\n---\nSee [y](/tables/y.md)."})
report = validate_bundle(d)
self.assertTrue(report["conformant"])
self.assertTrue(any(w["rule"] == "broken_link" for w in report["warnings"]))
class TestGoogleBundles(unittest.TestCase):
@unittest.skipUnless(GOOGLE.exists(), "Google reference bundles not present")
def test_google_sample_bundles_conformant(self):
for name in ("crypto_bitcoin", "ga4", "stackoverflow"):
bundle = GOOGLE / name
if not bundle.exists():
continue
report = validate_bundle(bundle)
self.assertTrue(
report["conformant"],
"%s non-conformant; first errors: %s" % (name, report["errors"][:3]),
)
if __name__ == "__main__":
unittest.main()