diff options
Diffstat (limited to 'tests/test_report.py')
| -rw-r--r-- | tests/test_report.py | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/tests/test_report.py b/tests/test_report.py index 6a340c9..b36252c 100644 --- a/tests/test_report.py +++ b/tests/test_report.py @@ -1,6 +1,7 @@ import copy import email import email.policy +import hashlib import json import unittest @@ -1596,5 +1597,114 @@ class DocumentIdentity(unittest.TestCase): self.assertEqual(addresses[0].display_name, name) +class Freeze(unittest.TestCase): + """A case that has reached a desk is an evidence record, not a draft.""" + + def _frozen(self, marker): + manifest = copy.deepcopy(MANIFEST) + manifest["frozen"] = marker + return manifest + + def test_a_frozen_case_refuses(self): + manifest = self._frozen({"at": "2026-09-07T10:00:00Z", + "by": "abusedb"}) + with self.assertRaises(report.Frozen) as caught: + report.check_regenerable(manifest, modified=[]) + self.assertIn("abusedb", str(caught.exception)) + + def test_a_frozen_case_refuses_even_when_forced(self): + manifest = self._frozen({"at": "2026-09-07T10:00:00Z", + "by": "abusedb"}) + with self.assertRaises(report.Frozen): + report.check_regenerable(manifest, modified=[], force=True) + + def test_a_frozen_case_refuses_before_the_modified_check(self): + """Frozen wins over force for EVERY shape of the second argument. + + The two rules are not independent: an edited body on a frozen case + must still refuse, and must refuse as Frozen rather than as + Modified, because Modified is the one --force can clear. + """ + manifest = self._frozen({"at": "2026-09-07T10:00:00Z", + "by": "abusedb"}) + with self.assertRaises(report.Frozen): + report.check_regenerable(manifest, modified=["email-1"]) + with self.assertRaises(report.Frozen): + report.check_regenerable(manifest, modified=["email-1"], + force=True) + + def test_a_present_but_empty_marker_still_freezes(self): + """Present means frozen. The marker is write-once and monotonic, so + a shape that is present and says nothing useful is a half-written or + corrupted record, and the safe direction is to refuse. Truthiness is + the wrong test: `{}` is present and falsy, and treating it as absent + silently unfreezes a case a desk may already hold. + """ + for marker in ({}, "", 0, []): + with self.subTest(marker=marker): + with self.assertRaises(report.Frozen): + report.check_regenerable(self._frozen(marker), + modified=[], force=True) + + def test_a_malformed_marker_refuses_rather_than_crashing(self): + """A marker that is not a mapping is still a refusal, not an + AttributeError. cli turns Frozen into a message and a traceback into + a bug report. + """ + for marker in ("2026-09-07T10:00:00Z", 1, ["abusedb"]): + with self.subTest(marker=marker): + with self.assertRaises(report.Frozen): + report.check_regenerable(self._frozen(marker), + modified=[]) + + def test_an_explicit_null_marker_is_absent(self): + """JSON has no way to omit a key it wrote as null, and null is the + one present value that unambiguously carries no record. + """ + manifest = self._frozen(None) + report.check_regenerable(manifest, modified=[]) + + def test_a_modified_body_refuses_without_force(self): + with self.assertRaises(report.Modified) as caught: + report.check_regenerable(MANIFEST, modified=["email-1"]) + self.assertIn("email-1", str(caught.exception)) + + def test_a_modified_body_is_allowed_with_force(self): + report.check_regenerable(MANIFEST, modified=["email-1"], force=True) + + def test_an_untouched_case_regenerates(self): + report.check_regenerable(MANIFEST, modified=[]) + + def test_it_does_not_mutate_the_manifest(self): + """It is a check, not a step. case.py is the only writer.""" + manifest = copy.deepcopy(MANIFEST) + before = copy.deepcopy(manifest) + report.check_regenerable(manifest, modified=[]) + self.assertEqual(manifest, before) + + +class Hashes(unittest.TestCase): + def test_the_hash_detects_a_changed_body(self): + first = report.body_hash("one") + self.assertNotEqual(first, report.body_hash("two")) + self.assertEqual(first, report.body_hash("one")) + + def test_it_is_sha256_of_the_utf8_bytes(self): + """The encoding is pinned, not incidental: the hash is compared + against a body read back off disk, and for a sent destination it is + the record of what was disclosed. + """ + text = "a body with a non-ascii org name: Örg\n" + self.assertEqual( + report.body_hash(text), + hashlib.sha256(text.encode("utf-8")).hexdigest(), + ) + + def test_it_hashes_a_real_generated_body(self): + destination = report.email_destinations(MANIFEST["contacts"])[0] + text = report.build(MANIFEST, destination, IDENTITY) + self.assertEqual(len(report.body_hash(text)), 64) + + if __name__ == "__main__": unittest.main() |
