● Zion Boggan
repos/Docket/services/ingest/tests/test_source_url.py
zionboggan.com ↗
169 lines · python
History for this file →
1
"""
2
Tests for ingest.source_validator  -  neutrality rule
3
 
4
Every stored fact MUST carry a source_url whose host is an allowlisted .gov domain.
5
"""
6
 
7
import sqlite3
8
import tempfile
9
import os
10
 
11
import pytest
12
 
13
from ingest.source_validator import (
14
    ALLOWLISTED_HOSTS,
15
    SourceURLError,
16
    is_allowlisted,
17
    validate_source_url,
18
)
19
from ingest.db import get_connection, init_schema
20
 
21
 
22
 
23
class TestIsAllowlisted:
24
    def test_explicit_allowlist_hosts(self):
25
        for host in ALLOWLISTED_HOSTS:
26
            url = f"https://{host}/some/path"
27
            assert is_allowlisted(url), f"Expected {host!r} to be allowlisted"
28
 
29
    def test_any_gov_domain_is_allowed(self):
30
        gov_urls = [
31
            "https://www.usaspending.gov/award/CONT_AWD_N0001923C0001",
32
            "https://congress.gov/bill/119th-congress/house-bill/1",
33
            "https://fiscaldata.treasury.gov/datasets/debt-to-the-penny/",
34
            "https://clerk.house.gov/evs/2026/ROLL_001.xml",
35
            "https://www.senate.gov/legislative/LIS/roll_call_votes/vote1191/vote_119_1_00001.xml",
36
            "https://leg.colorado.gov/bills/hb25-1001",
37
            "https://legiscan.gov/somepage",
38
            "https://www.congress.gov/bill/119th-congress/senate-bill/99",
39
        ]
40
        for url in gov_urls:
41
            assert is_allowlisted(url), f"Expected .gov URL to be allowed: {url}"
42
 
43
    def test_non_gov_domains_rejected(self):
44
        bad_urls = [
45
            "https://www.nytimes.com/article/congress-bill",
46
            "https://propublica.org/data",
47
            "https://govtrack.us/congress/bills",
48
            "https://ballotpedia.org/H.R._1",
49
            "https://en.wikipedia.org/wiki/U.S._Congress",
50
            "https://politico.com/story",
51
            "https://api.example.com/bills",
52
            "",
53
        ]
54
        for url in bad_urls:
55
            assert not is_allowlisted(url), f"Expected {url!r} to be rejected"
56
 
57
    def test_empty_string_rejected(self):
58
        assert not is_allowlisted("")
59
 
60
    def test_none_like_string_rejected(self):
61
        assert not is_allowlisted("None")
62
 
63
    def test_subdomain_gov_allowed(self):
64
        assert is_allowlisted("https://api.congress.gov/v3/bill")
65
        assert is_allowlisted("https://api.fiscaldata.treasury.gov/services/api/fiscal_service/v1/accounting/od/debt_to_penny")
66
 
67
    def test_gov_in_path_not_in_host_rejected(self):
68
        assert not is_allowlisted("https://example.com/gov/bills")
69
 
70
 
71
 
72
class TestValidateSourceUrl:
73
    def test_valid_gov_url_does_not_raise(self):
74
        validate_source_url("https://www.usaspending.gov/award/CONT_AWD_TEST")
75
        validate_source_url("https://congress.gov/bill/119th-congress/house-bill/1")
76
 
77
    def test_invalid_url_raises_source_url_error(self):
78
        with pytest.raises(SourceURLError):
79
            validate_source_url("https://www.reuters.com/article/xyz")
80
 
81
    def test_empty_url_raises(self):
82
        with pytest.raises(SourceURLError):
83
            validate_source_url("")
84
 
85
    def test_non_gov_subdomain_raises(self):
86
        with pytest.raises(SourceURLError):
87
            validate_source_url("https://api.example.io/bills")
88
 
89
    def test_error_message_contains_host(self):
90
        with pytest.raises(SourceURLError, match="nytimes.com"):
91
            validate_source_url("https://www.nytimes.com/article/bill")
92
 
93
 
94
 
95
class TestDatabaseSourceUrlConstraint:
96
    """
97
    Integration test: simulate writing rows to SQLite and verify that any row
98
    whose source_url is not allowlisted would have been caught by validate_source_url
99
    before insertion.
100
    """
101
 
102
    def _make_db(self):
103
        tmp = tempfile.mktemp(suffix=".db")
104
        conn = get_connection(tmp)
105
        init_schema(conn)
106
        return conn, tmp
107
 
108
    def test_awards_with_valid_source_url_stored_successfully(self):
109
        conn, tmp = self._make_db()
110
        source_url = "https://www.usaspending.gov/award/CONT_AWD_TEST001"
111
        validate_source_url(source_url)
112
        conn.execute(
113
            "INSERT INTO awards (source, external_id, agency, recipient, amount, action_date, program, source_url) "
114
            "VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
115
            ("usaspending", "TEST001", "DoD", "ACME Corp", 1_000_000, "2026-06-25", "contract", source_url),
116
        )
117
        conn.commit()
118
        row = conn.execute("SELECT source_url FROM awards WHERE external_id='TEST001'").fetchone()
119
        assert row is not None
120
        assert is_allowlisted(row["source_url"])
121
        conn.close()
122
        os.unlink(tmp)
123
 
124
    def test_awards_with_invalid_source_url_blocked(self):
125
        conn, tmp = self._make_db()
126
        bad_url = "https://www.politico.com/award/XYZ"
127
        with pytest.raises(SourceURLError):
128
            validate_source_url(bad_url)
129
            conn.execute(
130
                "INSERT INTO awards (source, external_id, agency, recipient, amount, action_date, program, source_url) "
131
                "VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
132
                ("test", "BAD001", "Agency", "Bad Corp", 100, "2026-06-25", "contract", bad_url),
133
            )
134
        conn.close()
135
        os.unlink(tmp)
136
 
137
    def test_all_awards_from_usaspending_have_gov_source_url(self):
138
        """
139
        Simulate a batch of awards as the poller would produce them and confirm
140
        every source_url passes the allowlist check.
141
        """
142
        internal_ids = [
143
            "CONT_AWD_N0001923C0001",
144
            "CONT_AWD_W912HV23C0001",
145
            "ASST_NON_2023MU00009",
146
        ]
147
        for iid in internal_ids:
148
            url = f"https://www.usaspending.gov/award/{iid}"
149
            assert is_allowlisted(url), f"USAspending award URL failed allowlist: {url}"
150
 
151
    def test_bills_from_congress_have_gov_source_url(self):
152
        urls = [
153
            "https://www.congress.gov/bill/119th-congress/house-bill/1",
154
            "https://www.congress.gov/bill/119th-congress/senate-bill/99",
155
        ]
156
        for url in urls:
157
            assert is_allowlisted(url)
158
 
159
    def test_fiscal_from_treasury_has_gov_source_url(self):
160
        url = "https://fiscaldata.treasury.gov/datasets/debt-to-the-penny/debt-to-the-penny"
161
        assert is_allowlisted(url)
162
 
163
    def test_house_votes_have_gov_source_url(self):
164
        url = "https://clerk.house.gov/evs/2026/ROLL_001.xml"
165
        assert is_allowlisted(url)
166
 
167
    def test_senate_votes_have_gov_source_url(self):
168
        url = "https://www.senate.gov/legislative/LIS/roll_call_votes/vote1191/vote_119_1_00001.xml"
169
        assert is_allowlisted(url)