Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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)