| """Unit tests for the ENF manual ingester (canlex/enf.py). Offline only. |
| |
| The ingester rebuilds enf.json from a fresh probe of the canada.ca dam |
| directory, so a renamed or unreachable chapter silently disappears from the |
| corpus -- the failure mode that emptied dmemos.json on 2026-07-27. These cover |
| the identity preserve_failed keys on and the failed-set it derives; the write |
| guard itself is covered by tests/test_common.py. |
| """ |
| import subprocess |
| import unittest |
| import urllib.error |
|
|
| from canlex import enf |
|
|
|
|
| def _pages(n, headings=7): |
| """A stand-in for pypdf's per-page text: a cover page plus numbered |
| headings, enough of them to take the heading-segmentation path.""" |
| body = "Body text for this section. " * 8 |
| return [f"ENF {n}\nLast updated: 2024-03-12\n", |
| "\n".join(f"{i}. Heading number {i}\n{body}" |
| for i in range(1, headings + 1))] |
|
|
|
|
| class ChapterIdentityTests(unittest.TestCase): |
| """preserve_failed keys on act_code, so the generator must keep emitting |
| exactly the identity the failed-set is built from.""" |
|
|
| def test_every_chunk_of_a_chapter_shares_one_act_code(self): |
| chunks = enf._chunks_for(5, _pages(5), "https://x/enf05-eng.pdf") |
| self.assertTrue(chunks) |
| self.assertEqual({c["act_code"] for c in chunks}, {"ENF-5"}) |
|
|
| def test_failed_set_matches_the_generated_act_code(self): |
| stored = enf._chunks_for(5, _pages(5), "https://x/enf05-eng.pdf") |
| chunks, preserved = enf.preserve_failed([], [(5, "fetch failed")], |
| stored) |
| self.assertEqual(preserved, ["ENF-5"]) |
| self.assertEqual(len(chunks), len(stored)) |
|
|
|
|
| class PreserveFailedTests(unittest.TestCase): |
| STORED = [{"act_code": "ENF-14", "text": "rehabilitation"}, |
| {"act_code": "ENF-14", "text": "part two"}, |
| {"act_code": "ENF-9", "text": "judicial review"}] |
|
|
| def test_unfetchable_chapter_keeps_its_last_good_chunks(self): |
| chunks, preserved = enf.preserve_failed( |
| [], [(14, "fetch failed: HTTPError: 404")], self.STORED) |
| self.assertEqual(preserved, ["ENF-14"]) |
| self.assertEqual([c["text"] for c in chunks], |
| ["rehabilitation", "part two"]) |
|
|
| def test_chapter_dropped_on_purpose_is_not_resurrected(self): |
| |
| |
| chunks, preserved = enf.preserve_failed( |
| [], [(14, "no text layer and no enf14-ocr.txt")], self.STORED) |
| self.assertNotIn("ENF-9", preserved) |
| self.assertNotIn("judicial review", [c["text"] for c in chunks]) |
|
|
| def test_freshly_parsed_chunks_win_over_stored(self): |
| fresh = [{"act_code": "ENF-14", "text": "new text"}] |
| chunks, preserved = enf.preserve_failed( |
| fresh, [(14, "parse failed: PdfReadError: x")], self.STORED) |
| self.assertEqual((chunks, preserved), (fresh, [])) |
|
|
| def test_chapter_we_never_had_cannot_be_preserved(self): |
| chunks, preserved = enf.preserve_failed([], [(31, "fetch failed")], |
| self.STORED) |
| self.assertEqual((chunks, preserved), ([], [])) |
|
|
| def test_a_clean_run_preserves_nothing(self): |
| fresh = [{"act_code": "ENF-14", "text": "x"}] |
| chunks, preserved = enf.preserve_failed(fresh, [], self.STORED) |
| self.assertEqual((chunks, preserved), (fresh, [])) |
|
|
| def test_every_failing_chapter_is_preserved_independently(self): |
| stored = self.STORED + [{"act_code": "ENF-3", "text": "hearings"}] |
| _chunks, preserved = enf.preserve_failed( |
| [], [(3, "fetch failed"), (14, "parse failed")], stored) |
| self.assertEqual(preserved, ["ENF-14", "ENF-3"]) |
|
|
|
|
| class MissingUpstreamTests(unittest.TestCase): |
| """A 404 is this module's retirement signal, not a failure to preserve |
| through -- _CHAPTERS is a blind range(1, 41) probe, so most numbers 404 on |
| a healthy run and a chapter IRCC withdraws starts 404ing too.""" |
|
|
| def _http_error(self, code): |
| |
| exc = urllib.error.HTTPError("http://x", code, "boom", {}, None) |
| self.addCleanup(exc.close) |
| return exc |
|
|
| def test_http_404_is_a_retirement(self): |
| self.assertTrue(enf.is_missing_upstream(self._http_error(404))) |
|
|
| def test_other_http_statuses_are_real_failures(self): |
| for code in (403, 500, 502, 503): |
| self.assertFalse(enf.is_missing_upstream(self._http_error(code))) |
|
|
| def test_network_errors_are_real_failures(self): |
| self.assertFalse(enf.is_missing_upstream(urllib.error.URLError("dns"))) |
| self.assertFalse(enf.is_missing_upstream(TimeoutError("timed out"))) |
|
|
| def test_powershell_404_is_read_off_stderr(self): |
| |
| exc = subprocess.CalledProcessError(1, "powershell") |
| exc.stderr = ("Invoke-WebRequest : The remote server returned an " |
| "error: (404) Not Found.") |
| self.assertTrue(enf.is_missing_upstream(exc)) |
|
|
| def test_powershell_bytes_stderr_is_decoded(self): |
| exc = subprocess.CalledProcessError(1, "powershell") |
| exc.stderr = b"error: (404) Not Found." |
| self.assertTrue(enf.is_missing_upstream(exc)) |
|
|
| def test_powershell_other_failure_is_preserved_through(self): |
| exc = subprocess.CalledProcessError(1, "powershell") |
| exc.stderr = "The underlying connection was closed: TLS failure." |
| self.assertFalse(enf.is_missing_upstream(exc)) |
|
|
| def test_no_stderr_at_all_counts_as_a_failure(self): |
| |
| |
| self.assertFalse(enf.is_missing_upstream(RuntimeError("who knows"))) |
|
|
|
|
| if __name__ == "__main__": |
| unittest.main() |
|
|