Explorar o código

fix(api): improve RRset creation/modification error codes

Peter Thomassen %!s(int64=7) %!d(string=hai) anos
pai
achega
433491d905
Modificáronse 3 ficheiros con 46 adicións e 10 borrados
  1. 30 0
      api/desecapi/tests/testrrsets.py
  2. 4 1
      api/desecapi/views.py
  3. 12 9
      docs/rrsets.rst

+ 30 - 0
api/desecapi/tests/testrrsets.py

@@ -159,6 +159,36 @@ class AuthenticatedRRsetTests(APITestCase):
         response = self.client.post(url, json.dumps(data), content_type='application/json')
         response = self.client.post(url, json.dumps(data), content_type='application/json')
         self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
         self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
 
 
+    def testCantPostTwiceRRsets(self):
+        url = reverse('rrsets', args=(self.ownedDomains[1].name,))
+        data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
+        response = self.client.post(url, json.dumps(data), content_type='application/json')
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+        url = reverse('rrsets', args=(self.ownedDomains[1].name,))
+        data = {'records': ['3.2.2.1'], 'ttl': 60, 'type': 'A'}
+        response = self.client.post(url, json.dumps(data), content_type='application/json')
+        self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
+
+    def testCantPostFaultyRRsets(self):
+        url = reverse('rrsets', args=(self.ownedDomains[1].name,))
+
+        # New record without a value is a syntactical error --> 400
+        data = {'records': [], 'ttl': 60, 'type': 'TXT'}
+        response = self.client.post(url, json.dumps(data), content_type='application/json')
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+        # Lower-case type is a syntactical error --> 400
+        data = {'records': ['123456'], 'ttl': 60, 'type': 'txt'}
+        response = self.client.post(url, json.dumps(data), content_type='application/json')
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+        # Unknown type is a semantical error --> 422
+        url = reverse('rrsets', args=(self.ownedDomains[1].name,))
+        data = {'records': ['123456'], 'ttl': 60, 'type': 'AA'}
+        response = self.client.post(url, json.dumps(data), content_type='application/json')
+        self.assertEqual(response.status_code, status.HTTP_422_UNPROCESSABLE_ENTITY)
+
     def testCanGetOwnRRset(self):
     def testCanGetOwnRRset(self):
         url = reverse('rrsets', args=(self.ownedDomains[1].name,))
         url = reverse('rrsets', args=(self.ownedDomains[1].name,))
         data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
         data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}

+ 4 - 1
api/desecapi/views.py

@@ -197,7 +197,10 @@ class RRsetList(generics.ListCreateAPIView):
             raise Http404
             raise Http404
         except django.core.exceptions.ValidationError as e:
         except django.core.exceptions.ValidationError as e:
             ex = ValidationError(detail=e.message_dict)
             ex = ValidationError(detail=e.message_dict)
-            ex.status_code = status.HTTP_422_UNPROCESSABLE_ENTITY
+            all = e.message_dict.get('__all__')
+            if all is not None \
+                    and any(msg.endswith(' already exists.') for msg in all):
+                ex.status_code = status.HTTP_409_CONFLICT
             raise ex
             raise ex
 
 
     def perform_create(self, serializer):
     def perform_create(self, serializer):

+ 12 - 9
docs/rrsets.rst

@@ -120,12 +120,13 @@ To create a new RRset, simply issue a ``POST`` request to the
 field is optional.
 field is optional.
 
 
 Upon success, the response status code will be ``201 Created``, with the RRset
 Upon success, the response status code will be ``201 Created``, with the RRset
-contained in the response body.  If the ``records`` value was semantically
-invalid or an invalid ``type`` was provided, ``422 Unprocessable Entity`` is
-returned.  If the RRset could not be created for another reason (for example
-because another RRset with the same name and type exists already, or because
-not all required fields were provided), the API responds with ``400 Bad
-Request``.
+contained in the response body.  If another RRset with the same name and type
+exists already, the API responds with ``409 Conflict``.  If there is a
+syntactical error (e.g. not all required fields were provided or the type was
+not specified in uppercase), the API responds with ``400 Bad Request``.  If
+field values were semantically invalid (e.g. when you provide an unknown record
+type, or an `A` value that is not an IPv4 address), ``422 Unprocessable
+Entity`` is returned.
 
 
 Note that the values of ``type`` and ``subname`` as well as the ``records``
 Note that the values of ``type`` and ``subname`` as well as the ``records``
 items are strings, and as such the JSON specification requires them to be
 items are strings, and as such the JSON specification requires them to be
@@ -246,9 +247,11 @@ fields.  Examples::
         Authorization:"Token {token}" ttl:=86400
         Authorization:"Token {token}" ttl:=86400
 
 
 If the RRset was updated successfully, the API returns ``200 OK`` with the
 If the RRset was updated successfully, the API returns ``200 OK`` with the
-updated RRset in the reponse body.  If not all required fields were provided,
-the API responds with ``400 Bad Request``.  If the ``records`` value was
-semantically invalid, ``422 Unprocessable Entity`` is returned.  If the RRset
+updated RRset in the reponse body.  If there is a syntactical error (e.g. not
+all required fields were provided or the type was not specified in uppercase),
+the API responds with ``400 Bad Request``.  If field values were semantically
+invalid (e.g. when you provide an unknown record type, or an `A` value that is
+not an IPv4 address), ``422 Unprocessable Entity`` is returned.  If the RRset
 does not exist, ``404 Not Found`` is returned.
 does not exist, ``404 Not Found`` is returned.