Skip to content

Commit 2f09fad

Browse files
committed
test(compiler): cover empty-content skip, scalar plan, malformed entity FM, Entities order
Add regression tests for the four compiler fixes: - empty {"content":""} response skips the page (no raw JSON body) - JSON scalar plan handled gracefully (no AttributeError) - _write_entity rebuilds frontmatter when closing --- is missing - _update_index inserts ## Entities before ## Explorations
1 parent b245128 commit 2f09fad

1 file changed

Lines changed: 114 additions & 0 deletions

File tree

tests/test_compiler.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,27 @@ def test_recovers_when_concepts_section_missing(self, tmp_path):
361361
assert "[[concepts/attention]] — Focus" in text
362362
assert "[[summaries/my-doc]]" in text
363363

364+
def test_entities_inserted_before_explorations(self, tmp_path):
365+
"""#8: an old index.md predating ## Entities must get it inserted
366+
before ## Explorations, not appended after it (canonical order)."""
367+
wiki = tmp_path / "wiki"
368+
wiki.mkdir()
369+
# Old order: no ## Entities section yet.
370+
(wiki / "index.md").write_text(
371+
"# Index\n\n## Documents\n\n## Concepts\n\n## Explorations\n",
372+
encoding="utf-8",
373+
)
374+
_update_index(
375+
wiki, "my-doc", [],
376+
entity_names=["anthropic"],
377+
entity_meta={"anthropic": ("organization", "AI lab.")},
378+
)
379+
text = (wiki / "index.md").read_text()
380+
assert "## Entities" in text
381+
# Canonical order: Entities before Explorations.
382+
assert text.index("## Entities") < text.index("## Explorations")
383+
assert "[[entities/anthropic]] (organization) — AI lab." in text
384+
364385

365386
class TestReadWikiContext:
366387
def test_empty_wiki(self, tmp_path):
@@ -561,6 +582,31 @@ def test_update_prepends_source_keeps_type(self, tmp_path):
561582
assert "v1." not in text
562583
assert "brief:" in text and "b2" in text
563584

585+
def test_update_rebuilds_frontmatter_when_no_closing_delim(self, tmp_path):
586+
"""#11: malformed existing file (opening --- but no closing ---) must
587+
not drop frontmatter; rebuild valid sources/type/brief on update."""
588+
entities = tmp_path / "entities"
589+
entities.mkdir(parents=True)
590+
# Opening delimiter, NO closing delimiter — find("---", 3) == -1.
591+
(entities / "anthropic.md").write_text(
592+
"---\nsources: [\"summaries/a.md\"]\ntype: organization\n"
593+
"# Anthropic (no closing fence)\n\nOld body.",
594+
encoding="utf-8",
595+
)
596+
_write_entity(
597+
tmp_path, "anthropic", "# Anthropic\n\nv2 rewritten.",
598+
"summaries/b.md", is_update=True,
599+
brief="AI lab.", type_="organization", aliases=None,
600+
)
601+
text = (entities / "anthropic.md").read_text(encoding="utf-8")
602+
# Frontmatter rebuilt with a proper closing delimiter, not body-only.
603+
assert text.startswith("---\n")
604+
assert text.count("---") == 2
605+
assert "sources:" in text and "summaries/b.md" in text
606+
assert "type:" in text and "organization" in text
607+
assert "brief:" in text and "AI lab." in text
608+
assert "v2 rewritten." in text
609+
564610

565611
class TestBacklinkSummary:
566612
def test_adds_missing_concept_links(self, tmp_path):
@@ -1051,6 +1097,33 @@ async def test_empty_plan_strips_v1_summary_ghosts(self, tmp_path):
10511097
assert "[[concepts/imaginary]]" not in text
10521098
assert "imaginary" in text # plain text preserved
10531099

1100+
@pytest.mark.asyncio
1101+
async def test_scalar_plan_handled_gracefully(self, tmp_path):
1102+
"""#10: a JSON scalar plan (valid JSON, not object/array) must not
1103+
crash with AttributeError; it takes the graceful empty-plan path —
1104+
v1 summary written, index updated, no concept/entity pages."""
1105+
wiki, source_path = self._setup_kb(tmp_path)
1106+
1107+
summary_response = json.dumps({
1108+
"brief": "B", "content": "# Summary\n\nPlain body, no links.",
1109+
})
1110+
# Plan call returns a bare JSON scalar (an integer).
1111+
scalar_plan_response = "42"
1112+
1113+
with patch("openkb.agent.compiler.litellm") as mock_litellm:
1114+
mock_litellm.completion = MagicMock(
1115+
side_effect=_mock_completion([summary_response, scalar_plan_response])
1116+
)
1117+
# Must not raise (AttributeError) and must complete.
1118+
await compile_short_doc("doc", source_path, tmp_path, "gpt-4o-mini")
1119+
1120+
# Summary still written, index updated with the document.
1121+
assert (wiki / "summaries" / "doc.md").exists()
1122+
index_text = (wiki / "index.md").read_text()
1123+
assert "[[summaries/doc]]" in index_text
1124+
# No concept pages produced from the unusable plan.
1125+
assert not list((wiki / "concepts").glob("*.md"))
1126+
10541127

10551128
class TestCacheControl:
10561129
"""Verify cache_control breakpoints are emitted on the right messages
@@ -1346,6 +1419,47 @@ async def ordered_acompletion(*args, **kwargs):
13461419
assert "[[concepts/flash-attention]]" in index_text
13471420
assert "[[concepts/attention]]" in index_text
13481421

1422+
@pytest.mark.asyncio
1423+
async def test_empty_content_skips_page_no_json_body(self, tmp_path):
1424+
"""#9: when the page LLM returns parseable JSON with empty content
1425+
({"content": ""}), the page is skipped (not written as raw JSON)."""
1426+
wiki = self._setup_wiki(tmp_path)
1427+
1428+
plan_response = json.dumps({
1429+
"create": [{"name": "ghost-concept", "title": "Ghost Concept"}],
1430+
"update": [],
1431+
"related": [],
1432+
})
1433+
# Parseable JSON, but empty content — old code fell back to raw JSON.
1434+
empty_content_response = json.dumps({"brief": "B", "content": ""})
1435+
1436+
system_msg = {"role": "system", "content": "You are a wiki agent."}
1437+
doc_msg = {"role": "user", "content": "Document content."}
1438+
1439+
with patch("openkb.agent.compiler.litellm") as mock_litellm:
1440+
mock_litellm.completion = MagicMock(
1441+
side_effect=_mock_completion([plan_response])
1442+
)
1443+
mock_litellm.acompletion = AsyncMock(
1444+
side_effect=_mock_completion([empty_content_response])
1445+
)
1446+
await _compile_concepts(
1447+
wiki, tmp_path, "gpt-4o-mini", system_msg, doc_msg,
1448+
"Summary.", "test-doc", 5,
1449+
)
1450+
1451+
# The concept page must NOT be written (generation raised + dropped).
1452+
page = wiki / "concepts" / "ghost-concept.md"
1453+
assert not page.exists()
1454+
# And no concept index entry either.
1455+
index_text = (wiki / "index.md").read_text()
1456+
assert "[[concepts/ghost-concept]]" not in index_text
1457+
# Definitely no raw JSON written anywhere as a body.
1458+
assert not any(
1459+
'"content":' in p.read_text()
1460+
for p in (wiki / "concepts").glob("*.md")
1461+
)
1462+
13491463
@pytest.mark.asyncio
13501464
async def test_related_adds_link_no_llm(self, tmp_path):
13511465
"""Plan has only related items. No acompletion calls should be made."""

0 commit comments

Comments
 (0)