diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/test_website/tests | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/test_website/tests')
| -rw-r--r-- | addons/test_website/tests/__init__.py | 13 | ||||
| -rw-r--r-- | addons/test_website/tests/test_controller_args.py | 31 | ||||
| -rw-r--r-- | addons/test_website/tests/test_custom_snippet.py | 13 | ||||
| -rw-r--r-- | addons/test_website/tests/test_error.py | 10 | ||||
| -rw-r--r-- | addons/test_website/tests/test_is_multilang.py | 71 | ||||
| -rw-r--r-- | addons/test_website/tests/test_multi_company.py | 16 | ||||
| -rw-r--r-- | addons/test_website/tests/test_performance.py | 10 | ||||
| -rw-r--r-- | addons/test_website/tests/test_redirect.py | 162 | ||||
| -rw-r--r-- | addons/test_website/tests/test_reset_views.py | 111 | ||||
| -rw-r--r-- | addons/test_website/tests/test_session.py | 9 | ||||
| -rw-r--r-- | addons/test_website/tests/test_views_during_module_operation.py | 83 |
11 files changed, 529 insertions, 0 deletions
diff --git a/addons/test_website/tests/__init__.py b/addons/test_website/tests/__init__.py new file mode 100644 index 00000000..38da4577 --- /dev/null +++ b/addons/test_website/tests/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_controller_args +from . import test_custom_snippet +from . import test_error +from . import test_is_multilang +from . import test_multi_company +from . import test_performance +from . import test_redirect +from . import test_reset_views +from . import test_session +from . import test_views_during_module_operation diff --git a/addons/test_website/tests/test_controller_args.py b/addons/test_website/tests/test_controller_args.py new file mode 100644 index 00000000..fc21c22a --- /dev/null +++ b/addons/test_website/tests/test_controller_args.py @@ -0,0 +1,31 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import odoo.tests + + +@odoo.tests.common.tagged('post_install', '-at_install') +class TestWebsiteControllerArgs(odoo.tests.HttpCase): + + def test_crawl_args(self): + req = self.url_open('/ignore_args/converter/valueA/?b=valueB&c=valueC') + self.assertEqual(req.status_code, 200) + self.assertEqual(req.json(), {'a': 'valueA', 'b': 'valueB', 'kw': {'c': 'valueC'}}) + + req = self.url_open('/ignore_args/converter/valueA/nokw?b=valueB&c=valueC') + self.assertEqual(req.status_code, 200) + self.assertEqual(req.json(), {'a': 'valueA', 'b': 'valueB'}) + + req = self.url_open('/ignore_args/converteronly/valueA/?b=valueB&c=valueC') + self.assertEqual(req.status_code, 200) + self.assertEqual(req.json(), {'a': 'valueA', 'kw': None}) + + req = self.url_open('/ignore_args/none?a=valueA&b=valueB') + self.assertEqual(req.status_code, 200) + self.assertEqual(req.json(), {'a': None, 'kw': None}) + + req = self.url_open('/ignore_args/a?a=valueA&b=valueB') + self.assertEqual(req.status_code, 200) + self.assertEqual(req.json(), {'a': 'valueA', 'kw': None}) + + req = self.url_open('/ignore_args/kw?a=valueA&b=valueB') + self.assertEqual(req.status_code, 200) + self.assertEqual(req.json(), {'a': 'valueA', 'kw': {'b': 'valueB'}}) diff --git a/addons/test_website/tests/test_custom_snippet.py b/addons/test_website/tests/test_custom_snippet.py new file mode 100644 index 00000000..c79ed227 --- /dev/null +++ b/addons/test_website/tests/test_custom_snippet.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import odoo.tests +from odoo.tools import mute_logger + + +@odoo.tests.common.tagged('post_install', '-at_install') +class TestCustomSnippet(odoo.tests.HttpCase): + + @mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http') + def test_01_run_tour(self): + self.start_tour("/", 'test_custom_snippet', login="admin") diff --git a/addons/test_website/tests/test_error.py b/addons/test_website/tests/test_error.py new file mode 100644 index 00000000..f4c9334e --- /dev/null +++ b/addons/test_website/tests/test_error.py @@ -0,0 +1,10 @@ +import odoo.tests +from odoo.tools import mute_logger + + +@odoo.tests.common.tagged('post_install', '-at_install') +class TestWebsiteError(odoo.tests.HttpCase): + + @mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http') + def test_01_run_test(self): + self.start_tour("/test_error_view", 'test_error_website') diff --git a/addons/test_website/tests/test_is_multilang.py b/addons/test_website/tests/test_is_multilang.py new file mode 100644 index 00000000..1c7d6180 --- /dev/null +++ b/addons/test_website/tests/test_is_multilang.py @@ -0,0 +1,71 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import odoo.tests +import lxml + + +@odoo.tests.common.tagged('post_install', '-at_install') +class TestIsMultiLang(odoo.tests.HttpCase): + + def test_01_is_multilang_url(self): + website = self.env['website'].search([], limit=1) + fr = self.env.ref('base.lang_fr').sudo() + en = self.env.ref('base.lang_en').sudo() + + fr.active = True + fr_prefix = "/" + fr.iso_code + + website.default_lang_id = en + website.language_ids = en + fr + + for data in [None, {'post': True}]: # GET / POST + body = lxml.html.fromstring(self.url_open('/fr/multi_url', data=data).content) + + self.assertEqual(fr_prefix + '/get', body.find('./a[@id="get"]').get('href')) + self.assertEqual(fr_prefix + '/post', body.find('./form[@id="post"]').get('action')) + self.assertEqual(fr_prefix + '/get_post', body.find('./a[@id="get_post"]').get('href')) + self.assertEqual('/get_post_nomultilang', body.find('./a[@id="get_post_nomultilang"]').get('href')) + + def test_02_url_lang_code_underscore(self): + website = self.env['website'].browse(1) + it = self.env.ref('base.lang_it').sudo() + en = self.env.ref('base.lang_en').sudo() + be = self.env.ref('base.lang_fr_BE').sudo() + country1 = self.env['res.country'].create({'name': "My Super Country"}) + + it.active = True + be.active = True + website.domain = 'http://127.0.0.1:8069' # for _is_canonical_url + website.default_lang_id = en + website.language_ids = en + it + be + params = { + 'src': country1.name, + 'value': country1.name + ' Italia', + 'type': 'model', + 'name': 'res.country,name', + 'res_id': country1.id, + 'lang': it.code, + 'state': 'translated', + } + self.env['ir.translation'].create(params) + params.update({ + 'value': country1.name + ' Belgium', + 'lang': be.code, + }) + self.env['ir.translation'].create(params) + r = self.url_open('/test_lang_url/%s' % country1.id) + self.assertEqual(r.status_code, 200) + self.assertTrue(r.url.endswith('/test_lang_url/my-super-country-%s' % country1.id)) + + r = self.url_open('/%s/test_lang_url/%s' % (it.url_code, country1.id)) + self.assertEqual(r.status_code, 200) + self.assertTrue(r.url.endswith('/%s/test_lang_url/my-super-country-italia-%s' % (it.url_code, country1.id))) + + body = lxml.html.fromstring(r.content) + # Note: this test is indirectly testing the `ref=canonical` tag is correctly set, + # as it is required in order for `rel=alternate` tags to be inserted in the DOM + it_href = body.find('./head/link[@rel="alternate"][@hreflang="it"]').get('href') + fr_href = body.find('./head/link[@rel="alternate"][@hreflang="fr"]').get('href') + en_href = body.find('./head/link[@rel="alternate"][@hreflang="en"]').get('href') + self.assertTrue(it_href.endswith('/%s/test_lang_url/my-super-country-italia-%s' % (it.url_code, country1.id))) + self.assertTrue(fr_href.endswith('/%s/test_lang_url/my-super-country-belgium-%s' % (be.url_code, country1.id))) + self.assertTrue(en_href.endswith('/test_lang_url/my-super-country-%s' % country1.id)) diff --git a/addons/test_website/tests/test_multi_company.py b/addons/test_website/tests/test_multi_company.py new file mode 100644 index 00000000..1395466a --- /dev/null +++ b/addons/test_website/tests/test_multi_company.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests.common import HttpCase, tagged + + +@tagged('post_install', '-at_install') +class TestMultiCompany(HttpCase): + + def test_company_in_context(self): + """ Test website company is set in context """ + website = self.env.ref('website.default_website') + company = self.env['res.company'].create({'name': "Adaa"}) + website.company_id = company + response = self.url_open('/multi_company_website') + self.assertEqual(response.json()[0], company.id) diff --git a/addons/test_website/tests/test_performance.py b/addons/test_website/tests/test_performance.py new file mode 100644 index 00000000..cd06dfda --- /dev/null +++ b/addons/test_website/tests/test_performance.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.addons.website.tests.test_performance import UtilPerf + + +class TestPerformance(UtilPerf): + def test_10_perf_sql_website_controller_minimalist(self): + url = '/empty_controller_test' + self.assertEqual(self._get_url_hot_query(url), 3) diff --git a/addons/test_website/tests/test_redirect.py b/addons/test_website/tests/test_redirect.py new file mode 100644 index 00000000..38eebcde --- /dev/null +++ b/addons/test_website/tests/test_redirect.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import odoo +from odoo.tests import HttpCase, tagged +from odoo.tests.common import HOST +from odoo.tools import mute_logger +from odoo.addons.http_routing.models.ir_http import slug + +from unittest.mock import patch + + +@tagged('-at_install', 'post_install') +class TestRedirect(HttpCase): + + def setUp(self): + super(TestRedirect, self).setUp() + + self.user_portal = self.env['res.users'].with_context({'no_reset_password': True}).create({ + 'name': 'Test Website Portal User', + 'login': 'portal_user', + 'password': 'portal_user', + 'email': 'portal_user@mail.com', + 'groups_id': [(6, 0, [self.env.ref('base.group_portal').id])] + }) + + self.base_url = "http://%s:%s" % (HOST, odoo.tools.config['http_port']) + + def test_01_redirect_308_model_converter(self): + + self.env['website.rewrite'].create({ + 'name': 'Test Website Redirect', + 'redirect_type': '308', + 'url_from': '/test_website/country/<model("res.country"):country>', + 'url_to': '/redirected/country/<model("res.country"):country>', + }) + country_ad = self.env.ref('base.ad') + + """ Ensure 308 redirect with model converter works fine, including: + - Correct & working redirect as public user + - Correct & working redirect as logged in user + - Correct replace of url_for() URLs in DOM + """ + url = '/test_website/country/' + slug(country_ad) + redirect_url = url.replace('test_website', 'redirected') + + # [Public User] Open the original url and check redirect OK + r = self.url_open(url) + self.assertEqual(r.status_code, 200) + self.assertTrue(r.url.endswith(redirect_url), "Ensure URL got redirected") + self.assertTrue(country_ad.name in r.text, "Ensure the controller returned the expected value") + self.assertTrue(redirect_url in r.text, "Ensure the url_for has replaced the href URL in the DOM") + + # [Logged In User] Open the original url and check redirect OK + self.authenticate("portal_user", "portal_user") + r = self.url_open(url) + self.assertEqual(r.status_code, 200) + self.assertTrue(r.url.endswith(redirect_url), "Ensure URL got redirected (2)") + self.assertTrue('Logged In' in r.text, "Ensure logged in") + self.assertTrue(country_ad.name in r.text, "Ensure the controller returned the expected value (2)") + self.assertTrue(redirect_url in r.text, "Ensure the url_for has replaced the href URL in the DOM") + + @mute_logger('odoo.addons.http_routing.models.ir_http') # mute 403 warning + def test_02_redirect_308_RequestUID(self): + self.env['website.rewrite'].create({ + 'name': 'Test Website Redirect', + 'redirect_type': '308', + 'url_from': '/test_website/200/<model("test.model"):rec>', + 'url_to': '/test_website/308/<model("test.model"):rec>', + }) + + rec_published = self.env['test.model'].create({'name': 'name', 'website_published': True}) + rec_unpublished = self.env['test.model'].create({'name': 'name', 'website_published': False}) + + WebsiteHttp = odoo.addons.website.models.ir_http.Http + + def _get_error_html(env, code, value): + return str(code).split('_')[-1], "CUSTOM %s" % code + + with patch.object(WebsiteHttp, '_get_error_html', _get_error_html): + # Patch will avoid to display real 404 page and regenerate assets each time and unlink old one. + # And it allow to be sur that exception id handled by handle_exception and return a "managed error" page. + + # published + resp = self.url_open("/test_website/200/name-%s" % rec_published.id, allow_redirects=False) + self.assertEqual(resp.status_code, 308) + self.assertEqual(resp.headers.get('Location'), self.base_url + "/test_website/308/name-%s" % rec_published.id) + + resp = self.url_open("/test_website/308/name-%s" % rec_published.id, allow_redirects=False) + self.assertEqual(resp.status_code, 200) + + resp = self.url_open("/test_website/200/xx-%s" % rec_published.id, allow_redirects=False) + self.assertEqual(resp.status_code, 308) + self.assertEqual(resp.headers.get('Location'), self.base_url + "/test_website/308/xx-%s" % rec_published.id) + + resp = self.url_open("/test_website/308/xx-%s" % rec_published.id, allow_redirects=False) + self.assertEqual(resp.status_code, 301) + self.assertEqual(resp.headers.get('Location'), self.base_url + "/test_website/308/name-%s" % rec_published.id) + + resp = self.url_open("/test_website/200/xx-%s" % rec_published.id, allow_redirects=True) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.url, self.base_url + "/test_website/308/name-%s" % rec_published.id) + + # unexisting + resp = self.url_open("/test_website/200/name-100", allow_redirects=False) + self.assertEqual(resp.status_code, 308) + self.assertEqual(resp.headers.get('Location'), self.base_url + "/test_website/308/name-100") + + resp = self.url_open("/test_website/308/name-100", allow_redirects=False) + self.assertEqual(resp.status_code, 404) + self.assertEqual(resp.text, "CUSTOM 404") + + resp = self.url_open("/test_website/200/xx-100", allow_redirects=False) + self.assertEqual(resp.status_code, 308) + self.assertEqual(resp.headers.get('Location'), self.base_url + "/test_website/308/xx-100") + + resp = self.url_open("/test_website/308/xx-100", allow_redirects=False) + self.assertEqual(resp.status_code, 404) + self.assertEqual(resp.text, "CUSTOM 404") + + # unpublish + resp = self.url_open("/test_website/200/name-%s" % rec_unpublished.id, allow_redirects=False) + self.assertEqual(resp.status_code, 308) + self.assertEqual(resp.headers.get('Location'), self.base_url + "/test_website/308/name-%s" % rec_unpublished.id) + + resp = self.url_open("/test_website/308/name-%s" % rec_unpublished.id, allow_redirects=False) + self.assertEqual(resp.status_code, 403) + self.assertEqual(resp.text, "CUSTOM 403") + + resp = self.url_open("/test_website/200/xx-%s" % rec_unpublished.id, allow_redirects=False) + self.assertEqual(resp.status_code, 308) + self.assertEqual(resp.headers.get('Location'), self.base_url + "/test_website/308/xx-%s" % rec_unpublished.id) + + resp = self.url_open("/test_website/308/xx-%s" % rec_unpublished.id, allow_redirects=False) + self.assertEqual(resp.status_code, 403) + self.assertEqual(resp.text, "CUSTOM 403") + + # with seo_name as slug + rec_published.seo_name = "seo_name" + rec_unpublished.seo_name = "seo_name" + + resp = self.url_open("/test_website/200/seo-name-%s" % rec_published.id, allow_redirects=False) + self.assertEqual(resp.status_code, 308) + self.assertEqual(resp.headers.get('Location'), self.base_url + "/test_website/308/seo-name-%s" % rec_published.id) + + resp = self.url_open("/test_website/308/seo-name-%s" % rec_published.id, allow_redirects=False) + self.assertEqual(resp.status_code, 200) + + resp = self.url_open("/test_website/200/xx-%s" % rec_unpublished.id, allow_redirects=False) + self.assertEqual(resp.status_code, 308) + self.assertEqual(resp.headers.get('Location'), self.base_url + "/test_website/308/xx-%s" % rec_unpublished.id) + + resp = self.url_open("/test_website/308/xx-%s" % rec_unpublished.id, allow_redirects=False) + self.assertEqual(resp.status_code, 403) + self.assertEqual(resp.text, "CUSTOM 403") + + resp = self.url_open("/test_website/200/xx-100", allow_redirects=False) + self.assertEqual(resp.status_code, 308) + self.assertEqual(resp.headers.get('Location'), self.base_url + "/test_website/308/xx-100") + + resp = self.url_open("/test_website/308/xx-100", allow_redirects=False) + self.assertEqual(resp.status_code, 404) + self.assertEqual(resp.text, "CUSTOM 404") diff --git a/addons/test_website/tests/test_reset_views.py b/addons/test_website/tests/test_reset_views.py new file mode 100644 index 00000000..a11e8282 --- /dev/null +++ b/addons/test_website/tests/test_reset_views.py @@ -0,0 +1,111 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import re + +import odoo.tests +from odoo.tools import mute_logger + + +def break_view(view, fr='<p>placeholder</p>', to='<p t-field="not.exist"/>'): + view.arch = view.arch.replace(fr, to) + + +@odoo.tests.common.tagged('post_install', '-at_install') +class TestWebsiteResetViews(odoo.tests.HttpCase): + + def fix_it(self, page, mode='soft'): + self.authenticate("admin", "admin") + resp = self.url_open(page) + self.assertEqual(resp.status_code, 500, "Waiting 500") + self.assertTrue('<button data-mode="soft" class="reset_templates_button' in resp.text) + data = {'view_id': self.find_template(resp), 'redirect': page, 'mode': mode} + resp = self.url_open('/website/reset_template', data) + self.assertEqual(resp.status_code, 200, "Waiting 200") + + def find_template(self, response): + find = re.search(r'<input.*type="hidden".*name="view_id".*value="([0-9]+)?"', response.text) + return find and find.group(1) + + def setUp(self): + super(TestWebsiteResetViews, self).setUp() + self.Website = self.env['website'] + self.View = self.env['ir.ui.view'] + self.test_view = self.Website.viewref('test_website.test_view') + + @mute_logger('odoo.addons.http_routing.models.ir_http') + def test_01_reset_specific_page_view(self): + self.test_page_view = self.Website.viewref('test_website.test_page_view') + total_views = self.View.search_count([('type', '=', 'qweb')]) + # Trigger COW then break the QWEB XML on it + break_view(self.test_page_view.with_context(website_id=1)) + self.assertEqual(total_views + 1, self.View.search_count([('type', '=', 'qweb')]), "Missing COW view") + self.fix_it('/test_page_view') + + @mute_logger('odoo.addons.http_routing.models.ir_http') + def test_02_reset_specific_view_controller(self): + total_views = self.View.search_count([('type', '=', 'qweb')]) + # Trigger COW then break the QWEB XML on it + # `t-att-data="not.exist"` will test the case where exception.html contains branding + break_view(self.test_view.with_context(website_id=1), to='<p t-att-data="not.exist" />') + self.assertEqual(total_views + 1, self.View.search_count([('type', '=', 'qweb')]), "Missing COW view") + self.fix_it('/test_view') + + @mute_logger('odoo.addons.http_routing.models.ir_http') + def test_03_reset_specific_view_controller_t_called(self): + self.test_view_to_be_t_called = self.Website.viewref('test_website.test_view_to_be_t_called') + + total_views = self.View.search_count([('type', '=', 'qweb')]) + # Trigger COW then break the QWEB XML on it + break_view(self.test_view_to_be_t_called.with_context(website_id=1)) + break_view(self.test_view, to='<t t-call="test_website.test_view_to_be_t_called"/>') + self.assertEqual(total_views + 1, self.View.search_count([('type', '=', 'qweb')]), "Missing COW view") + self.fix_it('/test_view') + + @mute_logger('odoo.addons.http_routing.models.ir_http') + def test_04_reset_specific_view_controller_inherit(self): + self.test_view_child_broken = self.Website.viewref('test_website.test_view_child_broken') + + # Activate and break the inherited view + self.test_view_child_broken.active = True + break_view(self.test_view_child_broken.with_context(website_id=1, load_all_views=True)) + + self.fix_it('/test_view') + + # This test work in real life, but not in test mode since we cannot rollback savepoint. + # @mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.addons.website.models.ir_ui_view') + # def test_05_reset_specific_view_controller_broken_request(self): + # total_views = self.View.search_count([('type', '=', 'qweb')]) + # # Trigger COW then break the QWEB XML on it + # break_view(self.test_view.with_context(website_id=1), to='<t t-esc="request.env[\'website\'].browse(\'a\').name" />') + # self.assertEqual(total_views + 1, self.View.search_count([('type', '=', 'qweb')]), "Missing COW view (1)") + # self.fix_it('/test_view') + + # also mute ir.ui.view as `get_view_id()` will raise "Could not find view object with xml_id 'not.exist'"" + @mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.addons.website.models.ir_ui_view') + def test_06_reset_specific_view_controller_inexisting_template(self): + total_views = self.View.search_count([('type', '=', 'qweb')]) + # Trigger COW then break the QWEB XML on it + break_view(self.test_view.with_context(website_id=1), to='<t t-call="not.exist"/>') + self.assertEqual(total_views + 1, self.View.search_count([('type', '=', 'qweb')]), "Missing COW view (2)") + self.fix_it('/test_view') + + @mute_logger('odoo.addons.http_routing.models.ir_http') + def test_07_reset_page_view_complete_flow(self): + self.start_tour("/", 'test_reset_page_view_complete_flow_part1', login="admin") + self.fix_it('/test_page_view') + self.start_tour("/", 'test_reset_page_view_complete_flow_part2', login="admin") + self.fix_it('/test_page_view') + + @mute_logger('odoo.addons.http_routing.models.ir_http') + def test_08_reset_specific_page_view_hard_mode(self): + self.test_page_view = self.Website.viewref('test_website.test_page_view') + total_views = self.View.search_count([('type', '=', 'qweb')]) + # Trigger COW then break the QWEB XML on it + break_view(self.test_page_view.with_context(website_id=1)) + # Break it again to have a previous arch different than file arch + break_view(self.test_page_view.with_context(website_id=1)) + self.assertEqual(total_views + 1, self.View.search_count([('type', '=', 'qweb')]), "Missing COW view") + with self.assertRaises(AssertionError): + # soft reset should not be able to reset the view as previous + # version is also broken + self.fix_it('/test_page_view') + self.fix_it('/test_page_view', 'hard') diff --git a/addons/test_website/tests/test_session.py b/addons/test_website/tests/test_session.py new file mode 100644 index 00000000..22975ebd --- /dev/null +++ b/addons/test_website/tests/test_session.py @@ -0,0 +1,9 @@ +import odoo.tests +from odoo.tools import mute_logger + + +@odoo.tests.common.tagged('post_install', '-at_install') +class TestWebsiteSession(odoo.tests.HttpCase): + + def test_01_run_test(self): + self.start_tour('/', 'test_json_auth') diff --git a/addons/test_website/tests/test_views_during_module_operation.py b/addons/test_website/tests/test_views_during_module_operation.py new file mode 100644 index 00000000..c078afc0 --- /dev/null +++ b/addons/test_website/tests/test_views_during_module_operation.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests import standalone + + +@standalone('cow_views') +def test_01_cow_views_unlink_on_module_update(env): + """ Ensure COW views are correctly removed during module update. + Not removing the view could lead to traceback: + - Having a view A + - Having a view B that inherits from a view C + - View B t-call view A + - COW view B + - Delete view A and B from module datas and update it + - Rendering view C will crash since it will render child view B that + t-call unexisting view A + """ + + View = env['ir.ui.view'] + Imd = env['ir.model.data'] + + update_module_base_view = env.ref('test_website.update_module_base_view') + update_module_view_to_be_t_called = View.create({ + 'name': 'View to be t-called', + 'type': 'qweb', + 'arch': '<div>I will be t-called</div>', + 'key': 'test_website.update_module_view_to_be_t_called', + }) + update_module_child_view = View.create({ + 'name': 'Child View', + 'mode': 'extension', + 'inherit_id': update_module_base_view.id, + 'arch': ''' + <div position="inside"> + <t t-call="test_website.update_module_view_to_be_t_called"/> + </div> + ''', + 'key': 'test_website.update_module_child_view', + }) + + # Create IMD so when updating the module the views will be removed (not found in file) + Imd.create({ + 'module': 'test_website', + 'name': 'update_module_view_to_be_t_called', + 'model': 'ir.ui.view', + 'res_id': update_module_view_to_be_t_called.id, + }) + Imd.create({ + 'module': 'test_website', + 'name': 'update_module_child_view', + 'model': 'ir.ui.view', + 'res_id': update_module_child_view.id, + }) + + # Trigger COW on child view + update_module_child_view.with_context(website_id=1).write({'name': 'Child View (W1)'}) + + # Ensure views are correctly setup + msg = "View '%s' does not exist!" + assert View.search_count([ + ('type', '=', 'qweb'), + ('key', '=', update_module_child_view.key) + ]) == 2, msg % update_module_child_view.key + assert bool(env.ref(update_module_view_to_be_t_called.key)),\ + msg % update_module_view_to_be_t_called.key + assert bool(env.ref(update_module_base_view.key)), msg % update_module_base_view.key + + # Upgrade the module + test_website_module = env['ir.module.module'].search([('name', '=', 'test_website')]) + test_website_module.button_immediate_upgrade() + env.reset() # clear the set of environments + env = env() # get an environment that refers to the new registry + + # Ensure generic views got removed + view = env.ref('test_website.update_module_view_to_be_t_called', raise_if_not_found=False) + assert not view, "Generic view did not get removed!" + + # Ensure specific COW views got removed + assert not env['ir.ui.view'].search_count([ + ('type', '=', 'qweb'), + ('key', '=', 'test_website.update_module_child_view'), + ]), "Specific COW views did not get removed!" |
