def test_async_response(self): """ FakeHttpServer supports asynchronous responses. """ requests = [] fake_http = FakeHttpServer( lambda req: requests.append(req) or NOT_DONE_YET) response1_d = fake_http.get_agent().request( "GET", b"http://example.com/hello/1") response2_d = fake_http.get_agent().request( "HEAD", b"http://example.com/hello/2") # Wait for the requests to arrive. yield wait0() [request1, request2] = requests self.assertNoResult(response1_d) self.assertNoResult(response2_d) self.assertEqual(request1.method, "GET") self.assertEqual(request1.path, b"http://example.com/hello/1") self.assertEqual(request2.method, "HEAD") self.assertEqual(request2.path, b"http://example.com/hello/2") # Send a response to the second request. request2.finish() response2 = yield response2_d self.assertNoResult(response1_d) yield self.assert_response(response2, 200, "") # Send a response to the first request. request1.write("Thank you for waiting.") request1.finish() response1 = yield response1_d yield self.assert_response(response1, 200, "Thank you for waiting.")
def test_json_content_incorrect_content_type(self): """ When a request is made with the json_content callback and the content-type header is set to a value other than 'application/json' in the response headers then an error should be raised. """ d = self.cleanup_d(self.client.request('GET', path='/hello')) d.addCallback(json_content) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/hello'))) self.assertThat(request.requestHeaders, HasHeader('accept', ['application/json'])) request.setResponseCode(200) request.setHeader('Content-Type', 'application/octet-stream') request.write(json.dumps({}).encode('utf-8')) request.finish() yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Expected header "Content-Type" to be "application/json" but ' 'found "application/octet-stream" instead')))
def test_json_content_missing_content_type(self): """ When a request is made with the json_content callback and the content-type header is not set in the response headers then an error should be raised. """ d = self.cleanup_d(self.client.request('GET', path='/hello')) d.addCallback(json_content) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/hello'))) self.assertThat(request.requestHeaders, HasHeader('accept', ['application/json'])) request.setResponseCode(200) # Twisted will set the content type to "text/html" by default but this # can be disabled by setting the default content type to None: # https://twistedmatrix.com/documents/current/api/twisted.web.server.Request.html#defaultContentType request.defaultContentType = None request.write(json.dumps({}).encode('utf-8')) request.finish() yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Expected header "Content-Type" to be ' '"application/json" but header not found in response')))
def test_get_events(self): """ When a request is made to Marathon's event stream, a callback should receive JSON-decoded data before the connection is closed. """ data = [] d = self.cleanup_d(self.client.get_events({'test': data.append})) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/v2/events'))) self.assertThat(request.requestHeaders, HasHeader('accept', ['text/event-stream'])) request.setResponseCode(200) request.setHeader('Content-Type', 'text/event-stream') json_data = {'hello': 'world'} request.write(b'event: test\n') request.write(b'data: %s\n' % (json.dumps(json_data).encode('utf-8'),)) request.write(b'\n') yield wait0() self.assertThat(data, Equals([json_data])) request.finish() yield d # Expect request.finish() to result in a logged failure flush_logged_errors(ResponseDone)
def test_get_events_non_200(self): """ When a request is made to Marathon's event stream, and a non-200 response code is returned, an error should be raised. """ data = [] d = self.cleanup_d(self.client.get_events({'test': data.append})) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/v2/events'), query={'event_type': ['test']})) self.assertThat(request.requestHeaders, HasHeader('accept', ['text/event-stream'])) request.setResponseCode(202) request.setHeader('Content-Type', 'text/event-stream') json_data = {'hello': 'world'} write_json_event(request, 'test', json_data) yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Non-200 response code (202) for url: ' 'http://localhost:8080/v2/events?event_type=test'))) self.assertThat(data, Equals([])) request.finish() yield d
def test_get_events_incorrect_content_type(self): """ When a request is made to Marathon's event stream, and the content-type header value returned is not "text/event-stream", an error should be raised. """ data = [] d = self.cleanup_d(self.client.get_events({'test': data.append})) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/v2/events'), query={'event_type': ['test']})) self.assertThat(request.requestHeaders, HasHeader('accept', ['text/event-stream'])) request.setResponseCode(200) request.setHeader('Content-Type', 'application/json') json_data = {'hello': 'world'} write_json_event(request, 'test', json_data) yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Expected header "Content-Type" to be "text/event-stream" but ' 'found "application/json" instead'))) self.assertThat(data, Equals([])) request.finish() yield d
def test_get_events_incorrect_content_type(self): """ When a request is made to Marathon's event stream, and the content-type header value returned is not "text/event-stream", an error should be raised. """ data = [] d = self.cleanup_d(self.client.get_events({'test': data.append})) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/v2/events'))) self.assertThat(request.requestHeaders, HasHeader('accept', ['text/event-stream'])) request.setResponseCode(200) request.setHeader('Content-Type', 'application/json') json_data = {'hello': 'world'} request.write(b'event: test\n') request.write(b'data: %s\n' % (json.dumps(json_data).encode('utf-8'),)) request.write(b'\n') yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Expected header "Content-Type" to be "text/event-stream" but ' 'found "application/json" instead'))) self.assertThat(data, Equals([])) request.finish() yield d
def test_get_events_non_200(self): """ When a request is made to Marathon's event stream, and a non-200 response code is returned, an error should be raised. """ data = [] d = self.cleanup_d(self.client.get_events({'test': data.append})) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/v2/events'))) self.assertThat(request.requestHeaders, HasHeader('accept', ['text/event-stream'])) request.setResponseCode(202) request.setHeader('Content-Type', 'text/event-stream') json_data = {'hello': 'world'} request.write(b'event: test\n') request.write(b'data: %s\n' % (json.dumps(json_data).encode('utf-8'),)) request.write(b'\n') yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Non-200 response code (202) for url: ' 'http://localhost:8080/v2/events'))) self.assertThat(data, Equals([])) request.finish() yield d
def test_get_events_no_callback(self): """ When a request is made to Marathon's event stream, a callback should not receive event data if there is no callback for the event type. """ data = [] d = self.cleanup_d(self.client.get_events({'test': data.append})) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/v2/events'), query={'event_type': ['test']})) self.assertThat(request.requestHeaders, HasHeader('accept', ['text/event-stream'])) request.setResponseCode(200) request.setHeader('Content-Type', 'text/event-stream') json_data = {'hello': 'world'} request.write(b'event: not_test\n') request.write( 'data: {}\n'.format(json.dumps(json_data)).encode('utf-8')) request.write(b'\n') yield wait0() self.assertThat(data, Equals([])) request.finish() yield d # Expect request.finish() to result in a logged failure flush_logged_errors(ResponseDone)
def test_get_json_field_missing_content_type(self): """ When get_json_field is used to make a request and the content-type header is not set in the response headers then an error should be raised. """ d = self.cleanup_d( self.client.get_json_field('field-key', path='/my-path')) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/my-path'))) request.setResponseCode(200) # Twisted will set the content type to "text/html" by default but this # can be disabled by setting the default content type to None: # https://twistedmatrix.com/documents/current/api/twisted.web.server.Request.html#defaultContentType request.defaultContentType = None request.write(json.dumps({}).encode('utf-8')) request.finish() yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Expected header "Content-Type" to be ' '"application/json" but header not found in response')))
def test_get_json_field_error(self): """ When get_json_field is used to make a request but the response code indicates an error, an HTTPError should be raised. """ d = self.cleanup_d( self.client.get_json_field('field-key', path='/my-path')) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/my-path'))) request.setResponseCode(404) request.write(b'Not found\n') request.finish() yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, '404 Client Error for url: %s' % self.uri('/my-path'))))
def test_server_abort(self): """ If the server aborts, the client's connection is also lost. """ fake_server = FakeServer(self.server_factory) conn, client = self.connect_client(fake_server) finished_d = conn.await_finished() self.assertNoResult(finished_d) # The disconnection gets scheduled, but doesn't actually happen until # the next reactor tick. conn.server_protocol.transport.abortConnection() self.assert_connected(conn, client) self.assertNoResult(finished_d) # Allow the reactor to run so the disconnection gets processed. yield wait0() self.assert_disconnected(conn, server_reason=ConnectionAborted) self.successResultOf(finished_d)
def test_request_fallback_all_failed(self): """ When we make a request and there are multiple Marathon endpoints specified, and all the endpoints fail, the last failure should be returned. """ agent = PerLocationAgent() agent.add_agent(b'localhost:8080', FailingAgent(RuntimeError('8080'))) agent.add_agent(b'localhost:9090', FailingAgent(RuntimeError('9090'))) client = MarathonClient( ['http://localhost:8080', 'http://localhost:9090'], client=treq_HTTPClient(agent)) d = self.cleanup_d(client.request('GET', path='/my-path')) yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( RuntimeError, '9090'))) flush_logged_errors(RuntimeError)
def test_server_error_response(self): """ When a request is made and the raise_for_status callback is added and a 5xx response code is returned, a HTTPError should be raised to indicate a server error. """ d = self.cleanup_d(self.client.request('GET', path='/hello')) d.addCallback(raise_for_status) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/hello'))) request.setResponseCode(502) request.write(b'Bad gateway\n') request.finish() yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, '502 Server Error for url: %s' % self.uri('/hello'))))
def test_get_events_multiple_callbacks(self): """ When a request is made to Marathon's event stream, and there are events for multiple callbacks, those callbacks should receive JSON-decoded data for each event. """ data1 = [] data2 = [] d = self.cleanup_d(self.client.get_events({ 'test1': data1.append, 'test2': data2.append })) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/v2/events'))) self.assertThat(request.requestHeaders, HasHeader('accept', ['text/event-stream'])) request.setResponseCode(200) request.setHeader('Content-Type', 'text/event-stream') json_data1 = {'hello': 'world'} request.write(b'event: test1\n') request.write(b'data: %s\n' % (json.dumps(json_data1).encode('utf-8'))) request.write(b'\n') json_data2 = {'hello': 'computer'} request.write(b'event: test2\n') request.write(b'data: %s\n' % (json.dumps(json_data2).encode('utf-8'))) request.write(b'\n') yield wait0() self.assertThat(data1, Equals([json_data1])) self.assertThat(data2, Equals([json_data2])) request.finish() yield d # Expect request.finish() to result in a logged failure flush_logged_errors(ResponseDone)
def test_get_json_field_missing(self): """ When get_json_field is used to make a request, the response is deserialized from JSON and if the specified field is missing, an error is raised. """ d = self.cleanup_d( self.client.get_json_field('field-key', path='/my-path')) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/my-path'))) json_response(request, {'other-field-key': 'do-not-care'}) yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( KeyError, '\'Unable to get value for "field-key" from Marathon response: ' '"{"other-field-key": "do-not-care"}"\'' )))
def test_get_events_multiple_events(self): """ When a request is made to Marathon's event stream, and there are multiple events for a single callback, that callback should receive JSON-decoded data for each event. """ data = [] d = self.cleanup_d(self.client.get_events({'test': data.append})) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/v2/events'), query={'event_type': ['test']})) self.assertThat(request.requestHeaders, HasHeader('accept', ['text/event-stream'])) request.setResponseCode(200) request.setHeader('Content-Type', 'text/event-stream') json_data1 = {'hello': 'world'} request.write(b'event: test\n') request.write( 'data: {}\n'.format(json.dumps(json_data1)).encode('utf-8')) request.write(b'\n') json_data2 = {'hi': 'planet'} request.write( 'data: {}\n'.format(json.dumps(json_data2)).encode('utf-8')) request.write(b'event: test\n') request.write(b'\n') yield wait0() self.assertThat(data, Equals([json_data1, json_data2])) request.finish() yield d # Expect request.finish() to result in a logged failure flush_logged_errors(ResponseDone)
def test_get_events_multiple_callbacks(self): """ When a request is made to Marathon's event stream, and there are events for multiple callbacks, those callbacks should receive JSON-decoded data for each event. """ data1 = [] data2 = [] d = self.cleanup_d(self.client.get_events({ 'test1': data1.append, 'test2': data2.append })) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/v2/events'), query={'event_type': ['test1', 'test2']})) self.assertThat(request.requestHeaders, HasHeader('accept', ['text/event-stream'])) request.setResponseCode(200) request.setHeader('Content-Type', 'text/event-stream') json_data1 = {'hello': 'world'} write_json_event(request, 'test1', json_data1) json_data2 = {'hello': 'computer'} write_json_event(request, 'test2', json_data2) yield wait0() self.assertThat(data1, Equals([json_data1])) self.assertThat(data2, Equals([json_data2])) request.finish() yield d # Expect request.finish() to result in a logged failure flush_logged_errors(ResponseDone)
def test_get_json_field_incorrect_content_type(self): """ When get_json_field is used to make a request and the content-type header is set to a value other than 'application/json' in the response headers then an error should be raised. """ d = self.cleanup_d( self.client.get_json_field('field-key', path='/my-path')) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/my-path'))) request.setResponseCode(200) request.setHeader('Content-Type', 'application/octet-stream') request.write(json.dumps({}).encode('utf-8')) request.finish() yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( HTTPError, 'Expected header "Content-Type" to be "application/json" but ' 'found "application/octet-stream" instead')))
def test_request_failure(self): """ When the requests to all the marathon-lb instances have a bad status code then an error should be raised. """ d = self.cleanup_d(self.client.request('GET', path='/my-path')) for lb in ['lb1', 'lb2']: request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url='http://%s:9090/my-path' % (lb,))) request.setResponseCode(500) request.setHeader('content-type', 'text/plain') request.write(b'Internal Server Error') request.finish() yield wait0() self.assertThat(d, failed(WithErrorTypeAndMessage( RuntimeError, 'Failed to make a request to all marathon-lb instances' ))) flush_logged_errors(HTTPError)