fix(bib): upsert merges tags/collections with stored row on URL dedupe (closes #624)
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 ─────────────────────────────
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user