Files
stack/tests/bib/test_sync_deeper.py
kert 2dc0e9c586 feat(bib): vendor the staged LEAD corpus documents into the library
dev/scripts/import_lead_corpus.py files every distinct document in
lead_corpus/ as a bib `source` item with its file attached, tagged
source:lead-corpus, under a "LEAD corpus" collection tree:

- lead_research: 73 methodology papers, CMS model specifications and
  research articles, with title / authors / year / URL from
  sources.csv (people become an `Authors:` line, organisations the
  institution) and the per-topic LEAD notes as the abstract.
- lead_enrichment: 68 data dictionaries, methodology references and
  eCQM specifications, with title / publisher / release / download URL
  and "how LEAD could use it" from the _catalogs job lists.

Identical files (one PDF filed under two topic folders, the same
dictionary in every release folder) fold to one item by sha256: 123
items. Skipped: 81 `dictionary__*` files that are byte-identical
copies of data.cms.gov's JavaScript app shell (no dictionary content),
and text extracts sitting next to their PDF. Re-running is a no-op
(items match on URL, or on title within the tag; attachments on name).

bib.sync, so the import lands properly in Zotero:
- `source:lead-corpus` items route to LEAD corpus / Research /
  <topic>, / Dataset documentation, or / Measure specifications.
- An item already in Zotero is matched by URL, else by key when the
  Zotero row with that key has the same title. Matching on URL alone
  re-created every URL-less item on every sync — the second corpus
  sync duplicated the 11 documents without a public URL. The title
  guard keeps an unrelated row that merely holds the key from being
  overwritten (test_key_collision).
2026-10-27 12:38:39 -04:00

337 lines
11 KiB
Python

