Files
stack/tests/cli/test_bib_deep.py
kert 1c542ae708 fix(cli): code-tags --dry-run help text and capped_wide total label
--dry-run's help text now describes what it computes (the merged
item→codes mapping) instead of a stale "report per-collection item/tag
counts". The CLI's total line now prints capped_wide= for the
per-item --max-codes-per-item cap count (the stats dict key stays
skipped_wide); docs regenerated via docs/scripts/extract_cli.py.
2026-09-10 00:56:48 -04:00

457 lines
15 KiB
Python

"""Deep exercising tests for cli/bib.py."""
from __future__ import annotations
from unittest.mock import MagicMock, patch
from typer.testing import CliRunner
from cli.bib import app
runner = CliRunner()
class TestSync:
@patch("bib.meta.apply_column_comments", return_value=["s1"])
@patch("bib.meta.collect_column_comments", return_value={"r": "d"})
@patch("bib.connect", return_value=MagicMock())
@patch("conf.path", return_value="/tmp/test.duckdb")
@patch("duckdb.connect")
def test_sync(self, mc_duck, mc_path, mc_connect, mc_cc, mc_ac):
result = runner.invoke(app, ["sync"])
assert result.exit_code == 0
@patch("bib.meta.collect_column_comments", return_value={"r": "d"})
@patch("bib.connect", return_value=MagicMock())
def test_sync_dry(self, mc_connect, mc_cc):
result = runner.invoke(app, ["sync", "--dry-run"])
assert result.exit_code == 0
class TestTag:
@patch("bib.connect")
def test_tag(self, mc):
store = MagicMock()
store.list_tags.return_value = [{"name": "t1", "count": 5}]
mc.return_value = store
result = runner.invoke(app, ["tag"])
assert result.exit_code == 0
class TestQuery:
@patch("bib.connect")
def test_query(self, mc):
store = MagicMock()
store.list_items.return_value = []
mc.return_value = store
result = runner.invoke(app, ["query", "test"])
assert result.exit_code == 0
class TestDiscoverPfsRules:
@patch("bib.federalregister.pfs_rules", return_value=[])
def test_dry_run(self, mc):
result = runner.invoke(app, ["discover-pfs-rules", "--dry-run"])
assert result.exit_code == 0
class TestImportZotero:
@patch("bib.zotero_import.import_books")
@patch("bib.zotero_import.list_collection")
@patch("conf.connect.zotero", return_value=MagicMock())
@patch("bib.connect", return_value=MagicMock())
def test_dry_run_prints_would_create_and_writes_nothing(
self, mc_connect, mc_zotero, mc_list, mc_import
):
from bib.zotero_import import ZoteroBook
book = ZoteroBook(
key="ZKEY1",
title="CPT 2018",
url="https://x",
year="2017",
publisher="AMA",
attachments=(),
)
mc_list.return_value = [book]
mc_import.return_value = {
"created": 1,
"updated": 0,
"unchanged": 0,
"attached": 0,
"missing": 0,
"items": [
{
"key": "",
"zotero_key": "ZKEY1",
"title": "CPT 2018",
"status": "created",
"attached": 0,
"missing": 0,
"missing_paths": [],
}
],
}
result = runner.invoke(
app,
[
"import-zotero",
"--collection",
"AMA Coding Publications",
"--dry-run",
],
)
assert result.exit_code == 0
assert "would-created" in result.stdout
assert "CPT 2018" in result.stdout
# dry_run=True is forwarded to import_books, which is the sole
# place writes happen — the CLI itself performs none.
_, kwargs = mc_import.call_args
assert kwargs["dry_run"] is True
assert kwargs["tags"] == []
@patch("bib.zotero_import.import_books")
@patch("bib.zotero_import.list_collection")
@patch("conf.connect.zotero", return_value=MagicMock())
@patch("bib.connect", return_value=MagicMock())
def test_only_filters_by_title_substring(
self, mc_connect, mc_zotero, mc_list, mc_import
):
from bib.zotero_import import ZoteroBook
cpt = ZoteroBook(
key="A", title="CPT 2018", url="", year="2017", publisher="", attachments=()
)
icd = ZoteroBook(
key="B",
title="ICD-10-CM 2018",
url="",
year="2017",
publisher="",
attachments=(),
)
mc_list.return_value = [cpt, icd]
mc_import.return_value = {
"created": 0,
"updated": 0,
"unchanged": 0,
"attached": 0,
"missing": 0,
"items": [],
}
runner.invoke(
app,
[
"import-zotero",
"--collection",
"X",
"--only",
"cpt",
"--dry-run",
],
)
passed_books = mc_import.call_args[0][1]
assert [b.key for b in passed_books] == ["A"]
@patch("bib.zotero_import.import_books")
@patch("bib.zotero_import.list_collection", return_value=[])
@patch("conf.connect.zotero", return_value=MagicMock())
@patch("bib.connect", return_value=MagicMock())
def test_tags_passed_through(self, mc_connect, mc_zotero, mc_list, mc_import):
mc_import.return_value = {
"created": 0,
"updated": 0,
"unchanged": 0,
"attached": 0,
"missing": 0,
"items": [],
}
runner.invoke(
app,
[
"import-zotero",
"--collection",
"X",
"--tag",
"source:ama",
"--tag",
"module:coding",
],
)
_, kwargs = mc_import.call_args
assert kwargs["tags"] == ["source:ama", "module:coding"]
@patch("bib.zotero_import.import_books")
@patch("bib.zotero_import.list_collection")
@patch("conf.connect.zotero", return_value=MagicMock())
@patch("bib.connect", return_value=MagicMock())
def test_missing_file_printed_to_stderr(
self, mc_connect, mc_zotero, mc_list, mc_import
):
from bib.zotero_import import ZoteroBook
book = ZoteroBook(
key="G1", title="Ghost", url="", year="2020", publisher="", attachments=()
)
mc_list.return_value = [book]
mc_import.return_value = {
"created": 1,
"updated": 0,
"unchanged": 0,
"attached": 0,
"missing": 1,
"items": [
{
"key": "GKEY",
"zotero_key": "G1",
"title": "Ghost",
"status": "created",
"attached": 0,
"missing": 1,
"missing_paths": ["/data/storage/XX/gone.pdf"],
}
],
}
result = runner.invoke(app, ["import-zotero", "--collection", "X"])
assert "! missing on disk: /data/storage/XX/gone.pdf" in result.output
assert "missing=1" in result.output
class TestAllHelp:
"""Exercise every registered command's --help (exercises Typer paths)."""
def test_fetch_docket(self):
assert runner.invoke(app, ["fetch-docket-comments", "--help"]).exit_code == 0
def test_fetch_pfs(self):
assert runner.invoke(app, ["fetch-pfs-comments", "--help"]).exit_code == 0
def test_ingest_iom(self):
assert runner.invoke(app, ["ingest-iom", "--help"]).exit_code == 0
def test_attach_iom(self):
assert runner.invoke(app, ["attach-iom", "--help"]).exit_code == 0
def test_ingest_oig(self):
assert runner.invoke(app, ["ingest-oig", "--help"]).exit_code == 0
def test_attach_oig(self):
assert runner.invoke(app, ["attach-oig", "--help"]).exit_code == 0
def test_sync_zotero(self):
assert runner.invoke(app, ["sync-zotero", "--help"]).exit_code == 0
def test_import_zotero(self):
assert runner.invoke(app, ["import-zotero", "--help"]).exit_code == 0
def test_backfill(self):
assert runner.invoke(app, ["backfill-comments", "--help"]).exit_code == 0
def test_ingest_mail(self):
assert runner.invoke(app, ["ingest-mail", "--help"]).exit_code == 0
def test_refresh_iom(self):
assert runner.invoke(app, ["refresh-iom", "--help"]).exit_code == 0
def test_refresh_oig(self):
assert runner.invoke(app, ["refresh-oig", "--help"]).exit_code == 0
class TestCodeTags:
@patch("bib.codetags.apply_code_tags")
@patch("bib.codetags.item_codes_from_chunks")
@patch("bib.codetags.item_codes_from_anchors")
@patch("pfs.families.refresh_from")
@patch("pfs.anchors.code_family_index", return_value={})
@patch("os.path.exists", return_value=False)
@patch("llm.index._engine")
@patch("llm.config.load")
@patch("bib.connect")
def test_all_collections_merge_into_one_apply_call(
self,
mc_connect,
mc_cfg,
mc_engine,
mc_exists,
mc_code_index,
mc_refresh,
mc_anchors,
mc_chunks,
mc_apply,
):
mc_anchors.return_value = {"R1": ({"99490"}, {"CCM"})}
mc_chunks.return_value = {"C1": ({"99497"}, set())}
mc_apply.return_value = {"items": 2, "tags_added": 2, "skipped_wide": 0}
result = runner.invoke(app, ["code-tags", "--collection", "all"])
assert result.exit_code == 0, result.output
mc_anchors.assert_called_once()
# rules(anchors) + rules(chunks) + comments(chunks) + corpus(chunks)
# all feed ONE merged mapping — exactly one apply_code_tags call
# (Ruling A7: the cap must see an item's combined code set).
mc_apply.assert_called_once()
mapping = mc_apply.call_args.args[1]
assert mapping["R1"] == ({"99490"}, {"CCM"})
assert mapping["C1"] == ({"99497"}, set())
assert mc_apply.call_args.kwargs["dry_run"] is False
@patch("bib.codetags.apply_code_tags")
@patch("bib.codetags.item_codes_from_chunks")
@patch("bib.codetags.item_codes_from_anchors")
@patch("pfs.families.refresh_from")
@patch("pfs.anchors.code_family_index", return_value={})
@patch("os.path.exists", return_value=False)
@patch("llm.index._engine")
@patch("llm.config.load")
@patch("bib.connect")
def test_straddling_item_is_merged_before_apply(
self,
mc_connect,
mc_cfg,
mc_engine,
mc_exists,
mc_code_index,
mc_refresh,
mc_anchors,
mc_chunks,
mc_apply,
):
# Same item_key surfaces from rules-anchors and again from
# corpus-chunks (a rule item whose PDF also landed in corpus) —
# the codes must union into one mapping entry, not two capped
# separately.
mc_anchors.return_value = {"X1": ({"99490"}, {"CCM"})}
mc_chunks.return_value = {"X1": ({"99497"}, {"TCM"})}
mc_apply.return_value = {"items": 1, "tags_added": 3, "skipped_wide": 0}
result = runner.invoke(app, ["code-tags", "--collection", "all"])
assert result.exit_code == 0, result.output
mc_apply.assert_called_once()
mapping = mc_apply.call_args.args[1]
assert mapping["X1"] == ({"99490", "99497"}, {"CCM", "TCM"})
@patch("bib.codetags.apply_code_tags")
@patch("bib.codetags.item_codes_from_chunks")
@patch("bib.codetags.item_codes_from_anchors", return_value={})
@patch("pfs.families.refresh_from")
@patch("pfs.anchors.code_family_index", return_value={})
@patch("os.path.exists", return_value=False)
@patch("llm.index._engine")
@patch("llm.config.load")
@patch("bib.connect")
def test_dry_run_passes_through_and_writes_nothing(
self,
mc_connect,
mc_cfg,
mc_engine,
mc_exists,
mc_code_index,
mc_refresh,
mc_anchors,
mc_chunks,
mc_apply,
):
mc_chunks.return_value = {}
mc_apply.return_value = {"items": 0, "tags_added": 0, "skipped_wide": 0}
store = MagicMock()
mc_connect.return_value = store
result = runner.invoke(
app, ["code-tags", "--collection", "comments", "--dry-run"]
)
assert result.exit_code == 0, result.output
assert mc_apply.call_args.kwargs["dry_run"] is True
store.add_tag.assert_not_called()
store.add_tags.assert_not_called()
@patch("bib.codetags.apply_code_tags")
@patch("bib.codetags.item_codes_from_chunks")
@patch("bib.codetags.item_codes_from_anchors", return_value={})
@patch("pfs.families.refresh_from")
@patch("pfs.anchors.code_family_index", return_value={})
@patch("os.path.exists", return_value=False)
@patch("llm.index._engine")
@patch("llm.config.load")
@patch("bib.connect")
def test_anchors_only_skips_pgvector_entirely(
self,
mc_connect,
mc_cfg,
mc_engine,
mc_exists,
mc_code_index,
mc_refresh,
mc_anchors,
mc_chunks,
mc_apply,
):
mc_apply.return_value = {"items": 0, "tags_added": 0, "skipped_wide": 0}
result = runner.invoke(
app, ["code-tags", "--collection", "all", "--anchors-only"]
)
assert result.exit_code == 0, result.output
mc_anchors.assert_called_once()
mc_chunks.assert_not_called()
mc_engine.assert_not_called()
# only the rules mapping gets applied — comments/corpus skipped
assert mc_apply.call_count == 1
@patch("bib.codetags.apply_code_tags")
@patch("bib.codetags.item_codes_from_chunks")
@patch("bib.codetags.item_codes_from_anchors")
@patch("pfs.families.refresh_from")
@patch("pfs.anchors.code_family_index", return_value={})
@patch("os.path.exists", return_value=False)
@patch("llm.index._engine")
@patch("llm.config.load")
@patch("bib.connect")
def test_anchors_only_with_non_rules_collection_skips_everything(
self,
mc_connect,
mc_cfg,
mc_engine,
mc_exists,
mc_code_index,
mc_refresh,
mc_anchors,
mc_chunks,
mc_apply,
):
store = MagicMock()
mc_connect.return_value = store
result = runner.invoke(
app, ["code-tags", "--collection", "comments", "--anchors-only"]
)
assert result.exit_code == 0, result.output
assert "comments: skipped (--anchors-only)" in result.output
assert "total: items=0 tags_added=0 capped_wide=0" in result.output
mc_anchors.assert_not_called()
mc_chunks.assert_not_called()
mc_apply.assert_not_called()
mc_engine.assert_not_called()
store.add_tag.assert_not_called()
store.add_tags.assert_not_called()
def test_help(self):
assert runner.invoke(app, ["code-tags", "--help"]).exit_code == 0
@patch("bib.connect")
def test_bad_collection_raises_bad_parameter(self, mc_connect):
result = runner.invoke(app, ["code-tags", "--collection", "nope"])
assert result.exit_code != 0