fix(bib): upsert merges tags/collections with stored row on URL dedupe (closes #624)

This commit is contained in:
kert
2026-08-17 11:06:30 -04:00
parent a03801fb63
commit 91c33063ee
2 changed files with 43 additions and 5 deletions

View File

@@ -219,9 +219,6 @@ class Store:
).fetchone() ).fetchone()
if existing: if existing:
ekey = existing["key"] ekey = existing["key"]
row = item.to_row()
row.pop("key", None)
# Merge tags
if tags: if tags:
for tag in tags: for tag in tags:
label = tag.label if hasattr(tag, "label") else str(tag) label = tag.label if hasattr(tag, "label") else str(tag)
@@ -229,10 +226,19 @@ class Store:
if collection and collection not in item.collections: if collection and collection not in item.collections:
item.collections.append(collection) item.collections.append(collection)
item.stamp_access() item.stamp_access()
# Merge with what's already stored — a re-ingest must
# never clobber tags/collections curated on the row
# since the last ingest (#624: the CY2027 NPRM lost its
# sup: registration tag to this and silently vanished
# from the tag-scoped Zotero sync). Deliberate removal
# goes through remove_tag, not upsert.
current = self.get(ekey)
row = item.to_row() row = item.to_row()
row.pop("key", None) row.pop("key", None)
row["tags"] = item.tags row["tags"] = list(dict.fromkeys([*current.tags, *item.tags]))
row["collections"] = item.collections row["collections"] = list(
dict.fromkeys([*current.collections, *item.collections])
)
self.update(ekey, **row) self.update(ekey, **row)
return ekey return ekey

View File

@@ -732,6 +732,38 @@ class TestUpsertDedup:
assert "plain-tag" in got.tags assert "plain-tag" in got.tags
s.close() s.close()
def test_upsert_preserves_curated_tags_on_existing(self) -> None:
"""Regression #624: a re-ingest upsert must not clobber tags
added to the stored item since the last ingest (the CY2027 NPRM
lost sup:2027_PFS_NPRM this way and never reached Zotero)."""
s = Store(":memory:")
item1 = Rule(title="First", url="https://example.com/keep")
key = s.upsert(item1, tags=["source:federal-register"])
s.add_tag(key, "sup:2027_PFS_NPRM") # curated after ingest
item2 = Rule(title="Re-ingested", url="https://example.com/keep")
item2.add_tag("source:federal-register")
key2 = s.upsert(item2)
assert key2 == key
got = s.get(key)
assert "sup:2027_PFS_NPRM" in got.tags
assert "source:federal-register" in got.tags
assert got.tags.count("source:federal-register") == 1
s.close()
def test_upsert_preserves_existing_collections(self) -> None:
"""Regression #624: same wipe hazard via _sync_collections."""
s = Store(":memory:")
coll = s.ensure_collections({"Rules": {}})["Rules"]
item1 = Rule(title="First", url="https://example.com/keepc")
key = s.upsert(item1, collection=coll)
item2 = Rule(title="Re-ingested", url="https://example.com/keepc")
key2 = s.upsert(item2)
assert key2 == key
assert coll in s.get(key).collections
s.close()
# ── _sync_collections with unknown key ───────────────────────────── # ── _sync_collections with unknown key ─────────────────────────────