blob: 5c8c037da056e23a9a8d2be13161e66cb2799601 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Unit tests for the endpoints_server module."""
import httplib
import json
import logging
import unittest
import google
import mox
from google.appengine.tools.devappserver2 import dispatcher
from google.appengine.tools.devappserver2.endpoints import api_config_manager
from google.appengine.tools.devappserver2.endpoints import api_request
from google.appengine.tools.devappserver2.endpoints import discovery_api_proxy
from google.appengine.tools.devappserver2.endpoints import endpoints_server
from google.appengine.tools.devappserver2.endpoints import errors
from google.appengine.tools.devappserver2.endpoints import test_utils
class JsonMatches(mox.Comparator):
"""A Mox comparator to compare a string of a JSON object to a JSON object."""
def __init__(self, json_object):
"""Constructor.
Args:
json_object: The JSON object to compare against.
"""
self._json_object = json_object
def equals(self, json_string):
"""Check if the given object matches our json object.
This converts json_string from a string to a JSON object, then compares it
against our json object.
Args:
json_string: A string containing a JSON object to be compared against.
Returns:
True if the object matches, False if not.
"""
other_json = json.loads(json_string)
return self._json_object == other_json
def __repr__(self):
return '<JsonMatches %r>' % self._json_object
class DevAppserverEndpointsServerTest(test_utils.TestsWithStartResponse):
def setUp(self):
"""Set up a dev Endpoints server."""
super(DevAppserverEndpointsServerTest, self).setUp()
self.mox = mox.Mox()
self.config_manager = api_config_manager.ApiConfigManager()
self.mock_dispatcher = self.mox.CreateMock(dispatcher.Dispatcher)
self.server = endpoints_server.EndpointsDispatcher(self.mock_dispatcher,
self.config_manager)
def tearDown(self):
self.mox.UnsetStubs()
def prepare_dispatch(self, config):
# The dispatch call will make a call to get_api_configs, making a
# dispatcher request. Set up that request.
request_method = 'POST'
request_path = '/_ah/spi/BackendService.getApiConfigs'
request_headers = [('Content-Type', 'application/json')]
request_body = '{}'
response_body = json.dumps({'items': [config]})
self.mock_dispatcher.add_request(
request_method, request_path, request_headers, request_body,
endpoints_server._SERVER_SOURCE_IP).AndReturn(
dispatcher.ResponseTuple('200 OK',
[('Content-Type', 'application/json'),
('Content-Length',
str(len(response_body)))],
response_body))
def assert_dispatch_to_spi(self, request, config, spi_path,
expected_spi_body_json=None):
"""Assert that dispatching a request to the SPI works.
Mock out the dispatcher.add_request and handle_spi_response, and use these
to ensure that the correct request is being sent to the back end when
Dispatch is called.
Args:
request: An ApiRequest, the request to dispatch.
config: A dict containing the API configuration.
spi_path: A string containing the relative path to the SPI.
expected_spi_body_json: If not None, this is a JSON object containing
the mock response sent by the back end. If None, this will create an
empty response.
"""
self.prepare_dispatch(config)
spi_headers = [('Content-Type', 'application/json')]
spi_body_json = expected_spi_body_json or {}
spi_response = dispatcher.ResponseTuple('200 OK', [], 'Test')
self.mock_dispatcher.add_request(
'POST', spi_path, spi_headers, JsonMatches(spi_body_json),
request.source_ip).AndReturn(spi_response)
self.mox.StubOutWithMock(self.server, 'handle_spi_response')
self.server.handle_spi_response(
mox.IsA(api_request.ApiRequest), mox.IsA(api_request.ApiRequest),
spi_response, mox.IsA(dict), self.start_response).AndReturn('Test')
# Run the test.
self.mox.ReplayAll()
response = self.server.dispatch(request, self.start_response)
self.mox.VerifyAll()
self.assertEqual('Test', response)
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')
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_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_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_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_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_static_existing_file(self):
relative_url = '/_ah/api/static/proxy.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 = 200
static_response.reason = 'OK'
static_response.getheader('Content-Type').AndReturn('test/type')
test_body = 'test 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, '200 OK',
[('Content-Length', '%d' % len(test_body)),
('Content-Type', 'test/type')],
test_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(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_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_handle_non_json_spi_response_cors(self):
"""Test that an error response still handles CORS headers."""
server_response = dispatcher.ResponseTuple(
'200 OK', [('Content-type', 'text/plain')],
'This is an invalid response.')
response = self.check_cors([('origin', 'test.com')], True, 'test.com',
server_response=server_response)
self.assertEqual(
{'error': {'message': 'Non-JSON reply: This is an invalid response.'}},
json.loads(response))
def check_cors(self, request_headers, expect_response, expected_origin=None,
expected_allow_headers=None, server_response=None):
"""Check that CORS headers are handled correctly.
Args:
request_headers: A list of (header, value), to be used as headers in the
request.
expect_response: A boolean, whether or not CORS headers are expected in
the response.
expected_origin: A string or None. If this is a string, this is the value
that's expected in the response's allow origin header. This can be
None if expect_response is False.
expected_allow_headers: A string or None. If this is a string, this is
the value that's expected in the response's allow headers header. If
this is None, then the response shouldn't have any allow headers
headers.
server_response: A dispatcher.ResponseTuple or None. The backend's
response, to be wrapped and returned as the server's response. If
this is None, a generic response will be generated.
Returns:
A string containing the body of the response that would be sent.
"""
orig_request = test_utils.build_request('/_ah/api/fake/path',
http_headers=request_headers)
spi_request = orig_request.copy()
if server_response is None:
server_response = dispatcher.ResponseTuple(
'200 OK', [('Content-type', 'application/json')], '{}')
response = self.server.handle_spi_response(orig_request, spi_request,
server_response, {},
self.start_response)
headers = dict(self.response_headers)
if expect_response:
self.assertIn(endpoints_server._CORS_HEADER_ALLOW_ORIGIN, headers)
self.assertEqual(
headers[endpoints_server._CORS_HEADER_ALLOW_ORIGIN],
expected_origin)
self.assertIn(endpoints_server._CORS_HEADER_ALLOW_METHODS, headers)
self.assertEqual(set(headers[
endpoints_server._CORS_HEADER_ALLOW_METHODS].split(',')),
endpoints_server._CORS_ALLOWED_METHODS)
if expected_allow_headers is not None:
self.assertIn(endpoints_server._CORS_HEADER_ALLOW_HEADERS,
headers)
self.assertEqual(
headers[endpoints_server._CORS_HEADER_ALLOW_HEADERS],
expected_allow_headers)
else:
self.assertNotIn(endpoints_server._CORS_HEADER_ALLOW_HEADERS,
headers)
else:
self.assertNotIn(endpoints_server._CORS_HEADER_ALLOW_ORIGIN,
headers)
self.assertNotIn(endpoints_server._CORS_HEADER_ALLOW_METHODS,
headers)
self.assertNotIn(endpoints_server._CORS_HEADER_ALLOW_HEADERS,
headers)
return ''.join(response)
def test_handle_cors(self):
"""Test CORS support on a regular request."""
self.check_cors([('origin', 'test.com')], True, 'test.com')
def test_handle_cors_preflight(self):
"""Test a CORS preflight request."""
self.check_cors([('origin', 'http://example.com'),
('Access-control-request-method', 'GET')], True,
'http://example.com')
def test_handle_cors_preflight_invalid(self):
"""Test a CORS preflight request for an unaccepted OPTIONS request."""
self.check_cors([('origin', 'http://example.com'),
('Access-control-request-method', 'OPTIONS')], False)
def test_handle_cors_preflight_request_headers(self):
"""Test a CORS preflight request."""
self.check_cors([('origin', 'http://example.com'),
('Access-control-request-method', 'GET'),
('Access-Control-Request-Headers', 'Date,Expires')], True,
'http://example.com', 'Date,Expires')
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_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_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_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_transform_rest_response(self):
"""Verify the response is reformatted correctly."""
orig_response = '{"sample": "test", "value1": {"value2": 2}}'
expected_response = ('{\n'
' "sample": "test", \n'
' "value1": {\n'
' "value2": 2\n'
' }\n'
'}')
self.assertEqual(expected_response,
self.server.transform_rest_response(orig_response))
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_lookup_rpc_method_no_body(self):
orig_request = test_utils.build_request('/_ah/api/rpc', '')
self.assertEqual(None, self.server.lookup_rpc_method(orig_request))
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_verify_response(self):
response = dispatcher.ResponseTuple('200', [('Content-Type', 'a')], '')
# Expected response
self.assertEqual(True, self.server.verify_response(response, 200, 'a'))
# Any content type accepted
self.assertEqual(True, self.server.verify_response(response, 200, None))
# Status code mismatch
self.assertEqual(False, self.server.verify_response(response, 400, 'a'))
# Content type mismatch
self.assertEqual(False, self.server.verify_response(response, 200, 'b'))
response = dispatcher.ResponseTuple('200', [('Content-Length', '10')], '')
# Any content type accepted
self.assertEqual(True, self.server.verify_response(response, 200, None))
# Specified content type not matched
self.assertEqual(False, self.server.verify_response(response, 200, 'a'))
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_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)
class TransformRequestTests(unittest.TestCase):
"""Tests that only hit the request transformation functions."""
def setUp(self):
"""Set up a dev Endpoints server."""
super(TransformRequestTests, self).setUp()
self.mox = mox.Mox()
self.config_manager = api_config_manager.ApiConfigManager()
self.mock_dispatcher = self.mox.CreateMock(dispatcher.Dispatcher)
self.server = endpoints_server.EndpointsDispatcher(self.mock_dispatcher,
self.config_manager)
def tearDown(self):
self.mox.UnsetStubs()
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_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 _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))
# Path only
def test_transform_rest_request_path_only(self):
path_parameters = {'gid': 'X'}
query_parameters = {}
body_object = {}
expected = {'gid': 'X'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_path_only_message_field(self):
path_parameters = {'gid.val': 'X'}
query_parameters = {}
body_object = {}
expected = {'gid': {'val': 'X'}}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_path_only_enum(self):
query_parameters = {}
body_object = {}
enum_descriptor = {'X': {'backendValue': 'X'}}
method_params = {'gid': {'enum': enum_descriptor}}
# Good enum
path_parameters = {'gid': 'X'}
expected = {'gid': 'X'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected,
method_params=method_params)
# Bad enum
path_parameters = {'gid': 'Y'}
expected = {'gid': 'Y'}
try:
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected,
method_params=method_params)
self.fail('Bad enum should have caused failure.')
except errors.EnumRejectionError as error:
self.assertEqual(error.parameter_name, 'gid')
# Query only
def test_transform_rest_request_query_only(self):
path_parameters = {}
query_parameters = {'foo': ['bar']}
body_object = {}
expected = {'foo': 'bar'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_query_only_message_field(self):
path_parameters = {}
query_parameters = {'gid.val': ['X']}
body_object = {}
expected = {'gid': {'val': 'X'}}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_query_only_multiple_values_not_repeated(self):
path_parameters = {}
query_parameters = {'foo': ['bar', 'baz']}
body_object = {}
expected = {'foo': 'bar'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_query_only_multiple_values_repeated(self):
path_parameters = {}
query_parameters = {'foo': ['bar', 'baz']}
body_object = {}
method_params = {'foo': {'repeated': True}}
expected = {'foo': ['bar', 'baz']}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected,
method_params=method_params)
def test_transform_rest_request_query_only_enum(self):
path_parameters = {}
body_object = {}
enum_descriptor = {'X': {'backendValue': 'X'}}
method_params = {'gid': {'enum': enum_descriptor}}
# Good enum
query_parameters = {'gid': ['X']}
expected = {'gid': 'X'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected,
method_params=method_params)
# Bad enum
query_parameters = {'gid': ['Y']}
expected = {'gid': 'Y'}
try:
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected,
method_params=method_params)
self.fail('Bad enum should have caused failure.')
except errors.EnumRejectionError as error:
self.assertEqual(error.parameter_name, 'gid')
def test_transform_rest_request_query_only_repeated_enum(self):
path_parameters = {}
body_object = {}
enum_descriptor = {'X': {'backendValue': 'X'}, 'Y': {'backendValue': 'Y'}}
method_params = {'gid': {'enum': enum_descriptor, 'repeated': True}}
# Good enum
query_parameters = {'gid': ['X', 'Y']}
expected = {'gid': ['X', 'Y']}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected,
method_params=method_params)
# Bad enum
query_parameters = {'gid': ['X', 'Y', 'Z']}
expected = {'gid': ['X', 'Y', 'Z']}
try:
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected,
method_params=method_params)
self.fail('Bad enum should have caused failure.')
except errors.EnumRejectionError as error:
self.assertEqual(error.parameter_name, 'gid[2]')
# Body only
def test_transform_rest_request_body_only(self):
path_parameters = {}
query_parameters = {}
body_object = {'sample': 'body'}
expected = {'sample': 'body'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_body_only_any_old_value(self):
path_parameters = {}
query_parameters = {}
body_object = {'sample': {'body': ['can', 'be', 'anything']}}
expected = {'sample': {'body': ['can', 'be', 'anything']}}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_body_only_message_field(self):
path_parameters = {}
query_parameters = {}
body_object = {'gid': {'val': 'X'}}
expected = {'gid': {'val': 'X'}}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_body_only_enum(self):
path_parameters = {}
query_parameters = {}
enum_descriptor = {'X': {'backendValue': 'X'}}
method_params = {'gid': {'enum': enum_descriptor}}
# Good enum
body_object = {'gid': 'X'}
expected = {'gid': 'X'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected,
method_params=method_params)
# Bad enum
body_object = {'gid': 'Y'}
expected = {'gid': 'Y'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected,
method_params=method_params)
# Path and query only
def test_transform_rest_request_path_query_no_collision(self):
path_parameters = {'a': 'b'}
query_parameters = {'c': ['d']}
body_object = {}
expected = {'a': 'b', 'c': 'd'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_path_query_collision(self):
path_parameters = {'a': 'b'}
query_parameters = {'a': ['d']}
body_object = {}
expected = {'a': 'd'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_path_query_collision_in_repeated_param(self):
path_parameters = {'a': 'b'}
query_parameters = {'a': ['d', 'c']}
body_object = {}
expected = {'a': ['d', 'c', 'b']}
method_params = {'a': {'repeated': True}}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected,
method_params=method_params)
# Path and body only
def test_transform_rest_request_path_body_no_collision(self):
path_parameters = {'a': 'b'}
query_parameters = {}
body_object = {'c': 'd'}
expected = {'a': 'b', 'c': 'd'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_path_body_collision(self):
path_parameters = {'a': 'b'}
query_parameters = {}
body_object = {'a': 'd'}
expected = {'a': 'd'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_path_body_collision_in_repeated_param(self):
path_parameters = {'a': 'b'}
query_parameters = {}
body_object = {'a': ['d']}
expected = {'a': ['d']}
method_params = {'a': {'repeated': True}}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected,
method_params=method_params)
def test_transform_rest_request_path_body_message_field_cooperative(self):
path_parameters = {'gid.val1': 'X'}
query_parameters = {}
body_object = {'gid': {'val2': 'Y'}}
expected = {'gid': {'val1': 'X', 'val2': 'Y'}}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_path_body_message_field_collision(self):
path_parameters = {'gid.val': 'X'}
query_parameters = {}
body_object = {'gid': {'val': 'Y'}}
expected = {'gid': {'val': 'Y'}}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
# Query and body only
def test_transform_rest_request_query_body_no_collision(self):
path_parameters = {}
query_parameters = {'a': ['b']}
body_object = {'c': 'd'}
expected = {'a': 'b', 'c': 'd'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_query_body_collision(self):
path_parameters = {}
query_parameters = {'a': ['b']}
body_object = {'a': 'd'}
expected = {'a': 'd'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_query_body_collision_in_repeated_param(self):
path_parameters = {}
query_parameters = {'a': ['b']}
body_object = {'a': ['d']}
expected = {'a': ['d']}
method_params = {'a': {'repeated': True}}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected,
method_params=method_params)
def test_transform_rest_request_query_body_message_field_cooperative(self):
path_parameters = {}
query_parameters = {'gid.val1': ['X']}
body_object = {'gid': {'val2': 'Y'}}
expected = {'gid': {'val1': 'X', 'val2': 'Y'}}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_query_body_message_field_collision(self):
path_parameters = {}
query_parameters = {'gid.val': ['X']}
body_object = {'gid': {'val': 'Y'}}
expected = {'gid': {'val': 'Y'}}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
# Path, body and query
def test_transform_rest_request_path_query_body_no_collision(self):
path_parameters = {'a': 'b'}
query_parameters = {'c': ['d']}
body_object = {'e': 'f'}
expected = {'a': 'b', 'c': 'd', 'e': 'f'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_path_query_body_collision(self):
path_parameters = {'a': 'b'}
query_parameters = {'a': ['d']}
body_object = {'a': 'f'}
expected = {'a': 'f'}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected)
def test_transform_rest_request_unknown_parameters(self):
path_parameters = {'a': 'b'}
query_parameters = {'c': ['d']}
body_object = {'e': 'f'}
expected = {'a': 'b', 'c': 'd', 'e': 'f'}
method_params = {'X': {}, 'Y': {}}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected,
method_params=method_params)
# Other tests.
def test_type_conversions(self):
"""Verify that type conversion matches prod."""
path_parameters = {'int32_val': '1', 'uint32_val': '2',
'int64_val': '3', 'uint64_val': '4',
'true_bool_val': 'true', 'false_bool_val': 'FALSE'}
query_parameters = {'float_val': ['5.25'], 'double_val': ['6.5']}
body_object = {'int_body_val': '7'}
expected = {'int32_val': 1,
'uint32_val': 2,
'int64_val': '3',
'uint64_val': '4',
'true_bool_val': True,
'false_bool_val': False,
'float_val': 5.25,
'double_val': 6.5,
'int_body_val': '7'}
method_params = {'int32_val': {'type': 'int32'},
'uint32_val': {'type': 'uint32'},
'int64_val': {'type': 'int64'},
'uint64_val': {'type': 'uint64'},
'true_bool_val': {'type': 'boolean'},
'false_bool_val': {'type': 'boolean'},
'float_val': {'type': 'float'},
'double_val': {'type': 'double'},
'int_body_val': {'type': 'int32'}}
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected, method_params)
def test_invalid_conversions(self):
"""Verify that invalid parameter values for basic types raise errors."""
for type_name in ('int32', 'uint32', 'boolean', 'float', 'double'):
param_name = '%s_val' % type_name
path_parameters = {param_name: 'invalid'}
query_parameters = {}
body_object = {}
expected = {}
method_params = {param_name: {'type': type_name}}
try:
self._try_transform_rest_request(path_parameters, query_parameters,
body_object, expected,
method_params=method_params)
self.fail('Bad %s value should have caused failure.' % type_name)
except errors.BasicTypeParameterError as error:
self.assertEqual(error.parameter_name, param_name)
if __name__ == '__main__':
unittest.main()