aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_report.py
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-10 16:43:34 +0200
committerDanilo M. <danix@danix.xyz>2026-09-10 16:43:34 +0200
commit44ebca17e307a6c86308f59789f52f98c683e1c2 (patch)
tree272e454bdd736dfe3cf40d8554090a4006a8aeab /tests/test_report.py
parent4e3d9f9620c9f8c15effea95756257fd06834768 (diff)
downloadabusectl-44ebca17e307a6c86308f59789f52f98c683e1c2.tar.gz
abusectl-44ebca17e307a6c86308f59789f52f98c683e1c2.zip
feat: build vendor and MISP destination rows
vendor_destinations() turns the DESTINATIONS table plus a case's IOCs into destination rows, one per configured destination that has at least one IOC it accepts. Nothing calls it yet; that wiring is a later task. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176FYdVfpzUq8S9jecqQqL6
Diffstat (limited to 'tests/test_report.py')
-rw-r--r--tests/test_report.py57
1 files changed, 57 insertions, 0 deletions
diff --git a/tests/test_report.py b/tests/test_report.py
index 6d1f845..d0e8239 100644
--- a/tests/test_report.py
+++ b/tests/test_report.py
@@ -2049,3 +2049,60 @@ class DestinationTable(unittest.TestCase):
# MISP only, so this test names them rather than deriving them.
self.assertEqual(report.ALL_TYPES,
("ipv4", "ipv6", "domain", "url", "sha256"))
+
+
+class VendorDestinations(unittest.TestCase):
+ IOCS = [
+ {"id": "ioc-1", "type": "ipv4", "value": "198.51.100.7"},
+ {"id": "ioc-2", "type": "domain", "value": "example.invalid"},
+ {"id": "ioc-3", "type": "url", "value": "http://example.invalid/a"},
+ ]
+
+ def test_a_row_carries_only_the_types_its_destination_accepts(self):
+ rows = report.vendor_destinations(self.IOCS, {"abusedb", "urlhaus"})
+ by_id = {row["id"]: row for row in rows}
+ self.assertEqual(by_id["abusedb"]["iocs"], ["ioc-1"])
+ self.assertEqual(by_id["urlhaus"]["iocs"], ["ioc-3"])
+
+ def test_misp_carries_every_ioc_including_what_no_vendor_takes(self):
+ rows = report.vendor_destinations(self.IOCS, {"misp"})
+ self.assertEqual(rows[0]["iocs"], ["ioc-1", "ioc-2", "ioc-3"])
+
+ def test_a_destination_with_no_acceptable_ioc_gets_no_row(self):
+ # The rule's whole point: a case with no URL must not promise a
+ # urlhaus submission that would have nothing to submit.
+ no_urls = [ioc for ioc in self.IOCS if ioc["type"] != "url"]
+ rows = report.vendor_destinations(no_urls, {"abusedb", "urlhaus"})
+ self.assertEqual([row["id"] for row in rows], ["abusedb"])
+
+ def test_an_unconfigured_destination_gets_no_row(self):
+ rows = report.vendor_destinations(self.IOCS, set())
+ self.assertEqual(rows, [])
+
+ def test_the_row_shape_is_the_shape_submit_iterates(self):
+ rows = report.vendor_destinations(self.IOCS, {"abusedb"})
+ self.assertEqual(rows[0], {
+ "id": "abusedb",
+ "kind": "api",
+ "iocs": ["ioc-1"],
+ "body": None,
+ "status": "pending",
+ })
+
+ def test_no_row_carries_a_target(self):
+ # An endpoint is a property of the vendor, not of the case. Writing
+ # one into the evidence record would let a hand-edit redirect a
+ # submission somewhere the user never named.
+ rows = report.vendor_destinations(self.IOCS, {"misp", "abusedb"})
+ for row in rows:
+ self.assertNotIn("target", row)
+
+ def test_rows_come_out_in_table_order_not_set_order(self):
+ # The configured set is a set, so a stable order has to come from
+ # somewhere else or the manifest churns between runs.
+ first = report.vendor_destinations(self.IOCS, {"urlhaus", "misp", "abusedb"})
+ second = report.vendor_destinations(self.IOCS, {"abusedb", "misp", "urlhaus"})
+ self.assertEqual([row["id"] for row in first],
+ [row["id"] for row in second])
+ self.assertEqual([row["id"] for row in first],
+ ["misp", "abusedb", "urlhaus"])