refactor(tests): one definition each for the copied fixtures and fakes (#2825, milestone 296 area 1)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 24s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 54s
CI & Build / Build & push image (push) Successful in 18s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 24s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 54s
CI & Build / Build & push image (push) Successful in 18s
The shape ledger showed the same test scaffolding defined over and over: _bind_user x12 (byte-identical), _dispose_engine x10 in three wordings, _no_supersession x3, _make_mock_session x7 in three subsets, a get-or-create User helper x2 (+3 inlined), and fifteen hand-rolled MagicMock note factories each re-explaining the same "an auto-MagicMock attribute is truthy" hazard (note 2109). Now: conftest.py carries _bind_user / _dispose_engine / _no_supersession as opt-in fixtures (pytestmark = usefixtures(...) per module, so unit tests pay nothing), and tests/helpers.py carries make_mock_session(), ensure_user() and fake_note(**attrs) — the hazard documented once, real values on every attribute the product reads. Call sites were rewritten by AST so titles with dashes and commas survived; the three SimpleNamespace _note stand-ins that only feed a single function stay local. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,22 +1,10 @@
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from tests.helpers import fake_note
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _no_supersession():
|
||||
"""The auto-inject menu now asks which of its lines are superseded (#278).
|
||||
|
||||
That is a real database call on a path these tests exercise without one.
|
||||
Stubbed to "nothing superseded" — the ordinary state — rather than hidden
|
||||
behind a try/except in the product, which would make the code lie about
|
||||
what it does. The label's own behaviour is covered in
|
||||
tests/test_supersession_ranking.py.
|
||||
"""
|
||||
with patch("scribe.services.plugin_context.superseded_ids",
|
||||
AsyncMock(return_value=set())):
|
||||
yield
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("_no_supersession")
|
||||
|
||||
|
||||
def _rule(rid, title, topic_id):
|
||||
@@ -26,21 +14,6 @@ def _rule(rid, title, topic_id):
|
||||
return r
|
||||
|
||||
|
||||
def _note(nid, title, user_id=1, note_type="note", is_task=False, task_kind="work"):
|
||||
n = MagicMock()
|
||||
n.id, n.title = nid, title
|
||||
# Real values, defaulting to the caller used in these tests: the injected menu
|
||||
# compares user_id to decide whether the line needs a "shared by …"
|
||||
# attribution, and reads is_task/task_kind/note_type for the kind marker. An
|
||||
# auto-MagicMock is truthy, so every line would read as another user's task.
|
||||
n.user_id = user_id
|
||||
n.note_type, n.is_task, n.task_kind = note_type, is_task, task_kind
|
||||
# The write-path menu reads note.data for a language tag; an auto-mock there
|
||||
# is truthy and renders its repr into the marker.
|
||||
n.data = None
|
||||
return n
|
||||
|
||||
|
||||
# ─── knowledge auto-inject (Path A) ──────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -88,9 +61,9 @@ async def test_build_autoinject_hint_disabled_returns_empty_and_skips_search():
|
||||
async def test_build_autoinject_hint_titles_only_with_margin_gate():
|
||||
from scribe.services import plugin_context as pc
|
||||
# top=0.80; 0.74 within band (0.10), 0.61 outside → dropped.
|
||||
hits = [(0.80, _note(11, "Pool sizing decision")),
|
||||
(0.74, _note(22, "run_maintenance thresholds")),
|
||||
(0.61, _note(33, "unrelated-ish"))]
|
||||
hits = [(0.80, fake_note(id=11, title="Pool sizing decision", user_id=1)),
|
||||
(0.74, fake_note(id=22, title="run_maintenance thresholds", user_id=1)),
|
||||
(0.61, fake_note(id=33, title="unrelated-ish", user_id=1))]
|
||||
rec = MagicMock()
|
||||
with patch.object(pc, "get_autoinject_config",
|
||||
AsyncMock(return_value={"enabled": True, "threshold": 0.55, "top_k": 3})), \
|
||||
@@ -352,10 +325,10 @@ async def test_a_snippet_takes_the_last_slot_when_none_won_on_score():
|
||||
records ABOUT building the retrieval system, and zero snippets. Scribe's own
|
||||
records are about software work, so they share vocabulary with any coding
|
||||
prompt while answering none of them."""
|
||||
main = [(0.66, _note(1, "Step 3: title-first auto-inject")),
|
||||
(0.65, _note(2, "Task-reminder dedup query crashes", is_task=True)),
|
||||
(0.64, _note(3, "Drafter hardening · write-path trigger", is_task=True))]
|
||||
reuse = [(0.58, _note(9, "debounce — collapse rapid calls", note_type="snippet"))]
|
||||
main = [(0.66, fake_note(id=1, title="Step 3: title-first auto-inject", user_id=1)),
|
||||
(0.65, fake_note(id=2, title="Task-reminder dedup query crashes", user_id=1, is_task=True)),
|
||||
(0.64, fake_note(id=3, title="Drafter hardening · write-path trigger", user_id=1, is_task=True))]
|
||||
reuse = [(0.58, fake_note(id=9, title="debounce — collapse rapid calls", user_id=1, note_type="snippet"))]
|
||||
|
||||
out, calls = await _autoinject(main, reuse)
|
||||
|
||||
@@ -370,8 +343,8 @@ async def test_a_snippet_takes_the_last_slot_when_none_won_on_score():
|
||||
async def test_the_reserved_query_is_skipped_when_a_snippet_already_won():
|
||||
"""No second query, and no slot spent twice, when ranking already did the
|
||||
right thing — the fix must be invisible in the case it isn't needed."""
|
||||
main = [(0.81, _note(9, "debounce helper", note_type="snippet")),
|
||||
(0.80, _note(1, "some task", is_task=True))]
|
||||
main = [(0.81, fake_note(id=9, title="debounce helper", user_id=1, note_type="snippet")),
|
||||
(0.80, fake_note(id=1, title="some task", user_id=1, is_task=True))]
|
||||
|
||||
out, calls = await _autoinject(main, [])
|
||||
|
||||
@@ -384,7 +357,7 @@ async def test_a_weak_snippet_does_not_buy_the_slot():
|
||||
"""The reserved hit skips the MARGIN band — that band is what snippets lose
|
||||
to — but never the threshold. Silence stays the default; a slot spent on an
|
||||
irrelevant snippet is how a menu teaches people to ignore it."""
|
||||
main = [(0.66, _note(1, "a task", is_task=True))]
|
||||
main = [(0.66, fake_note(id=1, title="a task", user_id=1, is_task=True))]
|
||||
|
||||
out, calls = await _autoinject(main, []) # threshold returned nothing
|
||||
|
||||
@@ -397,8 +370,8 @@ async def test_the_reserved_hit_is_not_held_to_the_margin_band():
|
||||
"""0.58 is 0.08 below the top hit. Under the band it would survive; the point
|
||||
is that it must survive even when it wouldn't — a snippet losing to a
|
||||
same-vocabulary project record by a wide margin is the whole bug."""
|
||||
main = [(0.90, _note(1, "a task", is_task=True))]
|
||||
reuse = [(0.58, _note(9, "debounce", note_type="snippet"))]
|
||||
main = [(0.90, fake_note(id=1, title="a task", user_id=1, is_task=True))]
|
||||
reuse = [(0.58, fake_note(id=9, title="debounce", user_id=1, note_type="snippet"))]
|
||||
|
||||
out, _calls = await _autoinject(main, reuse)
|
||||
|
||||
@@ -409,15 +382,15 @@ async def test_the_reserved_hit_is_not_held_to_the_margin_band():
|
||||
async def test_a_process_counts_as_reuse_too():
|
||||
"""A stored process answers 'how do we do X here' the same way a snippet
|
||||
answers 'what do we already have' — both lose to the same project records."""
|
||||
main = [(0.70, _note(1, "a task", is_task=True))]
|
||||
reuse = [(0.60, _note(8, "DRY pass process", note_type="process"))]
|
||||
main = [(0.70, fake_note(id=1, title="a task", user_id=1, is_task=True))]
|
||||
reuse = [(0.60, fake_note(id=8, title="DRY pass process", user_id=1, note_type="process"))]
|
||||
|
||||
out, _ = await _autoinject(main, reuse)
|
||||
assert 8 in out["note_ids"]
|
||||
|
||||
# …and one already on the menu suppresses the reserved query.
|
||||
out2, calls2 = await _autoinject(
|
||||
[(0.70, _note(8, "DRY pass process", note_type="process"))], [])
|
||||
[(0.70, fake_note(id=8, title="DRY pass process", user_id=1, note_type="process"))], [])
|
||||
assert len(calls2) == 1
|
||||
|
||||
|
||||
@@ -431,9 +404,8 @@ async def test_write_path_semantic_arm_asks_for_experience_not_just_snippets():
|
||||
it says what NOT to do."""
|
||||
from scribe.services import plugin_context as pc
|
||||
|
||||
hits = [(0.72, _note(9, "debounce helper", note_type="snippet")),
|
||||
(0.70, _note(7, "Debounce dropped the trailing call", is_task=True,
|
||||
task_kind="issue"))]
|
||||
hits = [(0.72, fake_note(id=9, title="debounce helper", user_id=1, note_type="snippet")),
|
||||
(0.70, fake_note(id=7, title="Debounce dropped the trailing call", user_id=1, is_task=True, task_kind="issue"))]
|
||||
search = AsyncMock(return_value=hits)
|
||||
rec = MagicMock()
|
||||
with patch.object(pc, "get_writepath_config",
|
||||
@@ -466,9 +438,8 @@ async def test_write_path_labels_a_non_snippet_hit_with_its_kind():
|
||||
menu's default and the header's default reading."""
|
||||
from scribe.services import plugin_context as pc
|
||||
|
||||
hits = [(0.72, _note(9, "debounce helper", note_type="snippet")),
|
||||
(0.71, _note(7, "Debounce dropped the trailing call", is_task=True,
|
||||
task_kind="issue"))]
|
||||
hits = [(0.72, fake_note(id=9, title="debounce helper", user_id=1, note_type="snippet")),
|
||||
(0.71, fake_note(id=7, title="Debounce dropped the trailing call", user_id=1, is_task=True, task_kind="issue"))]
|
||||
with patch.object(pc, "get_writepath_config",
|
||||
AsyncMock(return_value={"enabled": True, "threshold": 0.6,
|
||||
"top_k": 3})), \
|
||||
|
||||
Reference in New Issue
Block a user