Enable localizable REST API responses via the Accept-Language header

Add support for doing language resolution for a request, based on the
Accept-Language HTTP header. Using the lazy gettext functionality, from
oslo gettextutils, it is possible to use the resolved language to
translate exception messages to the user requested language and
return that translation from the API.

The patch removes individually imported _() so they don't replace the
one installed service-wide.

Also, it adds the ability to fully re-create a remote error
with the same kwargs with which it was originally created, so that we
can translate it and show it to the user.

Partially implements bp user-locale-api.

Change-Id: I63edc8463836bfff257daa8a2c66ed5d3a444254
This commit is contained in:
Luis A. Garcia 2013-07-18 00:49:49 +00:00
parent 0af1565a5e
commit 9004239b9f
17 changed files with 169 additions and 54 deletions

View File

@ -33,7 +33,7 @@ if os.path.exists(os.path.join(possible_topdir, 'heat', '__init__.py')):
from heat.openstack.common import gettextutils
gettextutils.install('heat')
gettextutils.install('heat', lazy=True)
from oslo.config import cfg

View File

@ -35,7 +35,7 @@ if os.path.exists(os.path.join(possible_topdir, 'heat', '__init__.py')):
from heat.openstack.common import gettextutils
gettextutils.install('heat')
gettextutils.install('heat', lazy=True)
from oslo.config import cfg

View File

@ -35,7 +35,7 @@ if os.path.exists(os.path.join(possible_topdir, 'heat', '__init__.py')):
from heat.openstack.common import gettextutils
gettextutils.install('heat')
gettextutils.install('heat', lazy=True)
from oslo.config import cfg

View File

@ -41,7 +41,7 @@ scriptname = os.path.basename(sys.argv[0])
from heat.openstack.common import gettextutils
gettextutils.install('heat')
gettextutils.install('heat', lazy=True)
if scriptname == 'heat-boto':
from heat.cfn_client import boto_client as heat_client

View File

@ -36,7 +36,7 @@ if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'heat', '__init__.py')):
from heat.openstack.common import gettextutils
gettextutils.install('heat')
gettextutils.install('heat', lazy=True)
from oslo.config import cfg

View File

@ -25,6 +25,10 @@ POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'heat', '__init__.py')):
sys.path.insert(0, POSSIBLE_TOPDIR)
from heat.openstack.common import gettextutils
gettextutils.install('heat')
from heat.cmd import manage
manage.main()

View File

