Files
semantica/tests/context/test_context_graph_docstring_example.py
5c2901ae27 docs(context): fix unrunnable ContextGraph docstring example (#921)
* docs(context): fix unrunnable ContextGraph docstring example

The module docstring's Example Usage block called add_node/add_edge with
keyword arguments they do not accept. add_node(node_id, node_type, ...) takes
node_type positionally and has no properties parameter, so the documented call
raised TypeError; add_edge's parameter is edge_type, so type= fell through to
**properties and polluted edge metadata while appearing to work.

Two of the three broken forms failed silently rather than raising, storing a
nested properties dict or a stray type key instead of erroring.

Add regression tests that execute the documented calls and assert the docstring
itself does not reintroduce the invalid kwargs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(context): close two blind spots in the docstring regression guards

The guards added in the previous commit could pass while checking nothing.

_example_block() terminated the capture at the first "\n\n". The Example
Usage block already contains ">>> " spacer lines, so any reformatting that
turned one into a bare blank line would truncate the capture -- potentially
to empty -- and the guards would then scan a block that no longer held the
add_node/add_edge calls they exist to police.

Both guards also iterated over re.findall() without asserting a match. Zero
matches meant zero assertions and a green test, so the two failure modes
compounded: a truncated block produced no matches, and no matches produced
a pass.

Terminate the block at the next top-level section header (^\S) or end of
docstring instead, so blank lines inside the example are harmless, and
assert the captured block, the parsed statement list, and each guard's
match list are all non-empty.

Extract statements with doctest.DocTestParser rather than a line regex.
This also catches a call reformatted across "..." continuation lines, which
the ">>> graph.add_node(.*" pattern silently skipped, and lets
test_documented_calls_execute exec the docstring's own statements instead
of a retyped copy that could drift from it. Full doctest.testmod isn't
usable here: add_node/add_edge return True and the docs carry no
expected-output lines, so it reports 4 spurious failures.

Narrow the kwarg check to (?<![\w])type\s*= so a legitimate node_type=
or edge_type= in the docs no longer trips a guard aimed at bare type=.

Verified by mutating the module docstring and re-running the guards: extra
blank lines with a valid example still pass; regressed add_node/add_edge,
a type= on a continuation line, deleted calls, and a deleted section all
fail; a legitimate node_type= passes. 6 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(context): correct precedent lookup in docstring example

---------

Co-authored-by: Pravit Ampapathini <pravitampapathini@Pravits-MacBook-Air-3.local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
Co-authored-by: Mohd Kaif <98801504+KaifAhmad1@users.noreply.github.com>
2026-08-18 11:49:32 +05:30

143 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""Regression tests for the ContextGraph module docstring example.
The "Example Usage" block in ``semantica/context/context_graph.py`` previously
called ``add_node``/``add_edge`` with keyword arguments those methods do not
accept (``type=`` and ``properties=``), so the documented example raised
``TypeError`` -- and the near-miss variants silently nested the properties dict
instead of failing.
These tests keep the documented example executable and pin the two behaviours
that made the original mistake easy to miss.
"""
import doctest
import re
from typing import Dict, List
import pytest
import semantica.context.context_graph as context_graph_module
from semantica.context.context_graph import ContextGraph
# The example block runs to the next top-level section header (a line starting
# in column 0, e.g. "Production Use Cases:") or the end of the docstring.
# Terminating on the next header rather than on a blank line keeps the capture
# intact when the example gains blank lines or extra paragraphs.
_EXAMPLE_BLOCK_RE = re.compile(r"^Example Usage:\n(.*?)(?=^\S|\Z)", re.DOTALL | re.MULTILINE)
# ``type=`` as its own keyword, but not the legitimate ``node_type=``/``edge_type=``.
_BARE_TYPE_KWARG_RE = re.compile(r"(?<![\w])type\s*=")
def _example_block() -> str:
"""Return the 'Example Usage' block from the module docstring."""
doc = context_graph_module.__doc__ or ""
match = _EXAMPLE_BLOCK_RE.search(doc)
assert match, "module docstring no longer contains an 'Example Usage:' block"
block = match.group(1).strip()
assert block, "the 'Example Usage:' block in the module docstring is empty"
return block
def _example_statements() -> List[str]:
"""Return the documented ``>>>`` statements, continuation lines included."""
statements = [example.source for example in doctest.DocTestParser().get_examples(_example_block())]
assert statements, "the 'Example Usage:' block no longer contains any '>>>' statements"
return statements
def _statements_calling(method: str) -> List[str]:
"""Return the documented statements that call ``graph.<method>(``."""
return [stmt for stmt in _example_statements() if "graph.{}(".format(method) in stmt]
def _run_example() -> Dict[str, object]:
"""Execute the documented example verbatim and return its namespace."""
source = "".join(_example_statements())
namespace: Dict[str, object] = {}
exec(compile(source, "<context_graph module docstring>", "exec"), namespace)
return namespace
class TestDocstringExampleIsRunnable:
"""The documented example must execute exactly as written."""
def test_documented_calls_execute(self):
# Run the docstring text itself so this test cannot drift from the docs.
ns = _run_example()
graph = ns["graph"]
assert "Python" in graph.nodes
assert "Programming" in graph.nodes
assert graph.nodes["Python"].node_type == "language"
assert graph.nodes["Programming"].node_type == "concept"
neighbors = graph.get_neighbors("Python", hops=1)
assert any(n["id"] == "Programming" for n in neighbors)
# record_decision must return a non-empty string ID.
assert isinstance(ns["decision_id"], str) and ns["decision_id"]
# find_precedents must be called with that ID and return a list.
assert isinstance(ns["precedents"], list)
def test_node_properties_are_stored_flat(self):
"""``popularity`` must land as a top-level property, not nested.
Passing the previously documented ``properties={...}`` does not raise --
it stores a dict *inside* the properties dict, which is why the original
docs bug could reach a user's graph unnoticed.
"""
graph = ContextGraph(advanced_analytics=False)
graph.add_node("Python", "language", popularity="high")
assert graph.nodes["Python"].properties == {"popularity": "high"}
assert graph.find_node("Python")["metadata"]["popularity"] == "high"
assert "properties" not in graph.nodes["Python"].properties
def test_edge_type_is_positional_not_a_property(self):
"""``related_to`` must be the edge type, not a stray metadata key."""
graph = ContextGraph(advanced_analytics=False)
graph.add_node("Python", "language")
graph.add_node("Programming", "concept")
graph.add_edge("Python", "Programming", "related_to")
edge = graph.edges[0]
assert edge.edge_type == "related_to"
assert "type" not in edge.metadata
class TestDocstringExampleDoesNotRegress:
"""Guard the docstring text itself, not just equivalent code."""
def test_add_node_example_supplies_node_type_positionally(self):
calls = _statements_calling("add_node")
assert calls, "the 'Example Usage:' block no longer calls graph.add_node()"
for call in calls:
assert not _BARE_TYPE_KWARG_RE.search(call), (
f"add_node example passes type= as a keyword: {call!r}. "
"node_type is positional-required; type= falls through to "
"**properties and the call raises TypeError."
)
assert "properties=" not in call, (
f"add_node example passes properties=: {call!r}. "
"add_node has no properties parameter; extra properties are "
"passed as **kwargs."
)
def test_add_edge_example_supplies_edge_type_positionally(self):
calls = _statements_calling("add_edge")
assert calls, "the 'Example Usage:' block no longer calls graph.add_edge()"
for call in calls:
assert not _BARE_TYPE_KWARG_RE.search(call), (
f"add_edge example passes type= as a keyword: {call!r}. "
"The parameter is edge_type; type= is silently absorbed into "
"**properties and pollutes edge metadata."
)
def test_broken_form_still_raises(self):
"""Pin the signature contract the example has to respect."""
graph = ContextGraph(advanced_analytics=False)
with pytest.raises(TypeError, match="node_type"):
graph.add_node("Python", type="language", properties={"popularity": "high"})