diff options
Diffstat (limited to 'tests/test_case.py')
| -rw-r--r-- | tests/test_case.py | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/tests/test_case.py b/tests/test_case.py new file mode 100644 index 0000000..c6964a9 --- /dev/null +++ b/tests/test_case.py @@ -0,0 +1,102 @@ +# Copyright (C) 2026 Danilo M. <danix@danix.xyz> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Tests for the case directory: creation, manifest round trip, atomic saves.""" + +import json +import pathlib +import tempfile +import unittest + +from abusectl import case + + +class TestCaseCreation(unittest.TestCase): + def setUp(self): + self._tmp = tempfile.TemporaryDirectory() + self.root = pathlib.Path(self._tmp.name) + + def tearDown(self): + self._tmp.cleanup() + + def test_a_case_holds_the_source_and_a_manifest(self): + created = case.create(self.root, b"From: <a@b.example.invalid>\r\n\r\nhi") + self.assertTrue((created.path / "source.eml").is_file()) + self.assertTrue((created.path / "manifest.json").is_file()) + + def test_the_source_is_stored_byte_for_byte(self): + raw = b"From: <a@b.example.invalid>\r\n\r\nhi\r\n" + created = case.create(self.root, raw) + self.assertEqual((created.path / "source.eml").read_bytes(), raw) + + def test_the_manifest_carries_a_format_version(self): + created = case.create(self.root, b"x") + manifest = json.loads((created.path / "manifest.json").read_text()) + self.assertEqual(manifest["format"], case.FORMAT_VERSION) + + def test_two_cases_do_not_collide(self): + a = case.create(self.root, b"one") + b = case.create(self.root, b"two") + self.assertNotEqual(a.path, b.path) + + def test_a_case_id_is_filesystem_safe(self): + created = case.create(self.root, b"x") + self.assertRegex(created.path.name, r"^\d{4}-\d{2}-\d{2}-[0-9a-f]{4}$") + + def test_a_manifest_round_trips(self): + created = case.create(self.root, b"x") + manifest = case.load(created.path) + manifest["iocs"] = [{"id": "ioc-1", "type": "ipv4", "value": "203.0.113.1"}] + case.save(created.path, manifest) + self.assertEqual(case.load(created.path)["iocs"][0]["value"], "203.0.113.1") + + def test_saving_leaves_no_temporary_file_behind(self): + # The manifest is written atomically, temp file plus rename, because a + # half-written manifest during a review is a corrupted evidence record. + created = case.create(self.root, b"x") + case.save(created.path, case.load(created.path)) + leftovers = [p.name for p in created.path.iterdir() if p.suffix == ".tmp"] + self.assertEqual(leftovers, []) + + def test_an_unknown_manifest_format_is_refused(self): + # A newer writer may mean fields this build would silently drop on the + # next save, so refusing is correct rather than cautious. + created = case.create(self.root, b"x") + manifest = case.load(created.path) + manifest["format"] = case.FORMAT_VERSION + 1 + (created.path / "manifest.json").write_text(json.dumps(manifest)) + with self.assertRaises(ValueError): + case.load(created.path) + + def test_a_failed_save_leaves_the_existing_manifest_intact(self): + # An evidence record must not be destroyed by a write that fails. + created = case.create(self.root, b"x") + good = case.load(created.path) + good["iocs"] = [{"id": "ioc-1"}] + case.save(created.path, good) + + class Unserialisable: + pass + + with self.assertRaises(TypeError): + case.save(created.path, {"format": case.FORMAT_VERSION, + "bad": Unserialisable()}) + + self.assertEqual(case.load(created.path)["iocs"], [{"id": "ioc-1"}]) + leftovers = [p.name for p in created.path.iterdir() if p.suffix == ".tmp"] + self.assertEqual(leftovers, []) + + +if __name__ == "__main__": + unittest.main() |