@ -23,6 +23,7 @@ Cinder's faultwrapper
import traceback
import webob
from heat.common import exception
from heat.openstack.common import log as logging
import heat.openstack.common.rpc.common as rpc_common
@ -79,11 +80,18 @@ class FaultWrapper(wsgi.Middleware):
if ex_type.endswith(rpc_common._REMOTE_POSTFIX):
ex_type = ex_type[:-len(rpc_common._REMOTE_POSTFIX)]
message = str(ex)
if message.find('\n') > -1:
message, trace = message.split('\n', 1)
if isinstance(ex, exception.OpenstackException):
# If the exception is an OpenstackException it is going to have a
# translated Message object as the message, let's recreate it here
message = (ex.message % ex.kwargs
if hasattr(ex, 'kwargs') else ex.message)
else:
message = ex.message
trace = str(ex)
if trace.find('\n') > -1:
unused, trace = trace.split('\n', 1)
else:
message = str(ex)
trace = traceback.format_exc()
webob_exc = self.error_map.get(ex_type,

View File

@ -15,10 +15,6 @@
import routes
from heat.openstack.common import gettextutils
gettextutils.install('heat')
from heat.api.openstack.v1 import stacks
from heat.api.openstack.v1 import resources
from heat.api.openstack.v1 import events

View File

@ -21,7 +21,6 @@ from heat.common import wsgi
from heat.rpc import api as engine_api
from heat.common import identifier
from heat.rpc import client as rpc_client
from heat.openstack.common.gettextutils import _
summary_keys = [

View File

@ -29,7 +29,6 @@ from heat.rpc import client as rpc_client
from heat.common import urlfetch
from heat.openstack.common import log as logging
from heat.openstack.common.gettextutils import _
logger = logging.getLogger(__name__)

View File

@ -18,8 +18,6 @@ from functools import wraps
from heat.common import identifier
from heat.openstack.common.gettextutils import _
def tenant_local(handler):
'''

View File

@ -20,7 +20,6 @@
import functools
import urlparse
import sys
from heat.openstack.common.gettextutils import _
from heat.openstack.common import exception

View File

@ -2,6 +2,7 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -43,8 +44,8 @@ import webob.dec
import webob.exc
from heat.common import exception
from heat.openstack.common import gettextutils
from heat.openstack.common import importutils
from heat.openstack.common.gettextutils import _
URL_LENGTH_LIMIT = 50000
@ -431,6 +432,12 @@ class Request(webob.Request):
else:
return content_type
def best_match_language(self):
"""Determine language for returned response."""
all_languages = gettextutils.get_available_languages('heat')
return self.accept_language.best_match(all_languages,
default_match='en_US')
def is_json_content_type(request):
if request.method == 'GET':
@ -589,7 +596,17 @@ class Resource(object):
request, **action_args)
except TypeError as err:
logging.error(_('Exception handling resource: %s') % str(err))
raise webob.exc.HTTPBadRequest()
msg = _('The server could not comply with the request since\r\n'
'it is either malformed or otherwise incorrect.\r\n')
err = webob.exc.HTTPBadRequest(msg)
raise translate_exception(err, request.best_match_language())
except webob.exc.HTTPException as err:
logging.error(_("Returning %(code)s to user: %(explanation)s"),
{'code': err.code, 'explanation': err.explanation})
raise translate_exception(err, request.best_match_language())
except Exception as err:
logging.error(_("Unexpected error occurred serving API: %s") % err)
raise translate_exception(err, request.best_match_language())
# Here we support either passing in a serializer or detecting it
# based on the content type.
@ -653,6 +670,26 @@ class Resource(object):
return args
def translate_exception(exc, locale):
"""Translates all translatable elements of the given exception."""
exc.message = gettextutils.get_localized_message(exc.message, locale)
if isinstance(exc, webob.exc.HTTPError):
# If the explanation is not a Message, that means that the
# explanation is the default, generic and not translatable explanation
# from webop.exc. Since the explanation is the error shown when the
# exception is converted to a response, let's actually swap it with
# message, since message is what gets passed in at construction time
# in the API
if not isinstance(exc.explanation, gettextutils.Message):
exc.explanation = exc.message
exc.detail = ''
else:
exc.explanation = \
gettextutils.get_localized_message(exc.explanation, locale)
exc.detail = gettextutils.get_localized_message(exc.detail, locale)
return exc
class BasePasteFactory(object):
"""A base class for paste app and filter factories.

View File

@ -21,8 +21,6 @@ Exceptions common to OpenStack projects
import logging
from heat.openstack.common.gettextutils import _
_FATAL_EXCEPTION_FORMAT_ERRORS = False
@ -120,6 +118,7 @@ class OpenstackException(Exception):
def __init__(self, **kwargs):
try:
self.kwargs = kwargs
self._error_string = self.message % kwargs
except Exception as e:

View File

@ -24,7 +24,6 @@ import traceback
from oslo.config import cfg
import six
from heat.openstack.common.gettextutils import _
from heat.openstack.common import importutils
from heat.openstack.common import jsonutils
from heat.openstack.common import local

View File

@ -47,11 +47,15 @@ def request_with_middleware(middleware, func, req, *args, **kwargs):
return resp
def remote_error(ex_type, message=''):
"""convert rpc original exception to the one with _Remote suffix."""
# NOTE(jianingy): this function helps simulate the real world exceptions
def to_remote_error(error):
"""Converts the given exception to the one with the _Remote suffix.
This is how RPC exceptions are recreated on the caller's side, so
this helps better simulate how the exception mechanism actually works.
"""
ex_type = type(error)
kwargs = error.kwargs if hasattr(error, 'kwargs') else {}
message = error.message
module = ex_type().__class__.__module__
str_override = lambda self: "%s\n<Traceback>" % message
new_ex_type = type(ex_type.__name__ + rpc_common._REMOTE_POSTFIX,
@ -59,7 +63,7 @@ def remote_error(ex_type, message=''):
{'__str__': str_override, '__unicode__': str_override})
new_ex_type.__module__ = '%s%s' % (module, rpc_common._REMOTE_POSTFIX)
return new_ex_type()
return new_ex_type(**kwargs)
class InstantiationDataTest(HeatTestCase):
@ -398,7 +402,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
'method': 'list_stacks',
'args': {},
'version': self.api_version},
None).AndRaise(remote_error(AttributeError))
None).AndRaise(to_remote_error(AttributeError()))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -418,7 +422,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
'method': 'list_stacks',
'args': {},
'version': self.api_version},
None).AndRaise(remote_error(Exception))
None).AndRaise(to_remote_error(Exception()))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -513,6 +517,8 @@ class StackControllerTest(ControllerTest, HeatTestCase):
req = self._post('/stacks', json.dumps(body))
unknown_parameter = heat_exc.UnknownUserParameter(key='a')
missing_parameter = heat_exc.UserParameterMissing(key='a')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
@ -523,7 +529,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
'files': {},
'args': {'timeout_mins': 30}},
'version': self.api_version},
None).AndRaise(remote_error(AttributeError))
None).AndRaise(to_remote_error(AttributeError()))
rpc.call(req.context, self.topic,
{'namespace': None,
'method': 'create_stack',
@ -533,7 +539,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
'files': {},
'args': {'timeout_mins': 30}},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.UnknownUserParameter))
None).AndRaise(to_remote_error(unknown_parameter))
rpc.call(req.context, self.topic,
{'namespace': None,
'method': 'create_stack',
@ -543,7 +549,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
'files': {},
'args': {'timeout_mins': 30}},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.UserParameterMissing))
None).AndRaise(to_remote_error(missing_parameter))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
self.controller.create,
@ -579,6 +585,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
req = self._post('/stacks', json.dumps(body))
error = heat_exc.StackExists(stack_name='s')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
@ -589,7 +596,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
'files': {},
'args': {'timeout_mins': 30}},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.StackExists))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -612,6 +619,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
req = self._post('/stacks', json.dumps(body))
error = heat_exc.StackValidationFailed(message='')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
@ -622,7 +630,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
'files': {},
'args': {'timeout_mins': 30}},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.StackValidationFailed))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -678,13 +686,14 @@ class StackControllerTest(ControllerTest, HeatTestCase):
req = self._get('/stacks/%(stack_name)s' % locals())
error = heat_exc.StackNotFound(stack_name='a')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
'method': 'identify_stack',
'args': {'stack_name': stack_name},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.StackNotFound))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -727,13 +736,14 @@ class StackControllerTest(ControllerTest, HeatTestCase):
req = self._get('/stacks/%(stack_name)s/resources' % locals())
error = heat_exc.StackNotFound(stack_name='a')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
'method': 'identify_stack',
'args': {'stack_name': stack_name},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.StackNotFound))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -820,13 +830,14 @@ class StackControllerTest(ControllerTest, HeatTestCase):
req = self._get('/stacks/%(stack_name)s/%(stack_id)s' % identity)
error = heat_exc.StackNotFound(stack_name='a')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
'method': 'show_stack',
'args': {'stack_identity': dict(identity)},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.StackNotFound))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -844,13 +855,14 @@ class StackControllerTest(ControllerTest, HeatTestCase):
req = self._get('/stacks/%(stack_name)s/%(stack_id)s' % identity)
error = heat_exc.InvalidTenant(target='a', actual='b')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
'method': 'show_stack',
'args': {'stack_identity': dict(identity)},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.InvalidTenant))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -887,15 +899,15 @@ class StackControllerTest(ControllerTest, HeatTestCase):
def test_get_template_err_notfound(self):
identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6')
req = self._get('/stacks/%(stack_name)s/%(stack_id)s' % identity)
template = {u'Foo': u'bar'}
error = error = heat_exc.StackNotFound(stack_name='a')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
'method': 'get_template',
'args': {'stack_identity': dict(identity)},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.StackNotFound))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
@ -958,6 +970,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
req = self._put('/stacks/%(stack_name)s/%(stack_id)s' % identity,
json.dumps(body))
error = heat_exc.StackNotFound(stack_name='a')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
@ -968,7 +981,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
'files': {},
'args': {'timeout_mins': 30}},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.StackNotFound))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -1021,6 +1034,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
req = self._delete('/stacks/%(stack_name)s/%(stack_id)s' % identity)
error = heat_exc.StackNotFound(stack_name='a')
self.m.StubOutWithMock(rpc, 'call')
# Engine returns None when delete successful
rpc.call(req.context, self.topic,
@ -1028,7 +1042,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
'method': 'delete_stack',
'args': {'stack_identity': dict(identity)},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.StackNotFound))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -1122,13 +1136,14 @@ class StackControllerTest(ControllerTest, HeatTestCase):
'AWS::EC2::EIP',
'AWS::EC2::EIPAssociation']
error = heat_exc.ServerError(body='')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
'method': 'list_resource_types',
'args': {},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.ServerError))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -1158,13 +1173,15 @@ class StackControllerTest(ControllerTest, HeatTestCase):
def test_generate_template_not_found(self):
req = self._get('/resource_types/NOT_FOUND/template')
error = heat_exc.ResourceTypeNotFound(type_name='a')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
'method': 'generate_template',
'args': {'type_name': 'NOT_FOUND'},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.ResourceTypeNotFound))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
self.controller.generate_template,
@ -1267,13 +1284,14 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
req = self._get(stack_identity._tenant_path() + '/resources')
error = heat_exc.StackNotFound(stack_name='a')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
'method': 'list_stack_resources',
'args': {'stack_identity': stack_identity},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.StackNotFound))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -1354,6 +1372,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
req = self._get(res_identity._tenant_path())
error = heat_exc.StackNotFound(stack_name='a')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
@ -1361,7 +1380,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
'args': {'stack_identity': stack_identity,
'resource_name': res_name},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.StackNotFound))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -1384,6 +1403,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
req = self._get(res_identity._tenant_path())
error = heat_exc.ResourceNotFound(stack_name='a', resource_name='b')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
@ -1391,7 +1411,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
'args': {'stack_identity': stack_identity,
'resource_name': res_name},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.ResourceNotFound))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -1414,6 +1434,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
req = self._get(res_identity._tenant_path())
error = heat_exc.ResourceNotAvailable(resource_name='')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
@ -1421,7 +1442,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
'args': {'stack_identity': stack_identity,
'resource_name': res_name},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.ResourceNotAvailable))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -1488,6 +1509,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
req = self._get(res_identity._tenant_path() + '/metadata')
error = heat_exc.StackNotFound(stack_name='a')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
@ -1495,7 +1517,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
'args': {'stack_identity': stack_identity,
'resource_name': res_name},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.StackNotFound))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -1518,6 +1540,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
req = self._get(res_identity._tenant_path() + '/metadata')
error = heat_exc.ResourceNotFound(stack_name='a', resource_name='b')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
@ -1525,7 +1548,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
'args': {'stack_identity': stack_identity,
'resource_name': res_name},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.ResourceNotFound))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -1699,13 +1722,14 @@ class EventControllerTest(ControllerTest, HeatTestCase):
req = self._get(stack_identity._tenant_path() + '/events')
error = error = heat_exc.StackNotFound(stack_name='a')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
'method': 'list_events',
'args': {'stack_identity': stack_identity},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.StackNotFound))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -1943,13 +1967,14 @@ class EventControllerTest(ControllerTest, HeatTestCase):
req = self._get(stack_identity._tenant_path() +
'/resources/' + res_name + '/events/' + event_id)
error = error = heat_exc.StackNotFound(stack_name='a')
self.m.StubOutWithMock(rpc, 'call')
rpc.call(req.context, self.topic,
{'namespace': None,
'method': 'list_events',
'args': {'stack_identity': stack_identity},
'version': self.api_version},
None).AndRaise(remote_error(heat_exc.StackNotFound))
None).AndRaise(to_remote_error(error))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -2380,7 +2405,7 @@ class ActionControllerTest(ControllerTest, HeatTestCase):
'method': 'stack_suspend',
'args': {'stack_identity': stack_identity},
'version': self.api_version},
None).AndRaise(remote_error(AttributeError))
None).AndRaise(to_remote_error(AttributeError()))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,

View File

@ -17,6 +17,7 @@
import datetime
import stubout
import webob
from heat.common import exception
@ -26,6 +27,10 @@ from heat.tests.common import HeatTestCase
class RequestTest(HeatTestCase):
def setUp(self):
self.stubs = stubout.StubOutForTesting()
super(RequestTest, self).setUp()
def test_content_type_missing(self):
request = wsgi.Request.blank('/tests/123')
self.assertRaises(exception.InvalidContentType,
@ -74,9 +79,28 @@ class RequestTest(HeatTestCase):
result = request.best_match_content_type()
self.assertEqual(result, "application/json")
def test_best_match_language(self):
# Here we test that we are actually invoking language negotiation
# by webop and also that the default locale always available is en-US
request = wsgi.Request.blank('/')
accepted = 'unknown-lang'
request.headers = {'Accept-Language': accepted}
def fake_best_match(self, offers, default_match=None):
return default_match
self.stubs.SmartSet(request.accept_language,
'best_match', fake_best_match)
self.assertEqual(request.best_match_language(), 'en_US')
class ResourceTest(HeatTestCase):
def setUp(self):
self.stubs = stubout.StubOutForTesting()
super(ResourceTest, self).setUp()
def test_get_action_args(self):
env = {
'wsgiorg.routing_args': [
@ -160,6 +184,34 @@ class ResourceTest(HeatTestCase):
None)
self.assertRaises(webob.exc.HTTPBadRequest, resource, request)
def test_resource_call_error_handle_localized(self):
class Controller(object):
def delete(self, req, identity):
return (req, identity)
actions = {'action': 'delete', 'id': 12, 'body': 'data'}
env = {'wsgiorg.routing_args': [None, actions]}
request = wsgi.Request.blank('/tests/123', environ=env)
request.body = '{"foo" : "value"}'
message_es = "No Encontrado"
translated_ex = webob.exc.HTTPBadRequest(message_es)
resource = wsgi.Resource(Controller(),
wsgi.JSONRequestDeserializer(),
None)
def fake_translate_exception(ex, locale):
return translated_ex
self.stubs.SmartSet(wsgi,
'translate_exception', fake_translate_exception)
try:
resource(request)
except webob.exc.HTTPBadRequest as e:
self.assertEquals(message_es, e.message)
self.m.VerifyAll()
class JSONResponseSerializerTest(HeatTestCase):