forked from ChelseaKR/permit-bearings
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_program_registry.py
More file actions
209 lines (171 loc) · 6.96 KB
/
Copy pathtest_program_registry.py
File metadata and controls
209 lines (171 loc) · 6.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
import json
from pathlib import Path
import pytest
from permit_pathways import program_registry as pr
ROOT = Path(__file__).parent.parent
REGISTRY_PATH = ROOT / "program-pages.json"
def _page(**overrides):
page = {
"page_id": "city-preapproved-plans",
"label": "City preapproved plan list",
"url": "https://example.org/plans",
"excerpt": "No plans are listed on this prototype page.",
"excerpt_fingerprint": None,
}
import hashlib
normalized = pr.normalize_excerpt(page["excerpt"])
page["excerpt_fingerprint"] = (
"sha256:" + hashlib.sha256(normalized.encode("utf-8")).hexdigest()
)
page.update(overrides)
return page
def _write(tmp_path, pages):
path = tmp_path / "program-pages.json"
path.write_text(json.dumps({"schema_version": 1, "pages": pages}), encoding="utf-8")
return path
def _load(tmp_path, pages):
return pr.load_program_registry(_write(tmp_path, pages))
# ---------------------------------------------------------------------------
# The committed registry
def test_committed_registry_loads_and_matches_the_availability_record():
pages = pr.load_program_registry(REGISTRY_PATH)
assert [page.page_id for page in pages] == ["woodland-preapproved-adu-program"]
assert pages[0].url.endswith("/Preapproved-ADU-Plan-Program")
assert pages[0].excerpt_fingerprint.startswith("sha256:")
# ---------------------------------------------------------------------------
# Schema validation
def test_fingerprint_must_match_its_own_excerpt(tmp_path):
with pytest.raises(ValueError, match="does not match the recorded excerpt"):
_load(tmp_path, [_page(excerpt_fingerprint="sha256:" + "0" * 64)])
def test_schema_errors_are_rejected(tmp_path):
cases = [
([_page(page_id="Bad ID")], "stable identifier"),
([_page(url="http://example.org/plans")], "canonical HTTPS URL"),
([_page(label=" ")], "non-blank text"),
([_page(), _page()], "duplicate page ID"),
([_page(excerpt_fingerprint="deadbeef")], "normalized SHA-256"),
]
for pages, match in cases:
with pytest.raises(ValueError, match=match):
_load(tmp_path, pages)
extra = _page()
extra["note"] = "unexpected"
with pytest.raises(ValueError, match="unknown fields"):
_load(tmp_path, [extra])
missing = _page()
del missing["label"]
with pytest.raises(ValueError, match="missing fields"):
_load(tmp_path, [missing])
# ---------------------------------------------------------------------------
# Classification
def _fetcher(responses):
def fetch(url):
result = responses[url]
if isinstance(result, Exception):
raise result
return result
return fetch
def test_pages_classify_as_changed_unverifiable_or_unchanged(tmp_path):
pages = _load(
tmp_path,
[
_page(page_id="a-still-there"),
_page(page_id="b-moved", url="https://example.org/moved"),
_page(page_id="c-unreachable", url="https://example.org/unreachable"),
_page(page_id="d-whitespace", url="https://example.org/spacey"),
],
)
body = "<html><body>No plans are listed on this prototype page.</body></html>"
# A real U+00A0 and messy spacing normalize away; a literal HTML entity
# does not decode at this text level and would count as absence.
spacey = "<p>No\u00a0plans are listed\n on this prototype page.</p>"
result = pr.check_program_pages(
pages,
fetch=_fetcher(
{
"https://example.org/plans": body,
"https://example.org/moved": "<html>Coming soon!</html>",
"https://example.org/unreachable": RuntimeError("offline"),
"https://example.org/spacey": spacey,
}
),
)
by_id = {item.page_id: item.status for item in result.observations}
assert by_id == {
"a-still-there": "unchanged",
"b-moved": "changed",
"c-unreachable": "unverifiable",
# Presentation-only churn normalizes away and is never a change.
"d-whitespace": "unchanged",
}
assert result.changed_page_ids == ("b-moved",)
assert "1 changed" in result.summary()
def test_changed_pages_propose_the_pre_written_issue_only(tmp_path):
pages = _load(tmp_path, [_page()])
result = pr.check_program_pages(
pages, fetch=_fetcher({"https://example.org/plans": "<html>new content</html>"})
)
proposal = pr.issue_proposal(pages[0], result)
assert proposal["title"] == "Program page changed: City preapproved plan list"
assert "candidate change, not" in proposal["body"]
assert pages[0].excerpt_fingerprint in proposal["body"]
assert "never marks anything stale" in proposal["body"]
def test_report_is_stable_machine_readable_json(tmp_path):
pages = _load(tmp_path, [_page()])
result = pr.check_program_pages(
pages, fetch=_fetcher({"https://example.org/plans": "<html>x</html>"})
)
payload = json.loads(pr.encoded_report(result))
assert payload["schema_version"] == 1
assert payload["changed_page_ids"] == ["city-preapproved-plans"]
assert set(payload["observations"][0]) == {"page_id", "url", "status", "detail"}
def test_cli_exit_codes_mirror_the_watcher_contract(tmp_path, monkeypatch):
entries = [
{
"page_id": page_id,
"label": label,
"url": url,
"excerpt": "No plans are listed on this prototype page.",
"excerpt_fingerprint": None,
}
for page_id, label, url in [
("moved", "M", "https://example.org/plans"),
("dark", "D", "https://example.org/dark"),
("calm", "C", "https://example.org/calm"),
]
]
import hashlib
for entry in entries:
normalized = pr.normalize_excerpt(entry["excerpt"])
entry["excerpt_fingerprint"] = (
"sha256:" + hashlib.sha256(normalized.encode("utf-8")).hexdigest()
)
registry = _write(tmp_path, entries)
monkeypatch.setattr(
pr,
"_default_fetch",
_fetcher(
{
"https://example.org/calm": "No plans are listed on this prototype page.",
"https://example.org/dark": RuntimeError("offline"),
"https://example.org/plans": "<html>renovated</html>",
}
),
)
issues_dir = tmp_path / "issues"
code = pr.main(["--registry", str(registry), "--issues-out", str(issues_dir)])
assert code == 1
proposal_files = sorted(p.name for p in issues_dir.iterdir())
assert proposal_files == ["moved.md"]
monkeypatch.setattr(
pr,
"_default_fetch",
_fetcher(
{
"https://example.org/calm": RuntimeError("offline"),
"https://example.org/dark": RuntimeError("offline"),
"https://example.org/plans": RuntimeError("offline"),
}
),
)
assert pr.main(["--registry", str(registry)]) == 2