Files
stack/tests/bib/test_regulations_gov.py

236 lines
7.1 KiB
Python

"""Tests for bib.regulations_gov — reg.gov v4 API client."""
from __future__ import annotations
from unittest.mock import MagicMock, patch
import pytest
from bib.regulations_gov import (
Client,
Comment,
_byline,
_docket_from_comment_id,
_filename_from,
_hash,
_parse_comment,
_reg_date,
_slug,
upsert_comment,
)
# ── Pure helpers ──────────────────────────────────────────────────
class TestRegDate:
def test_iso_to_space(self):
assert _reg_date("2017-08-30T18:35:48Z") == "2017-08-30 18:35:48"
def test_already_space(self):
assert _reg_date("2017-08-30 18:35:48") == "2017-08-30 18:35:48"
def test_empty(self):
assert _reg_date("") == ""
def test_none(self):
assert _reg_date("") == ""
class TestSlug:
def test_basic(self):
assert _slug("American Medical Association") == "american-medical-association"
def test_special(self):
assert _slug("Test & Co., Inc.") == "test-co-inc"
def test_truncate(self):
assert len(_slug("x" * 100)) <= 40
class TestFilenameFrom:
def test_url(self):
assert (
_filename_from(
"https://downloads.regulations.gov/CMS-2021-0119-26001/attachment_1.pdf"
)
== "attachment_1.pdf"
)
def test_with_query(self):
assert _filename_from("https://x.com/file.pdf?token=abc") == "file.pdf"
def test_empty(self):
assert _filename_from("") == ""
class TestHash:
def test_deterministic(self):
assert _hash("test") == _hash("test")
assert len(_hash("test")) == 10
class TestByline:
def test_org(self):
c = Comment(
id="1",
title="",
posted_date="",
received_date="",
docket_id="",
comment_on_id="",
organization="AMA",
)
assert _byline(c) == "AMA"
def test_name(self):
c = Comment(
id="1",
title="",
posted_date="",
received_date="",
docket_id="",
comment_on_id="",
first_name="John",
last_name="Doe",
)
assert _byline(c) == "John Doe"
def test_empty(self):
c = Comment(
id="1",
title="",
posted_date="",
received_date="",
docket_id="",
comment_on_id="",
)
assert _byline(c) == ""
class TestDocketFromCommentId:
def test_standard(self):
assert _docket_from_comment_id("CMS-2025-0304-14107") == "CMS-2025-0304"
def test_short(self):
assert _docket_from_comment_id("AB") == "AB"
# ── _parse_comment ────────────────────────────────────────────────
class TestParseComment:
def test_basic(self):
row = {
"id": "CMS-2017-0092-0002",
"attributes": {
"title": "Test Comment",
"postedDate": "2017-07-18T00:00:00Z",
"receivedDate": "2017-07-15T00:00:00Z",
"docketId": "CMS-2017-0092",
"commentOnId": "0900006482921ba1",
"firstName": "Jane",
"lastName": "Doe",
"organization": "AMA",
"comment": "This is the body.",
"attachmentCount": 2,
},
}
c = _parse_comment(row)
assert c.id == "CMS-2017-0092-0002"
assert c.title == "Test Comment"
assert c.posted_date == "2017-07-18"
assert c.organization == "AMA"
assert c.comment_text == "This is the body."
assert c.attachment_count == 2
def test_missing_attrs(self):
c = _parse_comment({"id": "X", "attributes": {}})
assert c.id == "X"
assert c.title == ""
assert c.attachment_count == 0
# ── upsert_comment ────────────────────────────────────────────────
class TestUpsertComment:
def test_creates_item(self):
store = MagicMock()
store.upsert_status.return_value = ("KEY1", "created")
c = Comment(
id="CMS-2017-0092-0002",
title="Test",
posted_date="2017-07-18",
received_date="2017-07-15",
docket_id="CMS-2017-0092",
comment_on_id="obj123",
organization="AMA",
)
key = upsert_comment(store, c, cms_id="CMS-1676-P")
assert key == "KEY1"
store.upsert_status.assert_called_once()
item = store.upsert_status.call_args[0][0]
assert "AMA" in item.title
assert item.url == "https://www.regulations.gov/comment/CMS-2017-0092-0002"
# ── Client (mocked httpx) ────────────────────────────────────────
class TestClient:
@patch.dict("os.environ", {"REGULATIONS_GOV_API_KEY": "test-key"})
def test_init(self):
c = Client(sleep=0)
assert c._key == "test-key"
c.close()
@patch.dict("os.environ", {"REGULATIONS_GOV_API_KEY": ""})
def test_init_no_key(self):
with pytest.raises(RuntimeError, match="REGULATIONS_GOV_API_KEY"):
Client()
@patch.dict("os.environ", {"REGULATIONS_GOV_API_KEY": "test-key"})
def test_get_retry_429(self):
mock_client = MagicMock()
resp_429 = MagicMock()
resp_429.status_code = 429
resp_ok = MagicMock()
resp_ok.status_code = 200
resp_ok.json.return_value = {"data": []}
resp_ok.raise_for_status = MagicMock()
mock_client.get.side_effect = [resp_429, resp_ok]
with patch("bib.regulations_gov.time.sleep"):
c = Client(sleep=0, client=mock_client)
result = c._get("/test")
assert result == {"data": []}
@patch.dict("os.environ", {"REGULATIONS_GOV_API_KEY": "test-key"})
def test_resolve_docket(self):
mock_client = MagicMock()
resp = MagicMock()
resp.status_code = 200
resp.json.return_value = {
"data": [{"attributes": {"docketId": "CMS-2017-0092"}}]
}
resp.raise_for_status = MagicMock()
mock_client.get.return_value = resp
with patch("bib.regulations_gov.time.sleep"):
c = Client(sleep=0, client=mock_client)
result = c.resolve_docket("CMS-1676-P")
assert result == "CMS-2017-0092"
@patch.dict("os.environ", {"REGULATIONS_GOV_API_KEY": "test-key"})
def test_resolve_docket_not_found(self):
mock_client = MagicMock()
resp = MagicMock()
resp.status_code = 200
resp.json.return_value = {"data": []}
resp.raise_for_status = MagicMock()
mock_client.get.return_value = resp
with patch("bib.regulations_gov.time.sleep"):
c = Client(sleep=0, client=mock_client)
assert c.resolve_docket("NONEXISTENT") is None