diff options
Diffstat (limited to 'tests/test_report.py')
| -rw-r--r-- | tests/test_report.py | 112 |
1 files changed, 93 insertions, 19 deletions
diff --git a/tests/test_report.py b/tests/test_report.py index 6a0dc51..c72974e 100644 --- a/tests/test_report.py +++ b/tests/test_report.py @@ -22,17 +22,36 @@ class Grouping(unittest.TestCase): "abuse": ["a@host.invalid", "b@host.invalid"], "source": "rdap"}, ] destinations = report.email_destinations(contacts) - self.assertEqual( - sorted(d["target"] for d in destinations), - ["a@host.invalid", "b@host.invalid"], - ) + self.assertEqual([d["target"] for d in destinations], + ["a@host.invalid", "b@host.invalid"]) + # Both desks carry the indicator, and each gets its own id: a + # destination that reached only one desk, or two rows sharing an + # id, would pass an assertion on the sorted targets alone. + self.assertEqual([d["iocs"] for d in destinations], + [["ioc-1"], ["ioc-1"]]) + self.assertEqual(len({d["id"] for d in destinations}), 2) + for destination in destinations: + self.assertEqual(destination["id"], + report.email_destination_id( + destination["target"])) def test_a_contact_with_no_address_creates_no_destination(self): + """The contact that resolved must still produce its destination. + + Asserted alongside one that DOES resolve, because "no destination + for this contact" is also what returning nothing at all looks + like, and that is not the behaviour being described. + """ contacts = [ {"iocs": ["ioc-1"], "query": "example.invalid", "abuse": [], "source": "rdap", "error": "no abuse role published"}, + {"iocs": ["ioc-2"], "query": "198.51.100.7", + "abuse": ["abuse@host.invalid"], "source": "rdap"}, ] - self.assertEqual(report.email_destinations(contacts), []) + destinations = report.email_destinations(contacts) + self.assertEqual([d["target"] for d in destinations], + ["abuse@host.invalid"]) + self.assertEqual(destinations[0]["iocs"], ["ioc-2"]) def test_destinations_carry_stable_ids_and_pending_status(self): contacts = [ @@ -40,17 +59,26 @@ class Grouping(unittest.TestCase): "abuse": ["abuse@host.invalid"], "source": "rdap"}, ] destination = report.email_destinations(contacts)[0] - self.assertEqual(destination["id"], "email-1") + self.assertEqual(destination["id"], + report.email_destination_id("abuse@host.invalid")) self.assertEqual(destination["kind"], "email") self.assertEqual(destination["status"], "pending") - def test_ids_are_numbered_per_destination_not_per_contact(self): - """A skipped contact must not leave a hole in the numbering. + def test_an_id_is_the_literal_shape_a_reviewer_will_read(self): + """Pin the shape, since it becomes a filename in bodies/. + + Computed by hand rather than by calling the code under test, so + this fails if the derivation changes rather than following it. + """ + self.assertEqual(report.email_destination_id("abuse@host.invalid"), + "email-bc50e369") + + def test_ids_are_derived_per_destination_not_per_contact(self): + """A contact that resolved to no desk must not shift another's id. - The obvious implementation enumerates the contacts, and a contact - with no abuse address then burns an id: the desks come back as - "email-2" and "email-4", which reads to a reviewer as two reports - having gone missing. + The obvious implementation numbers destinations by position, and a + skipped contact then either burns an id or renumbers the rest. + Both are wrong for the same reason: an id names a desk. """ contacts = [ {"iocs": ["ioc-1"], "query": "example.invalid", "abuse": [], @@ -62,7 +90,8 @@ class Grouping(unittest.TestCase): ] destinations = report.email_destinations(contacts) self.assertEqual([d["id"] for d in destinations], - ["email-1", "email-2"]) + [report.email_destination_id("a@host.invalid"), + report.email_destination_id("b@host.invalid")]) self.assertEqual([d["target"] for d in destinations], ["a@host.invalid", "b@host.invalid"]) @@ -95,6 +124,16 @@ class Grouping(unittest.TestCase): self.assertEqual(destinations[0]["iocs"], ["ioc-1", "ioc-2"]) self.assertEqual(destinations[0]["target"], "abuse@Host.Invalid") + def test_two_spellings_of_one_desk_share_an_id(self): + """The id derives from the same normalised form the grouping uses. + + Otherwise the spelling RDAP happened to publish first would decide + a body's filename, and a re-run that saw the other spelling first + would look like a different desk. + """ + self.assertEqual(report.email_destination_id("abuse@Host.Invalid"), + report.email_destination_id("abuse@host.invalid")) + def test_two_local_part_cases_stay_two_destinations(self): """Only the receiving host knows whether its local parts fold. @@ -109,6 +148,7 @@ class Grouping(unittest.TestCase): destinations = report.email_destinations(contacts) self.assertEqual(sorted(d["target"] for d in destinations), ["Abuse@host.invalid", "abuse@host.invalid"]) + self.assertNotEqual(destinations[0]["id"], destinations[1]["id"]) def test_a_destination_starts_with_no_body(self): contacts = [ @@ -118,12 +158,7 @@ class Grouping(unittest.TestCase): self.assertIsNone(report.email_destinations(contacts)[0]["body"]) def test_the_same_contacts_produce_the_same_ids_twice(self): - """Ids must not depend on dict iteration luck or set ordering. - - Task 8 writes each body to bodies/<id>.xarf and records its hash - against that id, so an id that moved between two runs over the - same input would compare one desk's body against another's. - """ + """Ids must not depend on dict iteration luck or set ordering.""" contacts = [ {"iocs": ["ioc-1"], "query": "198.51.100.7", "abuse": ["b@host.invalid", "a@host.invalid"], "source": "rdap"}, @@ -138,6 +173,45 @@ class Grouping(unittest.TestCase): ["b@host.invalid", "a@host.invalid", "c@host.invalid"]) + def test_a_desks_id_survives_another_desk_appearing(self): + """An id names a DESK, not a position in this run's list. + + Task 8 writes each body to bodies/<id>.xarf and records its hash + against that id. With a positional id, re-running contacts on a + case that gained an indicator renumbers every desk after the new + one, so bodies/<id>.xarf on disk belongs to a different desk than + the manifest's entry of that id, and the edit check compares one + desk's body against another's. + """ + established = {"iocs": ["ioc-1"], "query": "198.51.100.7", + "abuse": ["b@host.invalid"], "source": "rdap"} + first = report.email_destinations([established]) + + # A later contacts run finds an indicator whose desk sorts ahead. + newcomer = {"iocs": ["ioc-2"], "query": "example.invalid", + "abuse": ["a@new.invalid"], "source": "rdap"} + second = report.email_destinations([newcomer, established]) + + by_target = {d["target"]: d["id"] for d in second} + self.assertEqual(by_target["b@host.invalid"], first[0]["id"]) + self.assertNotEqual(by_target["a@new.invalid"], first[0]["id"]) + + def test_an_ids_position_does_not_leak_into_it(self): + """The same desk alone and third in a list gets one id.""" + alone = report.email_destinations([ + {"iocs": ["ioc-1"], "query": "198.51.100.7", + "abuse": ["desk@host.invalid"], "source": "rdap"}, + ]) + crowded = report.email_destinations([ + {"iocs": ["ioc-2"], "query": "198.51.100.8", + "abuse": ["one@host.invalid", "two@host.invalid"], + "source": "rdap"}, + {"iocs": ["ioc-1"], "query": "198.51.100.7", + "abuse": ["desk@host.invalid"], "source": "rdap"}, + ]) + self.assertEqual(crowded[2]["target"], "desk@host.invalid") + self.assertEqual(crowded[2]["id"], alone[0]["id"]) + if __name__ == "__main__": unittest.main() |
