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
244 lines
6.9 KiB
Python
244 lines
6.9 KiB
Python
"""Exercise bib.pincite — AST discovery, parse, classify, upsert, list."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
import textwrap
|
|
from unittest.mock import MagicMock
|
|
|
|
from bib.pincite import (
|
|
Pincite,
|
|
_classify_locator,
|
|
_is_narwhalify,
|
|
_path_to_module,
|
|
extract_all_pincites,
|
|
list_pincites,
|
|
parse_pincites,
|
|
upsert_pincites,
|
|
)
|
|
|
|
|
|
class TestClassifyLocatorDeep:
|
|
def test_page_number(self):
|
|
assert _classify_locator("p.14") == "page"
|
|
assert _classify_locator("pp. 14-20") == "page"
|
|
|
|
def test_fr_citation(self):
|
|
assert _classify_locator("88 FR 1234") == "fr"
|
|
|
|
def test_chapter(self):
|
|
assert _classify_locator("Ch. 1") == "chapter"
|
|
assert _classify_locator("ch 5") == "chapter"
|
|
|
|
def test_paragraph(self):
|
|
assert _classify_locator("¶3") == "paragraph"
|
|
|
|
def test_section(self):
|
|
assert _classify_locator("§2.2.1") == "section"
|
|
assert _classify_locator("42.123") == "section"
|
|
|
|
def test_other(self):
|
|
assert _classify_locator("some random text") == "other"
|
|
|
|
def test_empty(self):
|
|
assert _classify_locator("") == ""
|
|
|
|
|
|
class TestParsePincitesDeep:
|
|
def test_with_locator_and_note(self):
|
|
doc = ':pincite:`ABCD2345 p.14` — "Relevant quote from the text."'
|
|
result = parse_pincites(doc, "module.func")
|
|
assert len(result) == 1
|
|
assert result[0].item_key == "ABCD2345"
|
|
assert result[0].locator == "p.14"
|
|
|
|
def test_multiple(self):
|
|
doc = ":pincite:`EFGH5678 p.1`\n:pincite:`EFGH6789 p.2`"
|
|
result = parse_pincites(doc, "mod.func")
|
|
assert len(result) == 2
|
|
|
|
def test_dedup(self):
|
|
doc = textwrap.dedent("""\
|
|
:pincite:`EFGH5678 p.1` — "First"
|
|
:pincite:`EFGH5678 p.1` — "Duplicate"
|
|
""")
|
|
result = parse_pincites(doc, "mod.func")
|
|
assert len(result) == 1
|
|
|
|
def test_empty_docstring(self):
|
|
assert parse_pincites("", "mod.func") == []
|
|
assert parse_pincites(None, "mod.func") == []
|
|
|
|
|
|
class TestIsNarwhalify:
|
|
def test_simple_decorator(self):
|
|
source = textwrap.dedent("""\
|
|
import narwhals as nw
|
|
@nw.narwhalify
|
|
def my_func():
|
|
pass
|
|
""")
|
|
tree = ast.parse(source)
|
|
funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
|
|
assert len(funcs) == 1
|
|
assert _is_narwhalify(funcs[0])
|
|
|
|
def test_no_decorator(self):
|
|
source = "def plain(): pass"
|
|
tree = ast.parse(source)
|
|
funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
|
|
assert not _is_narwhalify(funcs[0])
|
|
|
|
def test_call_decorator(self):
|
|
source = textwrap.dedent("""\
|
|
@narwhalify()
|
|
def my_func():
|
|
pass
|
|
""")
|
|
tree = ast.parse(source)
|
|
funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
|
|
assert _is_narwhalify(funcs[0])
|
|
|
|
def test_attr_call_decorator(self):
|
|
source = textwrap.dedent("""\
|
|
import narwhals as nw
|
|
@nw.narwhalify()
|
|
def my_func():
|
|
pass
|
|
""")
|
|
tree = ast.parse(source)
|
|
funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
|
|
assert _is_narwhalify(funcs[0])
|
|
|
|
|
|
class TestPathToModule:
|
|
def test_regular_file(self, tmp_path):
|
|
src = tmp_path / "src"
|
|
(src / "pkg").mkdir(parents=True)
|
|
f = src / "pkg" / "mod.py"
|
|
f.write_text("")
|
|
assert _path_to_module(f, src) == "pkg.mod"
|
|
|
|
def test_init_file(self, tmp_path):
|
|
src = tmp_path / "src"
|
|
(src / "pkg").mkdir(parents=True)
|
|
f = src / "pkg" / "__init__.py"
|
|
f.write_text("")
|
|
assert _path_to_module(f, src) == "pkg"
|
|
|
|
|
|
class TestExtractAllPincites:
|
|
def test_finds_pincites(self, tmp_path):
|
|
src = tmp_path / "src"
|
|
src.mkdir()
|
|
f = src / "mod.py"
|
|
f.write_text(
|
|
textwrap.dedent('''\
|
|
import narwhals as nw
|
|
|
|
@nw.narwhalify
|
|
def calc():
|
|
""":pincite:`ABCD2345 p.42` — "Important reference."
|
|
"""
|
|
pass
|
|
''')
|
|
)
|
|
result = extract_all_pincites(src)
|
|
assert len(result) == 1
|
|
assert result[0].item_key == "ABCD2345"
|
|
|
|
def test_skips_non_narwhalify(self, tmp_path):
|
|
src = tmp_path / "src"
|
|
src.mkdir()
|
|
f = src / "mod.py"
|
|
f.write_text(
|
|
textwrap.dedent('''\
|
|
def plain():
|
|
""":pincite:`ABCD2345 p.42` — "Not in narwhalify."
|
|
"""
|
|
pass
|
|
''')
|
|
)
|
|
result = extract_all_pincites(src)
|
|
assert len(result) == 0
|
|
|
|
def test_syntax_error_skipped(self, tmp_path):
|
|
src = tmp_path / "src"
|
|
src.mkdir()
|
|
(src / "bad.py").write_text("def incomplete(:")
|
|
(src / "good.py").write_text(
|
|
textwrap.dedent('''\
|
|
@narwhalify
|
|
def calc():
|
|
""":pincite:`EFGH5678 p.1` — "Ref."
|
|
"""
|
|
pass
|
|
''')
|
|
)
|
|
result = extract_all_pincites(src)
|
|
assert len(result) == 1
|
|
|
|
|
|
class TestUpsertPincites:
|
|
def test_inserts_with_matching_item(self):
|
|
store = MagicMock()
|
|
con = MagicMock()
|
|
store._con.return_value = con
|
|
con.execute.return_value.fetchone.return_value = {"key": "EFGH5678"}
|
|
|
|
pincites = [
|
|
Pincite(
|
|
fn_path="mod.func",
|
|
item_key="EFGH5678",
|
|
locator="p.1",
|
|
locator_type="page",
|
|
note="note",
|
|
),
|
|
]
|
|
count = upsert_pincites(store, pincites)
|
|
assert count == 1
|
|
|
|
def test_skips_missing_item(self):
|
|
store = MagicMock()
|
|
con = MagicMock()
|
|
store._con.return_value = con
|
|
con.execute.return_value.fetchone.return_value = None
|
|
|
|
pincites = [
|
|
Pincite(
|
|
fn_path="mod.func",
|
|
item_key="NOPE",
|
|
locator="p.1",
|
|
locator_type="page",
|
|
note="",
|
|
),
|
|
]
|
|
count = upsert_pincites(store, pincites)
|
|
assert count == 0
|
|
|
|
|
|
class TestListPincites:
|
|
def test_no_table(self):
|
|
store = MagicMock()
|
|
con = MagicMock()
|
|
store._con.return_value = con
|
|
con.execute.side_effect = Exception("no such table")
|
|
result = list_pincites(store)
|
|
assert result == []
|
|
|
|
def test_with_filters(self):
|
|
store = MagicMock()
|
|
con = MagicMock()
|
|
store._con.return_value = con
|
|
con.execute.return_value.fetchall.return_value = [
|
|
{
|
|
"fn_path": "mod.func",
|
|
"item_key": "K1",
|
|
"locator": "p.1",
|
|
"locator_type": "page",
|
|
"note": "n",
|
|
},
|
|
]
|
|
result = list_pincites(store, fn_path="mod.func", item_key="K1")
|
|
assert len(result) == 1
|