refactor(tests): per-model fakes, FakeMCP and session mocks come from tests/helpers (#2825, milestone 296 area 1, batch 2)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 24s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Failing after 36s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 24s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Failing after 36s
CI & Build / Build & push image (push) Skipped
Second pass over the tests/ ledger after bbee0d0. fake_record(**attrs) is the
one MagicMock-with-real-attributes builder (to_dict mirrors them; the
note-2109 hazard documented once); fake_note/fake_task/fake_snippet/
fake_project/fake_milestone/fake_system/fake_rulebook/fake_topic/fake_rule
carry each model's ordinary defaults on top of it, replacing 14 per-file
factories (two rulebook trios in tool-vs-service wordings, _fake_task, _fake_ms,
_fake_project, _plan_note, _fake_snippet, two _snippet adapters now one-liners
over fake_snippet). FakeMCP replaces the five closure-over-a-list registrar
fakes (+ _Recorder); loc() and design_token_stub() replace the paired _loc /
_token / _T stand-ins; every hand-built async_session mock (9 helper defs and
14 inline copies) now starts from make_mock_session(). Call sites rewritten by
AST with each file's former defaults made explicit, so behaviour is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,15 +7,12 @@ that source actually added. These tests pin that rule, and the refusal that
|
||||
guards the case where the attribution isn't there.
|
||||
"""
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from scribe.services import snippets as s
|
||||
|
||||
|
||||
def _loc(path, repo="r", symbol=""):
|
||||
return {"repo": repo, "path": path, "symbol": symbol}
|
||||
from tests.helpers import loc
|
||||
|
||||
|
||||
def _survivor(locations, merged_from, tags=None, owner=7):
|
||||
@@ -54,7 +51,7 @@ async def _run_unmerge(survivor, *, source_alive=None, restore=1):
|
||||
|
||||
async def test_unmerge_strips_only_what_the_source_contributed():
|
||||
"""The survivor's own location survives; the source's is removed."""
|
||||
mine, theirs = _loc("mine.py"), _loc("theirs.py")
|
||||
mine, theirs = loc(path="mine.py", repo="r"), loc(path="theirs.py", repo="r")
|
||||
survivor = _survivor(
|
||||
[mine, theirs],
|
||||
[{"id": 2, "locations": [theirs], "tags": ["helper"]}],
|
||||
@@ -70,7 +67,7 @@ async def test_a_location_the_survivor_also_owned_is_never_stripped():
|
||||
"""The central hazard the task named. If a source brought a location the
|
||||
survivor ALREADY had, merge attributes nothing to it — so reversing must
|
||||
leave that call site in place."""
|
||||
shared = _loc("shared.py")
|
||||
shared = loc(path="shared.py", repo="r")
|
||||
survivor = _survivor([shared], [{"id": 2, "locations": [], "tags": ["t"]}])
|
||||
kwargs = await _run_unmerge(survivor)
|
||||
assert kwargs["data"]["locations"] == [shared]
|
||||
@@ -78,8 +75,8 @@ async def test_a_location_the_survivor_also_owned_is_never_stripped():
|
||||
|
||||
async def test_the_reversed_entry_leaves_the_provenance_list():
|
||||
survivor = _survivor(
|
||||
[_loc("a.py"), _loc("b.py")],
|
||||
[{"id": 2, "locations": [_loc("b.py")]}, {"id": 3, "locations": []}],
|
||||
[loc(path="a.py", repo="r"), loc(path="b.py", repo="r")],
|
||||
[{"id": 2, "locations": [loc(path="b.py", repo="r")]}, {"id": 3, "locations": []}],
|
||||
)
|
||||
kwargs = await _run_unmerge(survivor)
|
||||
assert s.merged_from_ids(kwargs["data"]["merged_from"]) == [3]
|
||||
@@ -89,7 +86,7 @@ async def test_the_reversed_entry_leaves_the_provenance_list():
|
||||
|
||||
|
||||
async def test_unmerging_the_last_source_clears_the_provenance_line():
|
||||
survivor = _survivor([_loc("a.py")], [{"id": 2, "locations": [], "tags": ["t"]}])
|
||||
survivor = _survivor([loc(path="a.py", repo="r")], [{"id": 2, "locations": [], "tags": ["t"]}])
|
||||
kwargs = await _run_unmerge(survivor)
|
||||
assert "Merged from" not in kwargs["body"]
|
||||
|
||||
@@ -101,8 +98,8 @@ async def test_an_already_restored_source_is_repaired_not_refused():
|
||||
"""The scenario that motivated the feature: the operator restored the source
|
||||
from the trash by hand, so both records claim its call sites and nothing ever
|
||||
stripped the survivor's copy. Un-merge must fix that, not reject it."""
|
||||
theirs = _loc("theirs.py")
|
||||
survivor = _survivor([_loc("mine.py"), theirs],
|
||||
theirs = loc(path="theirs.py", repo="r")
|
||||
survivor = _survivor([loc(path="mine.py", repo="r"), theirs],
|
||||
[{"id": 2, "locations": [theirs]}])
|
||||
alive = SimpleNamespace(id=2, user_id=7, note_type="snippet", deleted_at=None,
|
||||
title="g — x", tags=["snippet"], body="", data=None)
|
||||
@@ -110,13 +107,13 @@ async def test_an_already_restored_source_is_repaired_not_refused():
|
||||
kwargs = await _run_unmerge(survivor, source_alive=alive)
|
||||
# Nothing to revive — it's already alive — but the subtraction still happens.
|
||||
revive.assert_not_called()
|
||||
assert kwargs["data"]["locations"] == [_loc("mine.py")]
|
||||
assert kwargs["data"]["locations"] == [loc(path="mine.py", repo="r")]
|
||||
|
||||
|
||||
async def test_a_purged_source_is_refused_and_the_survivor_is_untouched():
|
||||
"""If the source can't come back, stripping the survivor would lose the
|
||||
locations entirely — no record would claim them."""
|
||||
survivor = _survivor([_loc("a.py")], [{"id": 2, "locations": [_loc("a.py")]}])
|
||||
survivor = _survivor([loc(path="a.py", repo="r")], [{"id": 2, "locations": [loc(path="a.py", repo="r")]}])
|
||||
|
||||
async def fake_get(_uid, sid):
|
||||
return survivor if sid == 1 else None
|
||||
@@ -139,7 +136,7 @@ async def test_an_entry_without_attribution_is_refused_not_guessed():
|
||||
"""Bare-id provenance comes from parsing the body, which can only hold ids.
|
||||
Subtracting a guess could strip call sites the survivor owns — so refuse and
|
||||
say what to do instead."""
|
||||
survivor = _survivor([_loc("a.py")], [2])
|
||||
survivor = _survivor([loc(path="a.py", repo="r")], [2])
|
||||
|
||||
async def fake_get(_uid, sid):
|
||||
return survivor if sid == 1 else None
|
||||
@@ -155,7 +152,7 @@ async def test_an_entry_without_attribution_is_refused_not_guessed():
|
||||
|
||||
|
||||
async def test_unmerging_something_never_absorbed_is_refused():
|
||||
survivor = _survivor([_loc("a.py")], [{"id": 99, "locations": []}])
|
||||
survivor = _survivor([loc(path="a.py", repo="r")], [{"id": 99, "locations": []}])
|
||||
with (
|
||||
patch.object(s, "get_snippet", AsyncMock(return_value=survivor)),
|
||||
patch("scribe.services.access.can_write_note", AsyncMock(return_value=True)),
|
||||
@@ -166,7 +163,7 @@ async def test_unmerging_something_never_absorbed_is_refused():
|
||||
|
||||
async def test_unmerge_requires_write_access():
|
||||
"""Same rule as merge: a read-only share can see the record, not rearrange it."""
|
||||
survivor = _survivor([_loc("a.py")], [{"id": 2, "locations": []}])
|
||||
survivor = _survivor([loc(path="a.py", repo="r")], [{"id": 2, "locations": []}])
|
||||
with (
|
||||
patch.object(s, "get_snippet", AsyncMock(return_value=survivor)),
|
||||
patch("scribe.services.access.can_write_note", AsyncMock(return_value=False)),
|
||||
|
||||
Reference in New Issue
Block a user