From 4716c3b923f987747e7b6e1863091335226e1cd6 Mon Sep 17 00:00:00 2001 From: Tuomas Suutari Date: Tue, 12 Dec 2017 14:53:32 +0200 Subject: [PATCH] api/enforcement: Add endpoint for operators Add API endpoint for listing the operators. Also add operator identifier to the valid_parking endpoint so that it is possible to link them properly (not just by name). --- parkings/api/enforcement/operator.py | 22 ++++++ parkings/api/enforcement/urls.py | 2 + parkings/api/enforcement/valid_parking.py | 1 + .../tests/api/enforcement/test_operator.py | 71 +++++++++++++++++++ .../api/enforcement/test_valid_parking.py | 3 +- 5 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 parkings/api/enforcement/operator.py create mode 100644 parkings/tests/api/enforcement/test_operator.py diff --git a/parkings/api/enforcement/operator.py b/parkings/api/enforcement/operator.py new file mode 100644 index 00000000..b495e37c --- /dev/null +++ b/parkings/api/enforcement/operator.py @@ -0,0 +1,22 @@ +from rest_framework import permissions, serializers, viewsets + +from ...authentication import ApiKeyAuthentication +from ...models import Operator + + +class OperatorSerializer(serializers.ModelSerializer): + class Meta: + model = Operator + fields = [ + 'id', + 'created_at', + 'modified_at', + 'name', + ] + + +class OperatorViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Operator.objects.order_by('name') + serializer_class = OperatorSerializer + authentication_classes = [ApiKeyAuthentication] + permission_classes = [permissions.IsAdminUser] diff --git a/parkings/api/enforcement/urls.py b/parkings/api/enforcement/urls.py index 2833e2e0..4535824f 100644 --- a/parkings/api/enforcement/urls.py +++ b/parkings/api/enforcement/urls.py @@ -1,9 +1,11 @@ from django.conf.urls import include, url from rest_framework.routers import DefaultRouter +from .operator import OperatorViewSet from .valid_parking import ValidParkingViewSet router = DefaultRouter() +router.register('operator', OperatorViewSet, base_name='operator') router.register('valid_parking', ValidParkingViewSet, base_name='valid_parking') diff --git a/parkings/api/enforcement/valid_parking.py b/parkings/api/enforcement/valid_parking.py index 2d33e64f..b2179360 100644 --- a/parkings/api/enforcement/valid_parking.py +++ b/parkings/api/enforcement/valid_parking.py @@ -23,6 +23,7 @@ class Meta: 'time_start', 'time_end', 'zone', + 'operator', 'operator_name', ] diff --git a/parkings/tests/api/enforcement/test_operator.py b/parkings/tests/api/enforcement/test_operator.py new file mode 100644 index 00000000..033accf3 --- /dev/null +++ b/parkings/tests/api/enforcement/test_operator.py @@ -0,0 +1,71 @@ +import pytest +from django.core.urlresolvers import reverse +from rest_framework.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN + +from parkings.models import Operator + +from ..utils import ALL_METHODS, check_list_endpoint_base_fields, check_method_status_codes, get + +list_url = reverse('enforcement:v1:operator-list') + + +def get_url(kind, operator): + if kind == 'list': + return list_url + elif kind == 'detail': + return reverse('enforcement:v1:operator-detail', + kwargs={'pk': operator.pk}) + + +ALL_URL_KINDS = ['list', 'detail'] + + +@pytest.mark.parametrize('url_kind', ALL_URL_KINDS) +def test_permission_checks(api_client, operator_api_client, operator, url_kind): + url = get_url(url_kind, operator) + check_method_status_codes( + api_client, [url], ALL_METHODS, HTTP_401_UNAUTHORIZED) + check_method_status_codes( + operator_api_client, [url], ALL_METHODS, HTTP_403_FORBIDDEN, + error_code='permission_denied') + + +@pytest.mark.parametrize('url_kind', ALL_URL_KINDS) +def test_disallowed_methods(staff_api_client, operator, url_kind): + url = get_url(url_kind, operator) + disallowed_methods = ('post', 'put', 'patch', 'delete') + check_method_status_codes( + staff_api_client, [url], disallowed_methods, 405) + + +def test_list_endpoint_base_fields(staff_api_client): + operator_data = get(staff_api_client, list_url) + check_list_endpoint_base_fields(operator_data) + + +def test_list_endpoint_data(staff_api_client, operator): + assert Operator.objects.count() == 1 + data = get(staff_api_client, list_url) + assert len(data['results']) == 1 + operator_data = data['results'][0] + check_operator_data_keys(operator_data) + check_operator_data_matches_operator_object(data['results'][0], operator) + + +def check_operator_data_keys(operator_data): + assert set(operator_data.keys()) == { + 'id', 'created_at', 'modified_at', 'name'} + + +def check_operator_data_matches_operator_object(operator_data, operator_obj): + """ + Check that a operator data dict and an actual Operator object match. + """ + assert operator_data['id'] == str(operator_obj.id) # UUID -> str + assert operator_data['created_at'] == iso8601_us(operator_obj.created_at) + assert operator_data['modified_at'] == iso8601_us(operator_obj.modified_at) + assert operator_data['name'] == operator_obj.name + + +def iso8601_us(dt): + return dt.strftime('%Y-%m-%dT%H:%M:%S.%fZ') diff --git a/parkings/tests/api/enforcement/test_valid_parking.py b/parkings/tests/api/enforcement/test_valid_parking.py index b3d2ce55..5cde65a2 100644 --- a/parkings/tests/api/enforcement/test_valid_parking.py +++ b/parkings/tests/api/enforcement/test_valid_parking.py @@ -75,7 +75,7 @@ def check_parking_data_keys(parking_data): 'id', 'created_at', 'modified_at', 'registration_number', 'time_start', 'time_end', 'zone', - 'operator_name', + 'operator', 'operator_name', } @@ -89,6 +89,7 @@ def check_parking_data_matches_parking_object(parking_data, parking_obj): assert parking_data[field] == getattr(parking_obj, field) assert parking_data['id'] == str(parking_obj.id) # UUID -> str + assert parking_data['operator'] == str(parking_obj.operator.id) assert parking_data['created_at'] == iso8601_us(parking_obj.created_at) assert parking_data['modified_at'] == iso8601_us(parking_obj.modified_at) assert parking_data['time_start'] == iso8601(parking_obj.time_start)