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:
@@ -13,6 +13,7 @@ from pathlib import Path
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from tests.helpers import fake_note
|
||||
|
||||
PLUGIN = Path(__file__).resolve().parents[1] / "plugin"
|
||||
HOOK = PLUGIN / "hooks" / "scribe_prior_art.sh"
|
||||
@@ -23,20 +24,6 @@ def _snippet_item(nid, title, user_id=1):
|
||||
return {"id": nid, "title": title, "user_id": user_id, "note_type": "snippet"}
|
||||
|
||||
|
||||
def _note(nid, title, user_id=1, note_type="snippet", is_task=False, task_kind="work"):
|
||||
n = MagicMock()
|
||||
n.id, n.title, n.user_id = nid, title, user_id
|
||||
# Explicitly None, not left to MagicMock's auto-attribute: the semantic arm
|
||||
# reads `note.data` for the snippet's language (#2244), and an auto-created
|
||||
# mock there is truthy, so it would render its repr into the menu line.
|
||||
n.data = None
|
||||
# Same reasoning, second instance: since #2246 this arm returns issues and
|
||||
# dev-logs too, so the line names the kind. An auto-mock `is_task` is truthy,
|
||||
# which would label every snippet here "task".
|
||||
n.note_type, n.is_task, n.task_kind = note_type, is_task, task_kind
|
||||
return n
|
||||
|
||||
|
||||
def _cfg(**over):
|
||||
base = {"enabled": True, "threshold": 0.68, "top_k": 3}
|
||||
base.update(over)
|
||||
@@ -218,7 +205,7 @@ async def test_a_failing_location_lookup_does_not_sink_the_hint():
|
||||
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
|
||||
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(side_effect=RuntimeError("boom"))), \
|
||||
patch.object(pc, "semantic_search_notes",
|
||||
AsyncMock(return_value=[(0.80, _note(5, "throttle — …"))])), \
|
||||
AsyncMock(return_value=[(0.80, fake_note(id=5, title="throttle — …", user_id=1, note_type="snippet"))])), \
|
||||
patch.object(pc, "record_retrieval", MagicMock()), \
|
||||
patch.object(pc, "owner_names_for", AsyncMock(return_value={})):
|
||||
out = await pc.build_write_path_hint(1, "src/x.py", code=REAL_CODE)
|
||||
@@ -249,7 +236,7 @@ async def test_semantic_arm_is_snippet_only_and_browse_scoped():
|
||||
@pytest.mark.asyncio
|
||||
async def test_margin_gate_applies_to_the_semantic_arm():
|
||||
from scribe.services import plugin_context as pc
|
||||
hits = [(0.80, _note(11, "near")), (0.74, _note(22, "alsoNear")), (0.61, _note(33, "far"))]
|
||||
hits = [(0.80, fake_note(id=11, title="near", user_id=1, note_type="snippet")), (0.74, fake_note(id=22, title="alsoNear", user_id=1, note_type="snippet")), (0.61, fake_note(id=33, title="far", user_id=1, note_type="snippet"))]
|
||||
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg(top_k=5))), \
|
||||
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(return_value=([], 0))), \
|
||||
patch.object(pc, "semantic_search_notes", AsyncMock(return_value=hits)), \
|
||||
@@ -288,7 +275,7 @@ async def test_place_beats_meaning_and_the_cap_covers_both_arms():
|
||||
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg(top_k=2))), \
|
||||
patch.object(pc.snippets_svc, "list_snippets", _listing), \
|
||||
patch.object(pc, "semantic_search_notes",
|
||||
AsyncMock(return_value=[(0.9, _note(7, "scored"))])), \
|
||||
AsyncMock(return_value=[(0.9, fake_note(id=7, title="scored", user_id=1, note_type="snippet"))])), \
|
||||
patch.object(pc, "record_retrieval", MagicMock()), \
|
||||
patch.object(pc, "owner_names_for", AsyncMock(return_value={})):
|
||||
out = await pc.build_write_path_hint(1, "src/x.py", code=REAL_CODE)
|
||||
@@ -363,7 +350,7 @@ async def test_telemetry_uses_its_own_source():
|
||||
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
|
||||
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(return_value=([], 0))), \
|
||||
patch.object(pc, "semantic_search_notes",
|
||||
AsyncMock(return_value=[(0.9, _note(7, "scored"))])), \
|
||||
AsyncMock(return_value=[(0.9, fake_note(id=7, title="scored", user_id=1, note_type="snippet"))])), \
|
||||
patch.object(pc, "record_retrieval", rec), \
|
||||
patch.object(pc, "owner_names_for", AsyncMock(return_value={})):
|
||||
await pc.build_write_path_hint(1, "src/x.py", code=REAL_CODE, project_id=4)
|
||||
@@ -497,7 +484,7 @@ async def test_a_real_helper_clears_the_floor():
|
||||
"""The floor errs toward keeping recall — anything that plausibly IS a
|
||||
reusable helper has to get through, or the feature stops working."""
|
||||
from scribe.services import plugin_context as pc
|
||||
search = AsyncMock(return_value=[(0.80, _note(5, "debounce — …"))])
|
||||
search = AsyncMock(return_value=[(0.80, fake_note(id=5, title="debounce — …", user_id=1, note_type="snippet"))])
|
||||
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
|
||||
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(return_value=([], 0))), \
|
||||
patch.object(pc, "semantic_search_notes", search), \
|
||||
@@ -712,7 +699,7 @@ async def test_a_cross_language_hit_is_labelled_and_explained():
|
||||
"""The fail state this closes: an unlabelled Python hit offered while writing
|
||||
TypeScript is either dismissed as irrelevant or pasted into the .ts file."""
|
||||
from scribe.services import plugin_context as pc
|
||||
note = _note(7, "group_pairs — collapse related pairs into groups")
|
||||
note = fake_note(id=7, title="group_pairs — collapse related pairs into groups", user_id=1, note_type="snippet")
|
||||
note.data = {"language": "python"}
|
||||
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
|
||||
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(return_value=([], 0))), \
|
||||
@@ -732,7 +719,7 @@ async def test_a_same_language_hit_is_not_labelled_and_gets_no_preamble():
|
||||
"""The common case keeps a clean line; the explanation only appears when
|
||||
there is something on the menu it explains."""
|
||||
from scribe.services import plugin_context as pc
|
||||
note = _note(7, "debounce — rate-limit a callback")
|
||||
note = fake_note(id=7, title="debounce — rate-limit a callback", user_id=1, note_type="snippet")
|
||||
note.data = {"language": "python"}
|
||||
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
|
||||
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(return_value=([], 0))), \
|
||||
@@ -749,7 +736,7 @@ async def test_a_same_language_hit_is_not_labelled_and_gets_no_preamble():
|
||||
async def test_an_unknown_target_extension_never_invents_a_mismatch():
|
||||
"""A wrong "· python" tag is worse than no tag at all."""
|
||||
from scribe.services import plugin_context as pc
|
||||
note = _note(7, "helper — does a thing")
|
||||
note = fake_note(id=7, title="helper — does a thing", user_id=1, note_type="snippet")
|
||||
note.data = {"language": "python"}
|
||||
with patch.object(pc, "get_writepath_config", AsyncMock(return_value=_cfg())), \
|
||||
patch.object(pc.snippets_svc, "list_snippets", AsyncMock(return_value=([], 0))), \
|
||||
@@ -1054,7 +1041,7 @@ async def test_a_pulled_snippet_already_seen_is_evidence_not_menu():
|
||||
writes code resembling it. #7 must be scored for this payload — and handed
|
||||
to the stamp as resemblance — without being re-listed in the menu."""
|
||||
from scribe.services import plugin_context as pc
|
||||
search = AsyncMock(return_value=[(0.91, _note(7, "pulled")), (0.80, _note(8, "fresh"))])
|
||||
search = AsyncMock(return_value=[(0.91, fake_note(id=7, title="pulled", user_id=1, note_type="snippet")), (0.80, fake_note(id=8, title="fresh", user_id=1, note_type="snippet"))])
|
||||
stamp = AsyncMock(return_value=[{
|
||||
"path": "src/x.py", "symbol": "debounce", "kind": "sym",
|
||||
"snippet_id": 7, "reason": "hook: pulled #7; payload resembles it (0.91)",
|
||||
|
||||
Reference in New Issue
Block a user