Initial commit
This commit is contained in:
commit
5f2db08b43
100 files changed
+16985
No files matched your search
@@ -0,0 +1,151 @@
|
||||
"""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
|
||||
Reference in new issue
Block a user