| #!/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. |
| # |
| """Tests for google.apphosting.tools.devappserver2.api_server.""" |
| |
| |
| |
| import cStringIO |
| import pickle |
| import re |
| import tempfile |
| import unittest |
| import urllib |
| import wsgiref.util |
| |
| from google.net.rpc.python.testing import rpc_test_harness |
| |
| from google.appengine.api import apiproxy_stub |
| from google.appengine.api import apiproxy_stub_map |
| from google.appengine.api import urlfetch_service_pb |
| from google.appengine.api import user_service_pb |
| from google.appengine.datastore import datastore_stub_util |
| from google.appengine.datastore import datastore_v4_pb |
| from google.appengine.ext.remote_api import remote_api_pb |
| from google.appengine.runtime import apiproxy_errors |
| from google.appengine.tools.devappserver2 import api_server |
| from google.appengine.tools.devappserver2 import wsgi_request_info |
| from google.appengine.tools.devappserver2 import wsgi_test_utils |
| |
| APP_ID = 'test' |
| APPLICATION_ROOT = '/tmp' |
| TRUSTED = False |
| _, BLOBSTORE_PATH = tempfile.mkstemp(prefix='ae-blobstore') |
| _, DATASTORE_PATH = tempfile.mkstemp(prefix='ae-datastore') |
| DATASTORE_REQUIRE_INDEXES = False |
| IMAGES_HOST_PREFIX = 'localhost:8080' |
| LOGS_PATH = ':memory:' |
| MAIL_SMTP_HOST = 'localhost' |
| MAIL_SMTP_PORT = 80 |
| MAIL_SMTP_USER = 'user' |
| MAIL_SMTP_PASSWORD = 'abc123' |
| MAIL_ENABLE_SENDMAIL = False |
| MAIL_SHOW_MAIL_BODY = True |
| _, MATCHER_PROSPECTIVE_SEARCH_PATH = tempfile.mkstemp(prefix='ae-ps') |
| TASKQUEUE_AUTO_RUN_TASKS = False |
| TASKQUEUE_DEFAULT_HTTP_SERVER = 'localhost:8080' |
| USER_LOGIN_URL = 'https://localhost/Login?continue=%s' |
| USER_LOGOUT_URL = 'https://localhost/Logout?continue=%s' |
| |
| request_data = wsgi_request_info.WSGIRequestInfo(None) |
| |
| |
| class FakeURLFetchServiceStub(apiproxy_stub.APIProxyStub): |
| def __init__(self): |
| super(FakeURLFetchServiceStub, self).__init__('urlfetch') |
| |
| def _Dynamic_Fetch(self, request, unused_response): |
| if request.url() == 'exception': |
| raise IOError('the remote error') |
| elif request.url() == 'application_error': |
| raise apiproxy_errors.ApplicationError(23, 'details') |
| |
| |
| class FakeDatastoreV4ServiceStub(apiproxy_stub.APIProxyStub): |
| def __init__(self): |
| super(FakeDatastoreV4ServiceStub, self).__init__('datastore_v4') |
| |
| def _Dynamic_BeginTransaction(self, request, response): |
| response.set_transaction('whatever') |
| |
| |
| def setup_stubs(): |
| """Setup the API stubs. This can only be done once.""" |
| api_server.test_setup_stubs( |
| request_data, |
| app_id=APP_ID, |
| application_root=APPLICATION_ROOT, |
| trusted=TRUSTED, |
| blobstore_path=BLOBSTORE_PATH, |
| datastore_consistency=datastore_stub_util.TimeBasedHRConsistencyPolicy(), |
| datastore_path=DATASTORE_PATH, |
| datastore_require_indexes=DATASTORE_REQUIRE_INDEXES, |
| images_host_prefix=IMAGES_HOST_PREFIX, |
| logs_path=':memory:', |
| mail_smtp_host=MAIL_SMTP_HOST, |
| mail_smtp_port=MAIL_SMTP_PORT, |
| mail_smtp_user=MAIL_SMTP_USER, |
| mail_smtp_password=MAIL_SMTP_PASSWORD, |
| mail_enable_sendmail=MAIL_ENABLE_SENDMAIL, |
| mail_show_mail_body=MAIL_SHOW_MAIL_BODY, |
| matcher_prospective_search_path=MATCHER_PROSPECTIVE_SEARCH_PATH, |
| taskqueue_auto_run_tasks=TASKQUEUE_AUTO_RUN_TASKS, |
| taskqueue_default_http_server=TASKQUEUE_DEFAULT_HTTP_SERVER, |
| user_login_url=USER_LOGIN_URL, |
| user_logout_url=USER_LOGOUT_URL) |
| apiproxy_stub_map.apiproxy.ReplaceStub( |
| 'urlfetch', FakeURLFetchServiceStub()) |
| apiproxy_stub_map.apiproxy.ReplaceStub( |
| 'datastore_v4', FakeDatastoreV4ServiceStub()) |
| |
| |
| class TestAPIServer(wsgi_test_utils.WSGITestCase): |
| """Tests for api_server.APIServer.""" |
| |
| def setUp(self): |
| setup_stubs() |
| self.server = api_server.APIServer('localhost', |
| 0, |
| APP_ID) |
| |
| def tearDown(self): |
| api_server.cleanup_stubs() |
| |
| def _assert_remote_call( |
| self, expected_remote_response, stub_request, service, method): |
| """Test a call across the remote API to the API server. |
| |
| Args: |
| expected_remote_response: the remote response that is expected. |
| stub_request: the request protobuf that the stub expects. |
| service: the stub's service name. |
| method: which service method to call. |
| """ |
| request_environ = {'HTTP_HOST': 'machine:8080'} |
| wsgiref.util.setup_testing_defaults(request_environ) |
| |
| with request_data.request(request_environ, None) as request_id: |
| remote_request = remote_api_pb.Request() |
| remote_request.set_service_name(service) |
| remote_request.set_method(method) |
| remote_request.set_request(stub_request.Encode()) |
| remote_request.set_request_id(request_id) |
| remote_payload = remote_request.Encode() |
| |
| environ = {'CONTENT_LENGTH': len(remote_payload), |
| 'REQUEST_METHOD': 'POST', |
| 'wsgi.input': cStringIO.StringIO(remote_payload)} |
| |
| expected_headers = {'Content-Type': 'application/octet-stream'} |
| self.assertResponse('200 OK', |
| expected_headers, |
| expected_remote_response.Encode(), |
| self.server, |
| environ) |
| |
| def test_user_api_call(self): |
| logout_response = user_service_pb.CreateLogoutURLResponse() |
| logout_response.set_logout_url( |
| USER_LOGOUT_URL % urllib.quote('http://machine:8080/crazy_logout')) |
| |
| expected_remote_response = remote_api_pb.Response() |
| expected_remote_response.set_response(logout_response.Encode()) |
| |
| logout_request = user_service_pb.CreateLogoutURLRequest() |
| logout_request.set_destination_url('/crazy_logout') |
| |
| self._assert_remote_call( |
| expected_remote_response, logout_request, 'user', 'CreateLogoutURL') |
| |
| def test_datastore_v4_api_call(self): |
| begin_transaction_response = datastore_v4_pb.BeginTransactionResponse() |
| begin_transaction_response.set_transaction('whatever') |
| |
| expected_remote_response = remote_api_pb.Response() |
| expected_remote_response.set_response( |
| begin_transaction_response.Encode()) |
| |
| begin_transaction_request = datastore_v4_pb.BeginTransactionRequest() |
| |
| self._assert_remote_call( |
| expected_remote_response, begin_transaction_request, |
| 'datastore_v4', 'BeginTransaction') |
| |
| def test_datastore_v4_api_calls_handled(self): |
| # We are only using RpcTestHarness as a clean way to get the list of |
| # service methods. |
| harness = rpc_test_harness.RpcTestHarness( |
| datastore_v4_pb.DatastoreV4Service) |
| deprecated = ['Get', 'Write'] |
| methods = set([k for k in harness.__dict__.keys() |
| if k not in deprecated and not k.startswith('_')]) |
| self.assertEqual(methods, set(api_server._DATASTORE_V4_METHODS.keys())) |
| |
| def test_GET(self): |
| environ = {'REQUEST_METHOD': 'GET', |
| 'QUERY_STRING': 'rtok=23'} |
| self.assertResponse('200 OK', |
| {'Content-Type': 'text/plain'}, |
| "{app_id: test, rtok: '23'}\n", |
| self.server, |
| environ) |
| |
| def test_unsupported_method(self): |
| environ = {'REQUEST_METHOD': 'HEAD', |
| 'QUERY_STRING': 'rtok=23'} |
| self.assertResponse('405 Method Not Allowed', |
| {}, |
| '', |
| self.server, |
| environ) |
| |
| def test_exception(self): |
| urlfetch_request = urlfetch_service_pb.URLFetchRequest() |
| urlfetch_request.set_url('exception') |
| urlfetch_request.set_method(urlfetch_service_pb.URLFetchRequest.GET) |
| |
| expected_remote_response = remote_api_pb.Response() |
| expected_remote_response.set_exception(pickle.dumps( |
| RuntimeError(repr(IOError('the remote error'))))) |
| |
| self._assert_remote_call( |
| expected_remote_response, urlfetch_request, 'urlfetch', 'Fetch') |
| |
| def test_application_error(self): |
| urlfetch_request = urlfetch_service_pb.URLFetchRequest() |
| urlfetch_request.set_url('application_error') |
| urlfetch_request.set_method(urlfetch_service_pb.URLFetchRequest.GET) |
| |
| expected_remote_response = remote_api_pb.Response() |
| expected_remote_response.mutable_application_error().set_code(23) |
| expected_remote_response.mutable_application_error().set_detail('details') |
| expected_remote_response.set_exception(pickle.dumps( |
| apiproxy_errors.ApplicationError(23, 'details'))) |
| |
| self._assert_remote_call( |
| expected_remote_response, urlfetch_request, 'urlfetch', 'Fetch') |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |