Files
FabledGrimoire/tests/test_integration_milestone_search.py
T
bvandeusenandClaude Opus 5.5 79de8938b9 docs(6209): forty test files — design cascade and stylesheet, embeddings, family canon, forge, backup round trips, inception, placement, platforms, moments, rule move and verification, search scope, facets
Module and test docstrings state what is pinned without the record number
or step that introduced it. Incident accounts become the failure shape in
the present tense: the pipefail head, the code_shapes import, the export
order, the facet dialects, the cross-kind repeat, the write-once kind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 21:40:25 -04:00

127 lines
5.6 KiB
Python

"""Real-Postgres tests for finding a plan by meaning.
A project's roadmap is written as milestones, and `search` reaches them, so
"is there already a plan for this?" has a tool. What a mock cannot show is the join scoping the vectors to a project and
to what the caller may read, so these seed real milestones with hand-made
vectors and stub only the embedder.
"""
import uuid
from unittest.mock import AsyncMock, patch
import pytest
import pytest_asyncio
from scribe.models import async_session
from scribe.models.embedding import EMBEDDING_DIM, MilestoneEmbedding
from scribe.models.milestone import Milestone
from scribe.models.project import Project
from scribe.services import dedup as dedup_svc
from scribe.services.embeddings import CHUNKER_VERSION, EMBEDDING_MODEL, semantic_search_milestones
from tests.helpers import ensure_user
pytestmark = [pytest.mark.integration, pytest.mark.usefixtures("_dispose_engine", "_no_embedding")]
NEAR = [1.0] + [0.0] * (EMBEDDING_DIM - 1)
FAR = [0.0, 1.0] + [0.0] * (EMBEDDING_DIM - 2)
@pytest_asyncio.fixture
async def roadmap():
tag = uuid.uuid4().hex[:8]
async with async_session() as s:
owner = await ensure_user(s, f"ms_search_owner_{tag}")
stranger = await ensure_user(s, f"ms_search_stranger_{tag}")
mine = Project(user_id=owner.id, title="Librarian")
other = Project(user_id=owner.id, title="Elsewhere")
s.add_all([mine, other])
await s.flush()
m3 = Milestone(user_id=owner.id, project_id=mine.id, title="M3 — Metadata",
description="works, editions, providers, provenance", status="active")
done = Milestone(user_id=owner.id, project_id=mine.id, title="Covers",
description="cover art", status="done")
unrelated = Milestone(user_id=owner.id, project_id=mine.id, title="Android client",
description="native app", status="active")
foreign = Milestone(user_id=owner.id, project_id=other.id, title="Metadata elsewhere",
description="same words, other project", status="active")
s.add_all([m3, done, unrelated, foreign])
await s.flush()
for ms, vec in ((m3, NEAR), (done, NEAR), (unrelated, FAR), (foreign, NEAR)):
s.add(MilestoneEmbedding(milestone_id=ms.id, chunk_index=0, embedding=vec,
chunk_text=ms.title, chunker_version=CHUNKER_VERSION,
embedding_model=EMBEDDING_MODEL))
ids = {"owner": owner.id, "stranger": stranger.id, "mine": mine.id,
"m3": m3.id, "done": done.id, "unrelated": unrelated.id, "foreign": foreign.id}
await s.commit()
return ids
async def _found(user_id, **kw) -> list[int]:
with patch("scribe.services.embeddings.get_embedding", AsyncMock(return_value=NEAR)):
hits = await semantic_search_milestones(user_id, "book metadata and providers",
threshold=0.5, limit=10, **kw)
return [m.id for _s, m in hits]
async def test_a_plan_is_found_in_its_project_and_not_in_another(roadmap):
found = await _found(roadmap["owner"], project_id=roadmap["mine"])
assert set(found) == {roadmap["m3"], roadmap["done"]}
assert roadmap["foreign"] not in found and roadmap["unrelated"] not in found
async def test_status_narrows_to_open_plans(roadmap):
found = await _found(roadmap["owner"], project_id=roadmap["mine"], status="active")
assert found == [roadmap["m3"]]
async def test_without_a_project_it_searches_the_callers_own(roadmap):
found = await _found(roadmap["owner"])
assert {roadmap["m3"], roadmap["done"], roadmap["foreign"]} <= set(found)
async def test_a_project_the_caller_cannot_read_returns_nothing(roadmap):
assert await _found(roadmap["stranger"], project_id=roadmap["mine"]) == []
assert await _found(roadmap["stranger"]) == []
# ── the plan gate: start_planning finds the plan before making another ──
LONG = "Resolve works and editions against the metadata providers. " * 5
async def _gate(roadmap, title, text="", project=None):
with patch("scribe.services.embeddings.get_embedding", AsyncMock(return_value=NEAR)):
return await dedup_svc.plan_gate(
roadmap["owner"], project or roadmap["mine"], title, text,
)
async def test_the_gate_returns_an_active_plan_with_the_same_title(roadmap):
out = await _gate(roadmap, " m3 — METADATA ")
assert out["duplicate"] is True and out["match"] == "title"
assert out["existing_milestone"]["id"] == roadmap["m3"]
assert f"create_records(milestone_id={roadmap['m3']}" in out["message"]
async def test_the_gate_finds_a_plan_by_meaning_and_never_a_done_one(roadmap):
"""NEAR matches m3 (active), `done` (done) and `foreign` (another project).
Only m3 is a plan this project is still working through."""
out = await _gate(roadmap, "Book metadata", LONG)
assert out["match"] == "semantic"
assert out["existing_id"] == roadmap["m3"]
async def test_a_done_plan_and_another_projects_plan_do_not_block(roadmap):
assert await _gate(roadmap, "Covers") is None
assert await _gate(roadmap, "Metadata elsewhere") is None
async with async_session() as s:
m3 = await s.get(Milestone, roadmap["m3"])
m3.status = "done"
await s.commit()
assert await _gate(roadmap, "Book metadata", LONG) is None
async def test_a_short_candidate_is_judged_by_title_alone(roadmap):
"""A title-only embedding sits in a tight neighbourhood and matches
anything nearby, so a bare title never takes the semantic arm."""
assert await _gate(roadmap, "Book metadata", "just a title") is None