def test_batch_request_with_application_error(self, spy_plugin_controller, batch_request): job = Job(name='MyComponent.js', data={'foo': 'bar'}) token = batch_request.render('MyComponent.js', {'foo': 'bar'}) fake_response_json = { 'error': { 'name': 'SomeError', 'message': 'yikes', 'stack': ['line 1', 'line 2'] } } with mock.patch('fido.fetch') as mock_fetch: mock_fetch.return_value.wait.return_value.json.return_value = fake_response_json response = batch_request.submit() if batch_request.max_batch_size is None: assert mock_fetch.call_count == 1 else: # Division (rounded-up) up to get total number of calls jobs_count = len(batch_request.jobs) max_batch_size = batch_request.max_batch_size assert mock_fetch.call_count == (jobs_count + (max_batch_size - 1)) // max_batch_size assert response == { token.identifier: JobResult( error=HypernovaError( name='SomeError', message='yikes', stack=['line 1', 'line 2'], ), html=render_blank_markup(token.identifier, job, True), job=job, ), }
def _parse_response(self, response_json): """Parse a raw JSON response into a response dict. :rtype: Dict[str, JobResult] """ response = {} for identifier, result in response_json['results'].items(): job = self.jobs[identifier] error = None if result['error']: error = HypernovaError( name=result['error']['name'], message=result['error']['message'], stack=result['error']['stack'], ) self.plugin_controller.on_error(error, {identifier: job}, self.pyramid_request) html = result['html'] if not html: html = render_blank_markup(identifier, job, True, self.json_encoder) response[identifier] = JobResult(error=error, html=html, job=job) return response
def test_batch_request_with_unhealthy_service(self, spy_plugin_controller, test_data, batch_request): data = test_data[0] job = Job(name='MyComponent.js', data=data[0]) token = batch_request.render('MyComponent.js', data[0]) with mock.patch('fido.fetch') as mock_fetch: mock_fetch.return_value.wait.return_value.json.side_effect = NetworkError('oh no') response = batch_request.submit() if batch_request.max_batch_size is None: assert mock_fetch.call_count == 1 else: # Division (rounded-up) up to get total number of calls jobs_count = len(batch_request.jobs) max_batch_size = batch_request.max_batch_size assert mock_fetch.call_count == (jobs_count + (max_batch_size - 1)) // max_batch_size assert response == { token.identifier: JobResult( error=HypernovaError( name="<class 'fido.exceptions.NetworkError'>", message='oh no', stack=mock.ANY, ), html=render_blank_markup(token.identifier, job, True, batch_request.json_encoder), job=job, ), }
def test_calls_on_error_on_unhealthy_service( self, spy_plugin_controller, test_data, batch_request, mock_hypernova_query, ): data = test_data[0] batch_request.render('MyComponent.js', data[0]) with mock.patch( 'traceback.format_tb', return_value=[ 'Traceback:\n', ' foo:\n', ], ): mock_hypernova_query.return_value.json.side_effect = HypernovaQueryError( 'oh no') batch_request.submit() spy_plugin_controller.on_error.assert_called_once_with( HypernovaError( name='HypernovaQueryError', message='oh no', stack=['Traceback:', ' foo:'], ), batch_request.jobs, batch_request.pyramid_request)
def process_responses(self, query, jobs): """Retrieve response from EventualResponse object and calls lifecycle methods for corresponding jobs. :param query: a HypernovaQuery object :type query: HypernovaQuery :type jobs: Dict[str, Job] :rtype: Dict[str, JobResult] """ pyramid_response = {} try: response_json = query.json() if response_json['error']: error = HypernovaError( name=response_json['error']['name'], message=response_json['error']['message'], stack=response_json['error']['stack'], ) pyramid_response = create_fallback_response( jobs, True, self.json_encoder, error) self.plugin_controller.on_error(error, jobs, self.pyramid_request) else: pyramid_response = self._parse_response(response_json) self.plugin_controller.on_success(pyramid_response, jobs) except (HypernovaQueryError, ValueError) as e: # the service is unhealthy. fall back to client-side rendering __, __, exc_traceback = sys.exc_info() error = HypernovaError( type(e).__name__, str(e), [ line.rstrip('\n') for line in traceback.format_tb(exc_traceback) ], ) self.plugin_controller.on_error(error, jobs, self.pyramid_request) pyramid_response = create_fallback_response( jobs, True, self.json_encoder, error) return pyramid_response
def process_responses(self, future, jobs): """Retrieve response from EventualResponse object and calls lifecycle methods for corresponding jobs. :param future: a future for the HTTP request to render `jobs` :type future: EventualResult :type jobs: Dict[str, Job] :rtype: Dict[str, JobResult] """ response = {} try: r = future.wait() response_json = r.json() if response_json['error']: error = HypernovaError( name=response_json['error']['name'], message=response_json['error']['message'], stack=response_json['error']['stack'], ) response = create_fallback_response(jobs, True, error) self.plugin_controller.on_error(error, jobs) else: response = self._parse_response(response_json) self.plugin_controller.on_success(response, jobs) except (NetworkError, ValueError) as e: # the service is unhealthy. fall back to client-side rendering __, __, exc_traceback = sys.exc_info() error = HypernovaError( repr(type(e)), str(e), traceback.format_tb(exc_traceback), ) self.plugin_controller.on_error(error, jobs) response = create_fallback_response(jobs, True, error) return response
def test_batch_request_with_component_errors(self, spy_plugin_controller, test_data, batch_request): data = test_data[0] token_1 = batch_request.render('MyComponent1.js', data[0]) token_2 = batch_request.render('MyComponent2.js', data[1]) job_2 = Job(name='MyComponent2.js', data=data[1]) fake_response_json = { 'error': None, 'results': { token_1.identifier: { 'error': None, 'html': '<div>wow such SSR</div>', }, token_2.identifier: { 'error': { 'name': 'SomeError', 'message': 'we goofed', 'stack': ['line 1', 'line 2'] }, 'html': None, } } } with mock.patch('fido.fetch') as mock_fetch: mock_fetch.return_value.wait.return_value.json.return_value = fake_response_json response = batch_request.submit() if batch_request.max_batch_size is None: assert mock_fetch.call_count == 1 else: # Division (rounded-up) up to get total number of calls jobs_count = len(batch_request.jobs) max_batch_size = batch_request.max_batch_size assert mock_fetch.call_count == (jobs_count + (max_batch_size - 1)) // max_batch_size assert response == { token_1.identifier: JobResult( error=None, html='<div>wow such SSR</div>', job=Job(name='MyComponent1.js', data=data[0]) ), token_2.identifier: JobResult( error=HypernovaError( name='SomeError', message='we goofed', stack=['line 1', 'line 2'], ), html=render_blank_markup(token_2.identifier, job_2, True, batch_request.json_encoder), job=job_2, ) }
def test_batch_request_with_application_error( self, spy_plugin_controller, test_data, batch_request, mock_hypernova_query, ): data = test_data[0] job = Job(name='MyComponent.js', data=data[0], context={}) token = batch_request.render('MyComponent.js', data[0]) fake_response_json = { 'error': { 'name': 'SomeError', 'message': 'yikes', 'stack': ['line 1', 'line 2'] } } mock_hypernova_query.return_value.json.return_value = fake_response_json response = batch_request.submit() if batch_request.max_batch_size is None: assert mock_hypernova_query.call_count == 1 else: # Division (rounded-up) up to get total number of calls jobs_count = len(batch_request.jobs) max_batch_size = batch_request.max_batch_size batch_count = (jobs_count + (max_batch_size - 1)) // max_batch_size assert mock_hypernova_query.call_count == batch_count mock_hypernova_query.assert_called_with(mock.ANY, mock.ANY, mock.ANY, batch_count == 1, {}) assert response == { token.identifier: JobResult( error=HypernovaError( name='SomeError', message='yikes', stack=['line 1', 'line 2'], ), html=render_blank_markup(token.identifier, job, True, batch_request.json_encoder), job=job, ), }
def test_calls_on_error_on_unhealthy_service(self, spy_plugin_controller, batch_request): batch_request.render('MyComponent.js', {'foo': 'bar'}) with mock.patch( 'fido.fetch' ) as mock_fetch, mock.patch( 'traceback.format_tb' ) as mock_format_tb: mock_fetch.return_value.wait.return_value.json.side_effect = NetworkError('oh no') batch_request.submit() spy_plugin_controller.on_error.assert_called_once_with( HypernovaError( name="<class 'fido.exceptions.NetworkError'>", message='oh no', stack=mock_format_tb.return_value, ), batch_request.jobs, )
def test_batch_request_with_unhealthy_service( self, spy_plugin_controller, test_data, batch_request, mock_hypernova_query, ): data = test_data[0] job = Job(name='MyComponent.js', data=data[0], context={}) token = batch_request.render('MyComponent.js', data[0]) mock_hypernova_query.return_value.json.side_effect = HypernovaQueryError( 'oh no') response = batch_request.submit() if batch_request.max_batch_size is None: assert mock_hypernova_query.call_count == 1 else: # Division (rounded-up) up to get total number of calls jobs_count = len(batch_request.jobs) max_batch_size = batch_request.max_batch_size batch_count = (jobs_count + (max_batch_size - 1)) // max_batch_size assert mock_hypernova_query.call_count == batch_count mock_hypernova_query.assert_called_with(mock.ANY, mock.ANY, mock.ANY, batch_count == 1, {}) assert response == { token.identifier: JobResult( error=HypernovaError( name='HypernovaQueryError', message='oh no', stack=mock.ANY, ), html=render_blank_markup(token.identifier, job, True, batch_request.json_encoder), job=job, ), }
def test_calls_on_error(self, spy_plugin_controller, test_data, batch_request, mock_hypernova_query): data = test_data[0] batch_request.render('MyComponent.js', data[0]) fake_response_json = { 'error': { 'name': 'EverythingIsOnFire', 'message': 'ya goofed', 'stack': [] } } mock_hypernova_query.return_value.json.return_value = fake_response_json batch_request.submit() spy_plugin_controller.on_error.assert_called_once_with( HypernovaError( name='EverythingIsOnFire', message='ya goofed', stack=[], ), batch_request.jobs, batch_request.pyramid_request)
def test_calls_on_error(self, spy_plugin_controller, batch_request): batch_request.render('MyComponent.js', {'foo': 'bar'}) fake_response_json = { 'error': { 'name': 'EverythingIsOnFire', 'message': 'ya goofed', 'stack': [] } } with mock.patch('fido.fetch') as mock_fetch: mock_fetch.return_value.wait.return_value.json.return_value = fake_response_json batch_request.submit() spy_plugin_controller.on_error.assert_called_once_with( HypernovaError( name='EverythingIsOnFire', message='ya goofed', stack=[], ), batch_request.jobs, )