From 903420cfcfc78d64c0162ea507555b547861103d Mon Sep 17 00:00:00 2001 From: Miles Bryant Date: Mon, 12 Aug 2019 16:35:37 +0100 Subject: [PATCH 1/8] Update incidents-list (/api/incidents) --- requirements-dev.txt | 2 ++ response/core/serializers.py | 18 +++++++-------- response/core/views.py | 4 ++-- tests/api/__init__.py | 0 tests/api/conftest.py | 13 +++++++++++ tests/api/test_incidents.py | 44 ++++++++++++++++++++++++++++++++++++ tests/conftest.py | 6 ++++- tests/factories/__init__.py | 2 ++ tests/factories/incident.py | 32 ++++++++++++++++++++++++++ tests/factories/user.py | 21 +++++++++++++++++ 10 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 tests/api/__init__.py create mode 100644 tests/api/conftest.py create mode 100644 tests/api/test_incidents.py create mode 100644 tests/factories/__init__.py create mode 100644 tests/factories/incident.py create mode 100644 tests/factories/user.py diff --git a/requirements-dev.txt b/requirements-dev.txt index 38fed6c2..2cfe1a6c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -15,6 +15,8 @@ django-bootstrap4==0.0.8 djangorestframework==3.10.1 docopt==0.6.2 emoji-data-python==1.1.0 +factory-boy>=2.12.0 +faker>=2.0.0 idna==2.8 importlib-metadata==0.18 markdown2==2.3.8 diff --git a/response/core/serializers.py b/response/core/serializers.py index 859fa4ba..44858626 100644 --- a/response/core/serializers.py +++ b/response/core/serializers.py @@ -27,17 +27,17 @@ class Meta: class IncidentSerializer(serializers.HyperlinkedModelSerializer): - reporter = serializers.PrimaryKeyRelatedField(queryset=ExternalUser.objects.all(), required=False) - lead = serializers.PrimaryKeyRelatedField(queryset=ExternalUser.objects.all(), required=False) + reporter = ExternalUserSerializer() + lead = ExternalUserSerializer() class Meta: model = Incident - fields = ('pk','report', 'reporter', 'lead', 'start_time', 'end_time', 'report_time', 'action_set') + fields = ('pk','report', 'impact', 'summary', 'reporter', 'lead', 'start_time', 'end_time', 'report_time', 'action_set') - def __init__(self, *args, **kwargs): - super(IncidentSerializer, self).__init__(*args, **kwargs) - request = kwargs['context']['request'] - expand = request.GET.get('expand', "").split(',') +# def __init__(self, *args, **kwargs): +# super(IncidentSerializer, self).__init__(*args, **kwargs) +# request = kwargs['context']['request'] +# expand = request.GET.get('expand', "").split(',') - if 'actions' in expand: - self.fields['action_set'] = ActionSerializer(many=True, read_only=True) +# if 'actions' in expand: +# self.fields['action_set'] = ActionSerializer(many=True, read_only=True) diff --git a/response/core/views.py b/response/core/views.py index 19a7676a..1525f3ff 100644 --- a/response/core/views.py +++ b/response/core/views.py @@ -1,4 +1,4 @@ -from rest_framework import viewsets +from rest_framework import pagination, viewsets from response.core.models.incident import Incident from response.core.models.action import Action @@ -27,7 +27,7 @@ class IncidentViewSet(viewsets.ModelViewSet): # ViewSets define the view behavior. serializer_class = IncidentSerializer - pagination_class = None # Remove pagination + pagination_class = pagination.LimitOffsetPagination def get_queryset(self): # Same query is used to get single items so we check if pk is passed diff --git a/tests/api/__init__.py b/tests/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/api/conftest.py b/tests/api/conftest.py new file mode 100644 index 00000000..7071529f --- /dev/null +++ b/tests/api/conftest.py @@ -0,0 +1,13 @@ +import pytest +from rest_framework.test import APIRequestFactory + +from tests.factories import UserFactory + +@pytest.fixture +def arf(): + return APIRequestFactory() + + +@pytest.fixture +def api_user(): + return UserFactory() diff --git a/tests/api/test_incidents.py b/tests/api/test_incidents.py new file mode 100644 index 00000000..c118a33f --- /dev/null +++ b/tests/api/test_incidents.py @@ -0,0 +1,44 @@ +import json +import pytest +from rest_framework.test import force_authenticate +from django.urls import reverse + +from response.serializers import IncidentSerializer +from response.core.views import IncidentViewSet +from tests.factories import IncidentFactory + + +@pytest.mark.django_db +def test_list_incidents(arf, api_user): + persisted_incidents = IncidentFactory.create_batch(5) + + req = arf.get(reverse("Incidents-list")) + force_authenticate(req, user=api_user) + response = IncidentViewSet.as_view({"get": "list"})(req) + + assert response.status_code == 200 + content = json.loads(response.rendered_content) + + assert "results" in content + incidents = content["results"] + assert len(incidents) == len(persisted_incidents) + + for idx, incident in enumerate(incidents): + assert incident["report_time"] + + # incidents should be in order of newest to oldest + if idx != len(incidents) - 1: + assert incident["report_time"] >= incidents[idx + 1]["report_time"] + + assert "end_time" in incident + assert incident["impact"] + assert incident["report"] + assert incident["start_time"] + assert incident["summary"] + + reporter = incident["reporter"] + assert reporter["display_name"] + assert reporter["external_id"] + + # TODO: verify actions are serialised inline + diff --git a/tests/conftest.py b/tests/conftest.py index b8aa15dc..2ad3e323 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,9 +12,13 @@ from response.slack.authentication import generate_signature from response.slack.client import SlackClient -@pytest.fixture() +@pytest.fixture(autouse=True) def mock_slack(monkeypatch): mock_slack = MagicMock(spec=SlackClient("")) + mock_slack.send_or_update_message_block.return_value = { + "ok": True, + "ts": 1234, + } monkeypatch.setattr(settings, "SLACK_CLIENT", mock_slack) return mock_slack diff --git a/tests/factories/__init__.py b/tests/factories/__init__.py new file mode 100644 index 00000000..619d9106 --- /dev/null +++ b/tests/factories/__init__.py @@ -0,0 +1,2 @@ +from .incident import * +from .user import * diff --git a/tests/factories/incident.py b/tests/factories/incident.py new file mode 100644 index 00000000..194b6b8b --- /dev/null +++ b/tests/factories/incident.py @@ -0,0 +1,32 @@ +import random + +import factory +from faker import Factory + +from response.core.models import Incident + +from .user import ExternalUserFactory + +faker = Factory.create() + + +class IncidentFactory(factory.DjangoModelFactory): + class Meta: + model = Incident + + if random.random() > .5: + lead = factory.SubFactory(ExternalUserFactory) + + impact = faker.paragraph(nb_sentences=1, variable_nb_sentences=True) + report = faker.paragraph(nb_sentences=3, variable_nb_sentences=True) + report_time = faker.date_time_between(start_date="-3d", end_date="now", tzinfo=None) + reporter = factory.SubFactory(ExternalUserFactory) + start_time = faker.date_time_between(start_date="-3d", end_date="now", tzinfo=None) + + if random.random() > .5: + end_time = faker.date_between(start_date="-3d", end_date="today") + end_time = factory.LazyAttribute(lambda a: faker.date_between(start_date=a.start_time, end_date="today")) + + + severity = str(random.randint(1, 4)) + summary = faker.paragraph(nb_sentences=3, variable_nb_sentences=True) diff --git a/tests/factories/user.py b/tests/factories/user.py new file mode 100644 index 00000000..dc824971 --- /dev/null +++ b/tests/factories/user.py @@ -0,0 +1,21 @@ +from django.contrib.auth.models import User +import factory +from faker import Factory + + +from response.core.models import ExternalUser + +faker = Factory.create() + +class UserFactory(factory.DjangoModelFactory): + class Meta: + model = User + +class ExternalUserFactory(factory.DjangoModelFactory): + class Meta: + model = ExternalUser + + app_id = "slack" + display_name = faker.name() + external_id = "U" + faker.word() + From 236d362eba3d668174217339b5cd0d52fc6eb183 Mon Sep 17 00:00:00 2001 From: Miles Bryant Date: Mon, 12 Aug 2019 18:46:09 +0100 Subject: [PATCH 2/8] Update incidents with PUT /incidents/ --- response/core/serializers.py | 48 +++++++++++++++++++++------- response/core/urls.py | 2 +- response/core/views.py | 2 -- tests/api/conftest.py | 9 +++--- tests/api/test_incidents.py | 61 +++++++++++++++++++++++++++++++++--- tests/factories/incident.py | 48 +++++++++++++++++----------- tests/factories/user.py | 11 +++++-- 7 files changed, 136 insertions(+), 45 deletions(-) diff --git a/response/core/serializers.py b/response/core/serializers.py index 44858626..a7cca6a4 100644 --- a/response/core/serializers.py +++ b/response/core/serializers.py @@ -8,31 +8,57 @@ from django.contrib.auth.models import User -class ExternalUserSerializer(serializers.HyperlinkedModelSerializer): - owner = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), required=False) - +class ExternalUserSerializer(serializers.ModelSerializer): class Meta: model = ExternalUser - fields = ('app_id', 'external_id', 'owner', 'display_name') + fields = ("app_id", "external_id", "display_name") -class ActionSerializer(serializers.HyperlinkedModelSerializer): +class ActionSerializer(serializers.ModelSerializer): # Serializers define the API representation. - incident = serializers.PrimaryKeyRelatedField(queryset=Incident.objects.all(), required=False) - user = serializers.PrimaryKeyRelatedField(queryset=ExternalUser.objects.all(), required=False) + incident = serializers.PrimaryKeyRelatedField( + queryset=Incident.objects.all(), required=False + ) + user = serializers.PrimaryKeyRelatedField( + queryset=ExternalUser.objects.all(), required=False + ) class Meta: model = Action - fields = ('pk', 'details', 'done', 'incident', 'user') + fields = ("pk", "details", "done", "incident", "user") -class IncidentSerializer(serializers.HyperlinkedModelSerializer): - reporter = ExternalUserSerializer() +class IncidentSerializer(serializers.ModelSerializer): + reporter = ExternalUserSerializer(read_only=True) lead = ExternalUserSerializer() class Meta: model = Incident - fields = ('pk','report', 'impact', 'summary', 'reporter', 'lead', 'start_time', 'end_time', 'report_time', 'action_set') + fields = ( + "action_set", + "end_time", + "impact", + "lead", + "report", + "reporter", + "report_time", + "severity", + "start_time", + "summary", + ) + + def update(self, instance, validated_data): + instance.end_time = validated_data.get("end_time", instance.end_time) + instance.impact = validated_data.get("impact", instance.impact) + # instance.lead = validated_data.get("lead", instance.lead) + instance.report = validated_data.get("report", instance.report) + instance.start_time = validated_data.get("start_time", instance.start_time) + instance.summary = validated_data.get("summary", instance.summary) + instance.severity = validated_data.get("severity", instance.severity) + + instance.save() + return instance + # def __init__(self, *args, **kwargs): # super(IncidentSerializer, self).__init__(*args, **kwargs) diff --git a/response/core/urls.py b/response/core/urls.py index 9442a39f..78acdf0f 100644 --- a/response/core/urls.py +++ b/response/core/urls.py @@ -5,7 +5,7 @@ # Routers provide an easy way of automatically determining the URL conf. router = routers.DefaultRouter() -router.register(r'incidents', IncidentViewSet, base_name='Incidents') +router.register(r'incidents', IncidentViewSet, base_name='incident') router.register(r'actions', ActionViewSet) router.register(r'ExternalUser', ExternalUserViewSet) diff --git a/response/core/views.py b/response/core/views.py index 1525f3ff..7389e29e 100644 --- a/response/core/views.py +++ b/response/core/views.py @@ -21,8 +21,6 @@ class ActionViewSet(viewsets.ModelViewSet): serializer_class = ActionSerializer -# Will return the incidents of the current month -# Can pass ?start=2019-05-28&end=2019-06-03 to change range class IncidentViewSet(viewsets.ModelViewSet): # ViewSets define the view behavior. diff --git a/tests/api/conftest.py b/tests/api/conftest.py index 7071529f..632293cf 100644 --- a/tests/api/conftest.py +++ b/tests/api/conftest.py @@ -1,13 +1,14 @@ import pytest from rest_framework.test import APIRequestFactory -from tests.factories import UserFactory +from tests.factories import UserFactory, ExternalUserFactory @pytest.fixture def arf(): return APIRequestFactory() -@pytest.fixture -def api_user(): - return UserFactory() +@pytest.fixture() +def api_user(transactional_db): + e = ExternalUserFactory() + return e.owner diff --git a/tests/api/test_incidents.py b/tests/api/test_incidents.py index c118a33f..2ac3a544 100644 --- a/tests/api/test_incidents.py +++ b/tests/api/test_incidents.py @@ -1,18 +1,22 @@ +from django.urls import reverse +from faker import Faker +from rest_framework.test import force_authenticate import json import pytest -from rest_framework.test import force_authenticate -from django.urls import reverse +import random +from response.models import Incident from response.serializers import IncidentSerializer from response.core.views import IncidentViewSet from tests.factories import IncidentFactory +faker = Faker() + -@pytest.mark.django_db def test_list_incidents(arf, api_user): - persisted_incidents = IncidentFactory.create_batch(5) + persisted_incidents = IncidentFactory.create_batch(1) - req = arf.get(reverse("Incidents-list")) + req = arf.get(reverse("incident-list")) force_authenticate(req, user=api_user) response = IncidentViewSet.as_view({"get": "list"})(req) @@ -35,10 +39,57 @@ def test_list_incidents(arf, api_user): assert incident["report"] assert incident["start_time"] assert incident["summary"] + assert incident["severity"] reporter = incident["reporter"] assert reporter["display_name"] assert reporter["external_id"] + lead = incident["lead"] + assert lead["display_name"] + assert lead["external_id"] + # TODO: verify actions are serialised inline + +@pytest.mark.parametrize( + "update_key,update_value", + ( + ("", ""), + ("impact", faker.paragraph(nb_sentences=2, variable_nb_sentences=True)), + ("report", faker.paragraph(nb_sentences=1, variable_nb_sentences=True)), + ("summary", faker.paragraph(nb_sentences=3, variable_nb_sentences=True)), + ( + "start_time", + faker.date_time_between(start_date="-3d", end_date="now", tzinfo=None), + ), + ( + "end_time", + faker.date_time_between(start_date="-3d", end_date="now", tzinfo=None), + ), + ("severity",str(random.randint(1, 4))), + ), +) +def test_update_incident(arf, api_user, update_key, update_value): + persisted_incidents = IncidentFactory.create_batch(5) + + incident = persisted_incidents[0] + serializer = IncidentSerializer(incident) + serialized = serializer.data + + updated = serialized + if update_key: + updated[update_key] = update_value + + req = arf.put( + reverse("incident-detail", kwargs={"pk": incident.pk}), updated, format="json" + ) + force_authenticate(req, user=api_user) + + response = IncidentViewSet.as_view({"put": "update"})(req, pk=incident.pk) + print(response.rendered_content) + assert response.status_code == 200 + + if update_key: + new_incident = Incident.objects.get(pk=incident.pk) + assert getattr(new_incident, update_key) == update_value diff --git a/tests/factories/incident.py b/tests/factories/incident.py index 194b6b8b..c8fa5ebb 100644 --- a/tests/factories/incident.py +++ b/tests/factories/incident.py @@ -1,32 +1,42 @@ import random +from django.db.models.signals import post_save import factory from faker import Factory -from response.core.models import Incident - -from .user import ExternalUserFactory +from response.core.models import Incident, ExternalUser faker = Factory.create() +@factory.django.mute_signals(post_save) class IncidentFactory(factory.DjangoModelFactory): class Meta: model = Incident - if random.random() > .5: - lead = factory.SubFactory(ExternalUserFactory) - - impact = faker.paragraph(nb_sentences=1, variable_nb_sentences=True) - report = faker.paragraph(nb_sentences=3, variable_nb_sentences=True) - report_time = faker.date_time_between(start_date="-3d", end_date="now", tzinfo=None) - reporter = factory.SubFactory(ExternalUserFactory) - start_time = faker.date_time_between(start_date="-3d", end_date="now", tzinfo=None) - - if random.random() > .5: - end_time = faker.date_between(start_date="-3d", end_date="today") - end_time = factory.LazyAttribute(lambda a: faker.date_between(start_date=a.start_time, end_date="today")) - - - severity = str(random.randint(1, 4)) - summary = faker.paragraph(nb_sentences=3, variable_nb_sentences=True) + impact = factory.LazyFunction( + lambda: faker.paragraph(nb_sentences=1, variable_nb_sentences=True) + ) + report = factory.LazyFunction( + lambda: faker.paragraph(nb_sentences=3, variable_nb_sentences=True) + ) + report_time = factory.LazyFunction( + lambda: faker.date_time_between(start_date="-3d", end_date="now", tzinfo=None) + ) + + reporter = factory.SubFactory("tests.factories.ExternalUserFactory") + lead = factory.SubFactory("tests.factories.ExternalUserFactory") + + start_time = factory.LazyFunction( + lambda: faker.date_time_between(start_date="-3d", end_date="now", tzinfo=None) + ) + + if random.random() > 0.5: + end_time = factory.LazyAttribute( + lambda a: faker.date_time_between(start_date=a.start_time, end_date="now") + ) + + severity = factory.LazyFunction(lambda: str(random.randint(1, 4))) + summary = factory.LazyFunction( + lambda: faker.paragraph(nb_sentences=3, variable_nb_sentences=True) + ) diff --git a/tests/factories/user.py b/tests/factories/user.py index dc824971..331d0670 100644 --- a/tests/factories/user.py +++ b/tests/factories/user.py @@ -7,15 +7,20 @@ faker = Factory.create() + class UserFactory(factory.DjangoModelFactory): class Meta: model = User + username = factory.LazyFunction(faker.user_name) + password = factory.LazyFunction(faker.password) + + class ExternalUserFactory(factory.DjangoModelFactory): class Meta: model = ExternalUser app_id = "slack" - display_name = faker.name() - external_id = "U" + faker.word() - + display_name = factory.LazyFunction(faker.name) + external_id = factory.LazyFunction(lambda: "U" + faker.word()) + owner = factory.SubFactory("tests.factories.UserFactory") From f814f40f2fae2b8ca488e093344a794a6d16e27a Mon Sep 17 00:00:00 2001 From: Miles Bryant Date: Tue, 13 Aug 2019 10:28:59 +0100 Subject: [PATCH 3/8] Tidy up tests --- tests/api/test_incidents.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/tests/api/test_incidents.py b/tests/api/test_incidents.py index 2ac3a544..7a41282f 100644 --- a/tests/api/test_incidents.py +++ b/tests/api/test_incidents.py @@ -14,27 +14,33 @@ def test_list_incidents(arf, api_user): - persisted_incidents = IncidentFactory.create_batch(1) + persisted_incidents = IncidentFactory.create_batch(5) req = arf.get(reverse("incident-list")) force_authenticate(req, user=api_user) response = IncidentViewSet.as_view({"get": "list"})(req) - assert response.status_code == 200 + assert response.status_code == 200, "Got non-200 response from API" content = json.loads(response.rendered_content) - assert "results" in content + assert "results" in content, "Response didn't have results key" incidents = content["results"] - assert len(incidents) == len(persisted_incidents) + assert ( + len(incidents) == len(persisted_incidents), + "Didn't get expected number of incidents back", + ) for idx, incident in enumerate(incidents): assert incident["report_time"] # incidents should be in order of newest to oldest if idx != len(incidents) - 1: - assert incident["report_time"] >= incidents[idx + 1]["report_time"] + assert ( + incident["report_time"] >= incidents[idx + 1]["report_time"], + "Incidents are not in order of newest to oldest by report time", + ) - assert "end_time" in incident + assert "end_time" in incident # end_time can be null for open incidents assert incident["impact"] assert incident["report"] assert incident["start_time"] @@ -55,7 +61,7 @@ def test_list_incidents(arf, api_user): @pytest.mark.parametrize( "update_key,update_value", ( - ("", ""), + ("", ""), # no update ("impact", faker.paragraph(nb_sentences=2, variable_nb_sentences=True)), ("report", faker.paragraph(nb_sentences=1, variable_nb_sentences=True)), ("summary", faker.paragraph(nb_sentences=3, variable_nb_sentences=True)), @@ -67,10 +73,14 @@ def test_list_incidents(arf, api_user): "end_time", faker.date_time_between(start_date="-3d", end_date="now", tzinfo=None), ), - ("severity",str(random.randint(1, 4))), + ("severity", str(random.randint(1, 4))), ), ) def test_update_incident(arf, api_user, update_key, update_value): + """ + Tests that we can PUT /incidents/ and mutate fields that get saved to + the DB. + """ persisted_incidents = IncidentFactory.create_batch(5) incident = persisted_incidents[0] @@ -88,8 +98,11 @@ def test_update_incident(arf, api_user, update_key, update_value): response = IncidentViewSet.as_view({"put": "update"})(req, pk=incident.pk) print(response.rendered_content) - assert response.status_code == 200 + assert response.status_code == 200, "Got non-200 response from API" if update_key: new_incident = Incident.objects.get(pk=incident.pk) - assert getattr(new_incident, update_key) == update_value + assert ( + getattr(new_incident, update_key) == update_value, + "Updated value wasn't persisted to the DB", + ) From 0329d81ed1b2e277583c54232a760af77ab28f46 Mon Sep 17 00:00:00 2001 From: Miles Bryant Date: Tue, 13 Aug 2019 12:12:21 +0100 Subject: [PATCH 4/8] Update incident lead via API --- response/core/serializers.py | 18 +++++------- response/core/views.py | 8 ++--- tests/api/test_incidents.py | 57 ++++++++++++++++++++++++++++-------- 3 files changed, 56 insertions(+), 27 deletions(-) diff --git a/response/core/serializers.py b/response/core/serializers.py index a7cca6a4..bab9553c 100644 --- a/response/core/serializers.py +++ b/response/core/serializers.py @@ -50,7 +50,14 @@ class Meta: def update(self, instance, validated_data): instance.end_time = validated_data.get("end_time", instance.end_time) instance.impact = validated_data.get("impact", instance.impact) - # instance.lead = validated_data.get("lead", instance.lead) + + new_lead = validated_data.get("lead", None) + if new_lead: + instance.lead = ExternalUser.objects.get( + display_name=new_lead["display_name"], + external_id=new_lead["external_id"], + ) + instance.report = validated_data.get("report", instance.report) instance.start_time = validated_data.get("start_time", instance.start_time) instance.summary = validated_data.get("summary", instance.summary) @@ -58,12 +65,3 @@ def update(self, instance, validated_data): instance.save() return instance - - -# def __init__(self, *args, **kwargs): -# super(IncidentSerializer, self).__init__(*args, **kwargs) -# request = kwargs['context']['request'] -# expand = request.GET.get('expand', "").split(',') - -# if 'actions' in expand: -# self.fields['action_set'] = ActionSerializer(many=True, read_only=True) diff --git a/response/core/views.py b/response/core/views.py index 7389e29e..e2e974cd 100644 --- a/response/core/views.py +++ b/response/core/views.py @@ -3,7 +3,7 @@ from response.core.models.incident import Incident from response.core.models.action import Action from response.core.models.user_external import ExternalUser -from response.core.serializers import ExternalUserSerializer, ActionSerializer, IncidentSerializer +from response.core import serializers from datetime import datetime from calendar import monthrange @@ -12,19 +12,19 @@ class ExternalUserViewSet(viewsets.ModelViewSet): # ViewSets define the view behavior. queryset = ExternalUser.objects.all() - serializer_class = ExternalUserSerializer + serializer_class = serializers.ExternalUserSerializer class ActionViewSet(viewsets.ModelViewSet): # ViewSets define the view behavior. queryset = Action.objects.all() - serializer_class = ActionSerializer + serializer_class = serializers.ActionSerializer class IncidentViewSet(viewsets.ModelViewSet): # ViewSets define the view behavior. - serializer_class = IncidentSerializer + serializer_class = serializers.IncidentSerializer pagination_class = pagination.LimitOffsetPagination def get_queryset(self): diff --git a/tests/api/test_incidents.py b/tests/api/test_incidents.py index 7a41282f..aa6a079b 100644 --- a/tests/api/test_incidents.py +++ b/tests/api/test_incidents.py @@ -5,14 +5,15 @@ import pytest import random -from response.models import Incident -from response.serializers import IncidentSerializer +from response.models import Incident, ExternalUser +from response import serializers from response.core.views import IncidentViewSet from tests.factories import IncidentFactory faker = Faker() +@pytest.mark.skip def test_list_incidents(arf, api_user): persisted_incidents = IncidentFactory.create_batch(5) @@ -25,10 +26,9 @@ def test_list_incidents(arf, api_user): assert "results" in content, "Response didn't have results key" incidents = content["results"] - assert ( - len(incidents) == len(persisted_incidents), - "Didn't get expected number of incidents back", - ) + assert len(incidents) == len( + persisted_incidents + ), "Didn't get expected number of incidents back" for idx, incident in enumerate(incidents): assert incident["report_time"] @@ -36,9 +36,8 @@ def test_list_incidents(arf, api_user): # incidents should be in order of newest to oldest if idx != len(incidents) - 1: assert ( - incident["report_time"] >= incidents[idx + 1]["report_time"], - "Incidents are not in order of newest to oldest by report time", - ) + incident["report_time"] >= incidents[idx + 1]["report_time"] + ), "Incidents are not in order of newest to oldest by report time" assert "end_time" in incident # end_time can be null for open incidents assert incident["impact"] @@ -84,10 +83,11 @@ def test_update_incident(arf, api_user, update_key, update_value): persisted_incidents = IncidentFactory.create_batch(5) incident = persisted_incidents[0] - serializer = IncidentSerializer(incident) + serializer = serializers.IncidentSerializer(incident) serialized = serializer.data updated = serialized + del updated["reporter"] # can't update reporter if update_key: updated[update_key] = update_value @@ -103,6 +103,37 @@ def test_update_incident(arf, api_user, update_key, update_value): if update_key: new_incident = Incident.objects.get(pk=incident.pk) assert ( - getattr(new_incident, update_key) == update_value, - "Updated value wasn't persisted to the DB", - ) + getattr(new_incident, update_key) == update_value + ), "Updated value wasn't persisted to the DB" + + +def test_update_incident_lead(arf, api_user): + """ + Tests that we can update the incident lead by name + """ + persisted_incidents = IncidentFactory.create_batch(5) + + incident = persisted_incidents[0] + serializer = serializers.IncidentSerializer(incident) + updated = serializer.data + + users = ExternalUser.objects.all() + + new_lead = users[0] + while new_lead == incident.lead: + new_lead = random.choices(users) + + updated["lead"] = serializers.ExternalUserSerializer(new_lead).data + del updated["reporter"] # can't update reporter + + req = arf.put( + reverse("incident-detail", kwargs={"pk": incident.pk}), updated, format="json" + ) + force_authenticate(req, user=api_user) + + response = IncidentViewSet.as_view({"put": "update"})(req, pk=incident.pk) + print(response.rendered_content) + assert response.status_code == 200, "Got non-200 response from API" + + new_incident = Incident.objects.get(pk=incident.pk) + assert new_incident.lead == new_lead From f77ff75b6c6074aca7531481322185ea2b4b93ad Mon Sep 17 00:00:00 2001 From: Miles Bryant Date: Tue, 13 Aug 2019 12:15:12 +0100 Subject: [PATCH 5/8] /incidents list should be sorted by report date --- response/core/views.py | 18 ++---------------- tests/api/test_incidents.py | 1 - 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/response/core/views.py b/response/core/views.py index e2e974cd..343e69a3 100644 --- a/response/core/views.py +++ b/response/core/views.py @@ -24,21 +24,7 @@ class ActionViewSet(viewsets.ModelViewSet): class IncidentViewSet(viewsets.ModelViewSet): # ViewSets define the view behavior. + queryset = Incident.objects.order_by("-report_time") + serializer_class = serializers.IncidentSerializer pagination_class = pagination.LimitOffsetPagination - - def get_queryset(self): - # Same query is used to get single items so we check if pk is passed - # incident/2/ if we use the filter below we would have to have correct time range - if 'pk' in self.kwargs: - return Incident.objects.filter(pk=self.kwargs['pk']) - - today = datetime.today() - first_day_of_current_month = datetime(today.year, today.month, 1) - days_in_month = monthrange(today.year, today.month)[1] - last_day_of_current_month = datetime(today.year, today.month, days_in_month) - - start = self.request.GET.get('start', first_day_of_current_month) - end = self.request.GET.get('end', last_day_of_current_month) - - return Incident.objects.filter(start_time__gte=start, start_time__lte=end) diff --git a/tests/api/test_incidents.py b/tests/api/test_incidents.py index aa6a079b..34b636b6 100644 --- a/tests/api/test_incidents.py +++ b/tests/api/test_incidents.py @@ -13,7 +13,6 @@ faker = Faker() -@pytest.mark.skip def test_list_incidents(arf, api_user): persisted_incidents = IncidentFactory.create_batch(5) From e021843c8f4996956a796cb8d1c1c223bf2fc451 Mon Sep 17 00:00:00 2001 From: Miles Bryant Date: Tue, 13 Aug 2019 13:05:20 +0100 Subject: [PATCH 6/8] Return primary key in incident response --- response/core/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/response/core/serializers.py b/response/core/serializers.py index bab9553c..d22dee39 100644 --- a/response/core/serializers.py +++ b/response/core/serializers.py @@ -39,9 +39,10 @@ class Meta: "end_time", "impact", "lead", + "pk", "report", - "reporter", "report_time", + "reporter", "severity", "start_time", "summary", From 7d4e435d73df1fef86a3cfb12ca5270e7a12747f Mon Sep 17 00:00:00 2001 From: Miles Bryant Date: Tue, 13 Aug 2019 13:09:42 +0100 Subject: [PATCH 7/8] Change deprecated parameter --- response/core/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/response/core/urls.py b/response/core/urls.py index 78acdf0f..39792b11 100644 --- a/response/core/urls.py +++ b/response/core/urls.py @@ -5,7 +5,7 @@ # Routers provide an easy way of automatically determining the URL conf. router = routers.DefaultRouter() -router.register(r'incidents', IncidentViewSet, base_name='incident') +router.register(r'incidents', IncidentViewSet, basename='incident') router.register(r'actions', ActionViewSet) router.register(r'ExternalUser', ExternalUserViewSet) From 4fd1d790cd80df7cf9906abe82b57c4afa57019d Mon Sep 17 00:00:00 2001 From: Miles Bryant Date: Tue, 13 Aug 2019 13:20:35 +0100 Subject: [PATCH 8/8] Add docs for incidents endpoint --- response/core/views.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/response/core/views.py b/response/core/views.py index 343e69a3..ef07007a 100644 --- a/response/core/views.py +++ b/response/core/views.py @@ -22,7 +22,12 @@ class ActionViewSet(viewsets.ModelViewSet): class IncidentViewSet(viewsets.ModelViewSet): - # ViewSets define the view behavior. + """ + Allows getting a list of Incidents (sorted by report time from newest to + oldest), and updating existing ones. + + Note that Incidents can only be created via the Slack workflow. + """ queryset = Incident.objects.order_by("-report_time")