bmad 6.11
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env python3
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# ///
|
||||
"""Tests for recon_kit.py."""
|
||||
|
||||
import io
|
||||
import json
|
||||
import sys
|
||||
import unittest
|
||||
from contextlib import redirect_stdout
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from recon_kit import add_months, main, parse_date, slugify
|
||||
|
||||
REPORT = """---
|
||||
title: 'market research: widgets'
|
||||
---
|
||||
|
||||
# Report
|
||||
|
||||
The market is growing fast [1] and pricing clusters at $20 [2].
|
||||
An uncited assertion sits here [4].
|
||||
|
||||
```
|
||||
[9] inside a fence never counts
|
||||
```
|
||||
|
||||
## Source appendix
|
||||
|
||||
| [n] | Supports | Publisher | Pub date | Accessed | Confidence |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| [1] | market growth | [Gartner](https://example.com/g) | 2026-01 | 2026-07-01 | high |
|
||||
| [2] | pricing | [Acme](javascript:alert(1)) | 2026-05 | 2026-07-01 | medium |
|
||||
| [3] | never cited | https://example.com/x | 2025-11 | 2026-07-01 | low |
|
||||
"""
|
||||
|
||||
MEMLOG = """---
|
||||
topic: widgets
|
||||
updated: 2026-07-22T10:00
|
||||
---
|
||||
|
||||
- (decision) plan approved
|
||||
- (source) round 1 batch: 4 sources
|
||||
- (claim) ref=[1] status=verified class=size/growth pub=2026-01 — market growing 12% CAGR
|
||||
- (claim) ref=[2] status=unverified class=pricing pub=2026-05 — pricing clusters at $20
|
||||
- (claim) ref=[2] status=verified class=pricing pub=2026-05 — confirmed by second source
|
||||
- (claim) status=unverified class=behavior pub=2025-03 — users churn at day 8
|
||||
- (event) dimension 1 complete
|
||||
"""
|
||||
|
||||
|
||||
def run(argv):
|
||||
buf = io.StringIO()
|
||||
with redirect_stdout(buf):
|
||||
code = main(argv)
|
||||
return code, json.loads(buf.getvalue())
|
||||
|
||||
|
||||
class CitationsTest(unittest.TestCase):
|
||||
def test_cross_check(self):
|
||||
report = Path(__file__).parent / "_report.md"
|
||||
report.write_text(REPORT, encoding="utf-8")
|
||||
try:
|
||||
code, result = run(["citations", str(report)])
|
||||
finally:
|
||||
report.unlink()
|
||||
self.assertEqual(result["dangling_markers"], [4])
|
||||
self.assertEqual(result["orphaned_rows"], [3])
|
||||
self.assertNotIn(9, result["markers"]) # fenced content ignored
|
||||
self.assertEqual(code, 1)
|
||||
|
||||
|
||||
class TallyTest(unittest.TestCase):
|
||||
def test_last_status_wins_per_ref(self):
|
||||
log = Path(__file__).parent / "_memlog.md"
|
||||
log.write_text(MEMLOG, encoding="utf-8")
|
||||
try:
|
||||
code, result = run(["tally", str(log)])
|
||||
finally:
|
||||
log.unlink()
|
||||
self.assertEqual(result["by_type"]["claim"], 4)
|
||||
self.assertEqual(result["claims"], {"unverified": 1, "verified": 2})
|
||||
self.assertEqual(result["claims_total"], 3) # ref=[2] counted once
|
||||
self.assertEqual(code, 0)
|
||||
|
||||
|
||||
class StalenessTest(unittest.TestCase):
|
||||
def test_dates(self):
|
||||
self.assertEqual(parse_date("2026-01"), date(2026, 1, 1))
|
||||
self.assertEqual(add_months(date(2026, 1, 31), 1), date(2026, 2, 28))
|
||||
|
||||
def test_windows(self):
|
||||
claims = json.dumps([
|
||||
{"claim": "sizing", "class": "size/growth", "pub_date": "2024-06"},
|
||||
{"claim": "pricing", "class": "pricing", "pub_date": "2026-06"},
|
||||
{"claim": "odd", "class": "unmapped", "pub_date": "2026-06"},
|
||||
])
|
||||
f = Path(__file__).parent / "_claims.json"
|
||||
f.write_text(claims, encoding="utf-8")
|
||||
try:
|
||||
code, result = run([
|
||||
"staleness", str(f),
|
||||
"--windows", '{"size/growth": 18, "pricing": 3}',
|
||||
"--today", "2026-07-22",
|
||||
])
|
||||
finally:
|
||||
f.unlink()
|
||||
self.assertEqual(result["stale_count"], 1) # sizing recheck 2025-12 < today
|
||||
self.assertEqual(result["earliest_recheck"], "2025-12-01")
|
||||
self.assertEqual(result["no_window_classes"], ["unmapped"])
|
||||
self.assertEqual(code, 1)
|
||||
|
||||
|
||||
class SlugTest(unittest.TestCase):
|
||||
def test_deterministic_folder(self):
|
||||
self.assertEqual(slugify("Créme Brûlée: AI Tools!"), "creme-brulee-ai-tools")
|
||||
code, result = run(["slug", "SMB Accounting SaaS", "--type", "market",
|
||||
"--date", "2026-07-22"])
|
||||
self.assertEqual(result["folder"], "market-smb-accounting-saas-2026-07-22")
|
||||
self.assertEqual(code, 0)
|
||||
|
||||
|
||||
class EscapeSourcesTest(unittest.TestCase):
|
||||
def test_escaping_and_url_validation(self):
|
||||
report = Path(__file__).parent / "_report.md"
|
||||
report.write_text(REPORT, encoding="utf-8")
|
||||
try:
|
||||
code, result = run(["escape-sources", str(report)])
|
||||
finally:
|
||||
report.unlink()
|
||||
self.assertEqual(result["rows"], 3)
|
||||
self.assertTrue(any(u.startswith("javascript:") for u in result["invalid_urls"]))
|
||||
self.assertNotIn("javascript:", result["html"]) # never linked
|
||||
self.assertIn('href="https://example.com/g"', result["html"])
|
||||
self.assertIn('id="src-1"', result["html"])
|
||||
self.assertEqual(code, 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user