def _try_transform_rest_request(self, path_parameters, query_parameters, body_json, expected, method_params=None): """Takes body, query and path values from a rest request for testing. Args: path_parameters: A dict containing the parameters parsed from the path. For example if the request came through /a/b for the template /a/{x} then we'd have {'x': 'b'}. query_parameters: A dict containing the parameters parsed from the query string. body_json: A dict with the JSON object from the request body. expected: A dict with the expected JSON body after being transformed. method_params: Optional dictionary specifying the parameter configuration associated with the method. """ method_params = method_params or {} test_request = test_utils.build_request('/_ah/api/test') test_request.body_json = body_json test_request.body = json.dumps(body_json) test_request.parameters = query_parameters transformed_request = self.server.transform_rest_request(test_request, path_parameters, method_params) self.assertEqual(expected, transformed_request.body_json) self.assertEqual(transformed_request.body_json, json.loads(transformed_request.body))
def test_handle_spi_response_batch_json_rpc(self): """Verify that batch requests have an appropriate batch response.""" orig_request = test_utils.build_request( '/_ah/api/rpc', '[{"method": "foo.bar", "apiVersion": "X"}]') self.assertTrue(orig_request.is_batch()) self.assertTrue(orig_request.is_rpc()) orig_request.request_id = 'Z' spi_request = orig_request.copy() spi_response = dispatcher.ResponseTuple('200 OK', [('a', 'b')], '{"some": "response"}') response = self.server.handle_spi_response(orig_request, spi_request, spi_response, {}, self.start_response) response = ''.join( response) # Merge response iterator into single body. self.assertEqual(self.response_status, '200 OK') self.assertIn(('a', 'b'), self.response_headers) self.assertEqual([{ 'id': 'Z', 'result': { 'some': 'response' } }], json.loads(response))
def test_static_non_existing_file(self): relative_url = '/_ah/api/static/blah.html' # Set up mocks for the call to DiscoveryApiProxy.get_static_file. discovery_api = self.mox.CreateMock( discovery_api_proxy.DiscoveryApiProxy) self.mox.StubOutWithMock(discovery_api_proxy, 'DiscoveryApiProxy') discovery_api_proxy.DiscoveryApiProxy().AndReturn(discovery_api) static_response = self.mox.CreateMock(httplib.HTTPResponse) static_response.status = 404 static_response.reason = 'Not Found' static_response.getheaders().AndReturn([('Content-Type', 'test/type')]) test_body = 'No Body' discovery_api.get_static_file(relative_url).AndReturn( (static_response, test_body)) # Make sure the dispatch works as expected. request = test_utils.build_request(relative_url) self.mox.ReplayAll() response = self.server.dispatch(request, self.start_response) self.mox.VerifyAll() response = ''.join(response) self.assert_http_match(response, '404 Not Found', [('Content-Length', '%d' % len(test_body)), ('Content-Type', 'test/type')], test_body)
def test_transform_rest_request_with_query_params(self): """Verify body is updated with query parameters.""" orig_request = test_utils.build_request('/_ah/api/test?foo=bar', '{"sample": "body"}') new_request = self.server.transform_rest_request(orig_request, {}) self.assertEqual({'sample': 'body', 'foo': ['bar']}, json.loads(new_request.body))
def test_transform_rest_request(self): """Verify body is updated with path params.""" orig_request = test_utils.build_request('/_ah/api/test', '{"sample": "body"}') new_request = self.server.transform_rest_request(orig_request, {'gid': 'X'}) self.assertEqual({'sample': 'body', 'gid': 'X'}, json.loads(new_request.body))
def test_static_non_existing_file(self): relative_url = '/_ah/api/static/blah.html' # Set up mocks for the call to DiscoveryApiProxy.get_static_file. discovery_api = self.mox.CreateMock( discovery_api_proxy.DiscoveryApiProxy) self.mox.StubOutWithMock(discovery_api_proxy, 'DiscoveryApiProxy') discovery_api_proxy.DiscoveryApiProxy().AndReturn(discovery_api) static_response = self.mox.CreateMock(six.moves.http_client.HTTPResponse) static_response.status = 404 static_response.reason = 'Not Found' static_response.getheaders().AndReturn([('Content-Type', 'test/type')]) test_body = 'No Body' discovery_api.get_static_file(relative_url).AndReturn( (static_response, test_body)) # Make sure the dispatch works as expected. request = test_utils.build_request(relative_url) self.mox.ReplayAll() response = self.server.dispatch(request, self.start_response) self.mox.VerifyAll() response = ''.join(response) self.assert_http_match(response, '404 Not Found', [('Content-Length', '%d' % len(test_body)), ('Content-Type', 'test/type')], test_body)
def test_batch_too_large(self): """Verify that additional items are dropped if the batch size is > 1.""" request = test_utils.build_request('/_ah/api/rpc', '[{"method": "foo", "apiVersion": "v1"},' '{"method": "bar", "apiversion": "v1"}]') self.assertTrue(request.is_batch()) self.assertEqual(json.loads('{"method": "foo", "apiVersion": "v1"}'), request.body_json)
def test_batch_too_large(self): """Verify that additional items are dropped if the batch size is > 1.""" request = test_utils.build_request( '/_ah/api/rpc', '[{"method": "foo", "apiVersion": "v1"},' '{"method": "bar", "apiversion": "v1"}]') self.assertTrue(request.is_batch()) self.assertEqual(json.loads('{"method": "foo", "apiVersion": "v1"}'), request.body_json)
def test_check_empty_response(self): """Test that check_empty_response returns 204 for an empty response.""" orig_request = test_utils.build_request('/_ah/api/test', '{}') method_config = {'response': {'body': 'empty'}} empty_response = self.server.check_empty_response(orig_request, method_config, self.start_response) self.assert_http_match(empty_response, 204, [('Content-Length', '0')], '')
def test_transform_json_rpc_request(self): """Verify request_id is extracted and body is scoped to body.params.""" orig_request = test_utils.build_request( '/_ah/api/rpc', '{"params": {"sample": "body"}, "id": "42"}') new_request = self.server.transform_jsonrpc_request(orig_request) self.assertEqual({'sample': 'body'}, json.loads(new_request.body)) self.assertEqual('42', new_request.request_id)
def test_explorer_redirect(self): request = test_utils.build_request('/_ah/api/explorer') response = self.server.dispatch(request, self.start_response) self.assert_http_match(response, 302, [('Content-Length', '0'), ('Location', ('https://developers.google.com/' 'apis-explorer/?base=' 'http://localhost:42/_ah/api'))], '')
def test_generate_discovery_doc_rest_unknown_api(self): request = test_utils.build_request('/_ah/api/foo', '{"api": "blah", "version": "v1"}') discovery_api = discovery_service.DiscoveryService( self.api_config_manager) discovery_api.handle_discovery_request( discovery_service.DiscoveryService._GET_REST_API, request, self.start_response) self.assertEquals(self.response_status, '404')
def test_lookup_rpc_method(self): self.mox.StubOutWithMock(self.server.config_manager, 'lookup_rpc_method') self.server.config_manager.lookup_rpc_method('foo', 'v1').AndReturn('bar') self.mox.ReplayAll() orig_request = test_utils.build_request( '/_ah/api/rpc', '{"method": "foo", "apiVersion": "v1"}') self.assertEqual('bar', self.server.lookup_rpc_method(orig_request)) self.mox.VerifyAll()
def test_explorer_redirect(self): request = test_utils.build_request('/_ah/api/explorer') response = self.server.dispatch(request, self.start_response) self.assert_http_match(response, 302, [('Content-Length', '0'), ('Location', ('http://apis-explorer.appspot.com/' 'apis-explorer/?base=' 'http://localhost:42/_ah/api'))], '')
def test_transform_json_rpc_response_batch(self): """Verify request_id inserted into the body, and body into body.result.""" orig_request = test_utils.build_request( '/_ah/api/rpc', '[{"params": {"sample": "body"}, "id": "42"}]') request = orig_request.copy() request.request_id = '42' orig_response = '{"sample": "body"}' response = self.server.transform_jsonrpc_response(request, orig_response) self.assertEqual([{'result': {'sample': 'body'}, 'id': '42'}], json.loads(response))
def test_check_non_empty_response(self): """Test that check_empty_response returns None for a non-empty response.""" orig_request = test_utils.build_request('/_ah/api/test', '{}') method_config = {'response': {'body': 'autoTemplate(backendResponse)'}} empty_response = self.server.check_empty_response( orig_request, method_config, self.start_response) self.assertIsNone(empty_response) self.assertIsNone(self.response_status) self.assertIsNone(self.response_headers) self.assertIsNone(self.response_exc_info)
def test_parse_empty_values(self): request = test_utils.build_request('/_ah/api/foo?bar') self.assertEqual('foo', request.path) self.assertEqual('bar', request.query) self.assertEqual({'bar': ['']}, request.parameters) self.assertEqual('', request.body) self.assertEqual({}, request.body_json) self.assertEqual([('CONTENT-TYPE', 'application/json')], list(request.headers.items())) self.assertEqual(None, request.request_id)
def _common_setup(self): api_config_file = os.path.join(os.path.dirname(__file__), 'testdata/tictactoe-v1.api') with open(api_config_file, 'r') as api_file: api_config = api_file.read() api_config_dict = {'items': [api_config]} self.api_config_manager = api_config_manager.ApiConfigManager() self.api_config_manager.parse_api_config_response( json.dumps(api_config_dict)) self.api_request = test_utils.build_request( '/_ah/api/foo', '{"api": "tictactoe", "version": "v1"}')
def test_check_non_empty_response(self): """Test that check_empty_response returns None for a non-empty response.""" orig_request = test_utils.build_request('/_ah/api/test', '{}') method_config = {'response': {'body': 'autoTemplate(backendResponse)'}} empty_response = self.server.check_empty_response(orig_request, method_config, self.start_response) self.assertIsNone(empty_response) self.assertIsNone(self.response_status) self.assertIsNone(self.response_headers) self.assertIsNone(self.response_exc_info)
def test_transform_request(self): """Verify path is method name after a request is transformed.""" request = test_utils.build_request('/_ah/api/test/{gid}', '{"sample": "body"}') method_config = {'rosyMethod': 'GuestbookApi.greetings_get'} new_request = self.server.transform_request(request, {'gid': 'X'}, method_config) self.assertEqual({'sample': 'body', 'gid': 'X'}, json.loads(new_request.body)) self.assertEqual('GuestbookApi.greetings_get', new_request.path)
def test_parse_multiple_values(self): request = test_utils.build_request('/_ah/api/foo?bar=baz&foo=bar&bar=foo') self.assertEqual('foo', request.path) self.assertEqual('bar=baz&foo=bar&bar=foo', request.query) self.assertEqual({'bar': ['baz', 'foo'], 'foo': ['bar']}, request.parameters) self.assertEqual('', request.body) self.assertEqual({}, request.body_json) self.assertEqual([('CONTENT-TYPE', 'application/json')], request.headers.items()) self.assertEqual(None, request.request_id)
def test_handle_spi_response_rest(self): orig_request = test_utils.build_request('/_ah/api/test', '{}') spi_request = orig_request.copy() body = json.dumps({'some': 'response'}, indent=1) spi_response = dispatcher.ResponseTuple('200 OK', [('a', 'b')], body) response = self.server.handle_spi_response(orig_request, spi_request, spi_response, {}, self.start_response) self.assert_http_match(response, '200 OK', [('a', 'b'), ('Content-Length', '%d' % len(body))], body)
def test_parse_with_body(self): request = test_utils.build_request('/_ah/api/foo?bar=baz', '{"test": "body"}') self.assertEqual('foo', request.path) self.assertEqual('bar=baz', request.query) self.assertEqual({'bar': ['baz']}, request.parameters) self.assertEqual('{"test": "body"}', request.body) self.assertEqual({'test': 'body'}, request.body_json) self.assertEqual([('CONTENT-TYPE', 'application/json')], list(request.headers.items())) self.assertEqual(None, request.request_id)
def test_parse_multiple_values(self): request = test_utils.build_request( '/_ah/api/foo?bar=baz&foo=bar&bar=foo') self.assertEqual('foo', request.path) self.assertEqual('bar=baz&foo=bar&bar=foo', request.query) self.assertEqual({ 'bar': ['baz', 'foo'], 'foo': ['bar'] }, request.parameters) self.assertEqual('', request.body) self.assertEqual({}, request.body_json) self.assertEqual([('CONTENT-TYPE', 'application/json')], request.headers.items()) self.assertEqual(None, request.request_id)
def test_parse_gzipped_body(self): uncompressed = '{"test": "body"}' compressed = zlib.compress(uncompressed) request = test_utils.build_request('/_ah/api/foo?bar=baz', compressed, [('Content-encoding', 'gzip')]) self.assertEqual('foo', request.path) self.assertEqual('bar=baz', request.query) self.assertEqual({'bar': ['baz']}, request.parameters) self.assertEqual(uncompressed, request.body) self.assertEqual({'test': 'body'}, request.body_json) self.assertItemsEqual([('CONTENT-TYPE', 'application/json'), ('CONTENT-ENCODING', 'gzip')], request.headers.items()) self.assertEqual(None, request.request_id)
def test_dispatch_json_rpc(self): config = json.dumps({ 'name': 'guestbook_api', 'version': 'X', 'methods': { 'foo.bar': { 'httpMethod': 'GET', 'path': 'greetings/{gid}', 'rosyMethod': 'baz.bim' } } }) request = test_utils.build_request( '/_ah/api/rpc', '{"method": "foo.bar", "apiVersion": "X"}') self.assert_dispatch_to_spi(request, config, '/_ah/spi/baz.bim')
def test_dispatch_rest(self): config = json.dumps({ 'name': 'myapi', 'version': 'v1', 'methods': { 'bar': { 'httpMethod': 'GET', 'path': 'foo/{id}', 'rosyMethod': 'baz.bim' } } }) request = test_utils.build_request('/_ah/api/myapi/v1/foo/testId') self.assert_dispatch_to_spi(request, config, '/_ah/spi/baz.bim', {'id': 'testId'})
def test_handle_non_json_spi_response(self): orig_request = test_utils.build_request('/_ah/api/fake/path') spi_request = orig_request.copy() spi_response = dispatcher.ResponseTuple( 200, [('Content-type', 'text/plain')], 'This is an invalid response.') response = self.server.handle_spi_response(orig_request, spi_request, spi_response, {}, self.start_response) error_json = {'error': {'message': 'Non-JSON reply: This is an invalid response.'}} body = json.dumps(error_json) self.assert_http_match(response, '500', [('Content-Type', 'application/json'), ('Content-Length', '%d' % len(body))], body)
def test_dispatch_rpc_error(self): """Test than an RPC call that returns an error is handled properly.""" config = json.dumps({ 'name': 'guestbook_api', 'version': 'v1', 'methods': { 'guestbook.get': { 'httpMethod': 'GET', 'path': 'greetings/{gid}', 'rosyMethod': 'MyApi.greetings_get' } } }) request = test_utils.build_request( '/_ah/api/rpc', '{"method": "foo.bar", "apiVersion": "X", "id": "gapiRpc"}') self.prepare_dispatch(config) self.mox.StubOutWithMock(self.server, 'call_spi') # The application chose to throw a 404 error. response = dispatcher.ResponseTuple( '404 Not Found', [], ('{"state": "APPLICATION_ERROR",' ' "error_message": "Test error"}')) self.server.call_spi(request, mox.IgnoreArg()).AndRaise( errors.BackendError(response)) self.mox.ReplayAll() response = self.server.dispatch(request, self.start_response) self.mox.VerifyAll() expected_response = { 'error': { 'code': 404, 'message': 'Test error', 'data': [{ 'domain': 'global', 'reason': 'notFound', 'message': 'Test error', }] }, 'id': 'gapiRpc' } response = ''.join(response) self.assertEqual('200 OK', self.response_status) self.assertEqual(expected_response, json.loads(response))
def test_dispatch_spi_error(self): """Check the error response if the SPI returns an error.""" config = json.dumps({ 'name': 'guestbook_api', 'version': 'v1', 'methods': { 'guestbook.get': { 'httpMethod': 'GET', 'path': 'greetings/{gid}', 'rosyMethod': 'MyApi.greetings_get' } } }) request = test_utils.build_request('/_ah/api/foo') self.prepare_dispatch(config) self.mox.StubOutWithMock(self.server, 'call_spi') # The application chose to throw a 404 error. response = dispatcher.ResponseTuple('404 Not Found', [], ('{"state": "APPLICATION_ERROR",' ' "error_message": "Test error"}')) self.server.call_spi(request, mox.IgnoreArg()).AndRaise( errors.BackendError(response)) self.mox.ReplayAll() response = self.server.dispatch(request, self.start_response) self.mox.VerifyAll() expected_response = ( '{\n' ' "error": {\n' ' "code": 404, \n' ' "errors": [\n' ' {\n' ' "domain": "global", \n' ' "message": "Test error", \n' ' "reason": "notFound"\n' ' }\n' ' ], \n' ' "message": "Test error"\n' ' }\n' '}') response = ''.join(response) self.assert_http_match(response, '404 Not Found', [('Content-Length', '%d' % len(expected_response)), ('Content-Type', 'application/json')], expected_response)
def test_copy(self): request = test_utils.build_request('/_ah/api/foo?bar=baz', '{"test": "body"}') copied = request.copy() self.assertEqual(list(request.headers.items()), list(copied.headers.items())) self.assertEqual(request.body, copied.body) self.assertEqual(request.body_json, copied.body_json) self.assertEqual(request.path, copied.path) copied.headers['Content-Type'] = 'text/plain' copied.body = 'Got a whole new body!' copied.body_json = {'new': 'body'} copied.path = 'And/a/new/path/' self.assertNotEqual(list(request.headers.items()), list(copied.headers.items())) self.assertNotEqual(request.body, copied.body) self.assertNotEqual(request.body_json, copied.body_json) self.assertNotEqual(request.path, copied.path)
def test_handle_spi_response_json_rpc(self): """Verify headers transformed, JsonRpc response transformed, written.""" orig_request = test_utils.build_request( '/_ah/api/rpc', '{"method": "foo.bar", "apiVersion": "X"}') self.assertTrue(orig_request.is_rpc()) orig_request.request_id = 'Z' spi_request = orig_request.copy() spi_response = dispatcher.ResponseTuple('200 OK', [('a', 'b')], '{"some": "response"}') response = self.server.handle_spi_response(orig_request, spi_request, spi_response, self.start_response) response = ''.join(response) # Merge response iterator into single body. self.assertEqual(self.response_status, '200 OK') self.assertIn(('a', 'b'), self.response_headers) self.assertEqual({'id': 'Z', 'result': {'some': 'response'}}, json.loads(response))
def test_handle_spi_response_json_rpc(self): """Verify headers transformed, JsonRpc response transformed, written.""" orig_request = test_utils.build_request( '/_ah/api/rpc', '{"method": "foo.bar", "apiVersion": "X"}') self.assertTrue(orig_request.is_rpc()) orig_request.request_id = 'Z' spi_request = orig_request.copy() spi_response = dispatcher.ResponseTuple('200 OK', [('a', 'b')], '{"some": "response"}') response = self.server.handle_spi_response(orig_request, spi_request, spi_response, {}, self.start_response) response = ''.join(response) # Merge response iterator into single body. self.assertEqual(self.response_status, '200 OK') self.assertIn(('a', 'b'), self.response_headers) self.assertEqual({'id': 'Z', 'result': {'some': 'response'}}, json.loads(response))
def test_dispatch_rpc_error(self): """Test than an RPC call that returns an error is handled properly.""" config = json.dumps({ 'name': 'guestbook_api', 'version': 'v1', 'methods': { 'guestbook.get': { 'httpMethod': 'GET', 'path': 'greetings/{gid}', 'rosyMethod': 'MyApi.greetings_get' } } }) request = test_utils.build_request( '/_ah/api/rpc', '{"method": "foo.bar", "apiVersion": "X", "id": "gapiRpc"}') self.prepare_dispatch(config) self.mox.StubOutWithMock(self.server, 'call_spi') # The application chose to throw a 404 error. response = dispatcher.ResponseTuple('404 Not Found', [], ('{"state": "APPLICATION_ERROR",' ' "error_message": "Test error"}')) self.server.call_spi(request, mox.IgnoreArg()).AndRaise( errors.BackendError(response)) self.mox.ReplayAll() response = self.server.dispatch(request, self.start_response) self.mox.VerifyAll() expected_response = {'error': {'code': 404, 'message': 'Test error', 'data': [{ 'domain': 'global', 'reason': 'notFound', 'message': 'Test error', }] }, 'id': 'gapiRpc' } response = ''.join(response) self.assertEqual('200 OK', self.response_status) self.assertEqual(expected_response, json.loads(response))
def test_dispatch_invalid_enum(self): config = json.dumps({ 'name': 'guestbook_api', 'version': 'v1', 'methods': { 'guestbook.get': { 'httpMethod': 'GET', 'path': 'greetings/{gid}', 'rosyMethod': 'MyApi.greetings_get', 'request': { 'body': 'empty', 'parameters': { 'gid': { 'enum': { 'X': { 'backendValue': 'X' } }, 'type': 'string' } } } } } }) request = test_utils.build_request( '/_ah/api/guestbook_api/v1/greetings/invalid_enum') self.prepare_dispatch(config) self.mox.ReplayAll() response = self.server.dispatch(request, self.start_response) self.mox.VerifyAll() logging.warning('Config %s', self.server.config_manager.configs) self.assertEqual(self.response_status, '400 Bad Request') body = ''.join(response) body_json = json.loads(body) self.assertEqual(1, len(body_json['error']['errors'])) self.assertEqual('gid', body_json['error']['errors'][0]['location']) self.assertEqual('invalidParameter', body_json['error']['errors'][0]['reason'])
def test_parse_gzipped_body(self): def gzip_encode(content): out = cStringIO.StringIO() with gzip.GzipFile(fileobj=out, mode='w') as f: f.write(content) return out.getvalue() uncompressed = '{"test": "body"}' compressed = gzip_encode(uncompressed) request = test_utils.build_request('/_ah/api/foo?bar=baz', compressed, [('Content-encoding', 'gzip')]) self.assertEqual('foo', request.path) self.assertEqual('bar=baz', request.query) self.assertEqual({'bar': ['baz']}, request.parameters) self.assertEqual(uncompressed, request.body) self.assertEqual({'test': 'body'}, request.body_json) self.assertItemsEqual([('CONTENT-TYPE', 'application/json'), ('CONTENT-ENCODING', 'gzip')], request.headers.items()) self.assertEqual(None, request.request_id)
def test_parse_gzipped_body(self): def gzip_encode(content): out = io.StringIO() with gzip.GzipFile(fileobj=out, mode='w') as f: f.write(content) return out.getvalue() uncompressed = '{"test": "body"}' compressed = gzip_encode(uncompressed) request = test_utils.build_request('/_ah/api/foo?bar=baz', compressed, [('Content-encoding', 'gzip')]) self.assertEqual('foo', request.path) self.assertEqual('bar=baz', request.query) self.assertEqual({'bar': ['baz']}, request.parameters) self.assertEqual(uncompressed, request.body) self.assertEqual({'test': 'body'}, request.body_json) self.assertItemsEqual([('CONTENT-TYPE', 'application/json'), ('CONTENT-ENCODING', 'gzip')], list(request.headers.items())) self.assertEqual(None, request.request_id)
def test_handle_spi_response_batch_json_rpc(self): """Verify that batch requests have an appropriate batch response.""" orig_request = test_utils.build_request( '/_ah/api/rpc', '[{"method": "foo.bar", "apiVersion": "X"}]') self.assertTrue(orig_request.is_batch()) self.assertTrue(orig_request.is_rpc()) orig_request.request_id = 'Z' spi_request = orig_request.copy() spi_response = dispatcher.ResponseTuple('200 OK', [('a', 'b')], '{"some": "response"}') response = self.server.handle_spi_response(orig_request, spi_request, spi_response, self.start_response) response = ''.join(response) # Merge response iterator into single body. self.assertEqual(self.response_status, '200 OK') self.assertIn(('a', 'b'), self.response_headers) self.assertEqual([{'id': 'Z', 'result': {'some': 'response'}}], json.loads(response))
def test_lily_uses_python_method_name(self): """Verify Lily protocol correctly uses python method name. This test verifies the fix to http://b/7189819 """ config = json.dumps({ 'name': 'guestbook_api', 'version': 'X', 'methods': { 'author.greeting.info.get': { 'httpMethod': 'GET', 'path': 'authors/{aid}/greetings/{gid}/infos/{iid}', 'rosyMethod': 'InfoService.get' } } }) request = test_utils.build_request( '/_ah/api/rpc', '{"method": "author.greeting.info.get", "apiVersion": "X"}') self.assert_dispatch_to_spi(request, config, '/_ah/spi/InfoService.get', {})
def test_dispatch_invalid_path(self): config = json.dumps({ 'name': 'guestbook_api', 'version': 'v1', 'methods': { 'guestbook.get': { 'httpMethod': 'GET', 'path': 'greetings/{gid}', 'rosyMethod': 'MyApi.greetings_get' } } }) request = test_utils.build_request('/_ah/api/foo') self.prepare_dispatch(config) self.mox.ReplayAll() response = self.server.dispatch(request, self.start_response) self.mox.VerifyAll() self.assert_http_match(response, 404, [('Content-Type', 'text/plain'), ('Content-Length', '9')], 'Not Found')