CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 17s
CI & Build / TypeScript typecheck (push) Successful in 57s
CI & Build / integration (push) Successful in 1m19s
CI & Build / Python tests (push) Successful in 2m2s
CI & Build / Build & push image (push) Successful in 15s
Docstrings, section headers and assertion messages drop the record number, milestone or step that introduced the behaviour, and incident accounts are restated as the present-tense failure shape. A dead pointer to a design doc that is not in the repo is removed. Renamed to the property they check: test_the_old_shape_would_be_caught -> test_a_rank_then_scope_query_is_caught test_a_ledger_repeat_no_longer_produces_that_case_at_all -> test_a_ledger_repeat_is_counted_not_withheld test_the_header_no_longer_promises_once_per_session -> test_the_header_explains_the_seen_marker test_the_portal_case_no_longer_stamps_one_canon_onto_a_whole_file -> test_one_canon_is_not_stamped_onto_a_whole_file test_the_repos_own_prose_no_longer_defines_with -> test_the_repos_own_prose_does_not_define_with test_the_original_regression_create_note_with_content -> test_the_real_server_refuses_content_on_create_note Fixture and example ids stay: "note 5" in a supersession chain, "rule 156" in an illustrative question, `#2275` as the hex-versus-citation case, `#3244` as the example of a bare number, fixed dates in date-filter tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
105 lines
4.6 KiB
Python
105 lines
4.6 KiB
Python
"""Merge records what it absorbed.
|
|
|
|
Merging keeps the target's scalar fields, unions locations + tags, and trashes
|
|
the sources. Without provenance the fact that a variant ever existed survives
|
|
only in the trash — and only for someone who already knew to go looking. So the
|
|
survivor carries `merged_from`, written to the body and the indexed mirror from
|
|
one value, and ordinary edits must carry it forward rather than erase it.
|
|
"""
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
|
|
from scribe.services import snippets as s
|
|
from tests.helpers import fake_snippet
|
|
|
|
|
|
def _snippet(id=1, owner=9, body=None, data=None, tags=None):
|
|
return fake_snippet(
|
|
id=id, user_id=owner, title="formatDuration — humanize a millisecond count",
|
|
body=body if body is not None else s.compose_body(code="x = 1", language="ts"),
|
|
tags=tags if tags is not None else ["ts", "snippet"], data=data,
|
|
)
|
|
|
|
|
|
async def _run_merge(target, sources, source_ids):
|
|
"""Drive merge_snippets with the DB stubbed out; return update_note's kwargs."""
|
|
by_id = {target.id: target, **{x.id: x for x in sources}}
|
|
|
|
async def fake_get(_uid, sid):
|
|
return by_id.get(sid)
|
|
|
|
with patch.object(s, "get_snippet", AsyncMock(side_effect=fake_get)), \
|
|
patch("scribe.services.access.can_write_note", AsyncMock(return_value=True)), \
|
|
patch.object(s.notes_svc, "update_note",
|
|
AsyncMock(return_value=target)) as mock_update, \
|
|
patch("scribe.services.trash.delete", AsyncMock(return_value=object())):
|
|
await s.merge_snippets(7, target.id, source_ids)
|
|
return mock_update.await_args.kwargs
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_merge_records_what_it_absorbed_in_body_and_mirror():
|
|
kwargs = await _run_merge(
|
|
_snippet(id=1), [_snippet(id=2), _snippet(id=3)], [2, 3],
|
|
)
|
|
assert "**Merged from:** #2, #3" in kwargs["body"]
|
|
assert s.merged_from_ids(kwargs["data"]["merged_from"]) == [2, 3]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_merge_accumulates_across_merges():
|
|
"""A target merged twice keeps both histories — the second merge must not
|
|
replace the record of the first."""
|
|
target = _snippet(id=1, data=s.compose_data(name="f", merged_from=[2]))
|
|
kwargs = await _run_merge(target, [_snippet(id=3)], [3])
|
|
assert s.merged_from_ids(kwargs["data"]["merged_from"]) == [2, 3]
|
|
assert "**Merged from:** #2, #3" in kwargs["body"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_merge_does_not_record_a_skipped_cross_owner_source():
|
|
"""A source owned by someone else is skipped, not folded in — so it
|
|
must not appear in the provenance either, or the record would claim to
|
|
contain something it never absorbed."""
|
|
kwargs = await _run_merge(
|
|
_snippet(id=1, owner=9),
|
|
[_snippet(id=2, owner=9), _snippet(id=3, owner=42)],
|
|
[2, 3],
|
|
)
|
|
assert s.merged_from_ids(kwargs["data"]["merged_from"]) == [2]
|
|
assert "#3" not in kwargs["body"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_an_ordinary_edit_carries_provenance_forward():
|
|
"""Only a merge may add to it, but nothing else may drop it — update
|
|
recomposes body and mirror from scratch, so an omission here would silently
|
|
erase the history on the next unrelated edit."""
|
|
note = _snippet(id=1, data=s.compose_data(name="f", language="ts",
|
|
merged_from=[2, 3]))
|
|
with patch.object(s, "get_snippet", AsyncMock(return_value=note)), \
|
|
patch("scribe.services.access.can_write_note", AsyncMock(return_value=True)), \
|
|
patch.object(s.notes_svc, "update_note",
|
|
AsyncMock(return_value=note)) as mock_update:
|
|
await s.update_snippet(7, 1, signature="f(ms) -> string")
|
|
|
|
kwargs = mock_update.await_args.kwargs
|
|
assert s.merged_from_ids(kwargs["data"]["merged_from"]) == [2, 3]
|
|
assert "**Merged from:** #2, #3" in kwargs["body"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_a_pre_0070_row_keeps_provenance_through_the_body():
|
|
"""Rows written before the `data` column are never backfilled, so the body
|
|
line is their only copy — and it has to survive an edit too."""
|
|
body = s.compose_body(code="x = 1", language="ts", merged_from=[5])
|
|
note = _snippet(id=1, body=body, data=None)
|
|
with patch.object(s, "get_snippet", AsyncMock(return_value=note)), \
|
|
patch("scribe.services.access.can_write_note", AsyncMock(return_value=True)), \
|
|
patch.object(s.notes_svc, "update_note",
|
|
AsyncMock(return_value=note)) as mock_update:
|
|
await s.update_snippet(7, 1, when_to_use="humanize a ms count")
|
|
|
|
assert "**Merged from:** #5" in mock_update.await_args.kwargs["body"]
|