class TestBulkAPIView(TestCase): def setUp(self): super(TestBulkAPIView, self).setUp() self.view = SimpleBulkAPIView.as_view() self.request = RequestFactory() def test_get(self): """ Test that GET request is successful on bulk view. """ response = self.view(self.request.get('')) self.assertEqual(response.status_code, status.HTTP_200_OK) def test_post_single(self): """ Test that POST request with single resource only creates a single resource. """ response = self.view( self.request.post( '', json.dumps({ 'contents': 'hello world', 'number': 1 }), content_type='application/json', )) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(SimpleModel.objects.count(), 1) self.assertEqual(SimpleModel.objects.get().contents, 'hello world') def test_post_bulk(self): """ Test that POST request with multiple resources creates all posted resources. """ response = self.view( self.request.post( '', json.dumps([ { 'contents': 'hello world', 'number': 1 }, { 'contents': 'hello mars', 'number': 2 }, ]), content_type='application/json', )) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(SimpleModel.objects.count(), 2) self.assertEqual( list(SimpleModel.objects.all().values_list('contents', flat=True)), [ 'hello world', 'hello mars', ]) def test_put(self): """ Test that PUT request updates all submitted resources. """ obj1 = SimpleModel.objects.create(contents='hello world', number=1) obj2 = SimpleModel.objects.create(contents='hello mars', number=2) response = self.view( self.request.put( '', json.dumps([ { 'contents': 'foo', 'number': 3, 'id': obj1.pk }, { 'contents': 'bar', 'number': 4, 'id': obj2.pk }, ]), content_type='application/json', )) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(SimpleModel.objects.count(), 2) self.assertEqual( list(SimpleModel.objects.all().values_list('id', 'contents', 'number')), [ (obj1.pk, 'foo', 3), (obj2.pk, 'bar', 4), ]) def test_put_without_update_key(self): """ Test that PUT request updates all submitted resources. """ response = self.view( self.request.put( '', json.dumps([ { 'contents': 'foo', 'number': 3 }, { 'contents': 'rainbows', 'number': 4 }, # multiple objects without id { 'contents': 'bar', 'number': 4, 'id': 555 }, # non-existing id ]), content_type='application/json', )) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_patch(self): """ Test that PATCH request partially updates all submitted resources. """ obj1 = SimpleModel.objects.create(contents='hello world', number=1) obj2 = SimpleModel.objects.create(contents='hello mars', number=2) response = self.view( self.request.patch( '', json.dumps([ { 'contents': 'foo', 'id': obj1.pk }, { 'contents': 'bar', 'id': obj2.pk }, ]), content_type='application/json', )) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(SimpleModel.objects.count(), 2) self.assertEqual( list(SimpleModel.objects.all().values_list('id', 'contents', 'number')), [ (obj1.pk, 'foo', 1), (obj2.pk, 'bar', 2), ]) def test_delete_not_filtered(self): """ Test that DELETE is not allowed when results are not filtered. """ SimpleModel.objects.create(contents='hello world', number=1) SimpleModel.objects.create(contents='hello mars', number=10) response = self.view(self.request.delete('')) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_delete_filtered(self): """ Test that DELETE removes all filtered resources. """ SimpleModel.objects.create(contents='hello world', number=1) SimpleModel.objects.create(contents='hello mars', number=10) response = FilteredBulkAPIView.as_view()(self.request.delete('')) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(SimpleModel.objects.count(), 1) self.assertEqual(SimpleModel.objects.get().contents, 'hello world') def test_options(self): """ Test that OPTIONS request is successful on bulk view. """ response = self.view(self.request.options('')) self.assertEqual(response.status_code, status.HTTP_200_OK)
class TestPlaceTagListView (APITestMixin, TestCase): def setUp(self): cache_buffer.reset() django_cache.clear() self.owner = User.objects.create_user(username='******', password='******', email='*****@*****.**') self.submitter = User.objects.create_user(username='******', password='******', email='*****@*****.**') self.dataset = DataSet.objects.create(slug='ds', owner=self.owner) self.place = Place.objects.create( dataset=self.dataset, geometry='POINT(2 3)', submitter=self.submitter, data=json.dumps({ 'type': 'ATM', 'name': 'K-Mart', 'private-secrets': 42 }), ) self.tags = [ Tag.objects.create( name="status", dataset=self.dataset, ), ] self.tags.extend([ Tag.objects.create( name="approved", dataset=self.dataset, parent=self.tags[0] ), Tag.objects.create( name="rejected", dataset=self.dataset, parent=self.tags[0] ) ]) self.place_tags = [ PlaceTag.objects.create( place=self.place, submitter=self.submitter, tag=self.tags[1], note="I approve this place!" ), PlaceTag.objects.create( place=self.place, tag=self.tags[2], note="I reject this place!", ) ] self.origin = Origin.objects.create(pattern='def', dataset=self.dataset) Origin.objects.create(pattern='def2', dataset=self.dataset) self.authorized_user = User.objects.create_user( username='******', password='******' ) group = Group.objects.create( dataset=self.dataset, name='mygroup' ) group.submitters.add(self.authorized_user) GroupPermission.objects.create( group=group, # TODO: rename this to 'resource': submission_set='tags', can_create=True, ) self.unauthorized_user = User.objects.create_user( username='******', password='******' ) unauthorized_group = Group.objects.create( dataset=self.dataset, name='badgroup' ) unauthorized_group.submitters.add(self.unauthorized_user) unauthorized_group.submitters.add(self.authorized_user) GroupPermission.objects.create( group=unauthorized_group, # TODO: rename this to 'resource': submission_set='tags', ) self.other_dataset = DataSet.objects.create(slug='ds2', owner=self.owner) self.other_tag = Tag.objects.create( name="other status", dataset=self.other_dataset, ) other_group = Group.objects.create( dataset=self.other_dataset, name='othergroup' ) other_group.submitters.add(self.authorized_user) GroupPermission.objects.create( group=other_group, # TODO: rename this to 'resource': submission_set='tags', can_create=True, ) self.request_kwargs = { 'owner_username': self.owner.username, 'dataset_slug': self.dataset.slug, 'place_id': self.place.id, } self.factory = RequestFactory() self.path = reverse('place-tag-list', kwargs=self.request_kwargs) self.view = PlaceTagListView.as_view() def tearDown(self): User.objects.all().delete() DataSet.objects.all().delete() # this should delete all PlaceTags: Place.objects.all().delete() Tag.objects.all().delete() cache_buffer.reset() django_cache.clear() def test_OPTIONS_response(self): request = self.factory.options(self.path) response = self.view(request, **self.request_kwargs) # Check that the request was successful self.assertStatusCode(response, 200) def test_GET_response(self): request = self.factory.get(self.path) response = self.view(request, **self.request_kwargs) data = json.loads(response.rendered_content) # Check that the request was successful self.assertStatusCode(response, 200) # Check that it's a results collection self.assertIn('results', data) self.assertIn('metadata', data) # Check that the metadata looks right self.assertIn('length', data['metadata']) self.assertIn('next', data['metadata']) self.assertIn('previous', data['metadata']) self.assertIn('page', data['metadata']) # Check that we have the right number of results self.assertEqual(len(data['results']), 2) place_tag = None for result in data['results']: if result['id'] == self.place_tags[-1].id: place_tag = result self.assertEqual(place_tag['url'], 'http://testserver' + reverse('place-tag-detail', args=[ self.owner.username, self.dataset.slug, self.place.id, self.place_tags[-1].id, ])) def test_GET_invalid_url(self): # Make sure that we respond with 404 if a slug is supplied, but for # the wrong dataset or owner. request_kwargs = { 'owner_username': '******', 'dataset_slug': self.dataset.slug, 'place_id': self.place.id, } path = reverse('place-tag-list', kwargs=request_kwargs) request = self.factory.get(path) response = self.view(request, **request_kwargs) self.assertStatusCode(response, 404) def test_POST_response(self): tag_url = 'http://testserver' + reverse('tag-detail', args=[ self.owner.username, self.dataset.slug, self.tags[1].id, ]) place_url = 'http://testserver' + reverse('place-detail', args=[ self.owner.username, self.dataset.slug, self.place.id, ]) data = json.dumps({ 'note': "this is a comment", 'tag': tag_url, 'place': place_url, }) start_num_submissions = PlaceTag.objects.all().count() # # View should 401 when trying to create when not authenticated # request = self.factory.post(self.path, data=data, content_type='application/json') response = self.view(request, **self.request_kwargs) self.assertStatusCode(response, 401) # # View should 403 when trying to create when not authorized # request = self.factory.post(self.path, data=data, content_type='application/json') request.user = self.unauthorized_user response = self.view(request, **self.request_kwargs) self.assertStatusCode(response, 403) # # View should create the submission and set when owner is authenticated # request = self.factory.post(self.path, data=data, content_type='application/json') request.user = self.authorized_user response = self.view(request, **self.request_kwargs) data = json.loads(response.rendered_content) # Check that the request was successful self.assertStatusCode(response, 201) # # Check that the data attributes have been incorporated into the # # properties self.assertEqual(data.get('note'), 'this is a comment') self.assertEqual(data.get('submitter')['username'], 'temp_user2') # # visible should be true by default # self.assert_(data.get('visible')) # Check that we actually created a submission and set final_num_submissions = PlaceTag.objects.all().count() self.assertEqual(final_num_submissions, start_num_submissions + 1) def test_POST_response_with_invalid_tag(self): tag_url = 'http://testserver' + reverse('tag-detail', args=[ self.owner.username, self.other_dataset.slug, self.other_tag.id, ]) place_url = 'http://testserver' + reverse('place-detail', args=[ self.owner.username, self.dataset.slug, self.place.id, ]) data = json.dumps({ 'note': "this is a comment", 'tag': tag_url, 'place': place_url, }) # # Trying to add a Tag to a Place that comes from a different # dataset should give us a ValidationError: # request = self.factory.post(self.path, data=data, content_type='application/json') request.user = self.authorized_user with self.assertRaises(ValidationError): self.view(request, **self.request_kwargs)
class ResourceTestCase(TestCase): def setUp(self): self.factory = RequestFactory() def test_default(self): "Tests for the default Resource which is very limited." # Default resource resource = Resource() # Populated implicitly via the metaclass.. self.assertEqual(resource.allowed_methods, ("OPTIONS",)) # OPTIONS is successful, default response with no content is a 204 request = self.factory.options("/") response = resource(request) self.assertEqual(response.status_code, codes.no_content) # Try another non-default method request = self.factory.get("/") response = resource(request) self.assertEqual(response.status_code, codes.method_not_allowed) self.assertEqual(response["Allow"], "OPTIONS") def test_default_head(self): class GetResource(Resource): def get(self, request): return {} resource = GetResource() request = self.factory.head("/") response = resource(request) self.assertEqual(response.status_code, codes.ok) self.assertEqual(response["Content-Type"], "application/json") self.assertEqual(response.content, b"") def test_default_patch(self): # Resources supporting PATCH requests should have an additional # header in the response from an OPTIONS request class PatchResource(Resource): def patch(self, request): pass resource = PatchResource() request = self.factory.options("/") response = resource(request) self.assertEqual(response.status_code, codes.no_content) self.assertEqual(response["Accept-Patch"], "application/json") self.assertEqual(response["Content-Type"], "application/json") def test_service_unavailable(self): "Test service unavailability." class IndefiniteUnavailableResource(Resource): unavailable = True resource = IndefiniteUnavailableResource() # Simply setting `unavailable` to True will provide a 'Retry-After' # header request = self.factory.request() response = resource(request) self.assertEqual(response.status_code, codes.service_unavailable) self.assertTrue("Retry-After" not in response) self.assertEqual(response["Content-Type"], "application/json") def test_service_unavailable_retry_seconds(self): "Test service unavailability with seconds." class DeltaUnavailableResource(Resource): unavailable = 20 resource = DeltaUnavailableResource() # Set unavailable, but with a specific number of seconds to retry # after request = self.factory.request() response = resource(request) self.assertEqual(response.status_code, codes.service_unavailable) self.assertEqual(response["Retry-After"], "20") self.assertEqual(response["Content-Type"], "application/json") def test_service_unavailable_retry_date(self): "Test service unavailability with date." from datetime import datetime, timedelta from django.utils.http import http_date future = datetime.now() + timedelta(seconds=20) class DatetimeUnavailableResource(Resource): unavailable = future resource = DatetimeUnavailableResource() request = self.factory.request() response = resource(request) self.assertEqual(response.status_code, codes.service_unavailable) self.assertEqual(response["Retry-After"], http_date(timegm(future.utctimetuple()))) self.assertEqual(response["Content-Type"], "application/json") def test_unsupported_media_type(self): "Test various Content-* combinations." class NoOpResource(Resource): def post(self, request, *args, **kwargs): pass resource = NoOpResource() # Works.. default accept-type is application/json request = self.factory.post("/", data=b'{"message": "hello w\xc3\xb6"}', content_type="application/json") response = resource(request) self.assertEqual(response.status_code, codes.no_content) self.assertEqual(response["Content-Type"], "application/json") # Does not work.. XML not accepted by default request = self.factory.post("/", data="<message>hello world</message>", content_type="application/xml") response = resource(request) self.assertEqual(response.status_code, codes.unsupported_media_type) self.assertEqual(response["Content-Type"], "application/json") def test_not_acceptable(self): "Test Accept header." class ReadOnlyResource(Resource): def get(self, request, *args, **kwargs): return {} resource = ReadOnlyResource() # No accept-type is specified, defaults to highest priority one # for resource request = self.factory.request() response = resource(request) self.assertEqual(response.status_code, codes.ok) self.assertEqual(response["Content-Type"], "application/json") # Explicit accept header, application/json wins since it's equal # priority and supported request = self.factory.request(HTTP_ACCEPT="application/json,application/xml;q=0.9,*/*;q=0.8") response = resource(request) self.assertEqual(response.status_code, codes.ok) self.assertEqual(response["Content-Type"], "application/json") # No acceptable type list, */* has an explicit quality of 0 which # does not allow the server to use an alternate content-type request = self.factory.request(HTTP_ACCEPT="text/html,application/xhtml+xml," "application/xml;q=0.9,*/*;q=0") response = resource(request) self.assertEqual(response.status_code, codes.not_acceptable) self.assertEqual(response["Content-Type"], "application/json") # Like the first one, but an explicit "anything goes" request = self.factory.request(HTTP_ACCEPT="*/*") response = resource(request) self.assertEqual(response.status_code, codes.ok) self.assertEqual(response["Content-Type"], "application/json") def test_request_entity_too_large(self): "Test request entity too large." class TinyResource(Resource): max_request_entity_length = 20 def post(self, request, *args, **kwargs): pass resource = TinyResource() # No problem.. request = self.factory.post("/", data='{"message": "hello"}', content_type="application/json") response = resource(request) self.assertEqual(response.status_code, codes.no_content) self.assertEqual(response["Content-Type"], "application/json") # Too large request = self.factory.post("/", data='{"message": "hello world"}', content_type="application/json") response = resource(request) self.assertEqual(response.status_code, codes.request_entity_too_large) self.assertEqual(response["Content-Type"], "application/json") def test_too_many_requests(self): """Test a global rate limiting implementation. This test will take 3 seconds to run to mimic request handling over time. """ import time from datetime import datetime class RateLimitResource(Resource): # Maximum of 10 requests within a 2 second window rate_limit_count = 10 rate_limit_seconds = 2 # Keep track of requests globally for the resource.. only for test # purposes, not thread-safe request_frame_start = datetime.now() request_count = 0 # Implement rate-limiting logic def is_too_many_requests(self, request, *args, **kwargs): # Since the start of the frame, calculate the amount of time # that has passed interval = (datetime.now() - self.request_frame_start).seconds # Increment the request count self.request_count += 1 # Reset frame if the interval is greater than the rate limit # seconds, i.e on the 3rd second in this test if interval > self.rate_limit_seconds: self.request_frame_start = datetime.now() self.request_count = 1 # ..otherwise throttle if the count is greater than the limit elif self.request_count > self.rate_limit_count: return True return False resource = RateLimitResource() request = self.factory.request(REQUEST_METHOD="OPTIONS") # First ten requests are ok for _ in range(0, 10): response = resource(request) self.assertEqual(response.status_code, codes.no_content) self.assertEqual(response["Content-Type"], "application/json") # Mimic a slight delay time.sleep(1) # Another 10 all get throttled.. for _ in range(0, 10): response = resource(request) self.assertEqual(response.status_code, codes.too_many_requests) self.assertEqual(response["Content-Type"], "application/json") # Another two seconds exceeds the frame, should be good to go time.sleep(2) for _ in range(0, 10): response = resource(request) self.assertEqual(response.status_code, codes.no_content) self.assertEqual(response["Content-Type"], "application/json") def test_precondition_required(self): """"Reject non-idempotent requests without the use of a conditional header.""" class PreconditionResource(Resource): # Either etags or last-modified must be used otherwise it # is not enforced use_etags = True require_conditional_request = True def patch(self, request): pass def put(self, request): pass def delete(self, request): pass def get_etag(self, request, *args, **kwargs): return "abc123" resource = PreconditionResource() # Non-idempotent requests fail without a conditional header, these # responses should not be cached request = self.factory.put("/", data='{"message": "hello world"}', content_type="application/json") response = resource(request) self.assertEqual(response.status_code, codes.precondition_required) self.assertEqual(response["Content-Type"], "application/json") self.assertTrue("no-cache" in response["Cache-Control"]) self.assertTrue("must-revalidate" in response["Cache-Control"]) self.assertTrue("max-age=0" in response["Cache-Control"]) # Add the correct header for testing the Etag request = self.factory.put( "/", data='{"message": "hello world"}', content_type="application/json", HTTP_IF_MATCH="abc123" ) response = resource(request) self.assertEqual(response.status_code, codes.no_content) self.assertEqual(response["Content-Type"], "application/json") # Idempotent requests, such as DELETE, succeed.. request = self.factory.delete("/") response = resource(request) self.assertEqual(response.status_code, codes.no_content) self.assertEqual(response["Content-Type"], "application/json") def test_precondition_failed_etag(self): "Test precondition using etags." class PreconditionResource(Resource): use_etags = True def put(self, request): pass def get(self, request): return {} def get_etag(self, request, *args, **kwargs): return "abc123" resource = PreconditionResource() # Send a non-safe request with an incorrect Etag.. fail request = self.factory.put( "/", data='{"message": "hello world"}', content_type="application/json", HTTP_IF_MATCH='"def456"' ) response = resource(request) self.assertEqual(response.status_code, codes.precondition_failed) self.assertEqual(response["Content-Type"], "application/json") self.assertTrue("no-cache" in response["Cache-Control"]) self.assertTrue("must-revalidate" in response["Cache-Control"]) self.assertTrue("max-age=0" in response["Cache-Control"]) # Incorrect Etag match on GET, updated content is returned request = self.factory.get("/", HTTP_IF_NONE_MATCH='"def456"') response = resource(request) self.assertEqual(response.status_code, codes.ok) self.assertEqual(response["Content-Type"], "application/json") # Successful Etag match on GET, resource not modified request = self.factory.get("/", HTTP_IF_NONE_MATCH='"abc123"') response = resource(request) self.assertEqual(response.status_code, codes.not_modified) self.assertEqual(response["Content-Type"], "application/json") def test_precondition_failed_last_modified(self): "Test precondition using last-modified dates." from datetime import datetime, timedelta from django.utils.http import http_date last_modified_date = datetime.now() class PreconditionResource(Resource): use_etags = False use_last_modified = True def put(self, request): pass def get(self, request): return {} def get_last_modified(self, request, *args, **kwargs): return last_modified_date resource = PreconditionResource() # Send non-safe request with a old last-modified date.. fail if_modified_since = http_date(timegm((last_modified_date - timedelta(seconds=10)).utctimetuple())) request = self.factory.put( "/", data='{"message": "hello world"}', content_type="application/json", HTTP_IF_UNMODIFIED_SINCE=if_modified_since, ) response = resource(request) self.assertEqual(response.status_code, codes.precondition_failed) self.assertEqual(response["Content-Type"], "application/json") self.assertTrue("no-cache" in response["Cache-Control"]) self.assertTrue("must-revalidate" in response["Cache-Control"]) self.assertTrue("max-age=0" in response["Cache-Control"]) # Old last-modified on GET, updated content is returned if_modified_since = http_date(timegm((last_modified_date - timedelta(seconds=10)).utctimetuple())) request = self.factory.get("/", HTTP_IF_MODIFIED_SINCE=if_modified_since) response = resource(request) self.assertEqual(response.status_code, codes.ok) self.assertEqual(response["Content-Type"], "application/json") # Mimic future request on GET, resource not modified if_modified_since = http_date(timegm((last_modified_date + timedelta(seconds=20)).utctimetuple())) request = self.factory.get("/", HTTP_IF_MODIFIED_SINCE=if_modified_since) response = resource(request) self.assertEqual(response.status_code, codes.not_modified) self.assertEqual(response["Content-Type"], "application/json") def test_cache_control_default(self): class CacheableResource(Resource): def get(self, request): return {} resource = CacheableResource() request = self.factory.get("/") response = resource(request) self.assertFalse("Cache-Control" in response) self.assertEqual(response["Content-Type"], "application/json") def test_cache_control_seconds(self): class CacheableResource(Resource): cache_max_age = 60 * 60 # 1 hour def get(self, request): return {} resource = CacheableResource() request = self.factory.get("/") response = resource(request) self.assertEqual(response["Cache-Control"], "max-age=3600") self.assertEqual(response["Content-Type"], "application/json") def test_cache_control_date(self): from datetime import datetime, timedelta from django.utils.http import http_date class CacheableResource(Resource): cache_type = "private" cache_max_age = timedelta(seconds=60 * 60) # 1 hour def get(self, request): return {} resource = CacheableResource() request = self.factory.get("/") response = resource(request) self.assertEqual(response["Cache-Control"], "private") self.assertEqual(response["Expires"], http_date(timegm((datetime.now() + timedelta(hours=1)).utctimetuple()))) self.assertEqual(response["Content-Type"], "application/json")
class TestBulkAPIView(TestCase): def setUp(self): super(TestBulkAPIView, self).setUp() self.view = SimpleBulkAPIView.as_view() self.request = RequestFactory() def test_get(self): """ Test that GET request is successful on bulk view. """ response = self.view(self.request.get('')) self.assertEqual(response.status_code, status.HTTP_200_OK) def test_post_single(self): """ Test that POST request with single resource only creates a single resource. """ response = self.view(self.request.post( '', json.dumps({'contents': 'hello world', 'number': 1}), content_type='application/json', )) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(SimpleModel.objects.count(), 1) self.assertEqual(SimpleModel.objects.get().contents, 'hello world') def test_post_bulk(self): """ Test that POST request with multiple resources creates all posted resources. """ response = self.view(self.request.post( '', json.dumps([ {'contents': 'hello world', 'number': 1}, {'contents': 'hello mars', 'number': 2}, ]), content_type='application/json', )) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(SimpleModel.objects.count(), 2) self.assertEqual(list(SimpleModel.objects.all().values_list('contents', flat=True)), [ 'hello world', 'hello mars', ]) def test_put(self): """ Test that PUT request updates all submitted resources. """ obj1 = SimpleModel.objects.create(contents='hello world', number=1) obj2 = SimpleModel.objects.create(contents='hello mars', number=2) response = self.view(self.request.put( '', json.dumps([ {'contents': 'foo', 'number': 3, 'id': obj1.pk}, {'contents': 'bar', 'number': 4, 'id': obj2.pk}, ]), content_type='application/json', )) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(SimpleModel.objects.count(), 2) self.assertEqual( list(SimpleModel.objects.all().values_list('id', 'contents', 'number')), [ (obj1.pk, 'foo', 3), (obj2.pk, 'bar', 4), ] ) def test_put_without_update_key(self): """ Test that PUT request updates all submitted resources. """ response = self.view(self.request.put( '', json.dumps([ {'contents': 'foo', 'number': 3}, {'contents': 'rainbows', 'number': 4}, # multiple objects without id {'contents': 'bar', 'number': 4, 'id': 555}, # non-existing id ]), content_type='application/json', )) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_patch(self): """ Test that PATCH request partially updates all submitted resources. """ obj1 = SimpleModel.objects.create(contents='hello world', number=1) obj2 = SimpleModel.objects.create(contents='hello mars', number=2) response = self.view(self.request.patch( '', json.dumps([ {'contents': 'foo', 'id': obj1.pk}, {'contents': 'bar', 'id': obj2.pk}, ]), content_type='application/json', )) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(SimpleModel.objects.count(), 2) self.assertEqual( list(SimpleModel.objects.all().values_list('id', 'contents', 'number')), [ (obj1.pk, 'foo', 1), (obj2.pk, 'bar', 2), ] ) def test_delete_not_filtered(self): """ Test that DELETE is not allowed when results are not filtered. """ SimpleModel.objects.create(contents='hello world', number=1) SimpleModel.objects.create(contents='hello mars', number=10) response = self.view(self.request.delete('')) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_delete_filtered(self): """ Test that DELETE removes all filtered resources. """ SimpleModel.objects.create(contents='hello world', number=1) SimpleModel.objects.create(contents='hello mars', number=10) response = FilteredBulkAPIView.as_view()(self.request.delete('')) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(SimpleModel.objects.count(), 1) self.assertEqual(SimpleModel.objects.get().contents, 'hello world') def test_options(self): """ Test that OPTIONS request is successful on bulk view. """ response = self.view(self.request.options('')) self.assertEqual(response.status_code, status.HTTP_200_OK)