loosecanvas / tests /conftest.py
Joshua Sundance Bailey
loosecanvas: local AI thought-mapping canvas with a trust-tagged knowledge graph
6d1438c
Raw
History Blame Contribute Delete
4.47 kB
"""Shared pytest fixtures and builder helpers.
WS-04 M1 (SL-3): the in-process ``session_store`` is a module-global ``dict`` with a
hard capacity cap (``_MAX_SESSIONS``). Now that ``load_fixture_session`` /
``load_text_session`` / ``load_repo_session`` enforce that cap (previously only
``load_session`` did), tests that create sessions without tearing them down would
accumulate across the whole run and eventually trip ``OverflowError("session_cap")``
in unrelated tests.
This autouse fixture resets the session lifecycle state (records AND their paired
locks) around every test so each test starts from an empty store β€” standard
per-test isolation. Files with their own inline ``session_store``/``session_locks``
cleanup fixtures still work (clearing an already-empty dict is a no-op).
TST-2: shared builder functions so individual test files can ``from conftest import``
them instead of duplicating local copies. Files may keep local overrides (different
signatures, extra defaults) without touching these.
"""
from __future__ import annotations
from collections.abc import Iterator
import pytest
from loosecanvas.contracts import Claim, Edge, Node, SceneState, SourceSnapshot
from loosecanvas.graph_repository import LooseGraphRepository
from loosecanvas.turn_logic import session_locks, session_store
@pytest.fixture(autouse=True)
def _reset_session_store() -> Iterator[None]:
session_store.clear()
session_locks.clear()
yield
session_store.clear()
session_locks.clear()
# ── Shared builder helpers ────────────────────────────────────────────────────
# These are importable functions (not fixtures) so test files can call them
# directly: ``from conftest import _node, _edge``.
# Signatures are the widest common denominator across the existing test files;
# individual files may keep narrower local overrides.
def _node(
nid: str,
label: str | None = None,
*,
kind: str = "concept",
summary: str = "",
) -> Node:
"""Build a minimal ``Node``. Default ``kind="concept"`` matches most tests."""
return Node(id=nid, kind=kind, label=label or nid, summary=summary) # type: ignore[arg-type]
def _edge(
eid: str,
src: str,
tgt: str,
*,
kind: str = "relates_to",
) -> Edge:
"""Build a minimal ``Edge``. Default ``kind="relates_to"`` matches most tests."""
return Edge(id=eid, source=src, target=tgt, kind=kind) # type: ignore[arg-type]
def _graph(
*node_ids: str,
edges: list[Edge] | None = None,
) -> LooseGraphRepository:
"""Build a ``LooseGraphRepository`` from bare node-id strings."""
repo = LooseGraphRepository()
repo.upsert_nodes([_node(nid) for nid in node_ids])
if edges:
repo.upsert_edges(edges)
return repo
def _scene(
*,
visible_nodes: list[str] | None = None,
visible_edges: list[str] | None = None,
fogged: list[str] | None = None,
) -> SceneState:
"""Build a minimal ``SceneState``."""
return SceneState(
visible_node_ids=visible_nodes or [],
visible_edge_ids=visible_edges or [],
fogged_node_ids=fogged or [],
)
def _claim(
cid: str,
target: str,
*,
origin: str = "model_inferred",
claim_type: str = "label",
support_state: str = "unverified",
review_state: str = "pending",
text: str = "test",
summary: str = "",
) -> Claim:
"""Build a minimal ``Claim``.
The ``summary`` kwarg is accepted for compatibility with
``test_context_packer`` / ``test_t3_03`` callers that pass ``summary=``.
It is silently ignored here because ``Claim`` has no such field; those
callers should instead set it on the associated ``Node``.
"""
return Claim(
id=cid,
claim_type=claim_type, # type: ignore[arg-type]
target_id=target,
text=text,
origin=origin, # type: ignore[arg-type]
support_state=support_state, # type: ignore[arg-type]
review_state=review_state, # type: ignore[arg-type]
)
def _make_repo(
nodes: list[Node] | None = None,
edges: list[Edge] | None = None,
claims: list[Claim] | None = None,
) -> LooseGraphRepository:
"""Build a ``LooseGraphRepository`` from typed lists."""
return LooseGraphRepository.from_extraction(
nodes or [],
edges or [],
claims or [],
SourceSnapshot(),
)