Fix Zotero table models for Zotero 9:
- Remove stale Annotations/Highlights/Transaction* models
- Add ItemAnnotations, RetractedItems, DeletedCollections,
DeletedSearches, DbDebug1
- Fix ItemAttachments, Libraries, Users column mismatches
New test files covering all major modules:
- cli/{bib,prisma,rec,zot,mail,run} deep exercising tests
- mail/{droplet,postmark,resend,cloudflare} lifecycle tests
- bib/{iom,oig,pincite,sync,regulations_gov,email_ingest,format,store}
- prisma/{vpn,fetch,export,llm,screen,eligibility,extract,project,ingest,flow}
- aco/lake/{unity,quality,deploy} + api/aco coverage gaps
- zot/{ops,db,extract,duck} + rec/{report,engine,base,pricers}
- pfs/{pipe,rules,eq,files}
Add pytest-xdist for parallel test execution.
Tracks #353
520 lines
17 KiB
Python
520 lines
17 KiB
Python
"""Tests for bib.format — citation formatting functions."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from bib.format import format_bibliography, format_citation
|
|
from bib.item import Download, Item, Manual, Regulation, Rule, Source
|
|
|
|
# ── Rule ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestFormatRule:
|
|
def test_bluebook_full(self) -> None:
|
|
r = Rule(
|
|
title="Medicare and Medicaid Programs; CY 2026 PFS Final Rule",
|
|
date_published="2025-12-31",
|
|
fr_volume="90",
|
|
fr_page="101174",
|
|
)
|
|
cite = format_citation(r, style="bluebook")
|
|
assert "90 Fed. Reg. 101174" in cite
|
|
assert "(Dec. 31, 2025)" in cite
|
|
assert cite.endswith(".")
|
|
|
|
def test_bluebook_no_page(self) -> None:
|
|
r = Rule(title="Test Rule", fr_volume="90", date_published="2025-01-01")
|
|
cite = format_citation(r, style="bluebook")
|
|
assert "90 Fed. Reg. ___" in cite
|
|
|
|
def test_apa_with_volume_and_page(self) -> None:
|
|
r = Rule(
|
|
title="CY 2026 PFS Final Rule",
|
|
date_published="2025-11-15",
|
|
fr_volume="90",
|
|
fr_page="98452",
|
|
)
|
|
cite = format_citation(r)
|
|
assert "Centers for Medicare & Medicaid Services" in cite
|
|
assert "(2025)" in cite
|
|
assert "CY 2026 PFS Final Rule" in cite
|
|
assert "*Federal Register*" in cite
|
|
assert "*90*" in cite
|
|
assert "98452" in cite
|
|
|
|
def test_apa_with_url(self) -> None:
|
|
r = Rule(
|
|
title="Test Rule",
|
|
date_published="2025-01-01",
|
|
fr_volume="90",
|
|
fr_page="1000",
|
|
url="https://example.com/rule",
|
|
)
|
|
cite = format_citation(r)
|
|
assert "https://example.com/rule" in cite
|
|
|
|
def test_apa_missing_date(self) -> None:
|
|
r = Rule(title="Test Rule")
|
|
cite = format_citation(r)
|
|
assert "(n.d.)" in cite
|
|
|
|
def test_apa_volume_only(self) -> None:
|
|
r = Rule(title="Test", fr_volume="90", date_published="2025-01-01")
|
|
cite = format_citation(r)
|
|
assert "*90*" in cite
|
|
|
|
|
|
# ── Manual ───────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestFormatManual:
|
|
def test_bluebook_full(self) -> None:
|
|
m = Manual(
|
|
title="Physician Fee Schedule",
|
|
manual_name="Claims Processing Manual",
|
|
pub_number="100-04",
|
|
chapter="12",
|
|
date_published="2025-01-01",
|
|
)
|
|
cite = format_citation(m, style="bluebook")
|
|
assert "Ctrs. for Medicare & Medicaid Servs." in cite
|
|
assert "Claims Processing Manual" in cite
|
|
assert "CMS Pub. 100-04" in cite
|
|
assert "Ch. 12" in cite
|
|
assert "(2025)" in cite
|
|
|
|
def test_apa_full(self) -> None:
|
|
m = Manual(
|
|
title="Physician Fee Schedule",
|
|
manual_name="Claims Processing Manual",
|
|
pub_number="100-04",
|
|
chapter="12",
|
|
date_published="2025-01-01",
|
|
)
|
|
cite = format_citation(m)
|
|
assert "Claims Processing Manual" in cite
|
|
assert "Pub. 100-04" in cite
|
|
assert "Ch. 12" in cite
|
|
|
|
def test_apa_with_url(self) -> None:
|
|
m = Manual(
|
|
title="Test",
|
|
manual_name="CPM",
|
|
url="https://example.com/manual",
|
|
)
|
|
cite = format_citation(m)
|
|
assert "https://example.com/manual" in cite
|
|
|
|
def test_apa_uses_institution(self) -> None:
|
|
m = Manual(title="Test", institution="Custom Institution")
|
|
cite = format_citation(m)
|
|
assert "Custom Institution" in cite
|
|
|
|
|
|
# ── Regulation ───────────────────────────────────────────────────────
|
|
|
|
|
|
class TestFormatRegulation:
|
|
def test_bluebook_with_section(self) -> None:
|
|
r = Regulation(
|
|
cfr_title="42",
|
|
cfr_section="414.22",
|
|
effective_date="2025-01-01",
|
|
)
|
|
cite = format_citation(r, style="bluebook")
|
|
assert cite == "42 C.F.R. § 414.22 (2025)."
|
|
|
|
def test_bluebook_part_only(self) -> None:
|
|
r = Regulation(
|
|
cfr_title="42",
|
|
cfr_part="414",
|
|
effective_date="2025-01-01",
|
|
)
|
|
cite = format_citation(r, style="bluebook")
|
|
assert cite == "42 C.F.R. pt. 414 (2025)."
|
|
|
|
def test_apa_with_section(self) -> None:
|
|
r = Regulation(
|
|
cfr_title="42",
|
|
cfr_part="414",
|
|
cfr_section="414.22",
|
|
effective_date="2025-01-01",
|
|
)
|
|
cite = format_citation(r)
|
|
assert "42 C.F.R. § 414.22" in cite
|
|
assert "(2025)" in cite
|
|
|
|
def test_apa_part_only(self) -> None:
|
|
r = Regulation(
|
|
cfr_title="42",
|
|
cfr_part="414",
|
|
effective_date="2025-01-01",
|
|
)
|
|
cite = format_citation(r)
|
|
assert "42 C.F.R. pt. 414" in cite
|
|
|
|
def test_apa_custom_title(self) -> None:
|
|
r = Regulation(
|
|
cfr_title="42",
|
|
cfr_part="414",
|
|
title="RVU Methodology",
|
|
effective_date="2025-01-01",
|
|
)
|
|
cite = format_citation(r)
|
|
assert "*RVU Methodology*" in cite
|
|
|
|
def test_apa_default_title_not_printed(self) -> None:
|
|
r = Regulation(
|
|
cfr_title="42",
|
|
cfr_part="414",
|
|
title="42 CFR Part 414",
|
|
effective_date="2025-01-01",
|
|
)
|
|
cite = format_citation(r)
|
|
assert "*42 CFR Part 414*" not in cite
|
|
|
|
def test_apa_with_url(self) -> None:
|
|
r = Regulation(
|
|
cfr_title="42",
|
|
cfr_part="414",
|
|
effective_date="2025-01-01",
|
|
url="https://ecfr.gov/title-42/part-414",
|
|
)
|
|
cite = format_citation(r)
|
|
assert "https://ecfr.gov/title-42/part-414" in cite
|
|
|
|
|
|
# ── Download ─────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestFormatDownload:
|
|
def test_apa(self) -> None:
|
|
d = Download(
|
|
title="RVU26A",
|
|
date_published="2026-01-01",
|
|
url="https://example.com/rvu",
|
|
)
|
|
cite = format_citation(d)
|
|
assert "Centers for Medicare & Medicaid Services" in cite
|
|
assert "(2026)" in cite
|
|
assert "*RVU26A*" in cite
|
|
assert "https://example.com/rvu" in cite
|
|
|
|
def test_custom_website_title(self) -> None:
|
|
d = Download(
|
|
title="Data",
|
|
website_title="Custom Portal",
|
|
date_published="2026-01-01",
|
|
)
|
|
cite = format_citation(d)
|
|
assert "Custom Portal" in cite
|
|
|
|
|
|
# ── Source / Journal Articles ────────────────────────────────────────
|
|
|
|
|
|
class TestFormatSource:
|
|
def test_apa_generic(self) -> None:
|
|
s = Source(
|
|
title="PY 2026 Financial Guarantees",
|
|
institution="CMS Innovation Center",
|
|
date_published="2025-06-01",
|
|
url="https://example.com/source",
|
|
)
|
|
cite = format_citation(s)
|
|
assert "CMS Innovation Center" in cite
|
|
assert "(2025)" in cite
|
|
assert "*PY 2026 Financial Guarantees*" in cite
|
|
|
|
def test_unknown_institution(self) -> None:
|
|
s = Source(title="Test")
|
|
cite = format_citation(s)
|
|
assert "Unknown" in cite
|
|
|
|
|
|
class TestFormatJournalArticle:
|
|
def _make_article(self, **kwargs) -> Source:
|
|
defaults = {
|
|
"title": "Skin substitute outcomes in wound healing",
|
|
"institution": "Journal of Wound Care",
|
|
"date_published": "2023-06-15",
|
|
"doc_type": "journal-article",
|
|
"extra": (
|
|
"PMID: 12345678\n"
|
|
"DOI: 10.1234/jwc.2023.001\n"
|
|
"Authors: Smith J; Jones K; Brown M\n"
|
|
"Journal: Journal of Wound Care\n"
|
|
),
|
|
}
|
|
defaults.update(kwargs)
|
|
return Source(**defaults)
|
|
|
|
def test_apa_three_authors(self) -> None:
|
|
s = self._make_article()
|
|
cite = format_citation(s)
|
|
assert "Smith et al." in cite
|
|
assert "(2023)" in cite
|
|
assert "Skin substitute outcomes" in cite
|
|
assert "*Journal of Wound Care*" in cite
|
|
assert "https://doi.org/10.1234/jwc.2023.001" in cite
|
|
|
|
def test_apa_two_authors(self) -> None:
|
|
s = self._make_article(extra="Authors: Smith J; Jones K\nDOI: 10.1/x\n")
|
|
cite = format_citation(s)
|
|
assert "Smith & Jones" in cite
|
|
|
|
def test_apa_one_author(self) -> None:
|
|
s = self._make_article(extra="Authors: Smith J\nDOI: 10.1/x\n")
|
|
cite = format_citation(s)
|
|
assert "Smith." in cite
|
|
|
|
def test_apa_no_doi_uses_url(self) -> None:
|
|
s = self._make_article(
|
|
extra="PMID: 123\nAuthors: Smith J\n",
|
|
url="https://example.com/article",
|
|
)
|
|
cite = format_citation(s)
|
|
assert "https://example.com/article" in cite
|
|
|
|
def test_jama_format(self) -> None:
|
|
s = self._make_article()
|
|
cite = format_citation(s, style="jama")
|
|
assert "Smith et al." in cite
|
|
assert "*Journal of Wound Care*" in cite
|
|
assert "doi:10.1234/jwc.2023.001" in cite
|
|
|
|
def test_jama_pmid_when_no_doi(self) -> None:
|
|
s = self._make_article(extra="PMID: 99999\nAuthors: Smith J\n")
|
|
cite = format_citation(s, style="jama")
|
|
assert "PMID: 99999" in cite
|
|
|
|
def test_detected_by_pmid(self) -> None:
|
|
"""Source with PMID in extra but no doc_type should still format as journal."""
|
|
s = Source(
|
|
title="Test Article",
|
|
doc_type="",
|
|
extra="PMID: 12345\nAuthors: Doe J\nJournal: Test J\n",
|
|
date_published="2024-01-01",
|
|
)
|
|
cite = format_citation(s)
|
|
assert "Doe." in cite
|
|
assert "*Test J*" in cite
|
|
|
|
|
|
# ── Generic / Bibliography ───────────────────────────────────────────
|
|
|
|
|
|
class TestFormatGeneric:
|
|
def test_unknown_item_type(self) -> None:
|
|
item = Item(
|
|
item_type="custom",
|
|
title="Custom Item",
|
|
institution="Test Org",
|
|
date_published="2025-01-01",
|
|
)
|
|
cite = format_citation(item)
|
|
assert "Test Org" in cite
|
|
assert "*Custom Item*" in cite
|
|
|
|
def test_generic_with_url(self) -> None:
|
|
item = Item(
|
|
item_type="custom",
|
|
title="Custom",
|
|
date_published="2025-01-01",
|
|
url="https://example.com/custom",
|
|
)
|
|
cite = format_citation(item)
|
|
assert "https://example.com/custom" in cite
|
|
|
|
def test_generic_no_title(self) -> None:
|
|
item = Item(
|
|
item_type="custom",
|
|
institution="Org",
|
|
date_published="2025-01-01",
|
|
)
|
|
cite = format_citation(item)
|
|
assert "Org. (2025)." in cite
|
|
|
|
def test_generic_no_institution(self) -> None:
|
|
item = Item(
|
|
item_type="custom",
|
|
title="Title",
|
|
date_published="2025-01-01",
|
|
)
|
|
cite = format_citation(item)
|
|
assert "Unknown" in cite
|
|
|
|
|
|
class TestFormatBibliography:
|
|
def test_numbered_entries(self) -> None:
|
|
items = [
|
|
Rule(title="Rule A", date_published="2025-01-01"),
|
|
Manual(title="Manual B"),
|
|
]
|
|
bib = format_bibliography(items)
|
|
lines = bib.strip().split("\n")
|
|
assert len(lines) == 2
|
|
assert lines[0].startswith("[1]")
|
|
assert lines[1].startswith("[2]")
|
|
|
|
def test_empty_list(self) -> None:
|
|
bib = format_bibliography([])
|
|
assert bib == ""
|
|
|
|
def test_single_item(self) -> None:
|
|
items = [Source(title="Test", date_published="2025-01-01")]
|
|
bib = format_bibliography(items)
|
|
assert bib.startswith("[1]")
|
|
assert "\n" not in bib
|
|
|
|
|
|
# ── Date parsing edge cases (lines 82, 83, 87, 88) ────────────────
|
|
|
|
|
|
class TestParseDateEdge:
|
|
def test_invalid_month_value_error(self) -> None:
|
|
"""Lines 82, 83: int() ValueError on non-numeric month."""
|
|
from bib.format import _parse_date
|
|
|
|
year, month, day = _parse_date("2025-XX-01")
|
|
assert year == "2025"
|
|
assert month == "" # ValueError caught
|
|
assert day == "1"
|
|
|
|
def test_invalid_day_value_error(self) -> None:
|
|
"""Lines 87, 88: int() ValueError on non-numeric day."""
|
|
from bib.format import _parse_date
|
|
|
|
year, month, day = _parse_date("2025-01-XX")
|
|
assert year == "2025"
|
|
assert month == "Jan."
|
|
assert day == "" # ValueError caught
|
|
|
|
|
|
# ── Bluebook date variants (lines 102, 105-107) ───────────────────
|
|
|
|
|
|
class TestBluebookDate:
|
|
def test_no_year(self) -> None:
|
|
"""Line 102: empty date → empty string."""
|
|
from bib.format import _bluebook_date
|
|
|
|
assert _bluebook_date("") == ""
|
|
|
|
def test_month_and_day(self) -> None:
|
|
"""Lines 105-107: full date with month and day."""
|
|
from bib.format import _bluebook_date
|
|
|
|
result = _bluebook_date("2025-12-31")
|
|
assert result == "Dec. 31, 2025"
|
|
|
|
def test_month_only(self) -> None:
|
|
"""Line 106: date with month but no day."""
|
|
from bib.format import _bluebook_date
|
|
|
|
result = _bluebook_date("2025-06")
|
|
assert result == "June 2025"
|
|
|
|
|
|
# ── Parse authors (line 121) ────────────────────────────────────────
|
|
|
|
|
|
class TestParseAuthors:
|
|
def test_parses_authors(self) -> None:
|
|
"""Line 121: _parse_authors extracts last names."""
|
|
from bib.format import _parse_authors
|
|
|
|
result = _parse_authors("Authors: Smith J; Jones K\nOther: data")
|
|
assert result == ["Smith", "Jones"]
|
|
|
|
def test_no_authors_line(self) -> None:
|
|
from bib.format import _parse_authors
|
|
|
|
assert _parse_authors("No authors here") == []
|
|
|
|
|
|
# ── Bluebook rule no date (line 184) ───────────────────────────────
|
|
|
|
|
|
class TestRuleBluebookNoDate:
|
|
def test_rule_bluebook_no_date(self) -> None:
|
|
"""Line 184: bluebook rule with no date ends with '.'."""
|
|
r = Rule(title="Test Rule", fr_volume="90", fr_page="101174")
|
|
cite = format_citation(r, style="bluebook")
|
|
assert cite.endswith(".")
|
|
assert "(" not in cite or ")" not in cite # no date paren
|
|
|
|
|
|
# ── Source format doc_type attribute error (lines 316, 317) ─────────
|
|
|
|
|
|
class TestFormatSourceDocType:
|
|
def test_source_without_doc_type_attr(self) -> None:
|
|
"""Lines 316, 317: Source with no doc_type still formats."""
|
|
# Item base class doesn't have doc_type; _format_source handles
|
|
# the AttributeError and falls to generic
|
|
item = Item(
|
|
item_type="source",
|
|
title="Generic Source",
|
|
institution="Test",
|
|
date_published="2025-01-01",
|
|
)
|
|
# This should trigger the generic source path since Item
|
|
# doesn't have doc_type attr directly accessible via format logic
|
|
cite = format_citation(item)
|
|
assert "Test" in cite
|
|
|
|
|
|
# ── Journal with no authors (line 341) ──────────────────────────────
|
|
|
|
|
|
class TestJournalNoAuthors:
|
|
def test_no_authors_uses_institution(self) -> None:
|
|
"""Line 341: journal with no Authors line uses institution."""
|
|
s = Source(
|
|
title="Test",
|
|
doc_type="journal-article",
|
|
extra="PMID: 123\nJournal: Test J\n",
|
|
institution="Fallback Journal",
|
|
date_published="2024-01-01",
|
|
)
|
|
cite = format_citation(s)
|
|
assert "Fallback Journal" in cite
|
|
|
|
def test_no_authors_unknown(self) -> None:
|
|
"""Line 341: journal with no Authors and no institution uses 'Unknown'."""
|
|
s = Source(
|
|
title="Test",
|
|
doc_type="journal-article",
|
|
extra="PMID: 123\n",
|
|
date_published="2024-01-01",
|
|
)
|
|
cite = format_citation(s)
|
|
assert "Unknown" in cite
|
|
|
|
|
|
# ── JAMA two authors and fallback (lines 368, 372) ─────────────────
|
|
|
|
|
|
class TestJamaTwoAuthors:
|
|
def test_two_authors_jama(self) -> None:
|
|
"""Line 368: JAMA format with two authors uses comma."""
|
|
s = Source(
|
|
title="Test",
|
|
doc_type="journal-article",
|
|
extra="Authors: Smith J; Jones K\nDOI: 10.1/x\nJournal: J\n",
|
|
date_published="2024-01-01",
|
|
)
|
|
cite = format_citation(s, style="jama")
|
|
assert "Smith, Jones" in cite
|
|
|
|
def test_no_authors_jama_unknown(self) -> None:
|
|
"""Line 372: JAMA with no authors/institution uses 'Unknown'."""
|
|
s = Source(
|
|
title="Test",
|
|
doc_type="journal-article",
|
|
extra="PMID: 123\n",
|
|
date_published="2024-01-01",
|
|
)
|
|
cite = format_citation(s, style="jama")
|
|
assert "Unknown" in cite
|