From 93ab7a07b6907767c6fedf767bd9598ea84fb535 Mon Sep 17 00:00:00 2001 From: Johanna England Date: Tue, 16 Jun 2026 14:23:25 +0200 Subject: [PATCH 01/10] Rename and rework test fixtures --- tests/incident/test_event.py | 125 +++++++++++++++++------------------ 1 file changed, 61 insertions(+), 64 deletions(-) diff --git a/tests/incident/test_event.py b/tests/incident/test_event.py index 21132ddb7..5012adb44 100644 --- a/tests/incident/test_event.py +++ b/tests/incident/test_event.py @@ -4,7 +4,6 @@ from django.urls import reverse from django.utils import timezone from django.utils.dateparse import parse_datetime -from django.utils.timezone import make_aware from rest_framework import status from rest_framework.test import APITestCase @@ -21,17 +20,15 @@ def setUp(self): super().init_test_objects() - self.stateful_incident1 = StatefulIncidentFactory( - start_time=make_aware(datetime(2000, 1, 1)), + self.stateful_incident = StatefulIncidentFactory( + start_time=timezone.now() - timedelta(weeks=1), end_time=timezone.now() + timedelta(weeks=1), source=self.source1, - source_incident_id="1", ) - self.stateful_incident1.create_first_event() - self.stateless_incident1 = StatelessIncidentFactory( - source_incident_id="2", source=self.stateful_incident1.source - ) - self.stateless_incident1.create_first_event() + self.stateful_incident.create_first_event() + + self.stateless_incident = StatelessIncidentFactory(source=self.source1) + self.stateless_incident.create_first_event() self.events_url = lambda incident: reverse("v2:incident:incident-events", args=[incident.pk]) @@ -55,22 +52,22 @@ def _assert_posting_event_is_rejected_and_does_not_change_end_time( ): event_count = Event.objects.count() - response = client.post(self.events_url(self.stateful_incident1), post_data) + response = client.post(self.events_url(self.stateful_incident), post_data) self._assert_response_field_invalid(response, "type") self.assertEqual(Event.objects.count(), event_count) - self.stateful_incident1.refresh_from_db() - self.assertEqual(self.stateful_incident1.end_time, original_end_time) + self.stateful_incident.refresh_from_db() + self.assertEqual(self.stateful_incident.end_time, original_end_time) def _assert_posting_event_succeeds(self, post_data: dict, client: Client): - event_count = self.stateful_incident1.events.count() + event_count = self.stateful_incident.events.count() - response = client.post(self.events_url(self.stateful_incident1), post_data) + response = client.post(self.events_url(self.stateful_incident), post_data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(self.stateful_incident1.events.count(), event_count + 1) - self.assertTrue(self.stateful_incident1.events.filter(pk=response.data["pk"]).exists()) - self.assertEqual(response.data["incident"], self.stateful_incident1.pk) + self.assertEqual(self.stateful_incident.events.count(), event_count + 1) + self.assertTrue(self.stateful_incident.events.filter(pk=response.data["pk"]).exists()) + self.assertEqual(response.data["incident"], self.stateful_incident.pk) def _assert_incident_is_closed_at_timestamp(self, incident: Incident, timestamp: datetime): incident.refresh_from_db() @@ -83,108 +80,108 @@ def _assert_incident_is_open(self, incident: Incident): self.assertEqual(datetime_utils.make_naive(incident.end_time), datetime.max) def test_posting_close_and_reopen_events_properly_changes_stateful_incidents(self): - self.assertTrue(self.stateful_incident1.stateful) - self.assertTrue(self.stateful_incident1.open) + self.assertTrue(self.stateful_incident.stateful) + self.assertTrue(self.stateful_incident.open) # Test closing incident close_event_dict = self._create_event_dict(Event.Type.CLOSE) event_timestamp = close_event_dict["timestamp"] - response = self.user1_rest_client.post(self.events_url(self.stateful_incident1), close_event_dict) + response = self.user1_rest_client.post(self.events_url(self.stateful_incident), close_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), event_timestamp) - self._assert_incident_is_closed_at_timestamp(self.stateful_incident1, event_timestamp) + self._assert_incident_is_closed_at_timestamp(self.stateful_incident, event_timestamp) # It's illegal to close an already closed incident self._assert_posting_event_is_rejected_and_does_not_change_end_time( - close_event_dict, self.stateful_incident1.end_time, self.user1_rest_client + close_event_dict, self.stateful_incident.end_time, self.user1_rest_client ) # Test reopening incident reopen_event_dict = self._create_event_dict(Event.Type.REOPEN) - response = self.user1_rest_client.post(self.events_url(self.stateful_incident1), reopen_event_dict) + response = self.user1_rest_client.post(self.events_url(self.stateful_incident), reopen_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), reopen_event_dict["timestamp"]) - self._assert_incident_is_open(self.stateful_incident1) + self._assert_incident_is_open(self.stateful_incident) # It's illegal to reopen an already opened incident self._assert_posting_event_is_rejected_and_does_not_change_end_time( - reopen_event_dict, self.stateful_incident1.end_time, self.user1_rest_client + reopen_event_dict, self.stateful_incident.end_time, self.user1_rest_client ) def test_posting_end_and_restart_events_properly_changes_stateful_incidents(self): - self.assertTrue(self.stateful_incident1.stateful) - self.assertTrue(self.stateful_incident1.open) + self.assertTrue(self.stateful_incident.stateful) + self.assertTrue(self.stateful_incident.open) # Test ending incident end_event_dict = self._create_event_dict(Event.Type.INCIDENT_END) event_timestamp = end_event_dict["timestamp"] - response = self.source1_rest_client.post(self.events_url(self.stateful_incident1), end_event_dict) + response = self.source1_rest_client.post(self.events_url(self.stateful_incident), end_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), event_timestamp) - self._assert_incident_is_closed_at_timestamp(self.stateful_incident1, event_timestamp) + self._assert_incident_is_closed_at_timestamp(self.stateful_incident, event_timestamp) # Test restarting incident restart_event_dict = self._create_event_dict(Event.Type.INCIDENT_RESTART) - response = self.source1_rest_client.post(self.events_url(self.stateful_incident1), restart_event_dict) + response = self.source1_rest_client.post(self.events_url(self.stateful_incident), restart_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), restart_event_dict["timestamp"]) - self._assert_incident_is_open(self.stateful_incident1) + self._assert_incident_is_open(self.stateful_incident) # Test ending again end_event_dict = self._create_event_dict(Event.Type.INCIDENT_END) event_timestamp = end_event_dict["timestamp"] - response = self.source1_rest_client.post(self.events_url(self.stateful_incident1), end_event_dict) + response = self.source1_rest_client.post(self.events_url(self.stateful_incident), end_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), event_timestamp) - self._assert_incident_is_closed_at_timestamp(self.stateful_incident1, event_timestamp) + self._assert_incident_is_closed_at_timestamp(self.stateful_incident, event_timestamp) def test_given_closed_incident_when_source_posts_end_then_records_event_without_state_change(self): # An end user posting a state-invalid event gets a 400; a source system does # not. Its event is recorded for the audit trail (201) but update_incident is # skipped, so the incident is unchanged. Here: END on an already-closed # incident must be accepted and recorded without re-touching end_time. - count_before = self.stateful_incident1.events.count() + count_before = self.stateful_incident.events.count() - self.stateful_incident1.end_time = timezone.now() - self.stateful_incident1.save(update_fields=["end_time"]) + self.stateful_incident.end_time = timezone.now() + self.stateful_incident.save(update_fields=["end_time"]) end_event_dict = self._create_event_dict(Event.Type.INCIDENT_END) - response = self.source1_rest_client.post(self.events_url(self.stateful_incident1), end_event_dict) + response = self.source1_rest_client.post(self.events_url(self.stateful_incident), end_event_dict) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self._assert_incident_is_closed_at_timestamp(self.stateful_incident1, self.stateful_incident1.end_time) - self.stateful_incident1.refresh_from_db() - self.assertEqual(self.stateful_incident1.events.count(), count_before + 1) + self._assert_incident_is_closed_at_timestamp(self.stateful_incident, self.stateful_incident.end_time) + self.stateful_incident.refresh_from_db() + self.assertEqual(self.stateful_incident.events.count(), count_before + 1) def test_given_open_incident_when_source_posts_restart_then_records_event_without_state_change(self): # An end user posting a state-invalid event gets a 400; a source system does # not. Its event is recorded for the audit trail (201) but update_incident is # skipped, so the incident is unchanged. Here: RESTART on an already-open # incident must be accepted and recorded without re-touching end_time. - self.stateful_incident1.end_time = datetime_utils.INFINITY_REPR - self.stateful_incident1.save(update_fields=["end_time"]) + self.stateful_incident.end_time = datetime_utils.INFINITY_REPR + self.stateful_incident.save(update_fields=["end_time"]) - count_before = self.stateful_incident1.events.count() + count_before = self.stateful_incident.events.count() restart_event_dict = self._create_event_dict(Event.Type.INCIDENT_RESTART) - response = self.source1_rest_client.post(self.events_url(self.stateful_incident1), restart_event_dict) + response = self.source1_rest_client.post(self.events_url(self.stateful_incident), restart_event_dict) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self._assert_incident_is_open(self.stateful_incident1) - self.stateful_incident1.refresh_from_db() - self.assertEqual(self.stateful_incident1.events.count(), count_before + 1) + self._assert_incident_is_open(self.stateful_incident) + self.stateful_incident.refresh_from_db() + self.assertEqual(self.stateful_incident.events.count(), count_before + 1) def test_posting_close_and_reopen_events_does_not_change_stateless_incidents(self): def assert_incident_stateless(): - self.stateless_incident1.refresh_from_db() - self.assertFalse(self.stateless_incident1.stateful) - self.assertFalse(self.stateless_incident1.open) + self.stateless_incident.refresh_from_db() + self.assertFalse(self.stateless_incident.stateful) + self.assertFalse(self.stateless_incident.open) assert_incident_stateless() response = self.user1_rest_client.post( - self.events_url(self.stateless_incident1), self._create_event_dict(Event.Type.CLOSE) + self.events_url(self.stateless_incident), self._create_event_dict(Event.Type.CLOSE) ) self._assert_response_field_invalid(response, "type") self.assertEqual(response.data["type"], "Cannot change the state of a stateless incident.") assert_incident_stateless() response = self.user1_rest_client.post( - self.events_url(self.stateless_incident1), self._create_event_dict(Event.Type.REOPEN) + self.events_url(self.stateless_incident), self._create_event_dict(Event.Type.REOPEN) ) self._assert_response_field_invalid(response, "type") self.assertEqual(response.data["type"], "Cannot change the state of a stateless incident.") @@ -192,26 +189,26 @@ def assert_incident_stateless(): def test_posting_end_and_restart_events_does_not_change_stateless_incidents_but_records_event(self): def assert_incident_stateless(): - self.stateless_incident1.refresh_from_db() - self.assertFalse(self.stateless_incident1.stateful) - self.assertFalse(self.stateless_incident1.open) + self.stateless_incident.refresh_from_db() + self.assertFalse(self.stateless_incident.stateful) + self.assertFalse(self.stateless_incident.open) - event_count = self.stateless_incident1.events.count() + event_count = self.stateless_incident.events.count() assert_incident_stateless() response = self.source1_rest_client.post( - self.events_url(self.stateless_incident1), self._create_event_dict(Event.Type.INCIDENT_END) + self.events_url(self.stateless_incident), self._create_event_dict(Event.Type.INCIDENT_END) ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) assert_incident_stateless() - self.assertEqual(self.stateless_incident1.events.count(), event_count + 1) + self.assertEqual(self.stateless_incident.events.count(), event_count + 1) response = self.source1_rest_client.post( - self.events_url(self.stateless_incident1), self._create_event_dict(Event.Type.INCIDENT_RESTART) + self.events_url(self.stateless_incident), self._create_event_dict(Event.Type.INCIDENT_RESTART) ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) assert_incident_stateless() - self.assertEqual(self.stateless_incident1.events.count(), event_count + 2) + self.assertEqual(self.stateless_incident.events.count(), event_count + 2) def test_posting_allowed_event_types_for_source_system_is_valid(self): def delete_start_event(incident: Incident): @@ -225,7 +222,7 @@ def delete_start_event(incident: Incident): } for event_type, ensure_precondition in source_system_allowed_types_and_preconditions.items(): with self.subTest(event_type=event_type): - ensure_precondition(self.stateful_incident1) + ensure_precondition(self.stateful_incident) self._assert_posting_event_succeeds(self._create_event_dict(event_type), self.source1_rest_client) def test_posting_allowed_event_types_for_end_user_is_valid(self): @@ -243,11 +240,11 @@ def set_end_time(incident: Incident): } for event_type, ensure_precondition in end_user_allowed_types_and_preconditions.items(): with self.subTest(event_type=event_type): - ensure_precondition(self.stateful_incident1) + ensure_precondition(self.stateful_incident) self._assert_posting_event_succeeds(self._create_event_dict(event_type), self.user1_rest_client) def test_posting_disallowed_event_types_for_source_system_is_invalid(self): - original_end_time = self.stateful_incident1.end_time + original_end_time = self.stateful_incident.end_time source_system_disallowed_types = set(Event.Type.values) - Event.ALLOWED_TYPES_FOR_SOURCE_SYSTEMS for event_type in source_system_disallowed_types: @@ -257,7 +254,7 @@ def test_posting_disallowed_event_types_for_source_system_is_invalid(self): ) def test_posting_disallowed_event_types_for_end_user_is_invalid(self): - original_end_time = self.stateful_incident1.end_time + original_end_time = self.stateful_incident.end_time end_user_disallowed_types = set(Event.Type.values) - Event.ALLOWED_TYPES_FOR_END_USERS for event_type in end_user_disallowed_types: From 74a8198470e6b72bd0a688652eb01f61b7bb0288 Mon Sep 17 00:00:00 2001 From: Johanna England Date: Wed, 17 Jun 2026 15:26:42 +0200 Subject: [PATCH 02/10] Remove asserts at beginning of tests --- tests/incident/test_event.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/incident/test_event.py b/tests/incident/test_event.py index 5012adb44..636ea0e47 100644 --- a/tests/incident/test_event.py +++ b/tests/incident/test_event.py @@ -80,9 +80,6 @@ def _assert_incident_is_open(self, incident: Incident): self.assertEqual(datetime_utils.make_naive(incident.end_time), datetime.max) def test_posting_close_and_reopen_events_properly_changes_stateful_incidents(self): - self.assertTrue(self.stateful_incident.stateful) - self.assertTrue(self.stateful_incident.open) - # Test closing incident close_event_dict = self._create_event_dict(Event.Type.CLOSE) event_timestamp = close_event_dict["timestamp"] @@ -107,9 +104,6 @@ def test_posting_close_and_reopen_events_properly_changes_stateful_incidents(sel ) def test_posting_end_and_restart_events_properly_changes_stateful_incidents(self): - self.assertTrue(self.stateful_incident.stateful) - self.assertTrue(self.stateful_incident.open) - # Test ending incident end_event_dict = self._create_event_dict(Event.Type.INCIDENT_END) event_timestamp = end_event_dict["timestamp"] @@ -172,7 +166,6 @@ def assert_incident_stateless(): self.assertFalse(self.stateless_incident.stateful) self.assertFalse(self.stateless_incident.open) - assert_incident_stateless() response = self.user1_rest_client.post( self.events_url(self.stateless_incident), self._create_event_dict(Event.Type.CLOSE) ) @@ -195,7 +188,6 @@ def assert_incident_stateless(): event_count = self.stateless_incident.events.count() - assert_incident_stateless() response = self.source1_rest_client.post( self.events_url(self.stateless_incident), self._create_event_dict(Event.Type.INCIDENT_END) ) From 15fc570f2a339d6810a6b2d0d198a0e87a72867c Mon Sep 17 00:00:00 2001 From: Johanna England Date: Wed, 17 Jun 2026 15:28:34 +0200 Subject: [PATCH 03/10] Remove helper functions They made the tests difficult to understand --- tests/incident/test_event.py | 204 +++++++++++++++++++---------------- 1 file changed, 112 insertions(+), 92 deletions(-) diff --git a/tests/incident/test_event.py b/tests/incident/test_event.py index 636ea0e47..0012fad21 100644 --- a/tests/incident/test_event.py +++ b/tests/incident/test_event.py @@ -1,6 +1,5 @@ from datetime import datetime, timedelta -from django.test import Client from django.urls import reverse from django.utils import timezone from django.utils.dateparse import parse_datetime @@ -35,94 +34,77 @@ def setUp(self): def tearDown(self): connect_signals() - @staticmethod - def _create_event_dict(event_type: str): - return { - "timestamp": timezone.now(), - "type": event_type, - } - - def _assert_response_field_invalid(self, response, field_name: str): - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertIn(field_name, response.data) - self.assertEqual(response.data[field_name].code, "invalid") - - def _assert_posting_event_is_rejected_and_does_not_change_end_time( - self, post_data: dict, original_end_time: datetime, client: Client - ): - event_count = Event.objects.count() - - response = client.post(self.events_url(self.stateful_incident), post_data) - self._assert_response_field_invalid(response, "type") - - self.assertEqual(Event.objects.count(), event_count) - self.stateful_incident.refresh_from_db() - self.assertEqual(self.stateful_incident.end_time, original_end_time) - - def _assert_posting_event_succeeds(self, post_data: dict, client: Client): - event_count = self.stateful_incident.events.count() - - response = client.post(self.events_url(self.stateful_incident), post_data) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - self.assertEqual(self.stateful_incident.events.count(), event_count + 1) - self.assertTrue(self.stateful_incident.events.filter(pk=response.data["pk"]).exists()) - self.assertEqual(response.data["incident"], self.stateful_incident.pk) - - def _assert_incident_is_closed_at_timestamp(self, incident: Incident, timestamp: datetime): - incident.refresh_from_db() - self.assertFalse(incident.open) - self.assertEqual(incident.end_time, timestamp) - - def _assert_incident_is_open(self, incident: Incident): - incident.refresh_from_db() - self.assertTrue(incident.open) - self.assertEqual(datetime_utils.make_naive(incident.end_time), datetime.max) - def test_posting_close_and_reopen_events_properly_changes_stateful_incidents(self): # Test closing incident - close_event_dict = self._create_event_dict(Event.Type.CLOSE) + close_event_dict = {"timestamp": timezone.now(), "type": Event.Type.CLOSE} event_timestamp = close_event_dict["timestamp"] response = self.user1_rest_client.post(self.events_url(self.stateful_incident), close_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), event_timestamp) - self._assert_incident_is_closed_at_timestamp(self.stateful_incident, event_timestamp) + self.stateful_incident.refresh_from_db() + self.assertFalse(self.stateful_incident.open) + self.assertEqual(self.stateful_incident.end_time, event_timestamp) # It's illegal to close an already closed incident - self._assert_posting_event_is_rejected_and_does_not_change_end_time( - close_event_dict, self.stateful_incident.end_time, self.user1_rest_client - ) + original_end_time = self.stateful_incident.end_time + event_count = Event.objects.count() + + response = self.user1_rest_client.post(self.events_url(self.stateful_incident), close_event_dict) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("type", response.data) + self.assertEqual(response.data["type"].code, "invalid") + + self.assertEqual(Event.objects.count(), event_count) + self.stateful_incident.refresh_from_db() + self.assertEqual(self.stateful_incident.end_time, original_end_time) # Test reopening incident - reopen_event_dict = self._create_event_dict(Event.Type.REOPEN) + reopen_event_dict = {"timestamp": timezone.now(), "type": Event.Type.REOPEN} response = self.user1_rest_client.post(self.events_url(self.stateful_incident), reopen_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), reopen_event_dict["timestamp"]) - self._assert_incident_is_open(self.stateful_incident) + self.stateful_incident.refresh_from_db() + self.assertTrue(self.stateful_incident.open) + self.assertEqual(datetime_utils.make_naive(self.stateful_incident.end_time), datetime.max) # It's illegal to reopen an already opened incident - self._assert_posting_event_is_rejected_and_does_not_change_end_time( - reopen_event_dict, self.stateful_incident.end_time, self.user1_rest_client - ) + original_end_time = self.stateful_incident.end_time + event_count = Event.objects.count() + + response = self.user1_rest_client.post(self.events_url(self.stateful_incident), reopen_event_dict) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("type", response.data) + self.assertEqual(response.data["type"].code, "invalid") + + self.assertEqual(Event.objects.count(), event_count) + self.stateful_incident.refresh_from_db() + self.assertEqual(self.stateful_incident.end_time, original_end_time) def test_posting_end_and_restart_events_properly_changes_stateful_incidents(self): # Test ending incident - end_event_dict = self._create_event_dict(Event.Type.INCIDENT_END) + end_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_END} event_timestamp = end_event_dict["timestamp"] response = self.source1_rest_client.post(self.events_url(self.stateful_incident), end_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), event_timestamp) - self._assert_incident_is_closed_at_timestamp(self.stateful_incident, event_timestamp) + self.stateful_incident.refresh_from_db() + self.assertFalse(self.stateful_incident.open) + self.assertEqual(self.stateful_incident.end_time, event_timestamp) # Test restarting incident - restart_event_dict = self._create_event_dict(Event.Type.INCIDENT_RESTART) + restart_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_RESTART} response = self.source1_rest_client.post(self.events_url(self.stateful_incident), restart_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), restart_event_dict["timestamp"]) - self._assert_incident_is_open(self.stateful_incident) + self.stateful_incident.refresh_from_db() + self.assertTrue(self.stateful_incident.open) + self.assertEqual(datetime_utils.make_naive(self.stateful_incident.end_time), datetime.max) # Test ending again - end_event_dict = self._create_event_dict(Event.Type.INCIDENT_END) + end_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_END} event_timestamp = end_event_dict["timestamp"] response = self.source1_rest_client.post(self.events_url(self.stateful_incident), end_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), event_timestamp) - self._assert_incident_is_closed_at_timestamp(self.stateful_incident, event_timestamp) + self.stateful_incident.refresh_from_db() + self.assertFalse(self.stateful_incident.open) + self.assertEqual(self.stateful_incident.end_time, event_timestamp) def test_given_closed_incident_when_source_posts_end_then_records_event_without_state_change(self): # An end user posting a state-invalid event gets a 400; a source system does @@ -131,15 +113,16 @@ def test_given_closed_incident_when_source_posts_end_then_records_event_without_ # incident must be accepted and recorded without re-touching end_time. count_before = self.stateful_incident.events.count() - self.stateful_incident.end_time = timezone.now() + self.stateful_incident.end_time = original_timestamp = timezone.now() self.stateful_incident.save(update_fields=["end_time"]) - end_event_dict = self._create_event_dict(Event.Type.INCIDENT_END) + end_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_END} response = self.source1_rest_client.post(self.events_url(self.stateful_incident), end_event_dict) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self._assert_incident_is_closed_at_timestamp(self.stateful_incident, self.stateful_incident.end_time) self.stateful_incident.refresh_from_db() + self.assertFalse(self.stateful_incident.open) + self.assertEqual(self.stateful_incident.end_time, original_timestamp) self.assertEqual(self.stateful_incident.events.count(), count_before + 1) def test_given_open_incident_when_source_posts_restart_then_records_event_without_state_change(self): @@ -152,54 +135,53 @@ def test_given_open_incident_when_source_posts_restart_then_records_event_withou count_before = self.stateful_incident.events.count() - restart_event_dict = self._create_event_dict(Event.Type.INCIDENT_RESTART) + restart_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_RESTART} response = self.source1_rest_client.post(self.events_url(self.stateful_incident), restart_event_dict) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self._assert_incident_is_open(self.stateful_incident) self.stateful_incident.refresh_from_db() + self.assertTrue(self.stateful_incident.open) + self.assertEqual(datetime_utils.make_naive(self.stateful_incident.end_time), datetime.max) self.assertEqual(self.stateful_incident.events.count(), count_before + 1) def test_posting_close_and_reopen_events_does_not_change_stateless_incidents(self): - def assert_incident_stateless(): - self.stateless_incident.refresh_from_db() - self.assertFalse(self.stateless_incident.stateful) - self.assertFalse(self.stateless_incident.open) - response = self.user1_rest_client.post( - self.events_url(self.stateless_incident), self._create_event_dict(Event.Type.CLOSE) + self.events_url(self.stateless_incident), {"timestamp": timezone.now(), "type": Event.Type.CLOSE} ) - self._assert_response_field_invalid(response, "type") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.data["type"], "Cannot change the state of a stateless incident.") - assert_incident_stateless() + self.stateless_incident.refresh_from_db() + self.assertFalse(self.stateless_incident.stateful) + self.assertFalse(self.stateless_incident.open) response = self.user1_rest_client.post( - self.events_url(self.stateless_incident), self._create_event_dict(Event.Type.REOPEN) + self.events_url(self.stateless_incident), {"timestamp": timezone.now(), "type": Event.Type.REOPEN} ) - self._assert_response_field_invalid(response, "type") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.data["type"], "Cannot change the state of a stateless incident.") - assert_incident_stateless() + self.stateless_incident.refresh_from_db() + self.assertFalse(self.stateless_incident.stateful) + self.assertFalse(self.stateless_incident.open) def test_posting_end_and_restart_events_does_not_change_stateless_incidents_but_records_event(self): - def assert_incident_stateless(): - self.stateless_incident.refresh_from_db() - self.assertFalse(self.stateless_incident.stateful) - self.assertFalse(self.stateless_incident.open) - event_count = self.stateless_incident.events.count() response = self.source1_rest_client.post( - self.events_url(self.stateless_incident), self._create_event_dict(Event.Type.INCIDENT_END) + self.events_url(self.stateless_incident), {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_END} ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - assert_incident_stateless() + self.stateless_incident.refresh_from_db() + self.assertFalse(self.stateless_incident.stateful) + self.assertFalse(self.stateless_incident.open) self.assertEqual(self.stateless_incident.events.count(), event_count + 1) response = self.source1_rest_client.post( - self.events_url(self.stateless_incident), self._create_event_dict(Event.Type.INCIDENT_RESTART) + self.events_url(self.stateless_incident), {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_RESTART} ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - assert_incident_stateless() + self.stateless_incident.refresh_from_db() + self.assertFalse(self.stateless_incident.stateful) + self.assertFalse(self.stateless_incident.open) self.assertEqual(self.stateless_incident.events.count(), event_count + 2) def test_posting_allowed_event_types_for_source_system_is_valid(self): @@ -215,7 +197,17 @@ def delete_start_event(incident: Incident): for event_type, ensure_precondition in source_system_allowed_types_and_preconditions.items(): with self.subTest(event_type=event_type): ensure_precondition(self.stateful_incident) - self._assert_posting_event_succeeds(self._create_event_dict(event_type), self.source1_rest_client) + event_count = self.stateful_incident.events.count() + + response = self.source1_rest_client.post( + self.events_url(self.stateful_incident), + {"timestamp": timezone.now(), "type": event_type}, + ) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + self.assertEqual(self.stateful_incident.events.count(), event_count + 1) + self.assertTrue(self.stateful_incident.events.filter(pk=response.data["pk"]).exists()) + self.assertEqual(response.data["incident"], self.stateful_incident.pk) def test_posting_allowed_event_types_for_end_user_is_valid(self): def set_end_time_to(end_time): @@ -233,7 +225,17 @@ def set_end_time(incident: Incident): for event_type, ensure_precondition in end_user_allowed_types_and_preconditions.items(): with self.subTest(event_type=event_type): ensure_precondition(self.stateful_incident) - self._assert_posting_event_succeeds(self._create_event_dict(event_type), self.user1_rest_client) + event_count = self.stateful_incident.events.count() + + response = self.user1_rest_client.post( + self.events_url(self.stateful_incident), + {"timestamp": timezone.now(), "type": event_type}, + ) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + self.assertEqual(self.stateful_incident.events.count(), event_count + 1) + self.assertTrue(self.stateful_incident.events.filter(pk=response.data["pk"]).exists()) + self.assertEqual(response.data["incident"], self.stateful_incident.pk) def test_posting_disallowed_event_types_for_source_system_is_invalid(self): original_end_time = self.stateful_incident.end_time @@ -241,9 +243,18 @@ def test_posting_disallowed_event_types_for_source_system_is_invalid(self): source_system_disallowed_types = set(Event.Type.values) - Event.ALLOWED_TYPES_FOR_SOURCE_SYSTEMS for event_type in source_system_disallowed_types: with self.subTest(event_type=event_type): - self._assert_posting_event_is_rejected_and_does_not_change_end_time( - self._create_event_dict(event_type), original_end_time, self.source1_rest_client + event_count = Event.objects.count() + + response = self.source1_rest_client.post( + self.events_url(self.stateful_incident), {"timestamp": timezone.now(), "type": event_type} ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("type", response.data) + self.assertEqual(response.data["type"].code, "invalid") + + self.assertEqual(Event.objects.count(), event_count) + self.stateful_incident.refresh_from_db() + self.assertEqual(self.stateful_incident.end_time, original_end_time) def test_posting_disallowed_event_types_for_end_user_is_invalid(self): original_end_time = self.stateful_incident.end_time @@ -251,6 +262,15 @@ def test_posting_disallowed_event_types_for_end_user_is_invalid(self): end_user_disallowed_types = set(Event.Type.values) - Event.ALLOWED_TYPES_FOR_END_USERS for event_type in end_user_disallowed_types: with self.subTest(event_type=event_type): - self._assert_posting_event_is_rejected_and_does_not_change_end_time( - self._create_event_dict(event_type), original_end_time, self.user1_rest_client + event_count = Event.objects.count() + + response = self.user1_rest_client.post( + self.events_url(self.stateful_incident), {"timestamp": timezone.now(), "type": event_type} ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("type", response.data) + self.assertEqual(response.data["type"].code, "invalid") + + self.assertEqual(Event.objects.count(), event_count) + self.stateful_incident.refresh_from_db() + self.assertEqual(self.stateful_incident.end_time, original_end_time) From 31bb0f0123ae2fbd31ad206744239975de52304e Mon Sep 17 00:00:00 2001 From: Johanna England Date: Wed, 17 Jun 2026 14:00:36 +0200 Subject: [PATCH 04/10] Split up stateful tests into separate tests Before we tested 3 or 4 different things in one test, now each assumption is one --- tests/incident/test_event.py | 115 ++++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 43 deletions(-) diff --git a/tests/incident/test_event.py b/tests/incident/test_event.py index 0012fad21..5b4a4f010 100644 --- a/tests/incident/test_event.py +++ b/tests/incident/test_event.py @@ -19,6 +19,19 @@ def setUp(self): super().init_test_objects() + self.closed_incident = StatefulIncidentFactory( + start_time=timezone.now() - timedelta(days=2), + end_time=timezone.now() - timedelta(days=1), + source=self.source1, + ) + self.closed_incident.create_first_event() + Event.objects.create( + incident=self.closed_incident, + actor=self.user1, + timestamp=self.closed_incident.end_time, + type=Event.Type.CLOSE, + ) + self.stateful_incident = StatefulIncidentFactory( start_time=timezone.now() - timedelta(weeks=1), end_time=timezone.now() + timedelta(weeks=1), @@ -34,96 +47,112 @@ def setUp(self): def tearDown(self): connect_signals() - def test_posting_close_and_reopen_events_properly_changes_stateful_incidents(self): - # Test closing incident + def test_when_posting_close_event_for_stateful_open_incident_then_incident_gets_closed(self): close_event_dict = {"timestamp": timezone.now(), "type": Event.Type.CLOSE} - event_timestamp = close_event_dict["timestamp"] + response = self.user1_rest_client.post(self.events_url(self.stateful_incident), close_event_dict) - self.assertEqual(parse_datetime(response.data["timestamp"]), event_timestamp) + + self.assertEqual(parse_datetime(response.data["timestamp"]), close_event_dict["timestamp"]) self.stateful_incident.refresh_from_db() self.assertFalse(self.stateful_incident.open) - self.assertEqual(self.stateful_incident.end_time, event_timestamp) + self.assertEqual(self.stateful_incident.end_time, close_event_dict["timestamp"]) - # It's illegal to close an already closed incident - original_end_time = self.stateful_incident.end_time - event_count = Event.objects.count() + def test_when_posting_close_event_for_stateful_closed_incident_then_end_time_does_not_change(self): + close_event_dict = {"timestamp": timezone.now(), "type": Event.Type.CLOSE} + original_end_time = self.closed_incident.end_time - response = self.user1_rest_client.post(self.events_url(self.stateful_incident), close_event_dict) + response = self.user1_rest_client.post(self.events_url(self.closed_incident), close_event_dict) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn("type", response.data) self.assertEqual(response.data["type"].code, "invalid") - self.assertEqual(Event.objects.count(), event_count) - self.stateful_incident.refresh_from_db() - self.assertEqual(self.stateful_incident.end_time, original_end_time) + self.closed_incident.refresh_from_db() + self.assertEqual(self.closed_incident.end_time, original_end_time) - # Test reopening incident + def test_when_posting_reopen_event_for_stateful_closed_incident_then_incident_gets_reopened(self): reopen_event_dict = {"timestamp": timezone.now(), "type": Event.Type.REOPEN} - response = self.user1_rest_client.post(self.events_url(self.stateful_incident), reopen_event_dict) + response = self.user1_rest_client.post(self.events_url(self.closed_incident), reopen_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), reopen_event_dict["timestamp"]) - self.stateful_incident.refresh_from_db() - self.assertTrue(self.stateful_incident.open) - self.assertEqual(datetime_utils.make_naive(self.stateful_incident.end_time), datetime.max) + self.closed_incident.refresh_from_db() + self.assertTrue(self.closed_incident.open) + self.assertEqual(datetime_utils.make_naive(self.closed_incident.end_time), datetime.max) - # It's illegal to reopen an already opened incident + def test_when_posting_reopen_event_for_stateful_open_incident_then_return_bad_request(self): + reopen_event_dict = {"timestamp": timezone.now(), "type": Event.Type.REOPEN} original_end_time = self.stateful_incident.end_time - event_count = Event.objects.count() response = self.user1_rest_client.post(self.events_url(self.stateful_incident), reopen_event_dict) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn("type", response.data) self.assertEqual(response.data["type"].code, "invalid") - self.assertEqual(Event.objects.count(), event_count) self.stateful_incident.refresh_from_db() self.assertEqual(self.stateful_incident.end_time, original_end_time) - def test_posting_end_and_restart_events_properly_changes_stateful_incidents(self): - # Test ending incident + def test_when_posting_end_event_for_stateful_open_incident_then_incident_gets_closed(self): end_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_END} - event_timestamp = end_event_dict["timestamp"] + response = self.source1_rest_client.post(self.events_url(self.stateful_incident), end_event_dict) - self.assertEqual(parse_datetime(response.data["timestamp"]), event_timestamp) + + self.assertEqual(parse_datetime(response.data["timestamp"]), end_event_dict["timestamp"]) self.stateful_incident.refresh_from_db() self.assertFalse(self.stateful_incident.open) - self.assertEqual(self.stateful_incident.end_time, event_timestamp) + self.assertEqual(self.stateful_incident.end_time, end_event_dict["timestamp"]) - # Test restarting incident + def test_when_posting_restart_event_for_stateful_closed_incident_then_incident_gets_reopened(self): restart_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_RESTART} - response = self.source1_rest_client.post(self.events_url(self.stateful_incident), restart_event_dict) + + response = self.source1_rest_client.post(self.events_url(self.closed_incident), restart_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), restart_event_dict["timestamp"]) - self.stateful_incident.refresh_from_db() - self.assertTrue(self.stateful_incident.open) - self.assertEqual(datetime_utils.make_naive(self.stateful_incident.end_time), datetime.max) + self.closed_incident.refresh_from_db() + self.assertTrue(self.closed_incident.open) + self.assertEqual(datetime_utils.make_naive(self.closed_incident.end_time), datetime.max) + + def test_when_posting_end_event_for_ended_and_restarted_stateful_incident_then_incident_gets_closed(self): + restarted_incident = StatefulIncidentFactory( + start_time=timezone.now() - timedelta(hours=1), + source=self.source1, + ) + restarted_incident.create_first_event() + Event.objects.create( + incident=self.closed_incident, + actor=self.source1_user, + timestamp=timezone.now() - timedelta(minutes=30), + type=Event.Type.INCIDENT_END, + ) + Event.objects.create( + incident=self.closed_incident, + actor=self.source1_user, + timestamp=timezone.now() - timedelta(minutes=10), + type=Event.Type.INCIDENT_RESTART, + ) - # Test ending again end_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_END} - event_timestamp = end_event_dict["timestamp"] + response = self.source1_rest_client.post(self.events_url(self.stateful_incident), end_event_dict) - self.assertEqual(parse_datetime(response.data["timestamp"]), event_timestamp) + self.assertEqual(parse_datetime(response.data["timestamp"]), end_event_dict["timestamp"]) self.stateful_incident.refresh_from_db() self.assertFalse(self.stateful_incident.open) - self.assertEqual(self.stateful_incident.end_time, event_timestamp) + self.assertEqual(self.stateful_incident.end_time, end_event_dict["timestamp"]) def test_given_closed_incident_when_source_posts_end_then_records_event_without_state_change(self): # An end user posting a state-invalid event gets a 400; a source system does # not. Its event is recorded for the audit trail (201) but update_incident is # skipped, so the incident is unchanged. Here: END on an already-closed # incident must be accepted and recorded without re-touching end_time. - count_before = self.stateful_incident.events.count() - - self.stateful_incident.end_time = original_timestamp = timezone.now() - self.stateful_incident.save(update_fields=["end_time"]) + count_before = self.closed_incident.events.count() + original_timestamp = self.closed_incident.end_time end_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_END} - response = self.source1_rest_client.post(self.events_url(self.stateful_incident), end_event_dict) + response = self.source1_rest_client.post(self.events_url(self.closed_incident), end_event_dict) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.stateful_incident.refresh_from_db() - self.assertFalse(self.stateful_incident.open) - self.assertEqual(self.stateful_incident.end_time, original_timestamp) - self.assertEqual(self.stateful_incident.events.count(), count_before + 1) + self.closed_incident.refresh_from_db() + self.assertFalse(self.closed_incident.open) + self.assertEqual(self.closed_incident.end_time, original_timestamp) + self.assertEqual(self.closed_incident.events.count(), count_before + 1) def test_given_open_incident_when_source_posts_restart_then_records_event_without_state_change(self): # An end user posting a state-invalid event gets a 400; a source system does From 5522ab97014c30e55bc272332de2f721fcb02142 Mon Sep 17 00:00:00 2001 From: Johanna England Date: Thu, 18 Jun 2026 08:57:11 +0200 Subject: [PATCH 05/10] Split up stateless tests into separate tests --- tests/incident/test_event.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/incident/test_event.py b/tests/incident/test_event.py index 5b4a4f010..9dce43d80 100644 --- a/tests/incident/test_event.py +++ b/tests/incident/test_event.py @@ -173,7 +173,7 @@ def test_given_open_incident_when_source_posts_restart_then_records_event_withou self.assertEqual(datetime_utils.make_naive(self.stateful_incident.end_time), datetime.max) self.assertEqual(self.stateful_incident.events.count(), count_before + 1) - def test_posting_close_and_reopen_events_does_not_change_stateless_incidents(self): + def test_when_posting_close_event_for_stateless_incident_then_incident_does_not_change(self): response = self.user1_rest_client.post( self.events_url(self.stateless_incident), {"timestamp": timezone.now(), "type": Event.Type.CLOSE} ) @@ -183,6 +183,7 @@ def test_posting_close_and_reopen_events_does_not_change_stateless_incidents(sel self.assertFalse(self.stateless_incident.stateful) self.assertFalse(self.stateless_incident.open) + def test_when_posting_reopen_event_for_stateless_incident_then_incident_does_not_change(self): response = self.user1_rest_client.post( self.events_url(self.stateless_incident), {"timestamp": timezone.now(), "type": Event.Type.REOPEN} ) @@ -192,7 +193,7 @@ def test_posting_close_and_reopen_events_does_not_change_stateless_incidents(sel self.assertFalse(self.stateless_incident.stateful) self.assertFalse(self.stateless_incident.open) - def test_posting_end_and_restart_events_does_not_change_stateless_incidents_but_records_event(self): + def test_when_posting_end_event_for_stateless_incident_then_incident_is_unchanged_and_event_is_recorded(self): event_count = self.stateless_incident.events.count() response = self.source1_rest_client.post( @@ -204,6 +205,9 @@ def test_posting_end_and_restart_events_does_not_change_stateless_incidents_but_ self.assertFalse(self.stateless_incident.open) self.assertEqual(self.stateless_incident.events.count(), event_count + 1) + def test_when_posting_restart_event_for_stateless_incident_then_incident_is_unchanged_and_event_is_recorded(self): + event_count = self.stateless_incident.events.count() + response = self.source1_rest_client.post( self.events_url(self.stateless_incident), {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_RESTART} ) @@ -211,7 +215,7 @@ def test_posting_end_and_restart_events_does_not_change_stateless_incidents_but_ self.stateless_incident.refresh_from_db() self.assertFalse(self.stateless_incident.stateful) self.assertFalse(self.stateless_incident.open) - self.assertEqual(self.stateless_incident.events.count(), event_count + 2) + self.assertEqual(self.stateless_incident.events.count(), event_count + 1) def test_posting_allowed_event_types_for_source_system_is_valid(self): def delete_start_event(incident: Incident): From 8b2372840317e1dac71e258b3102c16a8564944f Mon Sep 17 00:00:00 2001 From: Johanna England Date: Thu, 18 Jun 2026 12:17:49 +0200 Subject: [PATCH 06/10] Change stateful incident test fixture to open --- tests/incident/test_event.py | 92 +++++++++++++++++------------------- 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/tests/incident/test_event.py b/tests/incident/test_event.py index 9dce43d80..ef3772288 100644 --- a/tests/incident/test_event.py +++ b/tests/incident/test_event.py @@ -32,12 +32,11 @@ def setUp(self): type=Event.Type.CLOSE, ) - self.stateful_incident = StatefulIncidentFactory( + self.open_incident = StatefulIncidentFactory( start_time=timezone.now() - timedelta(weeks=1), - end_time=timezone.now() + timedelta(weeks=1), source=self.source1, ) - self.stateful_incident.create_first_event() + self.open_incident.create_first_event() self.stateless_incident = StatelessIncidentFactory(source=self.source1) self.stateless_incident.create_first_event() @@ -50,12 +49,12 @@ def tearDown(self): def test_when_posting_close_event_for_stateful_open_incident_then_incident_gets_closed(self): close_event_dict = {"timestamp": timezone.now(), "type": Event.Type.CLOSE} - response = self.user1_rest_client.post(self.events_url(self.stateful_incident), close_event_dict) + response = self.user1_rest_client.post(self.events_url(self.open_incident), close_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), close_event_dict["timestamp"]) - self.stateful_incident.refresh_from_db() - self.assertFalse(self.stateful_incident.open) - self.assertEqual(self.stateful_incident.end_time, close_event_dict["timestamp"]) + self.open_incident.refresh_from_db() + self.assertFalse(self.open_incident.open) + self.assertEqual(self.open_incident.end_time, close_event_dict["timestamp"]) def test_when_posting_close_event_for_stateful_closed_incident_then_end_time_does_not_change(self): close_event_dict = {"timestamp": timezone.now(), "type": Event.Type.CLOSE} @@ -80,26 +79,26 @@ def test_when_posting_reopen_event_for_stateful_closed_incident_then_incident_ge def test_when_posting_reopen_event_for_stateful_open_incident_then_return_bad_request(self): reopen_event_dict = {"timestamp": timezone.now(), "type": Event.Type.REOPEN} - original_end_time = self.stateful_incident.end_time + original_end_time = self.open_incident.end_time - response = self.user1_rest_client.post(self.events_url(self.stateful_incident), reopen_event_dict) + response = self.user1_rest_client.post(self.events_url(self.open_incident), reopen_event_dict) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn("type", response.data) self.assertEqual(response.data["type"].code, "invalid") - self.stateful_incident.refresh_from_db() - self.assertEqual(self.stateful_incident.end_time, original_end_time) + self.open_incident.refresh_from_db() + self.assertEqual(self.open_incident.end_time, original_end_time) def test_when_posting_end_event_for_stateful_open_incident_then_incident_gets_closed(self): end_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_END} - response = self.source1_rest_client.post(self.events_url(self.stateful_incident), end_event_dict) + response = self.source1_rest_client.post(self.events_url(self.open_incident), end_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), end_event_dict["timestamp"]) - self.stateful_incident.refresh_from_db() - self.assertFalse(self.stateful_incident.open) - self.assertEqual(self.stateful_incident.end_time, end_event_dict["timestamp"]) + self.open_incident.refresh_from_db() + self.assertFalse(self.open_incident.open) + self.assertEqual(self.open_incident.end_time, end_event_dict["timestamp"]) def test_when_posting_restart_event_for_stateful_closed_incident_then_incident_gets_reopened(self): restart_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_RESTART} @@ -131,11 +130,11 @@ def test_when_posting_end_event_for_ended_and_restarted_stateful_incident_then_i end_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_END} - response = self.source1_rest_client.post(self.events_url(self.stateful_incident), end_event_dict) + response = self.source1_rest_client.post(self.events_url(self.open_incident), end_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), end_event_dict["timestamp"]) - self.stateful_incident.refresh_from_db() - self.assertFalse(self.stateful_incident.open) - self.assertEqual(self.stateful_incident.end_time, end_event_dict["timestamp"]) + self.open_incident.refresh_from_db() + self.assertFalse(self.open_incident.open) + self.assertEqual(self.open_incident.end_time, end_event_dict["timestamp"]) def test_given_closed_incident_when_source_posts_end_then_records_event_without_state_change(self): # An end user posting a state-invalid event gets a 400; a source system does @@ -159,19 +158,16 @@ def test_given_open_incident_when_source_posts_restart_then_records_event_withou # not. Its event is recorded for the audit trail (201) but update_incident is # skipped, so the incident is unchanged. Here: RESTART on an already-open # incident must be accepted and recorded without re-touching end_time. - self.stateful_incident.end_time = datetime_utils.INFINITY_REPR - self.stateful_incident.save(update_fields=["end_time"]) - - count_before = self.stateful_incident.events.count() + count_before = self.open_incident.events.count() restart_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_RESTART} - response = self.source1_rest_client.post(self.events_url(self.stateful_incident), restart_event_dict) + response = self.source1_rest_client.post(self.events_url(self.open_incident), restart_event_dict) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.stateful_incident.refresh_from_db() - self.assertTrue(self.stateful_incident.open) - self.assertEqual(datetime_utils.make_naive(self.stateful_incident.end_time), datetime.max) - self.assertEqual(self.stateful_incident.events.count(), count_before + 1) + self.open_incident.refresh_from_db() + self.assertTrue(self.open_incident.open) + self.assertEqual(datetime_utils.make_naive(self.open_incident.end_time), datetime.max) + self.assertEqual(self.open_incident.events.count(), count_before + 1) def test_when_posting_close_event_for_stateless_incident_then_incident_does_not_change(self): response = self.user1_rest_client.post( @@ -229,18 +225,18 @@ def delete_start_event(incident: Incident): } for event_type, ensure_precondition in source_system_allowed_types_and_preconditions.items(): with self.subTest(event_type=event_type): - ensure_precondition(self.stateful_incident) - event_count = self.stateful_incident.events.count() + ensure_precondition(self.open_incident) + event_count = self.open_incident.events.count() response = self.source1_rest_client.post( - self.events_url(self.stateful_incident), + self.events_url(self.open_incident), {"timestamp": timezone.now(), "type": event_type}, ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(self.stateful_incident.events.count(), event_count + 1) - self.assertTrue(self.stateful_incident.events.filter(pk=response.data["pk"]).exists()) - self.assertEqual(response.data["incident"], self.stateful_incident.pk) + self.assertEqual(self.open_incident.events.count(), event_count + 1) + self.assertTrue(self.open_incident.events.filter(pk=response.data["pk"]).exists()) + self.assertEqual(response.data["incident"], self.open_incident.pk) def test_posting_allowed_event_types_for_end_user_is_valid(self): def set_end_time_to(end_time): @@ -257,21 +253,21 @@ def set_end_time(incident: Incident): } for event_type, ensure_precondition in end_user_allowed_types_and_preconditions.items(): with self.subTest(event_type=event_type): - ensure_precondition(self.stateful_incident) - event_count = self.stateful_incident.events.count() + ensure_precondition(self.open_incident) + event_count = self.open_incident.events.count() response = self.user1_rest_client.post( - self.events_url(self.stateful_incident), + self.events_url(self.open_incident), {"timestamp": timezone.now(), "type": event_type}, ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(self.stateful_incident.events.count(), event_count + 1) - self.assertTrue(self.stateful_incident.events.filter(pk=response.data["pk"]).exists()) - self.assertEqual(response.data["incident"], self.stateful_incident.pk) + self.assertEqual(self.open_incident.events.count(), event_count + 1) + self.assertTrue(self.open_incident.events.filter(pk=response.data["pk"]).exists()) + self.assertEqual(response.data["incident"], self.open_incident.pk) def test_posting_disallowed_event_types_for_source_system_is_invalid(self): - original_end_time = self.stateful_incident.end_time + original_end_time = self.open_incident.end_time source_system_disallowed_types = set(Event.Type.values) - Event.ALLOWED_TYPES_FOR_SOURCE_SYSTEMS for event_type in source_system_disallowed_types: @@ -279,18 +275,18 @@ def test_posting_disallowed_event_types_for_source_system_is_invalid(self): event_count = Event.objects.count() response = self.source1_rest_client.post( - self.events_url(self.stateful_incident), {"timestamp": timezone.now(), "type": event_type} + self.events_url(self.open_incident), {"timestamp": timezone.now(), "type": event_type} ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn("type", response.data) self.assertEqual(response.data["type"].code, "invalid") self.assertEqual(Event.objects.count(), event_count) - self.stateful_incident.refresh_from_db() - self.assertEqual(self.stateful_incident.end_time, original_end_time) + self.open_incident.refresh_from_db() + self.assertEqual(self.open_incident.end_time, original_end_time) def test_posting_disallowed_event_types_for_end_user_is_invalid(self): - original_end_time = self.stateful_incident.end_time + original_end_time = self.open_incident.end_time end_user_disallowed_types = set(Event.Type.values) - Event.ALLOWED_TYPES_FOR_END_USERS for event_type in end_user_disallowed_types: @@ -298,12 +294,12 @@ def test_posting_disallowed_event_types_for_end_user_is_invalid(self): event_count = Event.objects.count() response = self.user1_rest_client.post( - self.events_url(self.stateful_incident), {"timestamp": timezone.now(), "type": event_type} + self.events_url(self.open_incident), {"timestamp": timezone.now(), "type": event_type} ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn("type", response.data) self.assertEqual(response.data["type"].code, "invalid") self.assertEqual(Event.objects.count(), event_count) - self.stateful_incident.refresh_from_db() - self.assertEqual(self.stateful_incident.end_time, original_end_time) + self.open_incident.refresh_from_db() + self.assertEqual(self.open_incident.end_time, original_end_time) From 4a63ad662771cc0f2138b32bdb5e7b18ba091f26 Mon Sep 17 00:00:00 2001 From: Johanna England Date: Thu, 18 Jun 2026 12:33:26 +0200 Subject: [PATCH 07/10] Split up tests into classes --- tests/incident/test_event.py | 143 ++++++++++++++++++++++++++--------- 1 file changed, 107 insertions(+), 36 deletions(-) diff --git a/tests/incident/test_event.py b/tests/incident/test_event.py index ef3772288..8cc90b7ae 100644 --- a/tests/incident/test_event.py +++ b/tests/incident/test_event.py @@ -13,7 +13,7 @@ from . import IncidentBasedAPITestCaseHelper -class EventAPITests(APITestCase, IncidentBasedAPITestCaseHelper): +class EventAPIStatefulIncidentTests(APITestCase, IncidentBasedAPITestCaseHelper): def setUp(self): disconnect_signals() @@ -38,15 +38,12 @@ def setUp(self): ) self.open_incident.create_first_event() - self.stateless_incident = StatelessIncidentFactory(source=self.source1) - self.stateless_incident.create_first_event() - self.events_url = lambda incident: reverse("v2:incident:incident-events", args=[incident.pk]) def tearDown(self): connect_signals() - def test_when_posting_close_event_for_stateful_open_incident_then_incident_gets_closed(self): + def test_when_posting_close_event_for_open_incident_then_incident_gets_closed(self): close_event_dict = {"timestamp": timezone.now(), "type": Event.Type.CLOSE} response = self.user1_rest_client.post(self.events_url(self.open_incident), close_event_dict) @@ -56,7 +53,7 @@ def test_when_posting_close_event_for_stateful_open_incident_then_incident_gets_ self.assertFalse(self.open_incident.open) self.assertEqual(self.open_incident.end_time, close_event_dict["timestamp"]) - def test_when_posting_close_event_for_stateful_closed_incident_then_end_time_does_not_change(self): + def test_when_posting_close_event_for_closed_incident_then_end_time_does_not_change(self): close_event_dict = {"timestamp": timezone.now(), "type": Event.Type.CLOSE} original_end_time = self.closed_incident.end_time @@ -69,7 +66,7 @@ def test_when_posting_close_event_for_stateful_closed_incident_then_end_time_doe self.closed_incident.refresh_from_db() self.assertEqual(self.closed_incident.end_time, original_end_time) - def test_when_posting_reopen_event_for_stateful_closed_incident_then_incident_gets_reopened(self): + def test_when_posting_reopen_event_for_closed_incident_then_incident_gets_reopened(self): reopen_event_dict = {"timestamp": timezone.now(), "type": Event.Type.REOPEN} response = self.user1_rest_client.post(self.events_url(self.closed_incident), reopen_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), reopen_event_dict["timestamp"]) @@ -77,7 +74,7 @@ def test_when_posting_reopen_event_for_stateful_closed_incident_then_incident_ge self.assertTrue(self.closed_incident.open) self.assertEqual(datetime_utils.make_naive(self.closed_incident.end_time), datetime.max) - def test_when_posting_reopen_event_for_stateful_open_incident_then_return_bad_request(self): + def test_when_posting_reopen_event_for_open_incident_then_return_bad_request(self): reopen_event_dict = {"timestamp": timezone.now(), "type": Event.Type.REOPEN} original_end_time = self.open_incident.end_time @@ -90,7 +87,7 @@ def test_when_posting_reopen_event_for_stateful_open_incident_then_return_bad_re self.open_incident.refresh_from_db() self.assertEqual(self.open_incident.end_time, original_end_time) - def test_when_posting_end_event_for_stateful_open_incident_then_incident_gets_closed(self): + def test_when_posting_end_event_for_open_incident_then_incident_gets_closed(self): end_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_END} response = self.source1_rest_client.post(self.events_url(self.open_incident), end_event_dict) @@ -100,7 +97,7 @@ def test_when_posting_end_event_for_stateful_open_incident_then_incident_gets_cl self.assertFalse(self.open_incident.open) self.assertEqual(self.open_incident.end_time, end_event_dict["timestamp"]) - def test_when_posting_restart_event_for_stateful_closed_incident_then_incident_gets_reopened(self): + def test_when_posting_restart_event_for_closed_incident_then_incident_gets_reopened(self): restart_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_RESTART} response = self.source1_rest_client.post(self.events_url(self.closed_incident), restart_event_dict) @@ -109,7 +106,7 @@ def test_when_posting_restart_event_for_stateful_closed_incident_then_incident_g self.assertTrue(self.closed_incident.open) self.assertEqual(datetime_utils.make_naive(self.closed_incident.end_time), datetime.max) - def test_when_posting_end_event_for_ended_and_restarted_stateful_incident_then_incident_gets_closed(self): + def test_when_posting_end_event_for_ended_and_restarted_incident_then_incident_gets_closed(self): restarted_incident = StatefulIncidentFactory( start_time=timezone.now() - timedelta(hours=1), source=self.source1, @@ -169,6 +166,18 @@ def test_given_open_incident_when_source_posts_restart_then_records_event_withou self.assertEqual(datetime_utils.make_naive(self.open_incident.end_time), datetime.max) self.assertEqual(self.open_incident.events.count(), count_before + 1) + +class EventAPIStatelessIncidentTests(APITestCase, IncidentBasedAPITestCaseHelper): + def setUp(self): + disconnect_signals() + + super().init_test_objects() + + self.stateless_incident = StatelessIncidentFactory(source=self.source1) + self.stateless_incident.create_first_event() + + self.events_url = lambda incident: reverse("v2:incident:incident-events", args=[incident.pk]) + def test_when_posting_close_event_for_stateless_incident_then_incident_does_not_change(self): response = self.user1_rest_client.post( self.events_url(self.stateless_incident), {"timestamp": timezone.now(), "type": Event.Type.CLOSE} @@ -179,7 +188,7 @@ def test_when_posting_close_event_for_stateless_incident_then_incident_does_not_ self.assertFalse(self.stateless_incident.stateful) self.assertFalse(self.stateless_incident.open) - def test_when_posting_reopen_event_for_stateless_incident_then_incident_does_not_change(self): + def test_when_posting_reopen_event_then_incident_does_not_change(self): response = self.user1_rest_client.post( self.events_url(self.stateless_incident), {"timestamp": timezone.now(), "type": Event.Type.REOPEN} ) @@ -189,7 +198,7 @@ def test_when_posting_reopen_event_for_stateless_incident_then_incident_does_not self.assertFalse(self.stateless_incident.stateful) self.assertFalse(self.stateless_incident.open) - def test_when_posting_end_event_for_stateless_incident_then_incident_is_unchanged_and_event_is_recorded(self): + def test_when_posting_end_event_then_incident_does_not_change_and_event_is_recorded(self): event_count = self.stateless_incident.events.count() response = self.source1_rest_client.post( @@ -201,7 +210,7 @@ def test_when_posting_end_event_for_stateless_incident_then_incident_is_unchange self.assertFalse(self.stateless_incident.open) self.assertEqual(self.stateless_incident.events.count(), event_count + 1) - def test_when_posting_restart_event_for_stateless_incident_then_incident_is_unchanged_and_event_is_recorded(self): + def test_when_posting_restart_event_then_incident_does_not_change_and_event_is_recorded(self): event_count = self.stateless_incident.events.count() response = self.source1_rest_client.post( @@ -213,7 +222,38 @@ def test_when_posting_restart_event_for_stateless_incident_then_incident_is_unch self.assertFalse(self.stateless_incident.open) self.assertEqual(self.stateless_incident.events.count(), event_count + 1) - def test_posting_allowed_event_types_for_source_system_is_valid(self): + +class EventAPISourceSystemUserTests(APITestCase, IncidentBasedAPITestCaseHelper): + def setUp(self): + disconnect_signals() + + super().init_test_objects() + + self.closed_incident = StatefulIncidentFactory( + start_time=timezone.now() - timedelta(days=2), + end_time=timezone.now() - timedelta(days=1), + source=self.source1, + ) + self.closed_incident.create_first_event() + Event.objects.create( + incident=self.closed_incident, + actor=self.user1, + timestamp=self.closed_incident.end_time, + type=Event.Type.CLOSE, + ) + + self.open_incident = StatefulIncidentFactory( + start_time=timezone.now() - timedelta(weeks=1), + source=self.source1, + ) + self.open_incident.create_first_event() + + self.events_url = lambda incident: reverse("v2:incident:incident-events", args=[incident.pk]) + + def tearDown(self): + connect_signals() + + def test_when_posting_allowed_event_types_for_source_system_user_then_event_is_created(self): def delete_start_event(incident: Incident): incident.start_event.delete() @@ -238,7 +278,57 @@ def delete_start_event(incident: Incident): self.assertTrue(self.open_incident.events.filter(pk=response.data["pk"]).exists()) self.assertEqual(response.data["incident"], self.open_incident.pk) - def test_posting_allowed_event_types_for_end_user_is_valid(self): + def test_when_posting_disallowed_event_types_for_source_system_user_then_return_bad_request(self): + original_end_time = self.open_incident.end_time + + source_system_disallowed_types = set(Event.Type.values) - Event.ALLOWED_TYPES_FOR_SOURCE_SYSTEMS + for event_type in source_system_disallowed_types: + with self.subTest(event_type=event_type): + event_count = Event.objects.count() + + response = self.source1_rest_client.post( + self.events_url(self.open_incident), {"timestamp": timezone.now(), "type": event_type} + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("type", response.data) + self.assertEqual(response.data["type"].code, "invalid") + + self.assertEqual(Event.objects.count(), event_count) + self.open_incident.refresh_from_db() + self.assertEqual(self.open_incident.end_time, original_end_time) + + +class EventAPIEndUserTests(APITestCase, IncidentBasedAPITestCaseHelper): + def setUp(self): + disconnect_signals() + + super().init_test_objects() + + self.closed_incident = StatefulIncidentFactory( + start_time=timezone.now() - timedelta(days=2), + end_time=timezone.now() - timedelta(days=1), + source=self.source1, + ) + self.closed_incident.create_first_event() + Event.objects.create( + incident=self.closed_incident, + actor=self.user1, + timestamp=self.closed_incident.end_time, + type=Event.Type.CLOSE, + ) + + self.open_incident = StatefulIncidentFactory( + start_time=timezone.now() - timedelta(weeks=1), + source=self.source1, + ) + self.open_incident.create_first_event() + + self.events_url = lambda incident: reverse("v2:incident:incident-events", args=[incident.pk]) + + def tearDown(self): + connect_signals() + + def test_when_posting_allowed_event_types_for_end_user_then_event_is_created(self): def set_end_time_to(end_time): def set_end_time(incident: Incident): incident.end_time = end_time @@ -266,26 +356,7 @@ def set_end_time(incident: Incident): self.assertTrue(self.open_incident.events.filter(pk=response.data["pk"]).exists()) self.assertEqual(response.data["incident"], self.open_incident.pk) - def test_posting_disallowed_event_types_for_source_system_is_invalid(self): - original_end_time = self.open_incident.end_time - - source_system_disallowed_types = set(Event.Type.values) - Event.ALLOWED_TYPES_FOR_SOURCE_SYSTEMS - for event_type in source_system_disallowed_types: - with self.subTest(event_type=event_type): - event_count = Event.objects.count() - - response = self.source1_rest_client.post( - self.events_url(self.open_incident), {"timestamp": timezone.now(), "type": event_type} - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertIn("type", response.data) - self.assertEqual(response.data["type"].code, "invalid") - - self.assertEqual(Event.objects.count(), event_count) - self.open_incident.refresh_from_db() - self.assertEqual(self.open_incident.end_time, original_end_time) - - def test_posting_disallowed_event_types_for_end_user_is_invalid(self): + def test_when_posting_disallowed_event_types_for_end_user_then_return_bad_request(self): original_end_time = self.open_incident.end_time end_user_disallowed_types = set(Event.Type.values) - Event.ALLOWED_TYPES_FOR_END_USERS From eac9ee22c0b37f8e3413557758431d650f6861b6 Mon Sep 17 00:00:00 2001 From: Johanna England Date: Thu, 18 Jun 2026 12:45:06 +0200 Subject: [PATCH 08/10] Simplify (dis)allowed event types tests --- tests/incident/test_event.py | 69 ++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/tests/incident/test_event.py b/tests/incident/test_event.py index 8cc90b7ae..4ed563943 100644 --- a/tests/incident/test_event.py +++ b/tests/incident/test_event.py @@ -9,7 +9,7 @@ from argus.util import datetime_utils from argus.util.testing import disconnect_signals, connect_signals from argus.incident.factories import StatefulIncidentFactory, StatelessIncidentFactory -from argus.incident.models import Event, Incident +from argus.incident.models import Event from . import IncidentBasedAPITestCaseHelper @@ -248,35 +248,36 @@ def setUp(self): ) self.open_incident.create_first_event() + self.open_incident_without_start_event = StatefulIncidentFactory( + start_time=timezone.now() - timedelta(weeks=1), + source=self.source1, + ) + self.events_url = lambda incident: reverse("v2:incident:incident-events", args=[incident.pk]) def tearDown(self): connect_signals() def test_when_posting_allowed_event_types_for_source_system_user_then_event_is_created(self): - def delete_start_event(incident: Incident): - incident.start_event.delete() - - source_system_allowed_types_and_preconditions = { - Event.Type.INCIDENT_START: delete_start_event, - Event.Type.INCIDENT_END: lambda incident: None, - Event.Type.INCIDENT_RESTART: lambda incident: None, - Event.Type.OTHER: lambda incident: None, + source_system_allowed_types_and_incidents = { + Event.Type.INCIDENT_START: self.open_incident_without_start_event, + Event.Type.INCIDENT_END: self.open_incident, + Event.Type.INCIDENT_RESTART: self.closed_incident, + Event.Type.OTHER: self.open_incident, } - for event_type, ensure_precondition in source_system_allowed_types_and_preconditions.items(): + for event_type, incident in source_system_allowed_types_and_incidents.items(): with self.subTest(event_type=event_type): - ensure_precondition(self.open_incident) - event_count = self.open_incident.events.count() + event_count = incident.events.count() response = self.source1_rest_client.post( - self.events_url(self.open_incident), + self.events_url(incident), {"timestamp": timezone.now(), "type": event_type}, ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(self.open_incident.events.count(), event_count + 1) - self.assertTrue(self.open_incident.events.filter(pk=response.data["pk"]).exists()) - self.assertEqual(response.data["incident"], self.open_incident.pk) + self.assertEqual(incident.events.count(), event_count + 1) + self.assertTrue(incident.events.filter(pk=response.data["pk"]).exists()) + self.assertEqual(response.data["incident"], incident.pk) def test_when_posting_disallowed_event_types_for_source_system_user_then_return_bad_request(self): original_end_time = self.open_incident.end_time @@ -284,7 +285,7 @@ def test_when_posting_disallowed_event_types_for_source_system_user_then_return_ source_system_disallowed_types = set(Event.Type.values) - Event.ALLOWED_TYPES_FOR_SOURCE_SYSTEMS for event_type in source_system_disallowed_types: with self.subTest(event_type=event_type): - event_count = Event.objects.count() + event_count = self.open_incident.events.count() response = self.source1_rest_client.post( self.events_url(self.open_incident), {"timestamp": timezone.now(), "type": event_type} @@ -293,7 +294,7 @@ def test_when_posting_disallowed_event_types_for_source_system_user_then_return_ self.assertIn("type", response.data) self.assertEqual(response.data["type"].code, "invalid") - self.assertEqual(Event.objects.count(), event_count) + self.assertEqual(self.open_incident.events.count(), event_count) self.open_incident.refresh_from_db() self.assertEqual(self.open_incident.end_time, original_end_time) @@ -329,32 +330,24 @@ def tearDown(self): connect_signals() def test_when_posting_allowed_event_types_for_end_user_then_event_is_created(self): - def set_end_time_to(end_time): - def set_end_time(incident: Incident): - incident.end_time = end_time - incident.save() - - return set_end_time - - end_user_allowed_types_and_preconditions = { - Event.Type.CLOSE: set_end_time_to("infinity"), - Event.Type.REOPEN: set_end_time_to(timezone.now()), - Event.Type.OTHER: lambda incident: None, + end_user_allowed_types_and_incidents = { + Event.Type.CLOSE: self.open_incident, + Event.Type.REOPEN: self.closed_incident, + Event.Type.OTHER: self.open_incident, } - for event_type, ensure_precondition in end_user_allowed_types_and_preconditions.items(): + for event_type, incident in end_user_allowed_types_and_incidents.items(): with self.subTest(event_type=event_type): - ensure_precondition(self.open_incident) - event_count = self.open_incident.events.count() + event_count = incident.events.count() response = self.user1_rest_client.post( - self.events_url(self.open_incident), + self.events_url(incident), {"timestamp": timezone.now(), "type": event_type}, ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(self.open_incident.events.count(), event_count + 1) - self.assertTrue(self.open_incident.events.filter(pk=response.data["pk"]).exists()) - self.assertEqual(response.data["incident"], self.open_incident.pk) + self.assertEqual(incident.events.count(), event_count + 1) + self.assertTrue(incident.events.filter(pk=response.data["pk"]).exists()) + self.assertEqual(response.data["incident"], incident.pk) def test_when_posting_disallowed_event_types_for_end_user_then_return_bad_request(self): original_end_time = self.open_incident.end_time @@ -362,7 +355,7 @@ def test_when_posting_disallowed_event_types_for_end_user_then_return_bad_reques end_user_disallowed_types = set(Event.Type.values) - Event.ALLOWED_TYPES_FOR_END_USERS for event_type in end_user_disallowed_types: with self.subTest(event_type=event_type): - event_count = Event.objects.count() + event_count = self.open_incident.events.count() response = self.user1_rest_client.post( self.events_url(self.open_incident), {"timestamp": timezone.now(), "type": event_type} @@ -371,6 +364,6 @@ def test_when_posting_disallowed_event_types_for_end_user_then_return_bad_reques self.assertIn("type", response.data) self.assertEqual(response.data["type"].code, "invalid") - self.assertEqual(Event.objects.count(), event_count) + self.assertEqual(self.open_incident.events.count(), event_count) self.open_incident.refresh_from_db() self.assertEqual(self.open_incident.end_time, original_end_time) From 54248ab94c873d41c2e557041119b92fa9359832 Mon Sep 17 00:00:00 2001 From: Johanna England Date: Thu, 9 Jul 2026 13:30:52 +0200 Subject: [PATCH 09/10] fixup! Split up tests into classes --- tests/incident/test_event.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/incident/test_event.py b/tests/incident/test_event.py index 4ed563943..418292a1b 100644 --- a/tests/incident/test_event.py +++ b/tests/incident/test_event.py @@ -178,6 +178,9 @@ def setUp(self): self.events_url = lambda incident: reverse("v2:incident:incident-events", args=[incident.pk]) + def tearDown(self): + connect_signals() + def test_when_posting_close_event_for_stateless_incident_then_incident_does_not_change(self): response = self.user1_rest_client.post( self.events_url(self.stateless_incident), {"timestamp": timezone.now(), "type": Event.Type.CLOSE} From 5952abfe27fada9b8a979e7f70a6fc0b193b1c10 Mon Sep 17 00:00:00 2001 From: Johanna England Date: Thu, 9 Jul 2026 13:51:54 +0200 Subject: [PATCH 10/10] fixup! Split up stateful tests into separate tests --- tests/incident/test_event.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/incident/test_event.py b/tests/incident/test_event.py index 418292a1b..abca0383d 100644 --- a/tests/incident/test_event.py +++ b/tests/incident/test_event.py @@ -127,11 +127,11 @@ def test_when_posting_end_event_for_ended_and_restarted_incident_then_incident_g end_event_dict = {"timestamp": timezone.now(), "type": Event.Type.INCIDENT_END} - response = self.source1_rest_client.post(self.events_url(self.open_incident), end_event_dict) + response = self.source1_rest_client.post(self.events_url(restarted_incident), end_event_dict) self.assertEqual(parse_datetime(response.data["timestamp"]), end_event_dict["timestamp"]) - self.open_incident.refresh_from_db() - self.assertFalse(self.open_incident.open) - self.assertEqual(self.open_incident.end_time, end_event_dict["timestamp"]) + restarted_incident.refresh_from_db() + self.assertFalse(restarted_incident.open) + self.assertEqual(restarted_incident.end_time, end_event_dict["timestamp"]) def test_given_closed_incident_when_source_posts_end_then_records_event_without_state_change(self): # An end user posting a state-invalid event gets a 400; a source system does