| """Unit tests for Justice Laws XML parsing (canlex/ingest.py). |
| |
| Offline: builds a tiny Justice Laws-shaped XML document and parses it, so no |
| network or real corpus is touched. Focuses on the per-section in-force date and |
| repeal-status metadata. |
| """ |
| import tempfile |
| import unittest |
| from pathlib import Path |
|
|
| from canlex import ingest |
|
|
|
|
| _XML = b"""<?xml version="1.0" encoding="UTF-8"?> |
| <Statute xmlns:lims="http://justice.gc.ca/lims" lims:current-date="2026-03-31"> |
| <Body> |
| <Section lims:inforce-start-date="2015-01-01" lims:lastAmendedDate="2023-06-22"> |
| <Label>25</Label> |
| <MarginalNote>Humanitarian and compassionate considerations</MarginalNote> |
| <Text>The Minister may grant relief.</Text> |
| </Section> |
| <Section lims:inforce-start-date="2018-12-13" lims:lastAmendedDate="2018-12-13"> |
| <Label>13</Label> |
| <Text><Repealed>[Repealed, 2018, c. 27, s. 174]</Repealed></Text> |
| </Section> |
| <Section lims:inforce-start-date="2001-06-28"> |
| <Label>40</Label> |
| <MarginalNote>Live section with a repealed subsection</MarginalNote> |
| <Text>Intro.</Text> |
| <Subsection><Label>(1)</Label><Text>Still in force.</Text></Subsection> |
| <Subsection><Label>(2)</Label><Text><Repealed>[Repealed, 2020, c. 1]</Repealed></Text></Subsection> |
| </Section> |
| </Body> |
| <Schedule id="RelatedProvs"> |
| <BillPiece><RelatedOrNotInForce> |
| <Heading level="5" style="nifrp"><TitleText>- 1991, c. 43, s. 10</TitleText></Heading> |
| <Section><Label>10</Label><Text>A transitional related provision.</Text></Section> |
| </RelatedOrNotInForce></BillPiece> |
| </Schedule> |
| <Schedule id="NifProvs"> |
| <BillPiece><RelatedOrNotInForce> |
| <Heading level="5" style="nifrp"><TitleText>- 2026, c. 8, s. 3</TitleText></Heading> |
| <Section><MarginalNote>Coercive control</MarginalNote><Label>3</Label> |
| <Text>Section 264 is amended by adding a coercive control offence.</Text> |
| </Section> |
| </RelatedOrNotInForce></BillPiece> |
| </Schedule> |
| </Statute>""" |
|
|
|
|
| def _path(): |
| with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as f: |
| f.write(_XML) |
| return Path(f.name) |
|
|
|
|
| def _parse(): |
| |
| |
| |
| return {c["section"]: c for c in ingest.parse_legislation(_path(), "I-2.5") |
| if c["section"]} |
|
|
|
|
| class InForceTests(unittest.TestCase): |
| def test_every_section_carries_its_in_force_date(self): |
| by_sec = _parse() |
| self.assertEqual(by_sec["25"]["in_force"], "2015-01-01") |
| self.assertEqual(by_sec["40"]["in_force"], "2001-06-28") |
|
|
|
|
| class AmendmentStubTests(unittest.TestCase): |
| def test_matches_bare_amendment_markers(self): |
| for s in ["30 [Amendment]", "162 [Amendments]", "5 [Related provision]", |
| "5.1 [Related provisions]"]: |
| self.assertTrue(ingest._AMENDMENT_STUB.match(s), s) |
|
|
| def test_does_not_match_real_or_repealed_content(self): |
| for s in ["13 [Repealed, 2018, c. 27, s. 174]", |
| "34 The Minister may [Amendment] later amend...", |
| "40 Inadmissibility on security grounds"]: |
| self.assertIsNone(ingest._AMENDMENT_STUB.match(s), s) |
|
|
|
|
| class NotInForceTests(unittest.TestCase): |
| def test_nif_schedule_entry_ingested_as_pending_law(self): |
| chunks = ingest.parse_legislation(_path(), "I-2.5") |
| nif = [c for c in chunks if c["id"].startswith("I-2.5-nif-")] |
| self.assertEqual(len(nif), 1) |
| c = nif[0] |
| self.assertEqual(c["status"], "not-in-force") |
| self.assertEqual(c["section"], "") |
| self.assertIn("2026, c. 8, s. 3", c["history"]) |
| self.assertIn("coercive control", c["text"].lower()) |
| self.assertIn("not in force", c["citation"].lower()) |
|
|
| def test_related_provisions_schedule_is_not_ingested(self): |
| chunks = ingest.parse_legislation(_path(), "I-2.5") |
| self.assertFalse(any("transitional related provision" in c["text"].lower() |
| for c in chunks)) |
|
|
|
|
| class RepealStatusTests(unittest.TestCase): |
| def test_wholly_repealed_section_is_flagged(self): |
| self.assertEqual(_parse()["13"]["status"], "repealed") |
|
|
| def test_live_section_with_a_repealed_subsection_is_not_flagged(self): |
| |
| |
| self.assertEqual(_parse()["40"]["status"], "in force") |
|
|
| def test_ordinary_section_is_in_force(self): |
| self.assertEqual(_parse()["25"]["status"], "in force") |
|
|
|
|
| if __name__ == "__main__": |
| unittest.main() |
|
|