Joshua Sundance Bailey
loosecanvas: local AI thought-mapping canvas with a trust-tagged knowledge graph
6d1438c | """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 | |
| 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(), | |
| ) | |