"""Incremental maintenance == rebuild, with retraction + propagation (SHARD-WP-0011 T2).""" from shard_wiki.incremental import EquivalenceIndex from shard_wiki.model import Identity, Page from shard_wiki.provenance import ProvenanceEnvelope def _page(shard, key, body): return Page( identity=Identity(shard, key), body=body, envelope=ProvenanceEnvelope(source_shard=shard), ) def _rebuilt(pages, curator=()): idx = EquivalenceIndex() idx.build(pages, curator) return idx def _equal(a, b): return a.edges() == b.edges() and set(a.groups()) == set(b.groups()) def test_add_keeps_index_equal_to_rebuild(): pages = [_page("A", "Foo", "same content here"), _page("B", "Bar", "same content here")] idx = EquivalenceIndex() for p in pages: idx.add(p) assert _equal(idx, _rebuilt(pages)) assert idx.groups() # the two collapse def test_remove_keeps_index_equal_to_rebuild(): pages = [ _page("A", "Foo", "same content here"), _page("B", "Bar", "same content here"), _page("C", "Baz", "unrelated isolated material entirely"), ] idx = _rebuilt(pages) idx.remove(Identity("B", "Bar")) assert _equal(idx, _rebuilt([pages[0], pages[2]])) def test_edit_into_new_bucket_retracts_stale_edge(): a = _page("A", "Foo", "shared identical body text") b = _page("B", "Bar", "shared identical body text") idx = _rebuilt([a, b]) assert idx.groups() # A ≡ B initially # Edit B to something completely different: it exits A's buckets, the edge is retracted. b2 = _page("B", "Bar", "now totally divergent unrelated prose about nothing") idx.update(b2) assert idx.groups() == () # stale edge gone assert _equal(idx, _rebuilt([a, b2])) def test_edit_into_equivalence_adds_edge(): a = _page("A", "Foo", "target body to converge on later") b = _page("B", "Bar", "initially completely separate writing here") idx = _rebuilt([a, b]) assert idx.groups() == () b2 = _page("B", "Bar", "target body to converge on later") # now identical to A idx.update(b2) assert idx.equivalent_to(Identity("A", "Foo")) == frozenset( {Identity("A", "Foo"), Identity("B", "Bar")} ) assert _equal(idx, _rebuilt([a, b2])) def test_removing_connector_splits_a_chorus(): # Curator chain A—B—C (no direct A—C): one group of three. a, b, c = (_page("A", "X", "aaa"), _page("B", "Y", "bbb"), _page("C", "Z", "ccc")) idx = EquivalenceIndex() for p in (a, b, c): idx.add(p) idx.bind(a.identity, b.identity) idx.bind(b.identity, c.identity) assert idx.equivalent_to(a.identity) == {a.identity, b.identity, c.identity} # Removing the connector B retracts/propagates: the chorus splits. idx.remove(b.identity) assert idx.groups() == () chain = [(a.identity, b.identity), (b.identity, c.identity)] assert _equal(idx, _rebuilt([a, c], curator=chain))