blob: 6793f1c015c2e441ca0cf41581220e4ca9a1a85a [file] [log] [blame]
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from contextlib import contextmanager
import collections
import time
# appengine sdk is supposed to be on the path.
import dev_appserver
dev_appserver.fix_sys_path()
import endpoints
from google.appengine.api import oauth
from google.appengine.api import urlfetch
from google.appengine.ext import ndb
from google.appengine.ext import testbed
import webtest
from testing_support import auto_stub
class AppengineTestCase(auto_stub.TestCase): # pragma: no cover
"""Base class for Appengine test cases.
Must set app_module to use self.test_app.
"""
# To be set in tests that wants to use test_app
app_module = None
# To be set in tests that want to test with custom task queues.
taskqueue_stub_root_path = None
# To be set in tests that want to change test datastore consistency policy.
datastore_stub_consistency_policy = None
def setUp(self):
super(AppengineTestCase, self).setUp()
self.testbed = testbed.Testbed()
# needed because endpoints expects a . in this value
self.testbed.setup_env(current_version_id='testbed.version')
self.testbed.activate()
# Can't use init_all_stubs() because PIL isn't in wheel.
self.testbed.init_app_identity_stub()
self.testbed.init_blobstore_stub()
self.testbed.init_capability_stub()
self.testbed.init_channel_stub()
self.testbed.init_datastore_v3_stub(
consistency_policy=self.datastore_stub_consistency_policy)
self.testbed.init_files_stub()
self.testbed.init_logservice_stub()
self.testbed.init_mail_stub()
self.testbed.init_memcache_stub()
self.testbed.init_modules_stub()
self.testbed.init_search_stub()
self.testbed.init_taskqueue_stub(root_path=self.taskqueue_stub_root_path)
self.testbed.init_urlfetch_stub()
self.testbed.init_user_stub()
self.testbed.init_xmpp_stub()
# Test app is lazily initialized on a first use from app_module.
self._test_app = None
self.taskqueue_stub = self.testbed.get_stub(testbed.TASKQUEUE_SERVICE_NAME)
def tearDown(self):
try:
self.testbed.deactivate()
finally:
super(AppengineTestCase, self).tearDown()
@property
def test_app(self):
"""Returns instance of webtest.TestApp that wraps app_module."""
if self._test_app is None:
# app_module may be a property, so access it only once.
app = self.app_module
if app is None:
self.fail('self.app_module is not provided by the test class')
self._test_app = webtest.TestApp(
app, extra_environ={'REMOTE_ADDR': '127.0.0.1'})
return self._test_app
def mock_now(self, now):
"""Mocks time in ndb properties that use auto_now and auto_now_add.
Args:
now: instance of datetime.datetime.
"""
self.mock(ndb.DateTimeProperty, '_now', lambda _: now)
self.mock(ndb.DateProperty, '_now', lambda _: now.date())
def mock_current_user(self, user_id='', user_email='', is_admin=False):
# dev_appserver hack.
self.testbed.setup_env(
USER_ID=user_id,
USER_EMAIL=user_email,
USER_IS_ADMIN=str(int(is_admin)),
overwrite=True)
def mock_endpoints_user(self, user_id='', is_admin=False):
self.mock(endpoints, 'get_current_user', lambda: user_id)
self.mock(oauth, 'is_current_user_admin', lambda _: is_admin)
@contextmanager
def mock_urlfetch(self):
class UrlHandlers:
def __init__(self):
self.response_class = collections.namedtuple(
'response', ['content', 'status_code', 'headers'])
self.urls = collections.defaultdict(lambda: self.response_class(
content=None, status_code=404, headers={}))
def register_handler(
self, url, content, status_code=200, headers=None, data=None):
self.urls[(url, data)] = self.response_class(
content=content, status_code=status_code, headers=headers or {})
def handle_url(self, url, payload=None, **_kwargs):
return self.urls[(url, payload)]
url_handlers = UrlHandlers()
yield url_handlers
self.mock(urlfetch, 'fetch', url_handlers.handle_url)
def mock_sleep(self):
self.mock(time, 'sleep', lambda _: None)
def execute_queued_tasks(self):
tasks = self.taskqueue_stub.get_filtered_tasks()
responses = []
while tasks: # Some tasks spawn more tasks, we execute until empty.
for queue in self.taskqueue_stub.GetQueues():
self.taskqueue_stub.FlushQueue(queue['name'])
for task in tasks:
params = task.extract_params()
extra_environ = {
'HTTP_X_APPENGINE_TASKNAME': str(task.name),
'HTTP_X_APPENGINE_QUEUENAME': str(task.queue_name or 'default'),
}
method = {
'GET': self.test_app.get,
'POST': self.test_app.post,
}[task.method]
responses.append(method(task.url, params, extra_environ=extra_environ))
tasks = self.taskqueue_stub.get_filtered_tasks()
return responses
class EndpointsTestCase(AppengineTestCase): # pragma: no cover
"""Base class for a test case that tests Cloud Endpoint Service.
Usage:
class MyTestCase(testing.EndpointsTestCase):
api_service_cls = MyEndpointsService
def test_stuff(self):
response = self.call_api('my_method')
self.assertEqual(...)
def test_expected_fail(self):
with self.call_should_fail(403):
self.call_api('protected_method')
"""
# Should be set in subclasses to a subclass of remote.Service.
api_service_cls = None
# See call_should_fail.
expected_fail_status = None
@property
def app_module(self):
"""WSGI module that wraps the API class, used by AppengineTestCase."""
return endpoints.api_server([self.api_service_cls], restricted=False)
def call_api(self, method, body=None, status=None):
"""Calls endpoints API method identified by its name."""
self.assertTrue(hasattr(self.api_service_cls, method))
return self.test_app.post_json(
'/_ah/spi/%s.%s' % (self.api_service_cls.__name__, method),
body or {},
status=status or self.expected_fail_status)
@contextmanager
def call_should_fail(self, status):
"""Asserts that Endpoints call inside the guarded region of code fails."""
# TODO(vadimsh): Get rid of this function and just use
# call_api(..., status=...). It existed as a workaround for bug that has
# been fixed:
# https://code.google.com/p/googleappengine/issues/detail?id=10544
assert self.expected_fail_status is None, 'nested call_should_fail'
assert status is not None
self.expected_fail_status = status
try:
yield
except AssertionError:
# Assertion can happen if tests are running on GAE < 1.9.31, where
# endpoints bug still exists (and causes webapp guts to raise assertion).
# It should be rare (since we are switching to GAE >= 1.9.31), so don't
# bother to check that assertion was indeed raised. Just skip it if it
# did.
pass
finally:
self.expected_fail_status = None