[IMP] tool, website, website_sale: move test utils functions

We have multiple ways of simulating a frontend context while running python
tests (mainly during TransactionCase):
  - `MockObject` class from 8557bcfa30
  - `simulate_frontend_context` method from a5a5a57e48
  - `DotDict` from f8efc9a957

This commit moves `MockObject` to misc to be reusable and adapt it to be
accessible by dot notation with `DotDict`.

Part of #34396
This commit is contained in:
Romain Derie
2019-06-26 17:08:09 +00:00
parent bb81e4ccd7
commit 6ebcb1db80
4 changed files with 80 additions and 52 deletions
+8 -45
View File
@@ -2,10 +2,9 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
import re import re
import werkzeug
import odoo
from odoo import tools from odoo import tools
from odoo.addons.website.tools import MockRequest
from odoo.modules.module import get_module_resource from odoo.modules.module import get_module_resource
from odoo.tests.common import TransactionCase from odoo.tests.common import TransactionCase
@@ -66,42 +65,6 @@ class TestQweb(TransactionCase):
"user_id": demo.id, "user_id": demo.id,
}).encode('utf8')) }).encode('utf8'))
class MockObject(object):
_log_call = []
def __init__(self, *args, **kwargs):
self.__dict__ = kwargs
def __call__(self, *args, **kwargs):
self._log_call.append((args, kwargs))
return self
def __getitem__(self, index):
return self
def werkzeugRaiseNotFound(*args, **kwargs):
raise werkzeug.exceptions.NotFound()
class MockRequest(object):
""" Class with context manager mocking odoo.http.request for tests """
def __init__(self, env, website=None, context=None, multilang=True, routing=True):
app = MockObject(routing={
'type': 'http',
'website': True,
'multilang': multilang,
})
app.get_db_router = app.bind = app.match = app
if not routing:
app.match = werkzeugRaiseNotFound
self.request = MockObject(
env=env, context=context or {}, db=None, debug=False,
website=website, httprequest=MockObject(
path='/hello/',
app=app
)
)
odoo.http._request_stack.push(self.request)
def __enter__(self):
return self.request
def __exit__(self, exc_type, exc_value, traceback):
odoo.http._request_stack.pop()
class TestQwebProcessAtt(TransactionCase): class TestQwebProcessAtt(TransactionCase):
def setUp(self): def setUp(self):
@@ -128,7 +91,7 @@ class TestQwebProcessAtt(TransactionCase):
self._test_att('/a', {'href': '/a'}) self._test_att('/a', {'href': '/a'})
def test_process_att_no_website(self): def test_process_att_no_website(self):
with MockRequest(self.env) as request: with MockRequest(self.env):
# no website so URL rewriting # no website so URL rewriting
self._test_att('/', {'href': '/'}) self._test_att('/', {'href': '/'})
self._test_att('/en_US/', {'href': '/en_US/'}) self._test_att('/en_US/', {'href': '/en_US/'})
@@ -137,7 +100,7 @@ class TestQwebProcessAtt(TransactionCase):
self._test_att('/a', {'href': '/a'}) self._test_att('/a', {'href': '/a'})
def test_process_att_monolang_route(self): def test_process_att_monolang_route(self):
with MockRequest(self.env, website=self.website, multilang=False) as request: with MockRequest(self.env, website=self.website, multilang=False):
# lang not changed in URL but CDN enabled # lang not changed in URL but CDN enabled
self._test_att('/a', {'href': 'http://test.cdn/a'}) self._test_att('/a', {'href': 'http://test.cdn/a'})
self._test_att('/en_US/a', {'href': 'http://test.cdn/en_US/a'}) self._test_att('/en_US/a', {'href': 'http://test.cdn/en_US/a'})
@@ -145,19 +108,19 @@ class TestQwebProcessAtt(TransactionCase):
self._test_att('/en_US/b', {'href': '/en_US/b'}) self._test_att('/en_US/b', {'href': '/en_US/b'})
def test_process_att_no_request_lang(self): def test_process_att_no_request_lang(self):
with MockRequest(self.env, self.website) as request: with MockRequest(self.env, website=self.website):
self._test_att('/', {'href': '/'}) self._test_att('/', {'href': '/'})
self._test_att('/en_US/', {'href': '/'}) self._test_att('/en_US/', {'href': '/'})
self._test_att('/fr_FR/', {'href': '/fr_FR/'}) self._test_att('/fr_FR/', {'href': '/fr_FR/'})
def test_process_att_with_request_lang(self): def test_process_att_with_request_lang(self):
with MockRequest(self.env, self.website, context={'lang': 'fr_FR'}) as request: with MockRequest(self.env, website=self.website, context={'lang': 'fr_FR'}):
self._test_att('/', {'href': '/fr_FR/'}) self._test_att('/', {'href': '/fr_FR/'})
self._test_att('/en_US/', {'href': '/'}) self._test_att('/en_US/', {'href': '/'})
self._test_att('/fr_FR/', {'href': '/fr_FR/'}) self._test_att('/fr_FR/', {'href': '/fr_FR/'})
def test_process_att_matching_cdn_and_lang(self): def test_process_att_matching_cdn_and_lang(self):
with MockRequest(self.env, self.website) as request: with MockRequest(self.env, website=self.website):
# lang prefix is added before CDN # lang prefix is added before CDN
self._test_att('/a', {'href': 'http://test.cdn/a'}) self._test_att('/a', {'href': 'http://test.cdn/a'})
self._test_att('/en_US/a', {'href': 'http://test.cdn/a'}) self._test_att('/en_US/a', {'href': 'http://test.cdn/a'})
@@ -167,13 +130,13 @@ class TestQwebProcessAtt(TransactionCase):
self._test_att('/fr_FR/b', {'href': '/fr_FR/b'}) self._test_att('/fr_FR/b', {'href': '/fr_FR/b'})
def test_process_att_no_route(self): def test_process_att_no_route(self):
with MockRequest(self.env, self.website, context={'lang': 'fr_FR'}, routing=False) as request: with MockRequest(self.env, website=self.website, context={'lang': 'fr_FR'}, routing=False):
# default on multilang=True if route is not /{module}/static/ # default on multilang=True if route is not /{module}/static/
self._test_att('/web/static/hi', {'href': '/web/static/hi'}) self._test_att('/web/static/hi', {'href': '/web/static/hi'})
self._test_att('/my-page', {'href': '/fr_FR/my-page'}) self._test_att('/my-page', {'href': '/fr_FR/my-page'})
def test_process_att_url_crap(self): def test_process_att_url_crap(self):
with MockRequest(self.env, self.website) as request: with MockRequest(self.env, website=self.website) as request:
# #{fragment} is stripped from URL when testing route # #{fragment} is stripped from URL when testing route
self._test_att('/x#y?z', {'href': '/x#y?z'}) self._test_att('/x#y?z', {'href': '/x#y?z'})
self.assertEqual( self.assertEqual(
+60
View File
@@ -0,0 +1,60 @@
# -*- encoding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import odoo
import werkzeug
from odoo.tools import DotDict
class MockObject(object):
_log_call = []
def __init__(self, *args, **kwargs):
self.__dict__ = kwargs
def __call__(self, *args, **kwargs):
self._log_call.append((args, kwargs))
return self
def __getitem__(self, index):
return self
def werkzeugRaiseNotFound(*args, **kwargs):
raise werkzeug.exceptions.NotFound()
class MockRequest(object):
""" Class with context manager mocking odoo.http.request for tests """
def __init__(self, env, **kw):
app = MockObject(routing={
'type': 'http',
'website': True,
'multilang': kw.get('multilang', True),
})
app.get_db_router = app.bind = app.match = app
if not kw.get('routing', True):
app.match = werkzeugRaiseNotFound
self.request = DotDict({
'context': kw.get('context', {}),
'db': None,
'debug': False,
'env': env,
'httprequest': {
'path': '/hello/',
'app': app,
},
'redirect': werkzeug.utils.redirect,
'session': {
'sale_order_id': kw.get('sale_order_id'),
},
'website': kw.get('website'),
})
odoo.http._request_stack.push(self.request)
def __enter__(self):
return self.request
def __exit__(self, exc_type, exc_value, traceback):
odoo.http._request_stack.pop()
@@ -5,6 +5,7 @@ try:
except ImportError: except ImportError:
from mock import patch from mock import patch
from odoo.tests.common import HttpCase, TransactionCase from odoo.tests.common import HttpCase, TransactionCase
from odoo.tools import DotDict
''' /!\/!\ ''' /!\/!\
Calling `get_pricelist_available` after setting `property_product_pricelist` on Calling `get_pricelist_available` after setting `property_product_pricelist` on
@@ -154,13 +155,6 @@ def simulate_frontend_context(self, website_id=1):
self.addCleanup(patcher.stop) self.addCleanup(patcher.stop)
class DotDict(dict):
"""dot.notation access to dictionary attributes"""
def __getattr__(*args):
val = dict.get(*args)
return DotDict(val) if type(val) is dict else val
class TestWebsitePriceListAvailable(TransactionCase): class TestWebsitePriceListAvailable(TransactionCase):
# This is enough to avoid a mock (request.session/website do not exist during test) # This is enough to avoid a mock (request.session/website do not exist during test)
def get_pricelist_available(self, show_visible=False, website_id=1, country_code=None, website_sale_current_pl=None): def get_pricelist_available(self, show_visible=False, website_id=1, country_code=None, website_sale_current_pl=None):
+11
View File
@@ -1242,3 +1242,14 @@ def wrap_module(module, attr_list):
raise AttributeError(attrib) raise AttributeError(attrib)
# module and attr_list are in the closure # module and attr_list are in the closure
return WrappedModule() return WrappedModule()
class DotDict(dict):
"""Helper for dot.notation access to dictionary attributes
E.g.
foo = DotDict({'bar': False})
return foo.bar
"""
def __getattr__(self, attrib):
val = self.get(attrib)
return DotDict(val) if type(val) is dict else val