def test_get_apps(self): """ When the list of apps is requested, a list of apps added via add_app() should be returned. """ app = { 'id': '/my-app_1', 'cmd': 'sleep 50', 'tasks': [{ "host": "host1.local", "id": "my-app_1-1396592790353", "ports": [] }, { "host": "host2.local", "id": "my-app_1-1396592784349", "ports": [] }] } self.marathon.add_app(app) response = self.client.get('http://localhost/v2/apps') assert_that( response, succeeded( MatchesAll( IsJsonResponseWithCode(200), After(json_content, succeeded(Equals({'apps': [app]}))))))
def WithErrorTypeAndMessage(error_type, message): """ Check that a Twisted failure was caused by a certain error type with a certain message. """ return MatchesAll(MatchesStructure(value=IsInstance(error_type)), After(methodcaller('getErrorMessage'), Equals(message)))
def test_async_error_propagates(self): """ If an unhandled asynchronous error occurs in an interceptor it propogates along the execution. """ interceptors = [tracer('a'), tracer('b'), thrower('c'), tracer('d')] self.assertThat(execute(empty_context, interceptors), failed(After(lambda f: f.type, Is(TracingError))))
def test_tuple_list(self): """ The indexed items of lists are their children. """ node = (u'key', [u'a', u'b', u'c']) self.assertThat( # The ignores intentionally do nothing. get_children({2, u'c'}, node), After(list, Equals([(0, u'a'), (1, u'b'), (2, u'c')])))
def test_body(self): """ ``body`` is a file-like containing the request content. """ request = fakeNevowRequest(body=b'hello') self.assertThat( _nevow_request_to_request_map(request), ContainsDict({ 'body': After(lambda x: x.read(), Equals(b'hello')), 'content_length': Equals(5) }))
def test_get_apps_empty(self): """ When the list of apps is requested and there are no apps, an empty list of apps should be returned. """ response = self.client.get('http://localhost/v2/apps') assert_that( response, succeeded( MatchesAll( IsJsonResponseWithCode(200), After(json_content, succeeded(Equals({'apps': []}))))))
def test_get_events(self): """ When a request is made to the event stream endpoint, an SSE stream should be received in response and an event should be fired that indicates that the stream was attached to. """ response = self.client.get('http://localhost/v2/events', headers={'Accept': 'text/event-stream'}) assert_that( response, succeeded( MatchesAll( IsSseResponse(), After( partial(collect_events, 'event_stream_attached'), MatchesListwise([ After( json.loads, IsMarathonEvent( 'event_stream_attached', remoteAddress=Equals('127.0.0.1'))) ])))))
def test_add_app_triggers_api_post_event(self): """ When an app is added to the underlying fake Marathon, an ``api_post_event`` should be received by any event listeners. """ response = self.client.get('http://localhost/v2/events', headers={'Accept': 'text/event-stream'}) assert_that(response, succeeded(IsSseResponse())) app = { 'id': '/my-app_1', 'labels': { 'HAPROXY_GROUP': 'external', 'MARATHON_ACME_0_DOMAIN': 'example.com' }, 'portDefinitions': [{ 'port': 9000, 'protocol': 'tcp', 'labels': {} }] } self.marathon.add_app(app) assert_that( response, succeeded( After( partial(collect_events, 'api_post_event'), MatchesListwise([ After( json.loads, IsMarathonEvent('api_post_event', clientIp=Is(None), uri=Equals('/v2/apps/my-app_1'), appDefinition=Equals(app))) ]))))
def test_namespaced(self): """ `namespaced` creates a function that when called produces a namespaced name. """ self.assertThat( namespaced(u'foo'), MatchesAll( MatchesPredicate(callable, '%s is not callable'), After( lambda f: f(u'bar'), MatchesAll( MatchesListwise([Equals(u'foo'), Equals(u'bar')]), MatchesStructure(prefix=Equals(u'foo'), name=Equals(u'bar'))))))
def IsMarathonEvent(event_type, **kwargs): """ Match a dict (deserialized from JSON) as a Marathon event. Matches the event type and checks for a recent timestamp. :param event_type: The event type ('eventType' field value) :param kwargs: Any other matchers to apply to the dict """ matching_dict = { 'eventType': Equals(event_type), 'timestamp': After(_parse_marathon_event_timestamp, matches_time_or_just_before(datetime.utcnow())) } matching_dict.update(kwargs) return MatchesDict(matching_dict)
def test_health_unhealthy(self): """ When a GET request is made to the health endpoint, and the health handler reports that the service is unhealthy, a 503 status code should be returned together with the JSON message from the handler. """ self.server.set_health_handler( lambda: Health(False, {'error': "I'm sad :("})) response = self.client.get('http://localhost/health') assert_that( response, succeeded( MatchesAll( IsJsonResponseWithCode(503), After(json_content, succeeded(Equals({'error': "I'm sad :("}))))))
def Multipart(content_length, name, headers, body, character_encoding=Equals('latin1'), filename=Is(None), content_type=Is(None)): """ """ return MatchesDict({ u'content_type': content_type, u'content_length': content_length, u'name': name, u'character_encoding': character_encoding, u'filename': filename, u'headers': headers, u'body': After(lambda x: x.read(), body), })
def test_health_handler_unset(self): """ When a GET request is made to the health endpoint, and the health handler hasn't been set, a 501 status code should be returned together with a JSON message that explains that the handler is not set. """ response = self.client.get('http://localhost/health') assert_that( response, succeeded( MatchesAll( IsJsonResponseWithCode(501), After( json_content, succeeded( Equals({ 'error': 'Cannot determine service health: no handler set' }))))))
def HasRequestProperties(method=None, url=None, query={}): """ Check if a HTTP request object has certain properties. Parses the query dict from the request URI rather than using the request "args" property as the args do not include query parameters that have no value. :param str method: The HTTP method. :param str url: The HTTP URL, without any query parameters. Should already be percent encoded. :param dict query: A dictionary of HTTP query parameters. """ return MatchesStructure(method=Equals(method.encode('ascii')), path=Equals(url.encode('ascii')), uri=After(lambda u: urisplit(u).getquerydict(), Equals(query)))
def test_get_apps_check_called(self): """ When a client makes a call to the GET /v2/apps API, a flag should be set to indicate that the API has been called. Checking the flag should reset it. """ # The flag should start out False assert_that(self.marathon_api.check_called_get_apps(), Equals(False)) # Make a call to get_apps() response = self.client.get('http://localhost/v2/apps') assert_that( response, succeeded( MatchesAll( IsJsonResponseWithCode(200), After(json_content, succeeded(Equals({'apps': []})))))) # After the call the flag should be True assert_that(self.marathon_api.check_called_get_apps(), Equals(True)) # Checking the flag should reset it to False assert_that(self.marathon_api.check_called_get_apps(), Equals(False))
def test_signal_usr1(self): """ When a client calls the ``/mlb_signal/usr1`` endpoint, the correct response should be returned and the ``signalled_usr1`` flag set True. """ assert_that(self.marathon_lb.check_signalled_usr1(), Equals(False)) response = self.client.get('http://localhost/_mlb_signal/usr1') assert_that( response, succeeded( MatchesAll( MatchesStructure(code=Equals(200), headers=HasHeader('content-type', ['text/plain'])), After( methodcaller('text'), succeeded( Equals('Sent SIGUSR1 signal to marathon-lb')))))) assert_that(self.marathon_lb.check_signalled_usr1(), Equals(True)) # Signalled flag should be reset to false after it is checked assert_that(self.marathon_lb.check_signalled_usr1(), Equals(False))
def test_responder_resource_child(self): """ When a GET request is made to the ACME challenge path, and the responder resource has a child resource at the correct path, the value of the resource should be returned. """ self.responder_resource.putChild(b'foo', Data(b'bar', 'text/plain')) response = self.client.get( 'http://localhost/.well-known/acme-challenge/foo') assert_that( response, succeeded( MatchesAll( MatchesStructure(code=Equals(200), headers=HasHeader('Content-Type', ['text/plain'])), After(methodcaller('content'), succeeded(Equals(b'bar')))))) # Sanity check that a request to a different subpath does not succeed response = self.client.get( 'http://localhost/.well-known/acme-challenge/baz') assert_that(response, succeeded(MatchesStructure(code=Equals(404))))
def EnterStage(matcher, context=empty_context): """ """ return MatchesAll( IsInstance(Interceptor), After(lambda interceptor: interceptor.enter(context), matcher))
def test_get_events_lost_connection(self): """ When two connections are made to the event stream, the first connection should receive events for both connections attaching to the stream. Then, when the first connection is disconnected, the second should receive a detach event for the first. """ response1_d = self.client.get('http://localhost/v2/events', headers={'Accept': 'text/event-stream'}) # First listener attaches and receives event it attached assert_that(response1_d, succeeded(IsSseResponse())) response1 = response1_d.result attach_data1 = [] detach_data1 = [] handler1 = dict_handler({ 'event_stream_attached': attach_data1.append, 'event_stream_detached': detach_data1.append }) finished, protocol = _sse_content_with_protocol(response1, handler1) response2_d = self.client.get('http://localhost/v2/events', headers={'Accept': 'text/event-stream'}) assert_that(response2_d, succeeded(IsSseResponse())) response2 = response2_d.result attach_data2 = [] detach_data2 = [] handler2 = dict_handler({ 'event_stream_attached': attach_data2.append, 'event_stream_detached': detach_data2.append }) sse_content(response2, handler2) # Close request 1's connection # FIXME: Currently the only way to get the underlying transport so that # we can simulate a lost connection is to get the transport that the # SseProtocol receives. This transport is actually a # TransportProxyProducer (because that's what HTTP11ClientProtocol # gives our protocol). Get the actual wrapped transport from the # _producer attribute. protocol.transport._producer.loseConnection() # Flush the client so that the disconnection propagates self.client.flush() assert_that(finished, succeeded(Is(None))) # Assert request 1's response data assert_that( attach_data1, MatchesListwise([ # First attach event on request 1 from itself connecting After( json.loads, IsMarathonEvent('event_stream_attached', remoteAddress=Equals('127.0.0.1'))), # Second attach event on request 1 from request 2 connecting After( json.loads, IsMarathonEvent('event_stream_attached', remoteAddress=Equals('127.0.0.1'))) ])) # Request 1 shouldn't receive any detach events assert_that(detach_data1, Equals([])) # Now look at request 2's events # Attach event only for itself assert_that( attach_data2, MatchesListwise([ After( json.loads, IsMarathonEvent('event_stream_attached', remoteAddress=Equals('127.0.0.1'))) ])) # Detach event for request 1 assert_that( detach_data2, MatchesListwise([ After( json.loads, IsMarathonEvent('event_stream_detached', remoteAddress=Equals('127.0.0.1'))) ]))
def test_default(self): """ Default content parsers. """ interceptors = [body_params()] request = m( content_type= 'multipart/form-data; boundary=---------------------------114772229410704779042051621609', body=open_test_data('data/multipart_request')) context = empty_context.set(REQUEST, request) self.assertThat( execute(context, interceptors), succeeded( ContainsDict({ REQUEST: ContainsDict({ 'multipart_params': MatchesDict({ u'name': Multipart(content_length=Equals(8), name=Equals(u'name'), headers=MatchesDict({ u'Content-Disposition': Equals(u'form-data; name="name"'), }), body=Equals(b'Some One')), u'email': Multipart(content_length=Equals(16), name=Equals(u'email'), headers=MatchesDict({ u'Content-Disposition': Equals(u'form-data; name="email"'), }), body=Equals(b'*****@*****.**')), u'avatar': Multipart( content_length=Equals(869), content_type=Equals(u'image/png'), filename=Equals(u'smiley-cool.png'), name=Equals(u'avatar'), headers=MatchesDict({ u'Content-Type': Equals(u'image/png'), u'Content-Disposition': Equals( u'form-data; name="avatar"; filename="smiley-cool.png"' ), }), body=After( lambda x: hashlib.sha256(x).hexdigest(), Equals( b'25fbe073db80f71a13fb8e0a190a76c0fda494d18849fa6fa87ea5a0924baa07' ))), # XXX: This syntax isn't supported by the multipart # parser, multiple things with the same name are # overwritten. u'attachments[]': Always(), }), 'form_params': MatchesDict({ u'name': Equals(u'Some One'), u'email': Equals(u'*****@*****.**') }) }) })))
def is_response_with_body(body, content_type=None, code=200, method="read"): return MatchesAll( is_response(code, content_type), After(methodcaller(method), Equals(body)), )