Files
cali_scraper/tests/test_bill_id.py
T
2026-09-06 10:18:26 -07:00

152 lines
4.9 KiB
Python

"""Unit tests for scraper.bill_id -- the canonical bill_id codec (spec section 4).
These tests are the source of truth for the codec the parser depends on, so they
run first and are the most brittle-to-change. Any change to the codec that
breaks any of these must be a conscious decision, not an accidental refactor.
"""
from __future__ import annotations
import pytest
from scraper.bill_id import (
normalise_session,
origin_house,
parse_bill_id,
parse_display,
parse_supplement,
)
# ---------------------------------------------------------------- parse_bill_id
class TestParseBillId:
def test_assembly_bill_ab28(self):
dec = parse_bill_id("202520260AB28")
assert dec is not None
assert dec["session"] == "2025-2026"
assert dec["session_raw"] == "20252026"
assert dec["chamber"] == "AB"
assert dec["number"] == 28
assert dec["display"] == "AB 28"
assert dec["house"] == "assembly"
assert dec["raw"] == "202520260AB28"
def test_senate_bill_sb795(self):
dec = parse_bill_id("202520260SB795")
assert dec is not None
assert dec["chamber"] == "SB"
assert dec["session"] == "2025-2026"
assert dec["number"] == 795
assert dec["display"] == "SB 795"
assert dec["house"] == "senate"
def test_assembly_prefixes(self):
for raw, chamber, house in [
("202520260ACR5", "ACR", "assembly"),
("202520260HR7", "HR", "assembly"),
("202520260ABR1", "ABR", "assembly"),
]:
dec = parse_bill_id(raw)
assert dec is not None
assert dec["chamber"] == chamber, raw
assert dec["house"] == house, raw
def test_senate_prefixes(self):
for raw, chamber in [
("202520260SCR12", "SCR"),
("202520260SJR3", "SJR"),
]:
dec = parse_bill_id(raw)
assert dec is not None
assert dec["chamber"] == chamber, raw
assert dec["house"] == "senate", raw
def test_invalid_inputs_return_none(self):
for bad in [
"",
"garbage",
"202520260", # no chamber/number tail
"202520260AB", # chamber with no number
"202520260AB28JUNK",# trailing junk
"20250AB28", # wrong session length
"202520260XY12", # unknown chamber prefix
]:
assert parse_bill_id(bad) is None, f"expected None for {bad!r}"
def test_whitespace_is_stripped(self):
dec = parse_bill_id(" 202520260AB28 ")
assert dec is not None
assert dec["display"] == "AB 28"
assert dec["raw"] == " 202520260AB28 "
def test_session_decimal_boundary(self):
dec = parse_bill_id("202520260AB40")
assert dec is not None
assert dec["number"] == 40
assert dec["display"] == "AB 40"
# ---------------------------------------------------------------- normalise_session
class TestNormaliseSession:
def test_standard_session(self):
assert normalise_session("20252026") == "2025-2026"
def test_empty(self):
assert normalise_session("") == ""
def test_non_session_passthrough(self):
assert normalise_session("garbage") == "garbage"
def test_four_digit_passthrough(self):
assert normalise_session("2025") == "2025"
# ---------------------------------------------------------------- origin_house
class TestOriginHouse:
def test_assembly_prefixes(self):
for chamber in ("AB", "ACR", "HR", "ABR"):
assert origin_house(chamber) == "assembly", chamber
def test_senate_prefixes(self):
for chamber in ("SB", "SCR", "SJR"):
assert origin_house(chamber) == "senate", chamber
def test_unknown(self):
assert origin_house("ZZ") == "unknown"
assert origin_house("") == "unknown"
# ---------------------------------------------------------------- parse_display
class TestParseDisplay:
def test_padded(self):
assert parse_display("AB 00028") == ("AB", 28)
def test_unpadded(self):
assert parse_display("AB 28") == ("AB", 28)
def test_various_prefixes(self):
assert parse_display("SB 795") == ("SB", 795)
assert parse_display("ACR 5") == ("ACR", 5)
assert parse_display("SCR 12") == ("SCR", 12)
def test_invalid(self):
for garbage in ("", "SB", "AB 7 X", "XYZ 5", " "):
assert parse_display(garbage) is None, garbage
# ---------------------------------------------------------------- parse_supplement
class TestParseSupplement:
def test_standard(self):
assert parse_supplement("Supplemental 2") == 2
def test_case_insensitive(self):
assert parse_supplement("supplemental 1") == 1
def test_no_match(self):
for text in ("", "plain text", "Supplemental", "Supp 3"):
assert parse_supplement(text) is None, text