class FxOSMalformedPOSTTest(TestCase): """Bug 962225""" def setUp(self): self.rf = RequestFactory() def test_deals_with_broken_post_data(self, update_user_mock): """Should be able to parse data from the raw request body. FxOS sends POST requests with the wrong mime-type, so request.POST is never filled out. We should parse the raw request body to get the data until this is fixed in FxOS in bug 949170. """ req = self.rf.generic( "POST", "/news/subscribe/", data="[email protected]&newsletters=firefox-os", content_type="text/plain; charset=UTF-8", ) self.assertFalse(bool(req.POST)) views.subscribe(req) update_user_mock.assert_called_with( req, views.SUBSCRIBE, data={"email": "*****@*****.**", "newsletters": "firefox-os"}, optin=False, sync=False, )
class FxOSMalformedPOSTTest(TestCase): """Bug 962225""" def setUp(self): self.rf = RequestFactory() def test_deals_with_broken_post_data(self, update_user_mock): """Should be able to parse data from the raw request body. FxOS sends POST requests with the wrong mime-type, so request.POST is never filled out. We should parse the raw request body to get the data until this is fixed in FxOS in bug 949170. """ req = self.rf.generic( 'POST', '/news/subscribe/', data='[email protected]&newsletters=firefox-os', content_type='text/plain; charset=UTF-8') self.assertFalse(bool(req.POST)) views.subscribe(req) update_user_mock.assert_called_with(req, views.SUBSCRIBE, data={ 'email': '*****@*****.**', 'newsletters': 'firefox-os', }, optin=False, sync=False)
def create_request_from_job(job_data, raw_data=''): if DEBUG_BACKGROUND is True: logger.info('create_request_from_job: %r', job_data) if raw_data: logger.info('raw_data: %r, %d', type(raw_data), len(raw_data)) if len(raw_data) < 1000: logger.info('raw_data: %r', raw_data) else: logger.info('raw_data(trunc): %r', raw_data[:1000]) request_factory = RequestFactory() path = job_data[JOB.URI] method = job_data[JOB.METHOD] encoding = job_data[JOB.ENCODING] content_type = job_data[JOB.CONTENT_TYPE] # For job processing, force response to JSON # accept = urllib.unquote(job_data[JOB.HTTP_ACCEPT]) accept = JSON_MIMETYPE comment = job_data[JOB.COMMENT] params = job_data[JOB.PARAMS] if params: params = json.loads(params) else: params = {} params[HEADER_APILOG_COMMENT_CLIENT] = comment params[HEADER_APILOG_COMMENT] = comment if raw_data: if DEBUG_BACKGROUND is True: logger.info('add raw data: %d', len(raw_data)) if MULTIPART_MIMETYPE not in content_type: msg = 'content type must contain %r for raw data post: found: %r'\ % (MULTIPART_MIMETYPE, content_type) logger.error(msg) raise ValidationError(key=JOB.CONTENT_TYPE, msg=msg) if method != 'POST': errmsg = 'method %r is not %r, required for raw data post' % ( method, 'POST') raise ValidationError(key=JOB.METHOD, msg=errmsg) else: if DEBUG_BACKGROUND is True: logger.info('no raw data to add') if DEBUG_BACKGROUND is True: logger.info('create_request_from_job content type: %r', content_type) request = request_factory.generic(method, path, data=raw_data, HTTP_ACCEPT=accept, content_type=content_type, **params) if DEBUG_BACKGROUND is True: logger.info('create_request_from_job: META: %r', request.META) logger.info('create_request_from_job: FILES: %r', request.FILES) return request
def _create_mock_json_request(user, data, method='POST'): """ Returns a mock JSON request for the specified user. """ factory = RequestFactory() request = factory.generic(method, '/', content_type='application/json', data=json.dumps(data)) request.user = user request.session = {} return request
def create_request_from_job(job_data, raw_data=''): if DEBUG_BACKGROUND is True: logger.info('create_request_from_job: %r', job_data) if raw_data: logger.info('raw_data: %r, %d', type(raw_data), len(raw_data)) if len(raw_data) < 1000: logger.info('raw_data: %r', raw_data) else: logger.info('raw_data(trunc): %r', raw_data[:1000]) request_factory = RequestFactory() path = job_data[JOB.URI] method = job_data[JOB.METHOD] encoding = job_data[JOB.ENCODING] content_type = job_data[JOB.CONTENT_TYPE] # For job processing, force response to JSON # accept = urllib.unquote(job_data[JOB.HTTP_ACCEPT]) accept = JSON_MIMETYPE comment = job_data[JOB.COMMENT] params = job_data[JOB.PARAMS] if params: params = json.loads(params) else: params = {} params[HEADER_APILOG_COMMENT_CLIENT] = comment params[HEADER_APILOG_COMMENT] = comment if raw_data: if DEBUG_BACKGROUND is True: logger.info('add raw data: %d', len(raw_data)) if MULTIPART_MIMETYPE not in content_type: msg = 'content type must contain %r for raw data post: found: %r'\ % (MULTIPART_MIMETYPE, content_type) logger.error(msg) raise ValidationError(key=JOB.CONTENT_TYPE, msg=msg) if method != 'POST': errmsg = 'method %r is not %r, required for raw data post' % ( method, 'POST') raise ValidationError(key=JOB.METHOD, msg=errmsg) else: if DEBUG_BACKGROUND is True: logger.info('no raw data to add') if DEBUG_BACKGROUND is True: logger.info('create_request_from_job content type: %r', content_type) request = request_factory.generic( method, path, data=raw_data, HTTP_ACCEPT=accept, content_type=content_type,**params ) if DEBUG_BACKGROUND is True: logger.info('create_request_from_job: META: %r', request.META ) logger.info('create_request_from_job: FILES: %r', request.FILES ) return request
class DjangoTestAdapter(requests.adapters.HTTPAdapter): """ A transport adapter for `requests`, that makes requests via the Django WSGI app, rather than making actual HTTP requests over the network. """ def __init__(self): self.app = WSGIHandler() self.factory = DjangoRequestFactory() def get_environ(self, request): """ Given a `requests.PreparedRequest` instance, return a WSGI environ dict. """ method = request.method url = request.url kwargs = {} # Set request content, if any exists. if request.body is not None: if hasattr(request.body, 'read'): kwargs['data'] = request.body.read() else: kwargs['data'] = request.body if 'content-type' in request.headers: kwargs['content_type'] = request.headers['content-type'] # Set request headers. for key, value in request.headers.items(): key = key.upper() if key in ('CONNECTION', 'CONTENT-LENGTH', 'CONTENT-TYPE'): continue kwargs['HTTP_%s' % key.replace('-', '_')] = value return self.factory.generic(method, url, **kwargs).environ def send(self, request, *args, **kwargs): """ Make an outgoing request to the Django WSGI application. """ raw_kwargs = {} def start_response(wsgi_status, wsgi_headers): status, _, reason = wsgi_status.partition(' ') raw_kwargs['status'] = int(status) raw_kwargs['reason'] = reason raw_kwargs['headers'] = wsgi_headers raw_kwargs['version'] = 11 raw_kwargs['preload_content'] = False raw_kwargs['original_response'] = MockOriginalResponse(wsgi_headers) # Make the outgoing request via WSGI. environ = self.get_environ(request) wsgi_response = self.app(environ, start_response) # Build the underlying urllib3.HTTPResponse raw_kwargs['body'] = io.BytesIO(b''.join(wsgi_response)) raw = requests.packages.urllib3.HTTPResponse(**raw_kwargs) # Build the requests.Response return self.build_response(request, raw) def close(self): pass
class DjangoTestAdapter(requests.adapters.HTTPAdapter): """ A transport adapter for `requests`, that makes requests via the Django WSGI app, rather than making actual HTTP requests over the network. """ def __init__(self): self.app = WSGIHandler() self.factory = DjangoRequestFactory() def get_environ(self, request): """ Given a `requests.PreparedRequest` instance, return a WSGI environ dict. """ method = request.method url = request.url kwargs = {} # Set request content, if any exists. if request.body is not None: if hasattr(request.body, 'read'): kwargs['data'] = request.body.read() else: kwargs['data'] = request.body if 'content-type' in request.headers: kwargs['content_type'] = request.headers['content-type'] # Set request headers. for key, value in request.headers.items(): key = key.upper() if key in ('CONNECTION', 'CONTENT-LENGTH', 'CONTENT-TYPE'): continue kwargs['HTTP_%s' % key.replace('-', '_')] = value return self.factory.generic(method, url, **kwargs).environ def send(self, request, *args, **kwargs): """ Make an outgoing request to the Django WSGI application. """ raw_kwargs = {} def start_response(wsgi_status, wsgi_headers): status, _, reason = wsgi_status.partition(' ') raw_kwargs['status'] = int(status) raw_kwargs['reason'] = reason raw_kwargs['headers'] = wsgi_headers raw_kwargs['version'] = 11 raw_kwargs['preload_content'] = False raw_kwargs['original_response'] = MockOriginalResponse( wsgi_headers) # Make the outgoing request via WSGI. environ = self.get_environ(request) wsgi_response = self.app(environ, start_response) # Build the underlying urllib3.HTTPResponse raw_kwargs['body'] = io.BytesIO(b''.join(wsgi_response)) raw = requests.packages.urllib3.HTTPResponse(**raw_kwargs) # Build the requests.Response return self.build_response(request, raw) def close(self): pass
class LocalApiAdapter(BaseAdapter): SPECIAL_URL = "http://localapi" def __init__(self): self.request_factory = RequestFactory() super(LocalApiAdapter, self).__init__() def prepared_request_to_wsgi_request(self, prepared_request): """ transform a PreparedRequest into a WsgiRequest for django to use :param requests.models.PreparedRequest prepared_request: :return: the request ready to use for django :rtype: django.core.handlers.wsgi.WSGIRequest """ wsgi_request = self.request_factory.generic( method=prepared_request.method, path=prepared_request.url, data=prepared_request.body, content_type=prepared_request.headers.get( 'Content-Type', 'application/x-www-form-urlencoded')) for name, val in prepared_request.headers.items(): wsgi_request.META['HTTP_' + name.upper()] = val return wsgi_request def http_response_to_response(self, http_response, prepared_request): """ transform a WSGIResponse into a requests's Response model :param django.http.response.HttpResponse http_response: the http response send by django view :return: the requests's Response model corresponding to the http_response :rtype: Response """ response = Response() # Fallback to None if there's no status_code, for whatever reason. response.status_code = getattr(http_response, 'status_code', None) # Make headers case-insensitive. response.headers = CaseInsensitiveDict( getattr(http_response._headers, 'headers', {})) # Set encoding. response.encoding = get_encoding_from_headers(response.headers) response.raw = http_response response.reason = response.raw.reason_phrase response._content = http_response.content req = prepared_request if isinstance(req.url, bytes): # pragma: no cover response.url = req.url.decode('utf-8') else: response.url = req.url # Add new cookies from the server. extract_cookies_to_jar(response.cookies, req, response) # Give the Response some context. response.request = req response.connection = self return response def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None): handler = BaseHandler() handler.load_middleware() wsgi_request = self.prepared_request_to_wsgi_request(request) http_response = handler.get_response(wsgi_request) requests_response = self.http_response_to_response( http_response, request) return requests_response def close(self): # pragma: no cover pass
class SubscribeTests(ViewsPatcherMixin, TestCase): def setUp(self): self.factory = RequestFactory() self._patch_views('update_user_task') self._patch_views('process_email') self._patch_views('is_authorized') def tearDown(self): cache.clear() email_block_list_cache.clear() def assert_response_error(self, response, status_code, basket_code): self.assertEqual(response.status_code, status_code) response_data = json.loads(response.content) self.assertEqual(response_data['code'], basket_code) def test_newsletters_missing(self): """If the newsletters param is missing, return a 400.""" request = self.factory.post('/') response = views.subscribe(request) self.assert_response_error(response, 400, errors.BASKET_USAGE_ERROR) def test_optin_valid_api_key_required(self): """ If optin is 'Y' but the API key isn't valid, disable optin. """ request_data = { 'newsletters': 'asdf', 'optin': 'Y', 'email': '*****@*****.**' } update_data = request_data.copy() del update_data['optin'] self.process_email.return_value = update_data['email'] request = self.factory.post('/', request_data) self.is_authorized.return_value = False response = views.subscribe(request) self.assertEqual(response, self.update_user_task.return_value) self.update_user_task.assert_called_with(request, SUBSCRIBE, data=update_data, optin=False, sync=False) def test_sync_invalid_api_key(self): """ If sync is set to 'Y' and the request has an invalid API key, return a 401. """ request = self.factory.post('/', { 'newsletters': 'asdf', 'sync': 'Y', 'email': '*****@*****.**' }) self.is_authorized.return_value = False response = views.subscribe(request) self.assert_response_error(response, 401, errors.BASKET_AUTH_ERROR) self.is_authorized.assert_called_with(request, self.process_email.return_value) def test_email_validation_error(self): """ If process_email returns None, return an invalid email response. """ request_data = {'newsletters': 'asdf', 'email': '*****@*****.**'} request = self.factory.post('/', request_data) self.process_email.return_value = None with patch('basket.news.views.invalid_email_response' ) as invalid_email_response: response = views.subscribe(request) self.assertEqual(response, invalid_email_response.return_value) self.process_email.assert_called_with(request_data['email']) invalid_email_response.assert_called() @patch('basket.news.utils.get_email_block_list') def test_blocked_email(self, get_block_list_mock): """Test basic success case with no optin or sync.""" get_block_list_mock.return_value = ['example.com'] request_data = { 'newsletters': 'news,lets', 'optin': 'N', 'sync': 'N', 'email': '*****@*****.**' } request = self.factory.post('/', request_data) response = views.subscribe(request) self.assertEqual(response.status_code, 200) self.assertFalse(self.update_user_task.called) @patch('basket.news.views.update_user_task') def test_email_fxos_malformed_post_bad_data(self, update_user_mock): """Should be able to parse data from the raw request body even with bad data.""" # example from real error with PII details changed self.process_email.return_value = '*****@*****.**' req = self.factory.generic( 'POST', '/news/subscribe/', data='newsletters=mozilla-foundation&' 'source_url=https%3A%2F%2Fadvocacy.mozilla.org%2Fencrypt' '&lang=en&[email protected]' '&country=DE&first_name=Dude&Walter', content_type='text/plain; charset=UTF-8') views.subscribe(req) update_user_mock.assert_called_with( req, views.SUBSCRIBE, data={ 'email': '*****@*****.**', 'newsletters': 'mozilla-foundation', 'source_url': 'https%3A%2F%2Fadvocacy.mozilla.org%2Fencrypt', 'lang': 'en', 'country': 'DE', 'first_name': 'Dude', }, optin=False, sync=False) @patch('basket.news.views.update_user_task') def test_email_fxos_malformed_post(self, update_user_mock): """Should be able to parse data from the raw request body.""" self.process_email.return_value = '*****@*****.**' req = self.factory.generic( 'POST', '/news/subscribe/', data='[email protected]&newsletters=firefox-os', content_type='text/plain; charset=UTF-8') views.subscribe(req) update_user_mock.assert_called_with(req, views.SUBSCRIBE, data={ 'email': '*****@*****.**', 'newsletters': 'firefox-os', }, optin=False, sync=False) def test_no_source_url_referrer(self): """Test referrer used when no source_url.""" request_data = { 'newsletters': 'news,lets', 'optin': 'N', 'sync': 'N', 'email': '*****@*****.**', 'first_name': 'The', 'last_name': 'Dude' } update_data = request_data.copy() del update_data['optin'] del update_data['sync'] update_data['source_url'] = 'https://example.com/newsletter' self.process_email.return_value = update_data['email'] request = self.factory.post('/', request_data, HTTP_REFERER=update_data['source_url']) response = views.subscribe(request) self.assertEqual(response, self.update_user_task.return_value) self.process_email.assert_called_with(request_data['email']) self.update_user_task.assert_called_with(request, SUBSCRIBE, data=update_data, optin=False, sync=False) def test_source_url_overrides_referrer(self): """Test source_url used when referrer also provided.""" request_data = { 'newsletters': 'news,lets', 'optin': 'N', 'sync': 'N', 'email': '*****@*****.**', 'first_name': 'The', 'last_name': 'Dude', 'source_url': 'https://example.com/thedude' } update_data = request_data.copy() del update_data['optin'] del update_data['sync'] self.process_email.return_value = update_data['email'] request = self.factory.post('/', request_data, HTTP_REFERER='https://example.com/donnie') response = views.subscribe(request) self.assertEqual(response, self.update_user_task.return_value) self.process_email.assert_called_with(request_data['email']) self.update_user_task.assert_called_with(request, SUBSCRIBE, data=update_data, optin=False, sync=False) def test_success(self): """Test basic success case with no optin or sync.""" request_data = { 'newsletters': 'news,lets', 'optin': 'N', 'sync': 'N', 'email': '*****@*****.**', 'first_name': 'The', 'last_name': 'Dude' } update_data = request_data.copy() del update_data['optin'] del update_data['sync'] self.process_email.return_value = update_data['email'] request = self.factory.post('/', request_data) response = views.subscribe(request) self.assertEqual(response, self.update_user_task.return_value) self.process_email.assert_called_with(request_data['email']) self.update_user_task.assert_called_with(request, SUBSCRIBE, data=update_data, optin=False, sync=False) def test_success_sync_optin(self): """Test success case with optin and sync.""" request_data = { 'newsletters': 'news,lets', 'optin': 'Y', 'sync': 'Y', 'email': '*****@*****.**' } update_data = request_data.copy() del update_data['optin'] del update_data['sync'] request = self.factory.post('/', request_data) self.is_authorized.return_value = True self.process_email.return_value = update_data['email'] response = views.subscribe(request) self.is_authorized.assert_called_with(request, self.process_email.return_value) self.assertEqual(response, self.update_user_task.return_value) self.process_email.assert_called_with('*****@*****.**') self.update_user_task.assert_called_with(request, SUBSCRIBE, data=update_data, optin=True, sync=True) def test_success_sync_optin_lowercase(self): """Test success case with optin and sync, using lowercase y.""" request_data = { 'newsletters': 'news,lets', 'optin': 'y', 'sync': 'y', 'email': '*****@*****.**' } update_data = request_data.copy() del update_data['optin'] del update_data['sync'] request = self.factory.post('/', request_data) self.process_email.return_value = update_data['email'] self.is_authorized.return_value = True response = views.subscribe(request) self.is_authorized.assert_called_with(request, self.process_email.return_value) self.assertEqual(response, self.update_user_task.return_value) self.process_email.assert_called_with('*****@*****.**') self.update_user_task.assert_called_with(request, SUBSCRIBE, data=update_data, optin=True, sync=True)
class SubscribeEmailValidationTest(TestCase): email = '*****@*****.**' data = { 'email': email, 'newsletters': 'os', } view = 'subscribe' def setUp(self): self.rf = RequestFactory() @patch('basket.news.views.process_email') def test_invalid_email(self, mock_validate): """Should return proper error for invalid email.""" mock_validate.return_value = None view = getattr(views, self.view) resp = view(self.rf.post('/', self.data)) resp_data = json.loads(resp.content) mock_validate.assert_called_with(self.email) self.assertEqual(resp.status_code, 400) self.assertEqual(resp_data['status'], 'error') self.assertEqual(resp_data['code'], errors.BASKET_INVALID_EMAIL) @patch('basket.news.views.update_user_task') def test_non_ascii_email_fxos_malformed_post(self, update_user_mock): """Should be able to parse data from the raw request body including non-ascii chars.""" req = self.rf.generic('POST', '/news/subscribe/', data='email=dude@黒川.日本&newsletters=firefox-os', content_type='text/plain; charset=UTF-8') views.subscribe(req) update_user_mock.assert_called_with(req, views.SUBSCRIBE, data={ 'email': u'[email protected]', 'newsletters': 'firefox-os', }, optin=False, sync=False) @patch('basket.news.views.update_user_task') def test_non_ascii_email(self, update_user_mock): """Should be able to accept valid email including non-ascii chars.""" req = self.rf.post('/news/subscribe/', data={ 'email': 'dude@黒川.日本', 'newsletters': 'firefox-os' }) views.subscribe(req) update_user_mock.assert_called_with(req, views.SUBSCRIBE, data={ 'email': u'[email protected]', 'newsletters': 'firefox-os', }, optin=False, sync=False) @patch('basket.news.views.update_user_task') def test_empty_email_invalid(self, update_user_mock): """Should report an error for missing or empty value.""" req = self.rf.post('/news/subscribe/', data={ 'email': '', 'newsletters': 'firefox-os' }) resp = views.subscribe(req) resp_data = json.loads(resp.content) self.assertEqual(resp_data['status'], 'error') self.assertEqual(resp_data['code'], errors.BASKET_INVALID_EMAIL) self.assertFalse(update_user_mock.called) # no email at all req = self.rf.post('/news/subscribe/', data={'newsletters': 'firefox-os'}) resp = views.subscribe(req) resp_data = json.loads(resp.content) self.assertEqual(resp_data['status'], 'error') self.assertEqual(resp_data['code'], errors.BASKET_USAGE_ERROR) self.assertFalse(update_user_mock.called)
class SubscribeTests(ViewsPatcherMixin, TestCase): def setUp(self): self.factory = RequestFactory() self._patch_views('update_user_task') self._patch_views('process_email') self._patch_views('has_valid_api_key') def tearDown(self): cache.clear() email_block_list_cache.clear() def assert_response_error(self, response, status_code, basket_code): self.assertEqual(response.status_code, status_code) response_data = json.loads(response.content) self.assertEqual(response_data['code'], basket_code) def test_newsletters_missing(self): """If the newsletters param is missing, return a 400.""" request = self.factory.post('/') response = views.subscribe(request) self.assert_response_error(response, 400, errors.BASKET_USAGE_ERROR) def test_optin_valid_api_key_required(self): """ If optin is 'Y' but the API key isn't valid, disable optin. """ request_data = {'newsletters': 'asdf', 'optin': 'Y', 'email': '*****@*****.**'} update_data = request_data.copy() del update_data['optin'] self.process_email.return_value = update_data['email'] request = self.factory.post('/', request_data) self.has_valid_api_key.return_value = False response = views.subscribe(request) self.assertEqual(response, self.update_user_task.return_value) self.update_user_task.assert_called_with(request, SUBSCRIBE, data=update_data, optin=False, sync=False) def test_sync_invalid_api_key(self): """ If sync is set to 'Y' and the request has an invalid API key, return a 401. """ request = self.factory.post('/', {'newsletters': 'asdf', 'sync': 'Y', 'email': '*****@*****.**'}) self.has_valid_api_key.return_value = False response = views.subscribe(request) self.assert_response_error(response, 401, errors.BASKET_AUTH_ERROR) self.has_valid_api_key.assert_called_with(request) def test_email_validation_error(self): """ If process_email returns None, return an invalid email response. """ request_data = {'newsletters': 'asdf', 'email': '*****@*****.**'} request = self.factory.post('/', request_data) self.process_email.return_value = None with patch('basket.news.views.invalid_email_response') as invalid_email_response: response = views.subscribe(request) self.assertEqual(response, invalid_email_response.return_value) self.process_email.assert_called_with(request_data['email']) invalid_email_response.assert_called() @patch('basket.news.utils.get_email_block_list') def test_blocked_email(self, get_block_list_mock): """Test basic success case with no optin or sync.""" get_block_list_mock.return_value = ['example.com'] request_data = {'newsletters': 'news,lets', 'optin': 'N', 'sync': 'N', 'email': '*****@*****.**'} request = self.factory.post('/', request_data) response = views.subscribe(request) self.assertEqual(response.status_code, 200) self.assertFalse(self.update_user_task.called) @patch('basket.news.views.update_user_task') def test_email_fxos_malformed_post_bad_data(self, update_user_mock): """Should be able to parse data from the raw request body even with bad data.""" # example from real error with PII details changed self.process_email.return_value = '*****@*****.**' req = self.factory.generic('POST', '/news/subscribe/', data='newsletters=mozilla-foundation&' 'source_url=https%3A%2F%2Fadvocacy.mozilla.org%2Fencrypt' '&lang=en&[email protected]' '&country=DE&first_name=Dude&Walter', content_type='text/plain; charset=UTF-8') views.subscribe(req) update_user_mock.assert_called_with(req, views.SUBSCRIBE, data={ 'email': '*****@*****.**', 'newsletters': 'mozilla-foundation', 'source_url': 'https%3A%2F%2Fadvocacy.mozilla.org%2Fencrypt', 'lang': 'en', 'country': 'DE', 'first_name': 'Dude', }, optin=False, sync=False) @patch('basket.news.views.update_user_task') def test_email_fxos_malformed_post(self, update_user_mock): """Should be able to parse data from the raw request body.""" self.process_email.return_value = '*****@*****.**' req = self.factory.generic('POST', '/news/subscribe/', data='[email protected]&newsletters=firefox-os', content_type='text/plain; charset=UTF-8') views.subscribe(req) update_user_mock.assert_called_with(req, views.SUBSCRIBE, data={ 'email': '*****@*****.**', 'newsletters': 'firefox-os', }, optin=False, sync=False) def test_no_source_url_referrer(self): """Test referrer used when no source_url.""" request_data = {'newsletters': 'news,lets', 'optin': 'N', 'sync': 'N', 'email': '*****@*****.**', 'first_name': 'The', 'last_name': 'Dude'} update_data = request_data.copy() del update_data['optin'] del update_data['sync'] update_data['source_url'] = 'https://example.com/newsletter' self.process_email.return_value = update_data['email'] request = self.factory.post('/', request_data, HTTP_REFERER=update_data['source_url']) response = views.subscribe(request) self.assertEqual(response, self.update_user_task.return_value) self.process_email.assert_called_with(request_data['email']) self.update_user_task.assert_called_with(request, SUBSCRIBE, data=update_data, optin=False, sync=False) def test_source_url_overrides_referrer(self): """Test source_url used when referrer also provided.""" request_data = {'newsletters': 'news,lets', 'optin': 'N', 'sync': 'N', 'email': '*****@*****.**', 'first_name': 'The', 'last_name': 'Dude', 'source_url': 'https://example.com/thedude'} update_data = request_data.copy() del update_data['optin'] del update_data['sync'] self.process_email.return_value = update_data['email'] request = self.factory.post('/', request_data, HTTP_REFERER='https://example.com/donnie') response = views.subscribe(request) self.assertEqual(response, self.update_user_task.return_value) self.process_email.assert_called_with(request_data['email']) self.update_user_task.assert_called_with(request, SUBSCRIBE, data=update_data, optin=False, sync=False) def test_success(self): """Test basic success case with no optin or sync.""" request_data = {'newsletters': 'news,lets', 'optin': 'N', 'sync': 'N', 'email': '*****@*****.**', 'first_name': 'The', 'last_name': 'Dude'} update_data = request_data.copy() del update_data['optin'] del update_data['sync'] self.process_email.return_value = update_data['email'] request = self.factory.post('/', request_data) response = views.subscribe(request) self.assertEqual(response, self.update_user_task.return_value) self.process_email.assert_called_with(request_data['email']) self.update_user_task.assert_called_with(request, SUBSCRIBE, data=update_data, optin=False, sync=False) def test_success_sync_optin(self): """Test success case with optin and sync.""" request_data = {'newsletters': 'news,lets', 'optin': 'Y', 'sync': 'Y', 'email': '*****@*****.**'} update_data = request_data.copy() del update_data['optin'] del update_data['sync'] request = self.factory.post('/', request_data) self.has_valid_api_key.return_value = True self.process_email.return_value = update_data['email'] response = views.subscribe(request) self.has_valid_api_key.assert_called_with(request) self.assertEqual(response, self.update_user_task.return_value) self.process_email.assert_called_with('*****@*****.**') self.update_user_task.assert_called_with(request, SUBSCRIBE, data=update_data, optin=True, sync=True) def test_success_sync_optin_lowercase(self): """Test success case with optin and sync, using lowercase y.""" request_data = {'newsletters': 'news,lets', 'optin': 'y', 'sync': 'y', 'email': '*****@*****.**'} update_data = request_data.copy() del update_data['optin'] del update_data['sync'] request = self.factory.post('/', request_data) self.process_email.return_value = update_data['email'] with patch('basket.news.views.has_valid_api_key') as has_valid_api_key: has_valid_api_key.return_value = True response = views.subscribe(request) has_valid_api_key.assert_called_with(request) self.assertEqual(response, self.update_user_task.return_value) self.process_email.assert_called_with('*****@*****.**') self.update_user_task.assert_called_with(request, SUBSCRIBE, data=update_data, optin=True, sync=True)
class SubscribeEmailValidationTest(TestCase): email = '*****@*****.**' data = { 'email': email, 'newsletters': 'os', } view = 'subscribe' def setUp(self): self.rf = RequestFactory() @patch('basket.news.views.process_email') def test_invalid_email(self, mock_validate): """Should return proper error for invalid email.""" mock_validate.return_value = None view = getattr(views, self.view) resp = view(self.rf.post('/', self.data)) resp_data = json.loads(resp.content) mock_validate.assert_called_with(self.email) self.assertEqual(resp.status_code, 400) self.assertEqual(resp_data['status'], 'error') self.assertEqual(resp_data['code'], errors.BASKET_INVALID_EMAIL) @patch('basket.news.views.update_user_task') def test_non_ascii_email_fxos_malformed_post(self, update_user_mock): """Should be able to parse data from the raw request body including non-ascii chars.""" req = self.rf.generic('POST', '/news/subscribe/', data='email=dude@黒川.日本&newsletters=firefox-os', content_type='text/plain; charset=UTF-8') views.subscribe(req) update_user_mock.assert_called_with(req, views.SUBSCRIBE, data={ 'email': u'[email protected]', 'newsletters': 'firefox-os', }, optin=False, sync=False) @patch('basket.news.views.update_user_task') def test_non_ascii_email(self, update_user_mock): """Should be able to accept valid email including non-ascii chars.""" req = self.rf.post('/news/subscribe/', data={'email': 'dude@黒川.日本', 'newsletters': 'firefox-os'}) views.subscribe(req) update_user_mock.assert_called_with(req, views.SUBSCRIBE, data={ 'email': u'[email protected]', 'newsletters': 'firefox-os', }, optin=False, sync=False) @patch('basket.news.views.update_user_task') def test_empty_email_invalid(self, update_user_mock): """Should report an error for missing or empty value.""" req = self.rf.post('/news/subscribe/', data={'email': '', 'newsletters': 'firefox-os'}) resp = views.subscribe(req) resp_data = json.loads(resp.content) self.assertEqual(resp_data['status'], 'error') self.assertEqual(resp_data['code'], errors.BASKET_INVALID_EMAIL) self.assertFalse(update_user_mock.called) # no email at all req = self.rf.post('/news/subscribe/', data={'newsletters': 'firefox-os'}) resp = views.subscribe(req) resp_data = json.loads(resp.content) self.assertEqual(resp_data['status'], 'error') self.assertEqual(resp_data['code'], errors.BASKET_USAGE_ERROR) self.assertFalse(update_user_mock.called)
class SubscribeEmailValidationTest(TestCase): email = "*****@*****.**" data = {"email": email, "newsletters": "os"} view = "subscribe" def setUp(self): self.rf = RequestFactory() @patch("news.views.validate_email") def test_invalid_email(self, mock_validate): """Should return proper error for invalid email.""" mock_validate.side_effect = utils.EmailValidationError("Invalid email") view = getattr(views, self.view) resp = view(self.rf.post("/", self.data)) resp_data = json.loads(resp.content) mock_validate.assert_called_with(self.email) self.assertEqual(resp.status_code, 400) self.assertEqual(resp_data["status"], "error") self.assertEqual(resp_data["code"], errors.BASKET_INVALID_EMAIL) self.assertNotIn("suggestion", resp_data) @patch("news.views.validate_email") def test_invalid_email_suggestion(self, mock_validate): """Should return proper error for invalid email.""" mock_validate.side_effect = utils.EmailValidationError("Invalid email", "*****@*****.**") view = getattr(views, self.view) resp = view(self.rf.post("/", self.data)) resp_data = json.loads(resp.content) mock_validate.assert_called_with(self.email) self.assertEqual(resp.status_code, 400) self.assertEqual(resp_data["status"], "error") self.assertEqual(resp_data["code"], errors.BASKET_INVALID_EMAIL) self.assertEqual(resp_data["suggestion"], "*****@*****.**") @patch("news.views.update_user_task") def test_non_ascii_email_fxos_malformed_post(self, update_user_mock): """Should be able to parse data from the raw request body including non-ascii chars.""" req = self.rf.generic( "POST", "/news/subscribe/", data="email=dude@黒川.日本&newsletters=firefox-os", content_type="text/plain; charset=UTF-8", ) views.subscribe(req) update_user_mock.assert_called_with( req, views.SUBSCRIBE, data={"email": u"dude@黒川.日本", "newsletters": "firefox-os"}, optin=False, sync=False ) @patch("news.views.update_user_task") def test_non_ascii_email(self, update_user_mock): """Should be able to accept valid email including non-ascii chars.""" req = self.rf.post("/news/subscribe/", data={"email": "dude@黒川.日本", "newsletters": "firefox-os"}) views.subscribe(req) update_user_mock.assert_called_with( req, views.SUBSCRIBE, data={"email": u"dude@黒川.日本", "newsletters": "firefox-os"}, optin=False, sync=False ) @patch("news.views.update_user_task") def test_empty_email_invalid(self, update_user_mock): """Should report an error for missing or empty value.""" req = self.rf.post("/news/subscribe/", data={"email": "", "newsletters": "firefox-os"}) resp = views.subscribe(req) resp_data = json.loads(resp.content) self.assertEqual(resp_data["status"], "error") self.assertEqual(resp_data["code"], errors.BASKET_INVALID_EMAIL) self.assertFalse(update_user_mock.called) # no email at all req = self.rf.post("/news/subscribe/", data={"newsletters": "firefox-os"}) resp = views.subscribe(req) resp_data = json.loads(resp.content) self.assertEqual(resp_data["status"], "error") self.assertEqual(resp_data["code"], errors.BASKET_USAGE_ERROR) self.assertFalse(update_user_mock.called)