def test_not_supported(self): """Test sending an unsupported request method.""" #request = mock.Mock(method='FOO') factory_request = RequestFactory() request = factory_request.head('/mobiles') mobiles_view = MobileView.as_view() response = mobiles_view(request) self.assertEqual(response.status_code, 405, 'Should return a 405 NOT ALLOWED.') self.assertIn('GET', response['Allow'], 'Should allow GET.') self.assertIn('POST', response['Allow'], 'Should allow POST.') self.assertIn('PUT', response['Allow'], 'Should allow PUT.') self.assertIn('DELETE', response['Allow'], 'Should allow DELETE.')
class ViewFunctionTests(TestCase): """Tests for the inbound view function receive_inbound_email. The view function is responsible for loading the correct backend, and firing the signal once the email is parsed. This test suite contains no parsing tests - these are covered in the relevant backend tests - just tests for the signals. """ def setUp(self): # Every test needs access to the request factory. self.factory = RequestFactory() self.url = reverse('receive_inbound_email') self.test_upload_txt = path.join(path.dirname(__file__), 'test_files/test_upload_file.txt') def _get_payloads_and_parsers(self, with_attachments=False): mpwa = mandrill_payload_with_attachments mp = mandrill_payload return [ (MANDRILL_REQUEST_PARSER, mpwa if with_attachments else mp), (SENDGRID_REQUEST_PARSER, sendgrid_payload), (MAILGUN_REQUEST_PARSER, mailgun_payload), ] def test_log_inbound_requests(self): """Test the internal log function.""" # just to exercise the function - it doesn't 'do' anything other than # log to the console, but is good to know that it doesn't break. for klass, payload in self._get_payloads_and_parsers(): settings.INBOUND_EMAIL_PARSER = klass request = self.factory.post(self.url, data=payload) _log_request(request) def test_inbound_request_HEAD_200(self): """Return 200 OK to a HEAD request.""" request = self.factory.head(self.url) response = receive_inbound_email(request) self.assertEqual(response.status_code, 200) def test_valid_request(self): """Test the RequestParseErrors are handled correctly, and return HTTP 200.""" for klass, payload in self._get_payloads_and_parsers(): settings.INBOUND_EMAIL_PARSER = klass request = self.factory.post(self.url, data=payload) response = receive_inbound_email(request) self.assertContains(response, u"Successfully parsed", status_code=200) def test_parse_error_response_200(self): """Test the RequestParseErrors are handled correctly, and return HTTP 200.""" settings.INBOUND_EMAIL_RESPONSE_200 = True for klass, payload in self._get_payloads_and_parsers(): settings.INBOUND_EMAIL_PARSER = klass request = self.factory.post(self.url, data={}) response = receive_inbound_email(request) self.assertContains(response, u"Unable to parse", status_code=200) def test_parse_error_response_400(self): """Test the RequestParseErrors are handled correctly, and return HTTP 400.""" settings.INBOUND_EMAIL_RESPONSE_200 = False request = self.factory.post(self.url, data={}) response = receive_inbound_email(request) self.assertContains(response, u"Unable to parse", status_code=400) def test_email_received_signal(self): """Test that a valid POST fires the email_received signal.""" # define handler for klass, payload in self._get_payloads_and_parsers(): def on_email_received(sender, **kwargs): self.on_email_received_fired = True self.assertEqual(sender.__name__, klass.split('.')[-1]) request = kwargs.pop('request', None) email = kwargs.pop('email', None) self.assertIsNotNone(email) self.assertIsInstance(email, EmailMultiAlternatives) self.assertIsNotNone(request) email_received.connect(on_email_received) settings.INBOUND_EMAIL_PARSER = klass request = self.factory.post(self.url, data=payload) # connect handler self.on_email_received_fired = False # fire a request in to force the signal to fire receive_inbound_email(request) self.assertTrue(self.on_email_received_fired) email_received.disconnect(on_email_received) def test_email_received_unacceptable_signal_fired_for_too_large_attachment(self): # set a zero allowed max attachment size settings.INBOUND_EMAIL_ATTACHMENT_SIZE_MAX = 0 for klass, payload in self._get_payloads_and_parsers(with_attachments=True): settings.INBOUND_EMAIL_PARSER = klass _payload = payload.copy() if klass == SENDGRID_REQUEST_PARSER: _payload['attachment'] = open(self.test_upload_txt, 'r') if klass == MAILGUN_REQUEST_PARSER: _payload['attachment-1'] = open(self.test_upload_txt, 'r') # define handler def on_email_received(sender, **kwargs): self.on_email_received_fired = True request = kwargs.pop('request', None) email = kwargs.pop('email', None) exception = kwargs.pop('exception', None) self.assertEqual(sender.__name__, klass.split('.')[-1]) self.assertIsNotNone(request) self.assertIsInstance(email, EmailMultiAlternatives) self.assertIsInstance(exception, AttachmentTooLargeError) email_received_unacceptable.connect(on_email_received) self.on_email_received_fired = False request = self.factory.post(self.url, data=_payload) receive_inbound_email(request) self.assertTrue(self.on_email_received_fired, klass) email_received_unacceptable.disconnect(on_email_received)
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 ViewFunctionTests(TestCase): """Tests for the inbound view function receive_inbound_email. The view function is responsible for loading the correct backend, and firing the signal once the email is parsed. This test suite contains no parsing tests - these are covered in the relevant backend tests - just tests for the signals. """ def setUp(self): # Every test needs access to the request factory. self.factory = RequestFactory() self.url = reverse('inbound:receive_inbound_email') self.test_upload_txt = path.join(path.dirname(__file__), 'test_files/test_upload_file.txt') def _get_payloads_and_parsers(self, with_attachments=False): mpwa = mandrill_payload_with_attachments mp = mandrill_payload return [ (MANDRILL_REQUEST_PARSER, mpwa if with_attachments else mp), (SENDGRID_REQUEST_PARSER, sendgrid_payload), (MAILGUN_REQUEST_PARSER, mailgun_payload), ] def test_log_inbound_requests(self): """Test the internal log function.""" # just to exercise the function - it doesn't 'do' anything other than # log to the console, but is good to know that it doesn't break. for klass, payload in self._get_payloads_and_parsers(): settings.INBOUND_EMAIL_PARSER = klass request = self.factory.post(self.url, data=payload) _log_request(request) def test_inbound_request_HEAD_200(self): """Return 200 OK to a HEAD request.""" request = self.factory.head(self.url) response = receive_inbound_email(request) self.assertEqual(response.status_code, 200) def test_valid_request(self): """Test the RequestParseErrors are handled correctly, and return HTTP 200.""" for klass, payload in self._get_payloads_and_parsers(): settings.INBOUND_EMAIL_PARSER = klass request = self.factory.post(self.url, data=payload) response = receive_inbound_email(request) self.assertContains(response, u"Successfully parsed", status_code=200) def test_parse_error_response_200(self): """Test the RequestParseErrors are handled correctly, and return HTTP 200.""" settings.INBOUND_EMAIL_RESPONSE_200 = True for klass, payload in self._get_payloads_and_parsers(): settings.INBOUND_EMAIL_PARSER = klass request = self.factory.post(self.url, data={}) response = receive_inbound_email(request) self.assertContains(response, u"Unable to parse", status_code=200) def test_parse_error_response_400(self): """Test the RequestParseErrors are handled correctly, and return HTTP 400.""" settings.INBOUND_EMAIL_RESPONSE_200 = False request = self.factory.post(self.url, data={}) response = receive_inbound_email(request) self.assertContains(response, u"Unable to parse", status_code=400) def test_email_received_signal(self): """Test that a valid POST fires the email_received signal.""" # define handler for klass, payload in self._get_payloads_and_parsers(): def on_email_received(sender, **kwargs): self.on_email_received_fired = True self.assertEqual(sender.__name__, klass.split('.')[-1]) request = kwargs.pop('request', None) email = kwargs.pop('email', None) self.assertIsNotNone(email) self.assertIsInstance(email, EmailMultiAlternatives) self.assertIsNotNone(request) email_received.connect(on_email_received) settings.INBOUND_EMAIL_PARSER = klass request = self.factory.post(self.url, data=payload) # connect handler self.on_email_received_fired = False # fire a request in to force the signal to fire receive_inbound_email(request) self.assertTrue(self.on_email_received_fired) email_received.disconnect(on_email_received) def test_email_received_unacceptable_signal_fired_for_too_large_attachment( self): # set a zero allowed max attachment size settings.INBOUND_EMAIL_ATTACHMENT_SIZE_MAX = 0 for klass, payload in self._get_payloads_and_parsers( with_attachments=True): settings.INBOUND_EMAIL_PARSER = klass _payload = payload.copy() if klass == SENDGRID_REQUEST_PARSER: _payload['attachment'] = open(self.test_upload_txt, 'r') if klass == MAILGUN_REQUEST_PARSER: _payload['attachment-1'] = open(self.test_upload_txt, 'r') # define handler def on_email_received(sender, **kwargs): self.on_email_received_fired = True request = kwargs.pop('request', None) email = kwargs.pop('email', None) exception = kwargs.pop('exception', None) self.assertEqual(sender.__name__, klass.split('.')[-1]) self.assertIsNotNone(request) self.assertIsInstance(email, EmailMultiAlternatives) self.assertIsInstance(exception, AttachmentTooLargeError) email_received_unacceptable.connect(on_email_received) self.on_email_received_fired = False request = self.factory.post(self.url, data=_payload) receive_inbound_email(request) self.assertTrue(self.on_email_received_fired, klass) email_received_unacceptable.disconnect(on_email_received)
class SimpleWebProxyViewTestCase(SimpleTestCase): def setUp(self): self.factory = RequestFactory() self.test_view = SimpleWebProxyView(service_url='http://www.fake.com/service') def test_successful_get(self): with mock.patch('common.views.requests.get') as mock_requests_get: resp = mock.Mock resp.text = 'It was successful' resp.status_code = 200 resp.headers = {'content-type' : 'text/xml', 'content-disposition' : 'attachment;filename="Results.xml"'} args = [] kwargs = {'op' : 'specific_op/'} request = self.factory.get('/my_service/specific_op/?param1=1¶m2=2') response = self.test_view.get(request, *args, **kwargs) self.assertEqual(mock_requests_get.call_args[0], ('http://www.fake.com/service/specific_op/?param1=1¶m2=2',)) self.assertEqual(response.status_code, 200) self.assertContains(response, 'It was successful') self.assertEqual(response['Content-Type'], 'text/xml') self.assertEqual(response['Content-Disposition'], 'attachment;filename="Results.xml"') def test_unsuccssful_get(self): with mock.patch('common.views.requests.get') as mock_requests_get: resp = mock.Mock resp.text = 'Failure' resp.status_code = 404 args = [] kwargs = {'op' : 'specific_op/'} request = self.factory.get('/my_service/specific_op/?param1=1¶m2=2') response = self.test_view.get(request, *args, **kwargs) self.assertEqual(mock_requests_get.call_args[0], ('http://www.fake.com/service/specific_op/?param1=1¶m2=2',)) self.assertEqual(response.status_code, 404) def test_successful_head(self): with mock.patch('common.views.requests.head') as mock_requests_head: resp = mock.Mock resp.text = 'It was successful' resp.status_code = 200 resp.headers = {'content-type' : 'text/xml', 'content-disposition' : 'attachment;filename="Results.xml"', 'custom-header' : 'Custom header value'} args = [] kwargs = {'op' : 'specific_op/'} request = self.factory.head('/my_service/specific_op/?param1=1¶m2=2') response = self.test_view.head(request, *args, **kwargs) self.assertEqual(mock_requests_head.call_args[0], ('http://www.fake.com/service/specific_op/?param1=1¶m2=2',)) self.assertEqual(response.status_code, 200) self.assertContains(response, 'It was successful') self.assertEqual(response['Content-Type'], 'text/xml') self.assertEqual(response['Content-Disposition'], 'attachment;filename="Results.xml"') self.assertEqual(response['custom-header'], 'Custom header value') def test_unsuccssful_head(self): with mock.patch('common.views.requests.head') as mock_requests_head: resp = mock.Mock resp.text = 'Failure' resp.status_code = 404 args = [] kwargs = {'op' : 'specific_op/'} request = self.factory.head('/my_service/specific_op/?param1=1¶m2=2') response = self.test_view.head(request, *args, **kwargs) self.assertEqual(mock_requests_head.call_args[0], ('http://www.fake.com/service/specific_op/?param1=1¶m2=2',)) self.assertEqual(response.status_code, 404) def test_invalid_method(self): args = [] kwargs = {'op' : 'specific_op/'} request = self.factory.post('/my_service/specific_op/?param1=1¶m2=2') response = SimpleWebProxyView.as_view()(request, *args, **kwargs) self.assertEqual(response.status_code, 405)
class ResponseCacheTest(TestCase): def setUp(self): self.factory = RequestFactory() caches['testcache'].clear() def random_get(self): return self.factory.get('/%s' % uuid4()) def test_default_key_function(self): cache = ResponseCache(1, key_prefix='p') # Plain test with all defaults self.assertEqual(cache.key_func(self.factory.get('/url1/?k1=v1')), 'p#GET#http#testserver#/url1/?k1=v1') # Different method self.assertEqual(cache.key_func(self.factory.head('/url2/?k2=v2')), 'p#HEAD#http#testserver#/url2/?k2=v2') # Try HTTPS (hacky) request = self.factory.get('/url3/?k3=v3') request.is_secure = lambda: True self.assertEqual(cache.key_func(request), 'p#GET#https#testserver:80#/url3/?k3=v3') # Try different Host: + normalisation request = self.factory.get('/url4/?k4=v4', HTTP_HOST='FooBar') self.assertEqual(cache.key_func(request), 'p#GET#http#foobar#/url4/?k4=v4') def test_default_key_function_parts_omission(self): request = self.factory.get('/url10/?k10=v10') # Empty prefix self.assertEqual( ResponseCache(1).key_func(request), '#GET#http#testserver#/url10/?k10=v10') # Do not consider scheme self.assertEqual( ResponseCache(1, key_prefix='p', include_scheme=False).key_func(request), 'p#GET##testserver#/url10/?k10=v10') # Do not consider host self.assertEqual( ResponseCache(1, key_prefix='p', include_host=False).key_func(request), 'p#GET#http##/url10/?k10=v10') def test_user_supplied_key_function(self): # Test dumb user supplied key function cache = ResponseCache(1, key_func=lambda r: "KeyValue") request = self.factory.get('/') self.assertEqual(cache.key_func(request), 'KeyValue') def test_should_fetch(self): # test default behavious: GETs only cache = ResponseCache(1) self.assertTrue(cache.should_fetch(self.factory.get('/'))) self.assertFalse(cache.should_fetch(self.factory.head('/'))) # Allow HEAD too cache = ResponseCache(1, methods=('GET', 'HEAD')) self.assertTrue(cache.should_fetch(self.factory.head('/'))) # Check authenticated requests (shouldn't cache by default) request = self.factory.get('/') request.user = User.objects.create_user('u1', '*****@*****.**', 'u1') self.assertFalse(cache.should_fetch(request)) # This instance should ignore authenticated user self.assertTrue( ResponseCache(1, anonymous_only=False).should_fetch(request)) # Anonymous' responses should be cached request.user = AnonymousUser() self.assertTrue(cache.should_fetch(request)) def test_should_store(self): cache = ResponseCache(1) # Check normal response (200) self.assertTrue( cache.should_store(self.factory.get('/'), HttpResponse())) # Check some non-200 response codes for code in (201, 404, 403, 500, 502, 503, 301, 302): self.assertFalse( cache.should_store(self.factory.get('/'), HttpResponse(status=code))) @skipUnless(StreamingHttpResponse, "Too old for StreamingHttpResponse?") def test_should_store_streaming(self): cache = ResponseCache(1) # StreamingHttpResponse is never cached self.assertFalse( cache.should_store(self.factory.get('/'), StreamingHttpResponse())) def test_caching_decorator(self): decorated_view = rsp_cache(randomView) # Confirm that the same URL is cached and returns the same content request = self.random_get() rsp1 = decorated_view(request) time.sleep(0.1) # This should be still cached rsp2 = decorated_view(request) time.sleep(0.3) # But this will expire and be refreshed rsp3 = decorated_view(request) self.assertEqual(rsp1.content, rsp2.content) self.assertNotEqual(rsp1.content, rsp3.content) def test_caching_template_response(self): # Perform the same tests for SimpleTemplateResponse decorated_view = rsp_cache(randomTemplateView) request = self.random_get() rsp1 = decorated_view(request) time.sleep(0.1) rsp2 = decorated_view(request) # Should be already rendered because fetched from the cache time.sleep(0.3) rsp3 = decorated_view(request) # Shouldn't be rendered yet again # Compare content self.assertEqual(rsp1.content, rsp2.content) self.assertNotEqual(rsp1.content, rsp3.content) def test_caching_decorator_different_urls(self): decorated_view = rsp_cache(randomView) # Different URLs are cached under different keys request1 = self.random_get() request2 = self.random_get() self.assertNotEqual( decorated_view(request1).content, decorated_view(request2).content) def test_uncacheable_requests(self): # Test authenticated requests (shouldn't cache) decorated_view = rsp_cache(randomView) request = self.factory.get('/') request.user = User.objects.create_user('u1', '*****@*****.**', 'u1') self.assertNotEqual( decorated_view(request).content, decorated_view(request).content) # Test HEADs (should not cache) request = self.factory.head('/') self.assertNotEqual( decorated_view(request).content, decorated_view(request).content) def test_last_modified_default(self): decorated_view = rsp_cache(randomView) # Last-Modified has to be set to "now" request = self.random_get() rsp = decorated_view(request) self.assertEqual(rsp.get('Last-Modified'), http_date()) def test_last_modified_from_response(self): decorated_view = rsp_cache(randomView) # Last-Modified has to be set to whatever was in the original response request = self.random_get() rsp = decorated_view(request, 123456) self.assertEqual(rsp.get('Last-Modified'), http_date(123456)) def test_cache_arbitrary_header(self): decorated_view = rsp_cache(randomView) # Response setting some unknown header gets cached request = self.random_get() rsp1 = decorated_view(request, headers={'h1': 'v1'}) rsp2 = decorated_view(request, headers={'h2': 'v2'}) self.assertEqual(rsp1.content, rsp2.content) # First request had been cached with its headers self.assertEqual(rsp2['h1'], 'v1') self.assertFalse(rsp2.has_header('h2')) def test_not_caching_set_cookie(self): decorated_view = rsp_cache(randomView) # First request with Set-Cookie is not cached request = self.random_get() rsp1 = decorated_view(request, headers={'Set-Cookie': 'foobar'}) rsp2 = decorated_view(request) self.assertNotEqual(rsp1.content, rsp2.content) self.assertTrue(rsp1.has_header('Set-Cookie')) self.assertFalse(rsp2.has_header('Set-Cookie')) def test_not_caching_vary(self): decorated_view = rsp_cache(randomView) # First request with Set-Cookie is not cached request = self.random_get() rsp1 = decorated_view(request, headers={'Vary': '*'}) rsp2 = decorated_view(request) self.assertNotEqual(rsp1.content, rsp2.content) self.assertTrue(rsp1.has_header('Vary')) self.assertFalse(rsp2.has_header('Vary')) def test_not_caching_configured_req_hdr(self): decorated_view = ResponseCache(0.3, cache='testcache', nocache_req={'HTTP_HDR1': '.123[abc]'})(randomView) request = self.random_get() # Should match and pass not cached request.META['HTTP_HDR1'] = '0a123b' rsp1 = decorated_view(request) rsp2 = decorated_view(request) self.assertNotEqual(rsp1.content, rsp2.content) # Should not match and be cached request.META['HTTP_HDR1'] = 'other' rsp3 = decorated_view(request) rsp4 = decorated_view(request) self.assertEqual(rsp3.content, rsp4.content) def test_not_caching_configured_rsp_hdr(self): decorated_view = ResponseCache(0.3, cache='testcache', nocache_rsp=('Hdr1', ))(randomView) # First request with Set-Cookie is not cached request = self.random_get() rsp1 = decorated_view(request, headers={'Hdr1': 'val1'}) rsp2 = decorated_view(request) self.assertNotEqual(rsp1.content, rsp2.content) self.assertTrue(rsp1.has_header('Hdr1')) self.assertFalse(rsp2.has_header('Hdr1')) def test_not_caching_csrf_response(self): decorated_view = rsp_cache(csrfView) url = "/%s" % uuid4() # Responses that have CSRF token used should not be cached request1 = self.factory.get(url) request2 = self.factory.get(url) rsp1 = decorated_view(request1) rsp2 = decorated_view(request2) self.assertNotEqual(rsp1.content, rsp2.content) def test_not_caching_csrf_template_response(self): decorated_view = rsp_cache(csrfTemplateView) url = "/%s" % uuid4() # Responses that have CSRF token used should not be cached request1 = self.factory.get(url) request2 = self.factory.get(url) rsp1 = decorated_view(request1) rsp1.render() rsp2 = decorated_view(request2) rsp2.render() self.assertNotEqual(rsp1.content, rsp2.content) def test_not_caching_req_cookies(self): decorated_view = rsp_cache(randomView) # By default, requests with cookies aren't cached request = self.random_get() request.COOKIES['c1'] = 'v1' rsp1 = decorated_view(request) rsp2 = decorated_view(request) self.assertNotEqual(rsp1.content, rsp2.content) def test_whitelisted_req_cookies(self): cache = ResponseCache(0.3, cache='testcache', excluded_cookies=('c1', )) decorated_view = cache(randomView) # This cookie does not prevent request from being cached request = self.random_get() request.COOKIES['c1'] = 'v1' rsp1 = decorated_view(request) rsp2 = decorated_view(request) self.assertEqual(rsp1.content, rsp2.content) def test_caching_req_cookies(self): cache = ResponseCache(0.3, cache='testcache', cache_cookies=True) decorated_view = cache(randomView) # This request should be cached with any cookie set request = self.random_get() request.COOKIES['c1'] = 'v1' rsp1 = decorated_view(request) rsp2 = decorated_view(request) self.assertEqual(rsp1.content, rsp2.content) def test_blacklisted_req_cookies(self): cache = ResponseCache(0.3, cache='testcache', cache_cookies=True, excluded_cookies=('c1', )) decorated_view = cache(randomView) # This cookie prevents this request from being cached, # though generally cookies are allowed request = self.random_get() request.COOKIES['c1'] = 'v1' rsp1 = decorated_view(request) rsp2 = decorated_view(request) self.assertNotEqual(rsp1.content, rsp2.content) def test_hitmiss_header(self): decorated_view = rsp_cache(randomView) request = self.random_get() rsp1 = decorated_view(request) rsp2 = decorated_view(request) self.assertEqual(rsp1['X-Cache'], 'Miss') self.assertEqual(rsp2['X-Cache'], 'Hit') def test_custom_hitmiss_header(self): cache = ResponseCache(0.3, cache='testcache', hitmiss_header=('h', '+', '-')) decorated_view = cache(randomView) request = self.random_get() rsp1 = decorated_view(request) rsp2 = decorated_view(request) self.assertFalse(rsp1.has_header('X-Cache')) self.assertFalse(rsp2.has_header('X-Cache')) self.assertEqual(rsp1['h'], '-') self.assertEqual(rsp2['h'], '+') def test_absent_hitmiss_header(self): cache = ResponseCache(0.3, cache='testcache', hitmiss_header=None) decorated_view = cache(randomView) request = self.random_get() rsp1 = decorated_view(request) rsp2 = decorated_view(request) self.assertFalse(rsp1.has_header('X-Cache')) self.assertFalse(rsp2.has_header('X-Cache')) def test_custom_hitmiss_header_template_view(self): cache = ResponseCache(0.3, cache='testcache', hitmiss_header=('h', '+', '-')) decorated_view = cache(randomTemplateView) request = self.random_get() rsp1 = decorated_view(request) rsp2 = decorated_view(request) self.assertFalse(rsp1.has_header('X-Cache')) self.assertFalse(rsp2.has_header('X-Cache')) self.assertEqual(rsp1['h'], '-') self.assertEqual(rsp2['h'], '+') def test_wrapper_special_properties(self): # The wrapper should keep original function's special attributes decorated_view = rsp_cache(randomView) self.assertEqual(decorated_view.__doc__, randomView.__doc__) self.assertEqual(decorated_view.__module__, randomView.__module__) self.assertEqual(decorated_view.__name__, randomView.__name__)