def test_request_url_mix(self): @action(urls=[(None, '/action/1')], spec='''\ action my_action1 output int number ''') def my_action1(dummy_ctx, dummy_req): return {'number': 1} @action(urls=[('GET', '/action/{dummy_number}')], spec='''\ action my_action2 input int dummy_number output int number ''') def my_action2(dummy_ctx, dummy_req): return {'number': 2} app = Application() app.add_request(my_action1) app.add_request(my_action2) status, dummy_headers, response = app.request('GET', '/action/1') self.assertEqual(status, '200 OK') self.assertEqual(response, b'{"number":2}') status, dummy_headers, response = app.request('POST', '/action/1', wsgi_input=b'{}') self.assertEqual(status, '200 OK') self.assertEqual(response, b'{"number":1}')
def test_url_arg(self): # pylint: disable=invalid-name @action(method='GET', urls='/my_action/{a}', spec='''\ action my_action input int a int b output int sum ''') def my_action(unused_app, req): self.assertEqual(req['a'], 5) return {'sum': req['a'] + req['b']} app = Application() app.add_request(my_action) environ = {'wsgi.errors': StringIO()} status, headers, response = app.request('GET', '/my_action/5', query_string='b=7', environ=environ) self.assertEqual(status, '200 OK') self.assertEqual(sorted(headers), [('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"sum":12}') self.assertEqual(environ['wsgi.errors'].getvalue(), '') environ = {'wsgi.errors': StringIO()} status, headers, response = app.request('GET', '/my_action/5', query_string='a=3&b=7', environ=environ) self.assertEqual(status, '400 Bad Request') self.assertEqual(sorted(headers), [('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"error":"InvalidInput","message":"Duplicate URL argument member \'a\'"}') self.assertRegex( environ['wsgi.errors'].getvalue(), r"WARNING \[\d+ / \d+\] Duplicate URL argument member 'a' for action 'my_action'" )
def test_request_url_method(self): @action(urls=[('GET', '/my_action'), ('POST', '/my_action/')], spec='''\ action my_action ''') def my_action(dummy_ctx, dummy_req): pass app = Application() app.add_request(my_action) status, dummy_headers, response = app.request('GET', '/my_action') self.assertEqual(status, '200 OK') self.assertEqual(response, b'{}') status, dummy_headers, response = app.request('POST', '/my_action/', wsgi_input=b'{}') self.assertEqual(status, '200 OK') self.assertEqual(response, b'{}') status, dummy_headers, response = app.request('GET', '/my_action/') self.assertEqual(status, '405 Method Not Allowed') self.assertEqual(response, b'Method Not Allowed') status, dummy_headers, response = app.request('POST', '/my_action', wsgi_input=b'{}') self.assertEqual(status, '405 Method Not Allowed') self.assertEqual(response, b'Method Not Allowed') status, dummy_headers, response = app.request('PUT', '/my_action', wsgi_input=b'{}') self.assertEqual(status, '405 Method Not Allowed') self.assertEqual(response, b'Method Not Allowed')
def test_log_format_callable(self): def my_wsgi(environ, start_response): ctx = environ[Environ.CTX] ctx.log.warning('Hello log') start_response(HTTPStatus.OK, [('Content-Type', 'text/plain')]) return ['Hello'.encode('utf-8')] class MyFormatter: def __init__(self, ctx): assert ctx is not None @staticmethod def format(record): return record.getMessage() @staticmethod def formatTime(record, unused_datefmt=None): # pylint: disable=invalid-name return record.getMessage() @staticmethod def formatException(unused_exc_info): # pylint: disable=invalid-name return 'Bad' app = Application() app.add_request(Request(my_wsgi)) app.log_format = MyFormatter environ = {'wsgi.errors': StringIO()} status, headers, response = app.request('GET', '/my_wsgi', environ=environ) self.assertEqual(response, 'Hello'.encode('utf-8')) self.assertEqual(status, '200 OK') self.assertTrue(('Content-Type', 'text/plain') in headers) self.assertEqual(environ['wsgi.errors'].getvalue(), 'Hello log\n')
def test_error_bad_error(self): @action(spec='''\ action my_action errors MyError ''') def my_action(unused_app, unused_req): raise ActionError('MyBadError') app = Application() app.add_request(my_action) environ = {'wsgi.errors': StringIO()} status, headers, response = app.request('POST', '/my_action', wsgi_input=b'{}') self.assertEqual(status, '500 Internal Server Error') self.assertEqual(sorted(headers), [('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"error":"InvalidOutput","member":"error","message":"Invalid value \'MyBadError\' (type \'str\') ' 'for member \'error\', expected type \'my_action_error\'"}') self.assertEqual(environ['wsgi.errors'].getvalue(), '') app.validate_output = False environ = {'wsgi.errors': StringIO()} status, headers, response = app.request('POST', '/my_action', wsgi_input=b'{}') self.assertEqual(status, '400 Bad Request') self.assertEqual(sorted(headers), [('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"error":"MyBadError"}') self.assertEqual(environ['wsgi.errors'].getvalue(), '')
def test_request(self): def request1(environ, unused_start_response): ctx = environ[Environ.CTX] return ctx.response_text(HTTPStatus.OK, 'request1') def request2(environ, unused_start_response): ctx = environ[Environ.CTX] return ctx.response_text(HTTPStatus.OK, 'request2 ' + ctx.url_args['arg'] + ' ' + ctx.url_args.get('arg2', '?')) app = Application() app.add_request(Request(request1, urls=( ('GET', '/request1a'), (None, '/request1b') ))) app.add_request(Request(request2, urls=( ('GET', '/request2a/{arg}'), (None, '/request2b/{arg}/bar/{arg2}/bonk') ))) # Exact method and exact URL status, _, response = app.request('GET', '/request1a') self.assertEqual(status, '200 OK') self.assertEqual(response, b'request1') # Wrong method and exact URL status, _, response = app.request('POST', '/request1a') self.assertEqual(status, '405 Method Not Allowed') self.assertEqual(response, b'Method Not Allowed') # Any method and exact URL status, _, response = app.request('GET', '/request1b') self.assertEqual(status, '200 OK') self.assertEqual(response, b'request1') # Exact method and regex URL status, _, response = app.request('GET', '/request2a/foo') self.assertEqual(status, '200 OK') self.assertEqual(response, b'request2 foo ?') # Wrong method and regex URL status, _, response = app.request('POST', '/request2a/foo') self.assertEqual(status, '405 Method Not Allowed') self.assertEqual(response, b'Method Not Allowed') # Any method and regex URL status, _, response = app.request('POST', '/request2b/foo/bar/blue/bonk') self.assertEqual(status, '200 OK') self.assertEqual(response, b'request2 foo blue') # URL not found status, _, response = app.request('GET', '/request3') self.assertEqual(status, '404 Not Found') self.assertEqual(response, b'Not Found')
def test_request_exception(self): def request1(unused_environ, unused_start_response): raise Exception('') app = Application() app.add_request(Request(request1)) status, headers, response = app.request('GET', '/request1') self.assertEqual(status, '500 Internal Server Error') self.assertTrue(('Content-Type', 'text/plain') in headers) self.assertEqual(response, b'Internal Server Error')
def test_request_not_found(self): @request(doc=''' This is the request documentation. ''') def my_request(unused_environ, unused_start_response): pass application = Application() application.add_request(DocAction()) application.add_request(my_request) status, unused_headers, response = application.request('GET', '/doc', query_string='name=my_unknown_request') self.assertEqual(status, '404 Not Found') self.assertEqual(response, b'Unknown Request')
def test_error_none_output(self): @action(spec='''\ action my_action ''') def my_action(unused_app, unused_req): pass app = Application() app.add_request(my_action) status, headers, response = app.request('POST', '/my_action', wsgi_input=b'{}') self.assertEqual(status, '200 OK') self.assertEqual(sorted(headers), [('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{}')
def test_request_string_response(self): def string_response(environ, unused_start_response): ctx = environ[Environ.CTX] return ctx.response(HTTPStatus.OK, 'text/plain', 'Hello World') app = Application() app.add_request(Request(string_response)) environ = {'wsgi.errors': StringIO()} status, headers, response = app.request('GET', '/string_response', environ=environ) self.assertEqual(status, '500 Internal Server Error') self.assertListEqual(headers, [('Content-Type', 'text/plain')]) self.assertEqual(response, b'Internal Server Error') self.assertIn('response content cannot be of type str or bytes', environ['wsgi.errors'].getvalue())
def test_error_unexpected_custom(self): @action(wsgi_response=True, spec='''\ action my_action ''') def my_action(unused_app, unused_req): raise Exception('FAIL') app = Application() app.add_request(my_action) status, headers, response = app.request('POST', '/my_action', wsgi_input=b'{}') self.assertEqual(status, '500 Internal Server Error') self.assertEqual(sorted(headers), [('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"error":"UnexpectedError"}')
def test_decorator_unknown_action(self): @action def my_action(dummy_app, dummy_req): return {} self.assertTrue(isinstance(my_action, Action)) self.assertTrue(isinstance(my_action, Request)) app = Application() try: app.add_request(my_action) except AssertionError as exc: self.assertEqual(str(exc), "No spec defined for action 'my_action'") else: self.fail()
def test_decorator_other(self): # Action decorator with urls, custom response callback, and validate response bool @action(urls=('/foo',), wsgi_response=True, spec='''\ action my_action_default ''') def my_action_default(ctx, unused_req): return ctx.response_text(HTTPStatus.OK) app = Application() app.add_request(my_action_default) self.assertEqual(my_action_default.name, 'my_action_default') self.assertEqual(my_action_default.urls, (('POST', '/foo'),)) self.assertTrue(isinstance(my_action_default.model, ActionModel)) self.assertEqual(my_action_default.model.name, 'my_action_default') self.assertEqual(my_action_default.wsgi_response, True)
def test_error_raised_status(self): @action(spec='''\ action my_action errors MyError ''') def my_action(unused_app, unused_req): raise ActionError('MyError', message='My message', status=HTTPStatus.NOT_FOUND) app = Application() app.add_request(my_action) status, headers, response = app.request('POST', '/my_action', wsgi_input=b'{}') self.assertEqual(status, '404 Not Found') self.assertEqual(sorted(headers), [('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"error":"MyError","message":"My message"}')
def test_error_array_output(self): @action(spec='''\ action my_action ''') def my_action(unused_app, unused_req): return [] app = Application() app.add_request(my_action) status, headers, response = app.request('POST', '/my_action', wsgi_input=b'{}') self.assertEqual(status, '500 Internal Server Error') self.assertEqual(sorted(headers), [('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"error":"InvalidOutput","message":"Invalid value [] (type \'list\'), ' 'expected type \'my_action_output\'"}')
def test_decorator_other(self): # Action decorator with urls, custom response callback, and validate response bool @action(urls=('/foo',), wsgi_response=True) def my_action_default(app, dummy_req): return app.response_text('200 OK', 'OK') app = Application() app.specs.parse_string('''\ action my_action_default ''') app.add_request(my_action_default) self.assertEqual(my_action_default.name, 'my_action_default') self.assertEqual(my_action_default.urls, (('GET', '/foo'), ('POST', '/foo'))) self.assertTrue(isinstance(my_action_default.model, ActionModel)) self.assertEqual(my_action_default.model.name, 'my_action_default') self.assertEqual(my_action_default.wsgi_response, True)
def test_headers(self): @action(spec='''\ action my_action ''') def my_action(ctx, unused_req): ctx.add_header('MyHeader', 'MyInitialValue') ctx.add_header('MyHeader', 'MyValue') return {} app = Application() app.add_request(my_action) status, headers, response = app.request('POST', '/my_action') self.assertEqual(status, '200 OK') self.assertEqual(headers, [('Content-Type', 'application/json'), ('MyHeader', 'MyValue')]) self.assertEqual(response.decode('utf-8'), '{}')
def test_error_invalid_query_string(self): @action(method='GET', spec='''\ action my_action input int a ''') def my_action(unused_app, unused_req): return {} app = Application() app.add_request(my_action) status, headers, response = app.request('GET', '/my_action', query_string='a') self.assertEqual(status, '400 Bad Request') self.assertEqual(sorted(headers), [('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"error":"InvalidInput","message":"Invalid key/value pair \'a\'"}')
def test_error_response(self): @action(spec='''\ action my_action errors MyError ''') def my_action(unused_app, unused_req): return {'error': 'MyError'} app = Application() app.add_request(my_action) status, headers, response = app.request('POST', '/my_action', wsgi_input=b'{}') self.assertEqual(status, '500 Internal Server Error') self.assertEqual(sorted(headers), [('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"error":"InvalidOutput","message":"Unknown member \'error\'"}')
def test_error_raise_builtin(self): @action(spec='''\ action my_action ''') def my_action(unused_app, unused_req): raise ActionError('UnexpectedError', status=HTTPStatus.INTERNAL_SERVER_ERROR) app = Application() app.add_request(my_action) environ = {'wsgi.errors': StringIO()} status, headers, response = app.request('POST', '/my_action', wsgi_input=b'{}', environ=environ) self.assertEqual(status, '500 Internal Server Error') self.assertEqual(sorted(headers), [('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"error":"UnexpectedError"}') self.assertEqual(environ['wsgi.errors'].getvalue(), '')
def test_error_invalid_method(self): @action(spec='''\ action my_action input int a ''') def my_action(unused_app, unused_req): return {} app = Application() app.add_request(my_action) status, headers, response = app.request('FOO', '/my_action', wsgi_input=b'{"a": 7}') self.assertEqual(status, '405 Method Not Allowed') self.assertEqual(sorted(headers), [('Content-Type', 'text/plain')]) self.assertEqual(response.decode('utf-8'), 'Method Not Allowed')
def test_request_args(self): def request1(environ, unused_start_response): ctx = environ[Environ.CTX] self.assertEqual(environ['QUERY_STRING'], 'a=1&b=2') self.assertEqual(environ['wsgi.input'].read(), b'hello') ctx.log.warning('in request1') return ctx.response_text(HTTPStatus.OK, 'request1') app = Application() app.add_request(Request(request1)) environ = {'wsgi.errors': StringIO()} status, _, response = app.request('GET', '/request1', query_string='a=1&b=2', wsgi_input=b'hello', environ=environ) self.assertEqual(status, '200 OK') self.assertEqual(response, b'request1') self.assertIn('in request1', environ['wsgi.errors'].getvalue())
def test_error_raised(self): @action(spec='''\ action my_action errors MyError ''') def my_action(unused_app, unused_req): raise ActionError('MyError') app = Application() app.add_request(my_action) status, headers, response = app.request('POST', '/my_action', wsgi_input=b'{}') self.assertEqual(status, '400 Bad Request') self.assertEqual(sorted(headers), [('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"error":"MyError"}')
def test_decorator_spec(self): @action(spec='''\ action my_action ''') def my_action(unused_app, unused_req): return {} self.assertTrue(isinstance(my_action, Action)) self.assertTrue(isinstance(my_action, Request)) app = Application() app.add_request(my_action) self.assertEqual(my_action.name, 'my_action') self.assertEqual(my_action.urls, (('POST', '/my_action'),)) self.assertTrue(isinstance(my_action.model, ActionModel)) self.assertEqual(my_action.model.name, 'my_action') self.assertEqual(my_action.wsgi_response, False)
def test_error_raised_message(self): @action(spec='''\ action my_action errors MyError ''') def my_action(dummy_app, dummy_req): raise ActionError('MyError', 'My message') app = Application() app.add_request(my_action) status, headers, response = app.request('POST', '/my_action', wsgi_input=b'{}') self.assertEqual(status, '500 Internal Server Error') self.assertEqual(sorted(headers), [('Content-Length', '42'), ('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"error":"MyError","message":"My message"}')
def test_request_nested(self): def request1(environ, unused_start_response): ctx = environ[Environ.CTX] return ctx.response_text(HTTPStatus.OK, '7') def request2(environ, unused_start_response): ctx = environ[Environ.CTX] unused_status, _, response = ctx.app.request('GET', '/request1') return ctx.response_text(HTTPStatus.OK, str(5 + int(response))) app = Application() app.add_request(Request(request1)) app.add_request(Request(request2)) status, _, response = app.request('GET', '/request2') self.assertEqual(status, '200 OK') self.assertEqual(response, b'12')
def test_nested_requests(self): def request1(environ, dummy_start_response): ctx = environ[ENVIRON_CTX] return ctx.response_text('200 OK', '7') def request2(environ, dummy_start_response): ctx = environ[ENVIRON_CTX] dummy_status, dummy_headers, response = ctx.app.request('GET', '/request1') return ctx.response_text('200 OK', str(5 + int(response))) app = Application() app.add_request(Request(request1)) app.add_request(Request(request2)) status, dummy_headers, response = app.request('GET', '/request2') self.assertEqual(status, '200 OK') self.assertEqual(response, b'12')
def test_post(self): @action(spec='''\ action my_action input int a int b output int c ''') def my_action(dummy_app, req): return {'c': req['a'] + req['b']} app = Application() app.add_request(my_action) status, headers, response = app.request('POST', '/my_action', wsgi_input=b'{"a": 7, "b": 8}') self.assertEqual(status, '200 OK') self.assertEqual(sorted(headers), [('Content-Length', '8'), ('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"c":15}') # Mixed content and query string status, headers, response = app.request('POST', '/my_action', query_string='a=7', wsgi_input=b'{"b": 8}') self.assertEqual(status, '200 OK') self.assertEqual(sorted(headers), [('Content-Length', '8'), ('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"c":15}') # Mixed content and query string #2 status, headers, response = app.request('POST', '/my_action', query_string='a=7&b=8', wsgi_input=b'{}') self.assertEqual(status, '200 OK') self.assertEqual(sorted(headers), [('Content-Length', '8'), ('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"c":15}') # Mixed content and query string #3 status, headers, response = app.request('POST', '/my_action', query_string='a=7&b=8') self.assertEqual(status, '200 OK') self.assertEqual(sorted(headers), [('Content-Length', '8'), ('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"c":15}') # Duplicate query string argument status, headers, response = app.request('POST', '/my_action', query_string='a=7', wsgi_input=b'{"a": 7, "b": 8}') self.assertEqual(status, '400 Bad Request') self.assertEqual(sorted(headers), [('Content-Length', '79'), ('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"error":"InvalidInput","message":"Duplicate query string argument member \'a\'"}')
def test_request_head(self): def request(environ, unused_start_response): assert environ['REQUEST_METHOD'] == 'GET' ctx = environ[Environ.CTX] return ctx.response_text(HTTPStatus.OK, 'the response') app = Application() app.add_request(Request(request, method='GET')) status, headers, response = app.request('GET', '/request') self.assertEqual(status, '200 OK') self.assertEqual(response, b'the response') self.assertListEqual(headers, [('Content-Type', 'text/plain')]) status, headers, response = app.request('HEAD', '/request') self.assertEqual(status, '200 OK') self.assertEqual(response, b'') self.assertListEqual(headers, [('Content-Type', 'text/plain')])
def test_error_invalid_output(self): @action(spec='''\ action my_action output int a ''') def my_action(unused_app, unused_req): return {'a': 'asdf'} app = Application() app.add_request(my_action) status, headers, response = app.request('POST', '/my_action', wsgi_input=b'{}') self.assertEqual(status, '500 Internal Server Error') self.assertEqual(sorted(headers), [('Content-Type', 'application/json')]) self.assertEqual(response.decode('utf-8'), '{"error":"InvalidOutput","member":"a","message":"Invalid value \'asdf\' (type \'str\') ' 'for member \'a\', expected type \'int\'"}')