Files
stack/tests/cli/test_bib_payment_comments.py
kert b5e07e67b2 feat(bib): discover IPPS, OPPS and DMEPOS rules and pull their comments
The PFS register-and-walk path now serves the annual IPPS/LTCH and
OPPS/ASC rules and DMEPOS payment rules: title-filtered FR discovery
(discover-*-rules) and docket registration + comment walks
(fetch-*-comments). Correction documents (C1-...) parse, a null FR
abstract no longer becomes 'None', and the store waits out the mail
poller's write bursts (busy_timeout) instead of failing SQLITE_BUSY.
2026-11-11 12:53:22 -05:00

210 lines
7.0 KiB
Python

"""IPPS and OPPS comment discovery uses the PFS register-and-walk path."""
from __future__ import annotations
from unittest.mock import MagicMock, patch
from typer.testing import CliRunner
from bib.dockets import Docket
from bib.item import Rule
from bib.regulations_gov import WalkResult
from bib.store import Store
from cli.bib import app
runner = CliRunner()
def _api():
api = MagicMock()
api.__enter__ = MagicMock(return_value=api)
api.__exit__ = MagicMock(return_value=False)
api.find_documents_in_docket.return_value = []
api.iter_comments.return_value = []
return api
def _doc(number: str, cms_id: str):
doc = MagicMock()
doc.type = "Proposed Rule"
doc.publication_date = "2018-05-07"
doc.dockets = [cms_id]
doc.html_url = f"https://example.com/{number}"
doc.document_number = number
return doc
@patch("bib.federalregister.ipps_rules", return_value=[])
@patch("bib.connect")
def test_fetch_ipps_comments_empty(mc_connect, _rules):
mc_connect.return_value = MagicMock()
api = _api()
with patch("bib.regulations_gov.Client", return_value=api):
result = runner.invoke(app, ["fetch-ipps-comments"])
assert result.exit_code == 0, result.output
assert "IPPS rules" in result.output
@patch("bib.federalregister.opps_rules", return_value=[])
@patch("bib.connect")
def test_fetch_opps_comments_empty(mc_connect, _rules):
mc_connect.return_value = MagicMock()
api = _api()
with patch("bib.regulations_gov.Client", return_value=api):
result = runner.invoke(app, ["fetch-opps-comments"])
assert result.exit_code == 0, result.output
assert "OPPS rules" in result.output
@patch("bib.federalregister.ipps_rules", return_value=[])
def test_discover_ipps_dry_run(_rules):
result = runner.invoke(app, ["discover-ipps-rules", "--dry-run"])
assert result.exit_code == 0
@patch("bib.federalregister.opps_rules", return_value=[])
def test_discover_opps_dry_run(_rules):
result = runner.invoke(app, ["discover-opps-rules", "--dry-run"])
assert result.exit_code == 0
@patch("bib.regulations_gov.walk_docket", return_value=WalkResult())
@patch("bib.regulations_gov.discover_docket")
@patch("bib.regulations_gov.Client")
@patch("bib.translate.federal_register")
@patch("bib.federalregister.ipps_rules")
@patch("bib.connect")
def test_correction_reuses_the_proposed_rule_docket(
mc_connect, mc_rules, mc_fr, mc_client, mc_disc, mc_walk
):
store = Store(":memory:", storage_dir="/tmp/nope")
mc_connect.return_value = store
mc_rules.return_value = [
_doc("2018-1", "CMS-1694-P"),
_doc("2018-2", "CMS-1694-CN"),
]
mc_fr.return_value = Rule(title="FY2019 IPPS", url="https://example.com/2018-1")
api = _api()
api.resolve_docket.return_value = "CMS-2018-0040"
mc_client.return_value = api
def _discover(_api, docket_id, rule_cms_id="", title_contains=""):
assert title_contains == "inpatient prospective payment"
return Docket(
id=docket_id,
rule_cms_id=rule_cms_id,
fr_object_id="obj",
comment_end_date="2018-06-25",
)
mc_disc.side_effect = _discover
result = runner.invoke(app, ["fetch-ipps-comments"])
assert result.exit_code == 0, result.output
mc_disc.assert_called_once()
saved = store.docket_get("CMS-2018-0040")
assert saved is not None
assert saved.rule_cms_id == "CMS-1694-P"
assert mc_walk.call_count == 2
@patch("bib.regulations_gov.walk_docket", return_value=WalkResult())
@patch("bib.regulations_gov.discover_docket")
@patch("bib.regulations_gov.Client")
@patch("bib.translate.federal_register")
@patch("bib.federalregister.opps_rules")
@patch("bib.connect")
def test_reused_sealed_docket_is_not_walked(
mc_connect, mc_rules, mc_fr, mc_client, mc_disc, mc_walk
):
store = Store(":memory:", storage_dir="/tmp/nope")
store.docket_upsert(
Docket(
id="CMS-2018-0042",
rule_cms_id="CMS-1695-P",
fr_object_id="obj",
sealed_at="2020-01-01T00:00:00Z",
seal_reason="auto",
)
)
mc_connect.return_value = store
mc_rules.return_value = [_doc("2018-3", "CMS-1695-CN")]
mc_fr.return_value = Rule(title="CY2019 OPPS", url="https://example.com/2018-3")
api = _api()
api.resolve_docket.return_value = "CMS-2018-0042"
mc_client.return_value = api
result = runner.invoke(app, ["fetch-opps-comments"])
assert result.exit_code == 0, result.output
assert "sealed" in result.output
mc_disc.assert_not_called()
mc_walk.assert_not_called()
@patch("bib.federalregister.dmepos_rules", return_value=[])
@patch("bib.connect")
def test_fetch_dmepos_comments_empty(mc_connect, _rules):
mc_connect.return_value = MagicMock()
api = _api()
with patch("bib.regulations_gov.Client", return_value=api):
result = runner.invoke(app, ["fetch-dmepos-comments"])
assert result.exit_code == 0, result.output
assert "DMEPOS rules" in result.output
@patch("bib.federalregister.dmepos_rules", return_value=[])
def test_discover_dmepos_dry_run(_rules):
result = runner.invoke(app, ["discover-dmepos-rules", "--dry-run"])
assert result.exit_code == 0
@patch("bib.regulations_gov.walk_docket", return_value=WalkResult())
@patch("bib.regulations_gov.pull_titled_siblings", return_value=0)
@patch("bib.regulations_gov.discover_docket")
@patch("bib.regulations_gov.Client")
@patch("bib.translate.federal_register")
@patch("bib.federalregister.dmepos_rules")
@patch("bib.connect")
def test_dmepos_resolve_omits_the_title_needle(
mc_connect, mc_rules, mc_fr, mc_client, mc_disc, _siblings, _walk
):
store = Store(":memory:", storage_dir="/tmp/nope")
mc_connect.return_value = store
acronym = _doc("2018-14986", "CMS-1691-P")
acronym.title = (
"Durable Medical Equipment, Prosthetics, Orthotics and "
"Supplies (DMEPOS) Competitive Bidding Program"
)
spelled = _doc("2023-14044", "CMS-1780-P")
spelled.title = (
"Certain Requirements for Durable Medical Equipment "
"Prosthetics and Orthotics Supplies"
)
mc_rules.return_value = [acronym, spelled]
mc_fr.return_value = Rule(title="DMEPOS", url="https://example.com/dmepos")
api = _api()
needles: list[tuple[str, str]] = []
def _resolve(cms_id, title_contains=""):
needles.append((cms_id, title_contains))
return "CMS-2018-0099" if cms_id == "CMS-1691-P" else "CMS-2023-0100"
api.resolve_docket.side_effect = _resolve
mc_client.return_value = api
def _discover(_api, docket_id, rule_cms_id="", title_contains=""):
return Docket(
id=docket_id,
rule_cms_id=rule_cms_id,
fr_object_id="obj",
comment_end_date="2018-09-10",
)
mc_disc.side_effect = _discover
result = runner.invoke(app, ["fetch-dmepos-comments"])
assert result.exit_code == 0, result.output
assert needles == [("CMS-1691-P", ""), ("CMS-1780-P", "")]
for call in api.resolve_docket.call_args_list:
assert "title_contains" not in call.kwargs