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_mlb_signal_hup(self): """ When the marathon-lb client is used to send a SIGHUP signal to marathon-lb, all the correct API endpoints are called. """ d = self.cleanup_d(self.client.mlb_signal_hup()) for lb in ['lb1', 'lb2']: request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='POST', url='http://%s:9090/_mlb_signal/hup' % (lb,))) request.setResponseCode(200) request.setHeader('content-type', 'text/plain') request.write(b'Sent SIGHUP signal to marathon-lb') request.finish() responses = yield d self.assertThat(len(responses), Equals(2)) for response in responses: self.assertThat(response.code, Equals(200)) self.assertThat(response.headers, HasHeader( 'content-type', ['text/plain'])) response_text = yield response.text() self.assertThat(response_text, Equals('Sent SIGHUP signal to marathon-lb'))
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_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_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(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_request_partial_failure(self): """ When a request is made and an error status code is returned from some (but not all) of the matathon-lb instances, then the request returns the list of responses with a None value for the unhappy request. """ d = self.cleanup_d(self.client.request('GET', path='/my-path')) lb1_request = yield self.requests.get() self.assertThat(lb1_request, HasRequestProperties( method='GET', url='http://lb1:9090/my-path')) lb2_request = yield self.requests.get() self.assertThat(lb2_request, HasRequestProperties( method='GET', url='http://lb2:9090/my-path')) # Fail the first one lb1_request.setResponseCode(500) lb1_request.setHeader('content-type', 'text/plain') lb1_request.write(b'Internal Server Error') lb1_request.finish() # ...but succeed the second lb2_request.setResponseCode(200) lb2_request.setHeader('content-type', 'text/plain') lb2_request.write(b'Yes, I work') lb2_request.finish() responses = yield d self.assertThat(responses, HasLength(2)) lb1_response, lb2_response = responses self.assertThat(lb1_response, Is(None)) self.assertThat(lb2_response, MatchesStructure( code=Equals(200), headers=HasHeader('content-type', ['text/plain']) )) lb2_response_content = yield lb2_response.content() self.assertThat(lb2_response_content, Equals(b'Yes, I work')) flush_logged_errors(HTTPError)
def test_params_precedence_over_url_query(self): """ When query parameters are specified as both the params kwarg and in the URL, the params kwarg takes precedence. """ self.cleanup_d(self.client.request( 'GET', self.uri('/hello?from=mars'), params={'from': 'earth'})) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/hello'), query={'from': ['earth']})) request.setResponseCode(200) request.finish()
def test_request_url(self): """ When a request is made with the url parameter set, that parameter should be used as the base URL. """ self.cleanup_d(self.client.request( 'GET', path='/hello', url='http://localhost:9000')) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url='http://localhost:9000/hello')) request.setResponseCode(200) request.finish()
def test_url_query_as_params(self): """ When query parameters are specified in the URL, those parameters are reflected in the request. """ self.cleanup_d(self.client.request( 'GET', self.uri('/hello?from=earth'))) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/hello'), query={'from': ['earth']})) request.setResponseCode(200) request.finish()
def test_get_apps(self): """ When we request the list of apps from Marathon, we should receive the list of apps with some information. """ d = self.cleanup_d(self.client.get_apps()) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/v2/apps'))) apps = { 'apps': [ { 'id': '/product/us-east/service/myapp', 'cmd': 'env && sleep 60', 'constraints': [ [ 'hostname', 'UNIQUE', '' ] ], 'container': None, 'cpus': 0.1, 'env': { 'LD_LIBRARY_PATH': '/usr/local/lib/myLib' }, 'executor': '', 'instances': 3, 'mem': 5.0, 'ports': [ 15092, 14566 ], 'tasksRunning': 0, 'tasksStaged': 1, 'uris': [ 'https://raw.github.com/mesosphere/marathon/master/' 'README.md' ], 'version': '2014-03-01T23:42:20.938Z' } ] } json_response(request, apps) res = yield d self.assertThat(res, Equals(apps['apps']))
def test_url_overrides(self): """ When URL parts are overridden via keyword arguments, those overrides should be reflected in the request. """ self.cleanup_d(self.client.request( 'GET', 'http://example.com:8000/hello#section1', scheme='https', host='example2.com', port='9000', path='/goodbye', fragment='section2')) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url='https://example2.com:9000/goodbye#section2')) request.setResponseCode(200) request.finish()
def test_auth(self): """ When basic auth credentials are specified as the auth kwarg, the encoded credentials are present in the request headers. """ self.cleanup_d(self.client.request( 'GET', path='/hello', auth=('user', 'pa$$word'))) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/hello'))) self.assertThat( request.requestHeaders, HasHeader('Authorization', ['Basic dXNlcjpwYSQkd29yZA=='])) request.setResponseCode(200) request.finish()
def test_url_userinfo_as_auth(self): """ When basic auth credentials are specified in the URL, the encoded credentials are present in the request headers. """ self.cleanup_d(self.client.request( 'GET', 'http://*****:*****@localhost:8000/hello')) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/hello'))) self.assertThat( request.requestHeaders, HasHeader('Authorization', ['Basic dXNlcjpwYSQkd29yZA=='])) request.setResponseCode(200) request.finish()
def test_auth_precedence_over_url_userinfo(self): """ When basic auth credentials are specified as both the auth kwarg and in the URL, the credentials in the auth kwarg take precedence. """ self.cleanup_d(self.client.request( 'GET', 'http://*****:*****@localhost:8000/hello', auth=('user', 'pa$$word'))) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/hello'))) self.assertThat( request.requestHeaders, HasHeader('Authorization', ['Basic dXNlcjpwYSQkd29yZA=='])) request.setResponseCode(200) request.finish()
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_request_success(self): """ When a request is made, it is made to all marathon-lb instances and the responses are returned. """ 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(200) request.finish() responses = yield d self.assertThat(responses, HasLength(2)) for response in responses: self.assertThat(response.code, Equals(200))
def test_get_json_field(self): """ When get_json_field is used to make a request, the response is deserialized from JSON and the value of the specified field is returned. """ 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, { 'field-key': 'field-value', 'other-field-key': 'do-not-care' }) res = yield d self.assertThat(res, Equals('field-value'))
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_request_json_data(self): """ When a request is made with the json_data parameter set, that data should be sent as JSON and the content-type header should be set to indicate this. """ self.cleanup_d(self.client.request( 'GET', path='/hello', json_data={'test': 'hello'})) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/hello'))) self.assertThat(request.requestHeaders, HasHeader( 'content-type', ['application/json'])) self.assertThat(request.requestHeaders, HasHeader('accept', ['application/json'])) self.assertThat(read_request_json(request), Equals({'test': 'hello'})) request.setResponseCode(200) request.finish()
def test_request(self): """ When a request is made, it should be made with the correct method, address and headers, and should contain an empty body. The response should be returned. """ d = self.cleanup_d(self.client.request('GET', path='/hello')) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/hello'))) self.assertThat(request.content.read(), Equals(b'')) request.setResponseCode(200) request.write(b'hi\n') request.finish() response = yield d text = yield response.text() self.assertThat(text, Equals('hi\n'))
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_request_debug_log(self): """ When a request is made in debug mode, things should run smoothly. (Don't really want to check the log output here, just that things don't break.) """ self.client.debug = True d = self.cleanup_d(self.client.request('GET', path='/hello')) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url=self.uri('/hello'))) self.assertThat(request.content.read(), Equals(b'')) request.setResponseCode(200) request.write(b'hi\n') request.finish() response = yield d text = yield response.text() self.assertThat(text, Equals('hi\n'))
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_request_success(self): """ When we make a request and there are multiple Marathon endpoints specified, the first endpoint is used. """ agent = PerLocationAgent() agent.add_agent(b'localhost:8080', self.fake_server.get_agent()) agent.add_agent(b'localhost:9090', FailingAgent()) client = MarathonClient( ['http://localhost:8080', 'http://localhost:9090'], client=treq_HTTPClient(agent)) d = self.cleanup_d(client.request('GET', path='/my-path')) request = yield self.requests.get() self.assertThat(request, HasRequestProperties( method='GET', url='http://localhost:8080/my-path')) request.setResponseCode(200) request.finish() yield d
def test_json_content(self): """ When a request is made with the json_content callback and the 'application/json' content type is set in the response headers then the JSON should be successfully parsed. """ 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/json') request.write(json.dumps({}).encode('utf-8')) request.finish() response = yield d self.assertThat(response, Equals({}))
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)