"""Exercise bib.sync — push_to_zotero with real Zotero DB + mocked store."""
from __future__ import annotations
from unittest.mock import MagicMock
from bib.item import Manual, Source
from bib.sync import _sync_attachments, _zotero_collection_path, push_to_zotero
from zot.db import TYPE_MAP, Db
from zot.schema import create_db
def _setup(tmp_path):
path = str(tmp_path / "z.sqlite")
con = create_db(path)
con.close()
return path
class TestSyncAttachments:
def test_no_attachments(self, tmp_path):
path = _setup(tmp_path)
store = MagicMock()
store._con.return_value.execute.return_value.fetchall.return_value = []
with Db(path) as db:
parent_id = db.create_item(TYPE_MAP["document"])
db.commit()
n = _sync_attachments(
db, store, MagicMock(key="K1"), parent_id, tmp_path / "storage"
)
assert n == 0
def test_with_attachment(self, tmp_path):
path = _setup(tmp_path)
storage = tmp_path / "storage"
storage.mkdir()
# Create a fake source file
src_file = tmp_path / "source" / "paper.pdf"
src_file.parent.mkdir()
src_file.write_bytes(b"%PDF content")
store = MagicMock()
store._con.return_value.execute.return_value.fetchall.return_value = [
{
"filename": "paper.pdf",
"content_type": "application/pdf",
"storage_path": str(src_file),
},
]
bib_item = MagicMock(key="K1")
with Db(path) as db:
parent_id = db.create_item(TYPE_MAP["document"])
db.commit()
n = _sync_attachments(db, store, bib_item, parent_id, storage)
db.commit()
assert n == 1
def test_dedup(self, tmp_path):
path = _setup(tmp_path)
storage = tmp_path / "storage"
storage.mkdir()
src_file = tmp_path / "source" / "paper.pdf"
src_file.parent.mkdir()
src_file.write_bytes(b"%PDF content")
store = MagicMock()
store._con.return_value.execute.return_value.fetchall.return_value = [
{
"filename": "paper.pdf",
"content_type": "application/pdf",
"storage_path": str(src_file),
},
]
bib_item = MagicMock(key="K1")
with Db(path) as db:
parent_id = db.create_item(TYPE_MAP["document"])
db.commit()
# First attach
n1 = _sync_attachments(db, store, bib_item, parent_id, storage)
db.commit()
# Second attach should skip (dedup)
n2 = _sync_attachments(db, store, bib_item, parent_id, storage)
assert n1 == 1
assert n2 == 0
def test_missing_file(self, tmp_path):
path = _setup(tmp_path)
store = MagicMock()
store._con.return_value.execute.return_value.fetchall.return_value = [
{
"filename": "missing.pdf",
"content_type": "application/pdf",
"storage_path": "/nonexistent/file.pdf",
},
]
with Db(path) as db:
parent_id = db.create_item(TYPE_MAP["document"])
db.commit()
n = _sync_attachments(
db, store, MagicMock(key="K1"), parent_id, tmp_path / "storage"
)
assert n == 0
class TestPushToZotero:
def test_creates_source(self, tmp_path):
path = _setup(tmp_path)
item = Source(title="Test Document", url="https://example.com/doc")
item.doc_type = "Public Comment"
item.add_tag("source:regulations-gov")
stats = push_to_zotero([item], zotero_db=path)
assert stats["created"] >= 1
def test_skips_existing_url(self, tmp_path):
path = _setup(tmp_path)
item = Source(title="Test", url="https://example.com/doc")
item.doc_type = "Public Comment"
# First push creates
push_to_zotero([item], zotero_db=path)
# Second push skips
stats = push_to_zotero([item], zotero_db=path)
assert stats["skipped"] >= 1
def test_url_less_item_matches_on_key(self, tmp_path):
"""A staged document with no public URL is found again by its key
(Zotero keys mirror bib keys) instead of being re-created per sync."""
path = _setup(tmp_path)
item = Source(key="AB2CD3EF", title="Data dictionary", doc_type="dictionary")
first = push_to_zotero([item], zotero_db=path)
second = push_to_zotero([item], zotero_db=path)
assert first["created"] == 1
assert second == {**second, "created": 0, "skipped": 1}
with Db(path) as db:
assert db.find_item_by_key("AB2CD3EF") is not None
assert (
db.con.execute(
"SELECT count(*) FROM items WHERE itemID NOT IN "
"(SELECT itemID FROM itemAttachments)"
).fetchone()[0]
== 1
)
def test_collection_routing(self, tmp_path):
path = _setup(tmp_path)
item = Manual(title="IOM Ch1", manual_name="Claims Processing")
item.add_tag("source:iom")
stats = push_to_zotero([item], zotero_db=path)
assert stats["collections"] >= 1
def test_with_store_attachments(self, tmp_path):
path = _setup(tmp_path)
storage = tmp_path / "storage"
storage.mkdir()
src = tmp_path / "src" / "paper.pdf"
src.parent.mkdir()
src.write_bytes(b"%PDF content")
att_rows = [
{
"filename": "paper.pdf",
"content_type": "application/pdf",
"storage_path": str(src),
},
]
def _execute(sql, *args, **kwargs):
m = MagicMock()
# Notes query selects n.title; attachment query selects a.filename
m.fetchall.return_value = att_rows if "a.filename" in sql else []
return m
store = MagicMock()
store._con.return_value.execute.side_effect = _execute
item = Source(title="Test", url="https://unique-url-test.com/doc")
item.doc_type = "Public Comment"
stats = push_to_zotero(
[item], zotero_db=path, zotero_storage=str(storage), store=store
)
assert stats["created"] >= 1
class TestCollectionPathEdge:
def test_oig_with_guidance(self):
item = Source(title="SFA")
item.add_tag("agency:oig")
item.add_tag("guidance:sfa")
path = _zotero_collection_path(item)
assert "SFA" in path[-1]
def test_email_no_mailbox(self):
item = Source(title="Msg")
item.add_tag("source:email")
path = _zotero_collection_path(item)
assert path == ["Inbox"]
def test_oig_with_sector(self):
"""Line 421: OIG item with sector tag."""
item = Source(title="CPG")
item.add_tag("agency:oig")
item.add_tag("sector:hospitals")
path = _zotero_collection_path(item)
assert "Hospitals" in path[-1]
def test_lead_corpus_research_by_first_known_topic(self):
item = Source(title="CMS-HCC calibration")
for t in ("source:lead-corpus", "lead-topic:cms_primary", "lead-topic:ratchet"):
item.add_tag(t)
assert _zotero_collection_path(item) == [
"LEAD corpus",
"Research",
"CMS primary sources",
]
def test_lead_corpus_unknown_topic_lands_in_research(self):
item = Source(title="Paper")
item.add_tag("source:lead-corpus")
item.add_tag("lead-topic:ratchet")
assert _zotero_collection_path(item) == ["LEAD corpus", "Research"]
def test_lead_corpus_dataset_docs_and_specs(self):
dictionary = Source(title="ACO participants — data dictionary")
for t in ("source:lead-corpus", "lead-dataset:cms_aco", "doc:dictionary"):
dictionary.add_tag(t)
spec = Source(title="CMS122v14 eCQM specification (QDM)")
for t in ("source:lead-corpus", "lead-dataset:ecqi", "doc:specification"):
spec.add_tag(t)
assert _zotero_collection_path(dictionary) == [
"LEAD corpus",
"Dataset documentation",
]
assert _zotero_collection_path(spec) == [
"LEAD corpus",
"Measure specifications",
]
def test_oig_no_sector_no_guidance(self):
"""Line 421: OIG with neither sector nor guidance → base only."""
item = Source(title="OIG doc")
item.add_tag("agency:oig")
path = _zotero_collection_path(item)
assert path == ["Healthcare Data Platform", "OIG Guidance"]
class TestPushToZoteroCreators:
def test_journal_article_creators(self, tmp_path):
"""Lines 338, 339: creators are added for journal articles."""
path = _setup(tmp_path)
item = Source(
title="Test Article",
url="https://example.com/article",
doc_type="journal-article",
extra="Authors: Smith J; Jones K\nDOI: 10.1/x\nJournal: J\n",
date_published="2024-01-01",
)
stats = push_to_zotero([item], zotero_db=path)
assert stats["created"] >= 1
assert stats.get("creators", 0) >= 2
class TestPushToZoteroExistingWithAttachments:
def test_existing_url_syncs_attachments(self, tmp_path):
"""Lines 301, 312-314: existing URL item syncs attachments + collection path."""
path = _setup(tmp_path)
storage = tmp_path / "storage"
storage.mkdir()
src = tmp_path / "src" / "paper.pdf"
src.parent.mkdir()
src.write_bytes(b"%PDF content")
att_rows = [
{
"filename": "paper.pdf",
"content_type": "application/pdf",
"storage_path": str(src),
},
]
def _execute(sql, *args, **kwargs):
m = MagicMock()
# Notes query selects n.title; attachment query selects a.filename
m.fetchall.return_value = att_rows if "a.filename" in sql else []
return m
store = MagicMock()
store._con.return_value.execute.side_effect = _execute
item = Manual(
title="IOM Ch1",
url="https://example.com/iom-ch1",
manual_name="Claims Processing",
)
item.add_tag("source:iom")
# First push creates
stats1 = push_to_zotero(
[item], zotero_db=path, zotero_storage=str(storage), store=store
)
assert stats1["created"] >= 1
# Second push hits the existing-URL path (skips)
stats2 = push_to_zotero(
[item], zotero_db=path, zotero_storage=str(storage), store=store
)
assert stats2["skipped"] >= 1
class TestPushToZoteroCollectionPath:
def test_email_collection_path(self, tmp_path):
"""Lines 273: _resolve_path is cached across items."""
path = _setup(tmp_path)
item1 = Source(title="Email 1", url="https://x.com/e1")
item1.add_tag("source:email")
item1.add_tag("mailbox:updates")
item2 = Source(title="Email 2", url="https://x.com/e2")
item2.add_tag("source:email")
item2.add_tag("mailbox:updates")
stats = push_to_zotero([item1, item2], zotero_db=path)
assert stats["created"] >= 2