aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_report.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_report.py')
-rw-r--r--tests/test_report.py112
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()