mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-19 14:52:06 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
21
odoo-bringout-oca-ocb-web/web/tests/__init__.py
Normal file
21
odoo-bringout-oca-ocb-web/web/tests/__init__.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_db_manager
|
||||
from . import test_health
|
||||
from . import test_image
|
||||
from . import test_ir_model
|
||||
from . import test_js
|
||||
from . import test_menu
|
||||
from . import test_click_everywhere
|
||||
from . import test_base_document_layout
|
||||
from . import test_load_menus
|
||||
from . import test_profiler
|
||||
from . import test_session_info
|
||||
from . import test_read_progress_bar
|
||||
from . import test_assets
|
||||
from . import test_assets_xml
|
||||
from . import test_login
|
||||
from . import test_web_search_read
|
||||
from . import test_domain
|
||||
from . import test_ir_qweb
|
||||
from . import test_reports
|
||||
BIN
odoo-bringout-oca-ocb-web/web/tests/logo_ci.png
Normal file
BIN
odoo-bringout-oca-ocb-web/web/tests/logo_ci.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
BIN
odoo-bringout-oca-ocb-web/web/tests/odoo.png
Normal file
BIN
odoo-bringout-oca-ocb-web/web/tests/odoo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
BIN
odoo-bringout-oca-ocb-web/web/tests/sweden.png
Normal file
BIN
odoo-bringout-oca-ocb-web/web/tests/sweden.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
86
odoo-bringout-oca-ocb-web/web/tests/test_assets.py
Normal file
86
odoo-bringout-oca-ocb-web/web/tests/test_assets.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
import odoo
|
||||
import odoo.tests
|
||||
|
||||
from odoo.tests.common import HttpCase
|
||||
from odoo.modules.module import get_manifest
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TestAssetsGenerateTimeCommon(odoo.tests.TransactionCase):
|
||||
|
||||
def generate_bundles(self):
|
||||
self.env['ir.attachment'].search([('url', '=like', '/web/assets/%')]).unlink() # delete existing attachement
|
||||
installed_module_names = self.env['ir.module.module'].search([('state', '=', 'installed')]).mapped('name')
|
||||
bundles = {
|
||||
key
|
||||
for module in installed_module_names
|
||||
for key in get_manifest(module)['assets']
|
||||
}
|
||||
|
||||
for bundle in bundles:
|
||||
with mute_logger('odoo.addons.base.models.assetsbundle'):
|
||||
for assets_type in 'css', 'js':
|
||||
try:
|
||||
start_t = time.time()
|
||||
css = assets_type == 'css'
|
||||
js = assets_type == 'js'
|
||||
self.env['ir.qweb']._generate_asset_nodes(bundle, css=css, js=js)
|
||||
yield (f'{bundle}.{assets_type}', time.time() - start_t)
|
||||
except ValueError:
|
||||
_logger.info('Error detected while generating bundle %r %s', bundle, assets_type)
|
||||
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install', 'assets_bundle')
|
||||
class TestLogsAssetsGenerateTime(TestAssetsGenerateTimeCommon):
|
||||
|
||||
def test_logs_assets_generate_time(self):
|
||||
"""
|
||||
The purpose of this test is to monitor the time of assets bundle generation.
|
||||
This is not meant to test the generation failure, hence the try/except and the mute logger.
|
||||
"""
|
||||
for bundle, duration in self.generate_bundles():
|
||||
_logger.info('Bundle %r generated in %.2fs', bundle, duration)
|
||||
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install', '-standard', 'assets_bundle')
|
||||
class TestAssetsGenerateTime(TestAssetsGenerateTimeCommon):
|
||||
"""
|
||||
This test is meant to be run nightly to ensure bundle generation does not exceed
|
||||
a low threshold
|
||||
"""
|
||||
|
||||
def test_assets_generate_time(self):
|
||||
thresholds = {
|
||||
'web.qunit_suite_tests.js': 3.6,
|
||||
'project.webclient.js': 2.5,
|
||||
'point_of_sale.pos_assets_backend.js': 2.5,
|
||||
'web.assets_backend.js': 2.5,
|
||||
}
|
||||
for bundle, duration in self.generate_bundles():
|
||||
threshold = thresholds.get(bundle, 2)
|
||||
self.assertLess(duration, threshold, "Bundle %r took more than %s sec" % (bundle, threshold))
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class TestLoad(HttpCase):
|
||||
def test_assets_already_exists(self):
|
||||
self.authenticate('admin', 'admin')
|
||||
_save_attachment = odoo.addons.base.models.assetsbundle.AssetsBundle.save_attachment
|
||||
|
||||
def save_attachment(bundle, extension, content):
|
||||
attachment = _save_attachment(bundle, extension, content)
|
||||
message = f"Trying to save an attachement for {bundle.name} when it should already exist: {attachment.url}"
|
||||
_logger.error(message)
|
||||
return attachment
|
||||
|
||||
with patch('odoo.addons.base.models.assetsbundle.AssetsBundle.save_attachment', save_attachment):
|
||||
self.url_open('/web').raise_for_status()
|
||||
self.url_open('/').raise_for_status()
|
||||
933
odoo-bringout-oca-ocb-web/web/tests/test_assets_xml.py
Normal file
933
odoo-bringout-oca-ocb-web/web/tests/test_assets_xml.py
Normal file
|
|
@ -0,0 +1,933 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import random
|
||||
import re
|
||||
from unittest.mock import patch
|
||||
import textwrap
|
||||
from datetime import datetime
|
||||
from lxml import etree
|
||||
import logging
|
||||
|
||||
import odoo
|
||||
from odoo.tests.common import BaseCase, HttpCase, tagged
|
||||
from odoo.tools import topological_sort
|
||||
from odoo.addons.base.models.assetsbundle import AssetsBundle, WebAsset
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class TestStaticInheritanceCommon(odoo.tests.TransactionCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1" random-attr="gloria">
|
||||
<span>Ho !</span>
|
||||
<div>At first I was afraid</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
<t t-name="template_1_2">
|
||||
<div>And I grew strong</div>
|
||||
</t>
|
||||
</templates>
|
||||
""",
|
||||
'/module_2/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_2_1" t-inherit="module_1.template_1_1" t-inherit-mode="primary">
|
||||
<xpath expr="//div[1]" position="after">
|
||||
<div>I was petrified</div>
|
||||
</xpath>
|
||||
<xpath expr="//span" position="attributes">
|
||||
<attribute name="type">Scary screams</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[2]" position="after">
|
||||
<div>But then I spent so many nights thinking how you did me wrong</div>
|
||||
</xpath>
|
||||
</form>
|
||||
<div t-name="template_2_2">
|
||||
<div>And I learned how to get along</div>
|
||||
</div>
|
||||
<form t-inherit="module_1.template_1_2" t-inherit-mode="extension">
|
||||
<xpath expr="//div[1]" position="after">
|
||||
<div>And I learned how to get along</div>
|
||||
</xpath>
|
||||
</form>
|
||||
</templates>
|
||||
""",
|
||||
}
|
||||
self._patch = patch.object(WebAsset, '_fetch_content', lambda asset: self.template_files[asset.url])
|
||||
self.startPatcher(self._patch)
|
||||
|
||||
def renderBundle(self, debug=False):
|
||||
files = []
|
||||
for url in self.template_files:
|
||||
atype = 'text/xml'
|
||||
if '.js' in url:
|
||||
atype = 'text/javascript'
|
||||
files.append({
|
||||
'atype': atype,
|
||||
'url': url,
|
||||
'filename': url,
|
||||
'content': None,
|
||||
'media': None,
|
||||
})
|
||||
asset = AssetsBundle('web.test_bundle', files, env=self.env, css=False, js=True)
|
||||
# to_node return the files descriptions and generate attachments.
|
||||
asset.to_node(css=False, js=False, debug=debug and 'assets' or '')
|
||||
content = asset.xml(show_inherit_info=debug)
|
||||
return f'<templates xml:space="preserve">\n{content}\n</templates>'
|
||||
|
||||
# Custom Assert
|
||||
def assertXMLEqual(self, output, expected):
|
||||
self.assertTrue(output)
|
||||
self.assertTrue(expected)
|
||||
self.assertEqual(etree.fromstring(output), etree.fromstring(expected))
|
||||
|
||||
@tagged('assets_bundle', 'static_templates')
|
||||
class TestStaticInheritance(TestStaticInheritanceCommon):
|
||||
# Actual test cases
|
||||
def test_static_with_debug_mode(self):
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<!-- Filepath: /module_1/static/xml/file_1.xml -->
|
||||
<form t-name="template_1_1" random-attr="gloria">
|
||||
<span>Ho !</span>
|
||||
<div>At first I was afraid</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
|
||||
<!-- Filepath: /module_1/static/xml/file_1.xml => /module_2/static/xml/file_1.xml -->
|
||||
<t t-name="template_1_2">
|
||||
<div>And I grew strong</div>
|
||||
<!-- Filepath: /module_2/static/xml/file_1.xml ; position="after" ; {'expr': '//div[1]'} --><div>And I learned how to get along</div>
|
||||
</t>
|
||||
|
||||
<!-- Filepath: /module_1/static/xml/file_1.xml => /module_2/static/xml/file_1.xml -->
|
||||
<form t-name="template_2_1" random-attr="gloria"><!-- Filepath: /module_2/static/xml/file_1.xml ; position="attributes" ; {'expr': '//span'} -->
|
||||
<span type="Scary screams">Ho !</span>
|
||||
<div>At first I was afraid</div>
|
||||
<!-- Filepath: /module_2/static/xml/file_1.xml ; position="after" ; {'expr': '//div[1]'} --><div>I was petrified</div>
|
||||
<!-- Filepath: /module_2/static/xml/file_1.xml ; position="after" ; {'expr': '//div[2]'} --><div>But then I spent so many nights thinking how you did me wrong</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
|
||||
<!-- Filepath: /module_2/static/xml/file_1.xml -->
|
||||
<div t-name="template_2_2">
|
||||
<div>And I learned how to get along</div>
|
||||
</div>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=True), expected)
|
||||
|
||||
def test_static_inheritance_01(self):
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<form t-name="template_1_1" random-attr="gloria">
|
||||
<span>Ho !</span>
|
||||
<div>At first I was afraid</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
<t t-name="template_1_2">
|
||||
<div>And I grew strong</div>
|
||||
<div>And I learned how to get along</div>
|
||||
</t>
|
||||
<form t-name="template_2_1" random-attr="gloria">
|
||||
<span type="Scary screams">Ho !</span>
|
||||
<div>At first I was afraid</div>
|
||||
<div>I was petrified</div>
|
||||
<div>But then I spent so many nights thinking how you did me wrong</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
<div t-name="template_2_2">
|
||||
<div>And I learned how to get along</div>
|
||||
</div>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_static_inheritance_02(self):
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
<form t-name="template_1_2" t-inherit="template_1_1" added="true">
|
||||
<xpath expr="//div[1]" position="after">
|
||||
<div>I was petrified</div>
|
||||
</xpath>
|
||||
</form>
|
||||
</templates>
|
||||
""",
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<form t-name="template_1_1" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
<form t-name="template_1_2" random-attr="gloria" added="true">
|
||||
<div>At first I was afraid</div>
|
||||
<div>I was petrified</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_static_inheritance_03(self):
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': '''
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1">
|
||||
<div>At first I was afraid</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
<form t-name="template_1_2" t-inherit="template_1_1" added="true">
|
||||
<xpath expr="//div[1]" position="after">
|
||||
<div>I was petrified</div>
|
||||
</xpath>
|
||||
</form>
|
||||
<form t-name="template_1_3" t-inherit="template_1_2" added="false" other="here">
|
||||
<xpath expr="//div[2]" position="replace"/>
|
||||
</form>
|
||||
</templates>
|
||||
'''
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<form t-name="template_1_1">
|
||||
<div>At first I was afraid</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
<form t-name="template_1_2" added="true">
|
||||
<div>At first I was afraid</div>
|
||||
<div>I was petrified</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
<form t-name="template_1_3" added="false" other="here">
|
||||
<div>At first I was afraid</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_static_inheritance_in_same_module(self):
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': '''
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1">
|
||||
<div>At first I was afraid</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
</templates>
|
||||
''',
|
||||
|
||||
'/module_1/static/xml/file_2.xml': '''
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="primary">
|
||||
<xpath expr="//div[1]" position="after">
|
||||
<div>I was petrified</div>
|
||||
</xpath>
|
||||
</form>
|
||||
</templates>
|
||||
'''
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<form t-name="template_1_1">
|
||||
<div>At first I was afraid</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
<form t-name="template_1_2">
|
||||
<div>At first I was afraid</div>
|
||||
<div>I was petrified</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_static_inheritance_in_same_file(self):
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': '''
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1">
|
||||
<div>At first I was afraid</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
<form t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="primary">
|
||||
<xpath expr="//div[1]" position="after">
|
||||
<div>I was petrified</div>
|
||||
</xpath>
|
||||
</form>
|
||||
</templates>
|
||||
''',
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<form t-name="template_1_1">
|
||||
<div>At first I was afraid</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
<form t-name="template_1_2">
|
||||
<div>At first I was afraid</div>
|
||||
<div>I was petrified</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_static_inherit_extended_template(self):
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': '''
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1">
|
||||
<div>At first I was afraid</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
<form t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension">
|
||||
<xpath expr="//div[1]" position="after">
|
||||
<div>I was petrified</div>
|
||||
</xpath>
|
||||
</form>
|
||||
<form t-name="template_1_3" t-inherit="template_1_1" t-inherit-mode="primary">
|
||||
<xpath expr="//div[3]" position="after">
|
||||
<div>But then I spent so many nights thinking how you did me wrong</div>
|
||||
</xpath>
|
||||
</form>
|
||||
</templates>
|
||||
''',
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<form t-name="template_1_1">
|
||||
<div>At first I was afraid</div>
|
||||
<div>I was petrified</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
<form t-name="template_1_3">
|
||||
<div>At first I was afraid</div>
|
||||
<div>I was petrified</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
<div>But then I spent so many nights thinking how you did me wrong</div>
|
||||
</form>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_sibling_extension(self):
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': '''
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1">
|
||||
<div>I am a man of constant sorrow</div>
|
||||
<div>I've seen trouble all my days</div>
|
||||
</form>
|
||||
</templates>
|
||||
''',
|
||||
|
||||
'/module_2/static/xml/file_1.xml': '''
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_2_1" t-inherit="module_1.template_1_1" t-inherit-mode="extension">
|
||||
<xpath expr="//div[1]" position="after">
|
||||
<div>In constant sorrow all through his days</div>
|
||||
</xpath>
|
||||
</form>
|
||||
</templates>
|
||||
''',
|
||||
|
||||
'/module_3/static/xml/file_1.xml': '''
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_3_1" t-inherit="module_1.template_1_1" t-inherit-mode="extension">
|
||||
<xpath expr="//div[2]" position="after">
|
||||
<div>Oh Brother !</div>
|
||||
</xpath>
|
||||
</form>
|
||||
</templates>
|
||||
'''
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<form t-name="template_1_1">
|
||||
<div>I am a man of constant sorrow</div>
|
||||
<div>In constant sorrow all through his days</div>
|
||||
<div>Oh Brother !</div>
|
||||
<div>I've seen trouble all my days</div>
|
||||
</form>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_static_misordered_modules(self):
|
||||
files = self.template_files
|
||||
self.template_files = {
|
||||
'/module_2/static/xml/file_1.xml': files['/module_2/static/xml/file_1.xml'],
|
||||
'/module_1/static/xml/file_1.xml': files['/module_1/static/xml/file_1.xml'],
|
||||
}
|
||||
with self.assertRaises(ValueError) as ve:
|
||||
self.renderBundle(debug=False)
|
||||
|
||||
self.assertEqual(
|
||||
str(ve.exception),
|
||||
"Module 'module_1' not loaded or inexistent (try to inherit 'template_1_1'), or templates of addon being loaded 'module_2' are misordered (template 'template_2_1')"
|
||||
)
|
||||
|
||||
def test_static_misordered_templates(self):
|
||||
self.template_files['/module_2/static/xml/file_1.xml'] = """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_2_1" t-inherit="module_2.template_2_2" t-inherit-mode="primary">
|
||||
<xpath expr="//div[1]" position="after">
|
||||
<div>I was petrified</div>
|
||||
</xpath>
|
||||
</form>
|
||||
<div t-name="template_2_2">
|
||||
<div>And I learned how to get along</div>
|
||||
</div>
|
||||
</templates>
|
||||
"""
|
||||
with self.assertRaises(ValueError) as ve:
|
||||
self.renderBundle(debug=False)
|
||||
|
||||
self.assertEqual(
|
||||
str(ve.exception),
|
||||
"Cannot create 'module_2.template_2_1' because the template to inherit 'module_2.template_2_2' is not found.",
|
||||
)
|
||||
|
||||
def test_replace_in_debug_mode(self):
|
||||
"""
|
||||
Replacing a template's meta definition in place doesn't keep the original attrs of the template
|
||||
"""
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
<t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension">
|
||||
<xpath expr="." position="replace">
|
||||
<div overriden-attr="overriden">And I grew strong</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
""",
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<div overriden-attr="overriden" t-name="template_1_1">
|
||||
And I grew strong
|
||||
</div>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_replace_in_debug_mode2(self):
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
<t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension">
|
||||
<xpath expr="." position="replace">
|
||||
<div>
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
And so you're back
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
""",
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<div t-name="template_1_1">
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
And so you're back
|
||||
</div>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_replace_in_debug_mode3(self):
|
||||
"""Text outside of a div which will replace a whole template
|
||||
becomes outside of the template
|
||||
This doesn't mean anything in terms of the business of template inheritance
|
||||
But it is in the XPATH specs"""
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
<t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension">
|
||||
<xpath expr="." position="replace">
|
||||
<div>
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
</div>
|
||||
And so you're back
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
""",
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<div t-name="template_1_1">
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
</div>
|
||||
And so you're back
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_replace_root_node_tag(self):
|
||||
"""
|
||||
Root node IS targeted by //NODE_TAG in xpath
|
||||
"""
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
<form>Inner Form</form>
|
||||
</form>
|
||||
<t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension">
|
||||
<xpath expr="//form" position="replace">
|
||||
<div>
|
||||
Form replacer
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
""",
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<div t-name="template_1_1">
|
||||
Form replacer
|
||||
</div>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_replace_root_node_tag_in_primary(self):
|
||||
"""
|
||||
Root node IS targeted by //NODE_TAG in xpath
|
||||
"""
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
<form>Inner Form</form>
|
||||
</form>
|
||||
<form t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="primary">
|
||||
<xpath expr="//form" position="replace">
|
||||
<div>Form replacer</div>
|
||||
</xpath>
|
||||
</form>
|
||||
</templates>
|
||||
""",
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<form t-name="template_1_1" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
<form>Inner Form</form>
|
||||
</form>
|
||||
<div t-name="template_1_2">
|
||||
Form replacer
|
||||
</div>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_inherit_primary_replace_debug(self):
|
||||
"""
|
||||
The inheriting template has got both its own defining attrs
|
||||
and new ones if one is to replace its defining root node
|
||||
"""
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
<t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="primary">
|
||||
<xpath expr="." position="replace">
|
||||
<div overriden-attr="overriden">
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
""",
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<form t-name="template_1_1" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
<div overriden-attr="overriden" t-name="template_1_2">
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
</div>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_replace_in_nodebug_mode1(self):
|
||||
"""Comments already in the arch are ignored"""
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
<t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension">
|
||||
<xpath expr="." position="replace">
|
||||
<div>
|
||||
<!-- Random Comment -->
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
And so you're back
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
""",
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<div t-name="template_1_1">
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
And so you're back
|
||||
</div>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_inherit_from_dotted_tname_1(self):
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="module_1.template_1_1.dot" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
<t t-name="template_1_2" t-inherit="template_1_1.dot" t-inherit-mode="primary">
|
||||
<xpath expr="." position="replace">
|
||||
<div overriden-attr="overriden">
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
""",
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<form t-name="module_1.template_1_1.dot" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
<div overriden-attr="overriden" t-name="template_1_2">
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
</div>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_inherit_from_dotted_tname_2(self):
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1.dot" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
<t t-name="template_1_2" t-inherit="template_1_1.dot" t-inherit-mode="primary">
|
||||
<xpath expr="." position="replace">
|
||||
<div overriden-attr="overriden">
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
""",
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<form t-name="template_1_1.dot" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
<div overriden-attr="overriden" t-name="template_1_2">
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
</div>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_inherit_from_dotted_tname_2bis(self):
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="template_1_1.dot" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
<t t-name="template_1_2" t-inherit="module_1.template_1_1.dot" t-inherit-mode="primary">
|
||||
<xpath expr="." position="replace">
|
||||
<div overriden-attr="overriden">
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
""",
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<form t-name="template_1_1.dot" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
<div overriden-attr="overriden" t-name="template_1_2">
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
</div>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_inherit_from_dotted_tname_2ter(self):
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="module_1" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
<t t-name="template_1_2" t-inherit="module_1" t-inherit-mode="primary">
|
||||
<xpath expr="." position="replace">
|
||||
<div overriden-attr="overriden">
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
""",
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<form t-name="module_1" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
<div overriden-attr="overriden" t-name="template_1_2">
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
</div>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_inherit_from_dotted_tname_3(self):
|
||||
self.template_files = {
|
||||
'/module_1/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<form t-name="module_1.template_1_1.dot" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
</templates>
|
||||
""",
|
||||
|
||||
'/module_2/static/xml/file_1.xml': """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="template_2_1" t-inherit="module_1.template_1_1.dot" t-inherit-mode="primary">
|
||||
<xpath expr="." position="replace">
|
||||
<div overriden-attr="overriden">
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
"""
|
||||
}
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<form t-name="module_1.template_1_1.dot" random-attr="gloria">
|
||||
<div>At first I was afraid</div>
|
||||
</form>
|
||||
<div overriden-attr="overriden" t-name="template_2_1">
|
||||
And I grew strong
|
||||
<p>And I learned how to get along</p>
|
||||
</div>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
def test_inherit_and_qweb_extend(self):
|
||||
self.template_files['/module_1/static/xml/file_2.xml'] = """
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="template_qw_1">
|
||||
<div>111</div>
|
||||
</t>
|
||||
<form t-inherit="template_1_1" t-inherit-mode="extension">
|
||||
<xpath expr="//span[1]" position="replace">
|
||||
<article>!!!</article>
|
||||
</xpath>
|
||||
</form>
|
||||
<t t-name="template_qw_2">
|
||||
<div>222</div>
|
||||
</t>
|
||||
<t t-extend="template_qw_1">
|
||||
<t t-jquery="div" t-operation="after">
|
||||
<div>333</div>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
"""
|
||||
|
||||
expected = """
|
||||
<templates xml:space="preserve">
|
||||
<form t-name="template_1_1" random-attr="gloria">
|
||||
<article>!!!</article>
|
||||
<div>At first I was afraid</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
<t t-name="template_1_2">
|
||||
<div>And I grew strong</div>
|
||||
<div>And I learned how to get along</div>
|
||||
</t>
|
||||
<t t-name="template_qw_1">
|
||||
<div>111</div>
|
||||
</t>
|
||||
<t t-name="template_qw_2">
|
||||
<div>222</div>
|
||||
</t>
|
||||
<t t-extend="template_qw_1">
|
||||
<t t-jquery="div" t-operation="after">
|
||||
<div>333</div>
|
||||
</t>
|
||||
</t>
|
||||
<form t-name="template_2_1" random-attr="gloria">
|
||||
<span type="Scary screams">Ho !</span>
|
||||
<div>At first I was afraid</div>
|
||||
<div>I was petrified</div>
|
||||
<div>But then I spent so many nights thinking how you did me wrong</div>
|
||||
<div>Kept thinking I could never live without you by my side</div>
|
||||
</form>
|
||||
<div t-name="template_2_2">
|
||||
<div>And I learned how to get along</div>
|
||||
</div>
|
||||
</templates>
|
||||
"""
|
||||
self.assertXMLEqual(self.renderBundle(debug=False), expected)
|
||||
|
||||
|
||||
@tagged('-standard', 'assets_bundle', 'static_templates_performance')
|
||||
class TestStaticInheritancePerformance(TestStaticInheritanceCommon):
|
||||
def _sick_script(self, nMod, nFilePerMod, nTemplatePerFile, stepInheritInModule=2, stepInheritPreviousModule=3):
|
||||
"""
|
||||
Make a sick amount of templates to test perf
|
||||
nMod modules
|
||||
each module: has nFilesPerModule files, each of which contains nTemplatePerFile templates
|
||||
"""
|
||||
self.asset_paths = []
|
||||
self.template_files = {}
|
||||
number_templates = 0
|
||||
for m in range(nMod):
|
||||
for f in range(nFilePerMod):
|
||||
mname = 'mod_%s' % m
|
||||
fname = 'mod_%s/folder/file_%s.xml' % (m, f)
|
||||
self.asset_paths.append((fname, mname, 'bundle_1'))
|
||||
|
||||
_file = '<templates id="template" xml:space="preserve">'
|
||||
|
||||
for t in range(nTemplatePerFile):
|
||||
_template = ''
|
||||
if t % stepInheritInModule or t % stepInheritPreviousModule or t == 0:
|
||||
_template += """
|
||||
<div t-name="template_%(t_number)s_mod_%(m_number)s">
|
||||
<div>Parent</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
elif not t % stepInheritInModule and t >= 1:
|
||||
_template += """
|
||||
<div t-name="template_%(t_number)s_mod_%(m_number)s"
|
||||
t-inherit="template_%(t_inherit)s_mod_%(m_number)s"
|
||||
t-inherit-mode="primary">
|
||||
<xpath expr="/div/div[1]" position="before">
|
||||
<div>Sick XPath</div>
|
||||
</xpath>
|
||||
</div>
|
||||
"""
|
||||
|
||||
elif not t % stepInheritPreviousModule and m >= 1:
|
||||
_template += """
|
||||
<div t-name="template_%(t_number)s_mod_%(m_number)s"
|
||||
t-inherit="mod_%(m_module_inherit)s.template_%(t_module_inherit)s_mod_%(m_module_inherit)s"
|
||||
t-inherit-mode="primary">
|
||||
<xpath expr="/div/div[1]" position="inside">
|
||||
<div>Mental XPath</div>
|
||||
</xpath>
|
||||
</div>
|
||||
"""
|
||||
if _template:
|
||||
number_templates += 1
|
||||
|
||||
_template_number = 1000 * f + t
|
||||
_file += _template % {
|
||||
't_number': _template_number,
|
||||
'm_number': m,
|
||||
't_inherit': _template_number - 1,
|
||||
't_module_inherit': _template_number,
|
||||
'm_module_inherit': m - 1,
|
||||
}
|
||||
_file += '</templates>'
|
||||
|
||||
self.template_files[fname] = _file
|
||||
self.assertEqual(number_templates, nMod * nFilePerMod * nTemplatePerFile)
|
||||
|
||||
def test_static_templates_treatment_linearity(self):
|
||||
# With 2500 templates for starters
|
||||
nMod, nFilePerMod, nTemplatePerFile = 50, 5, 10
|
||||
self._sick_script(nMod, nFilePerMod, nTemplatePerFile)
|
||||
|
||||
before = datetime.now()
|
||||
contents = self.renderBundle(debug=False)
|
||||
after = datetime.now()
|
||||
delta2500 = after - before
|
||||
_logger.runbot('Static Templates Inheritance: 2500 templates treated in %s seconds' % delta2500.total_seconds())
|
||||
|
||||
whole_tree = etree.fromstring(contents)
|
||||
self.assertEqual(len(whole_tree), nMod * nFilePerMod * nTemplatePerFile)
|
||||
|
||||
# With 25000 templates next
|
||||
nMod, nFilePerMod, nTemplatePerFile = 50, 5, 100
|
||||
self._sick_script(nMod, nFilePerMod, nTemplatePerFile)
|
||||
|
||||
before = datetime.now()
|
||||
self.renderBundle(debug=False)
|
||||
after = datetime.now()
|
||||
delta25000 = after - before
|
||||
|
||||
time_ratio = delta25000.total_seconds() / delta2500.total_seconds()
|
||||
_logger.runbot('Static Templates Inheritance: 25000 templates treated in %s seconds' % delta25000.total_seconds())
|
||||
_logger.runbot('Static Templates Inheritance: Computed linearity ratio: %s' % time_ratio)
|
||||
self.assertLessEqual(time_ratio, 14)
|
||||
238
odoo-bringout-oca-ocb-web/web/tests/test_base_document_layout.py
Normal file
238
odoo-bringout-oca-ocb-web/web/tests/test_base_document_layout.py
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
import os
|
||||
from PIL import Image
|
||||
from functools import partial
|
||||
|
||||
from odoo.tests import TransactionCase, tagged, Form
|
||||
from odoo.tools import frozendict, image_to_base64, hex_to_rgb
|
||||
|
||||
|
||||
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||
_file_cache = {}
|
||||
|
||||
|
||||
class TestBaseDocumentLayoutHelpers(TransactionCase):
|
||||
#
|
||||
# Public
|
||||
#
|
||||
def setUp(self):
|
||||
super(TestBaseDocumentLayoutHelpers, self).setUp()
|
||||
self.color_fields = ['primary_color', 'secondary_color']
|
||||
self.company = self.env.company
|
||||
self.css_color_error = 0
|
||||
self._set_templates_and_layouts()
|
||||
self._set_images()
|
||||
|
||||
def assertColors(self, checked_obj, expected):
|
||||
_expected_getter = expected.get if isinstance(expected, dict) else partial(getattr, expected)
|
||||
for fname in self.color_fields:
|
||||
color1 = getattr(checked_obj, fname)
|
||||
color2 = _expected_getter(fname)
|
||||
if self.css_color_error:
|
||||
self._compare_colors_rgb(color1, color2)
|
||||
else:
|
||||
self.assertEqual(color1, color2)
|
||||
|
||||
#
|
||||
# Private
|
||||
#
|
||||
def _compare_colors_rgb(self, color1, color2):
|
||||
self.assertEqual(bool(color1), bool(color2))
|
||||
if not color1:
|
||||
return
|
||||
color1 = hex_to_rgb(color1)
|
||||
color2 = hex_to_rgb(color2)
|
||||
self.assertEqual(len(color1), len(color2))
|
||||
for i in range(len(color1)):
|
||||
self.assertAlmostEqual(color1[i], color2[i], delta=self.css_color_error)
|
||||
|
||||
def _get_images_for_test(self):
|
||||
return ['sweden.png', 'odoo.png']
|
||||
|
||||
def _set_images(self):
|
||||
for fname in self._get_images_for_test():
|
||||
fname_split = fname.split('.')
|
||||
if not fname_split[0] in _file_cache:
|
||||
with Image.open(os.path.join(dir_path, fname), 'r') as img:
|
||||
base64_img = image_to_base64(img, 'PNG')
|
||||
primary, secondary = self.env['base.document.layout'].extract_image_primary_secondary_colors(base64_img)
|
||||
_img = frozendict({
|
||||
'img': base64_img,
|
||||
'colors': {
|
||||
'primary_color': primary,
|
||||
'secondary_color': secondary,
|
||||
},
|
||||
})
|
||||
_file_cache[fname_split[0]] = _img
|
||||
self.company_imgs = frozendict(_file_cache)
|
||||
|
||||
def _set_templates_and_layouts(self):
|
||||
self.layout_template1 = self.env['ir.ui.view'].create({
|
||||
'name': 'layout_template1',
|
||||
'key': 'web.layout_template1',
|
||||
'type': 'qweb',
|
||||
'arch': '''<div></div>''',
|
||||
})
|
||||
self.env['ir.model.data'].create({
|
||||
'name': self.layout_template1.name,
|
||||
'model': 'ir.ui.view',
|
||||
'module': 'web',
|
||||
'res_id': self.layout_template1.id,
|
||||
})
|
||||
self.default_colors = {
|
||||
'primary_color': '#000000',
|
||||
'secondary_color': '#000000',
|
||||
}
|
||||
self.report_layout1 = self.env['report.layout'].create({
|
||||
'view_id': self.layout_template1.id,
|
||||
'name': 'report_%s' % self.layout_template1.name,
|
||||
})
|
||||
self.layout_template2 = self.env['ir.ui.view'].create({
|
||||
'name': 'layout_template2',
|
||||
'key': 'web.layout_template2',
|
||||
'type': 'qweb',
|
||||
'arch': '''<div></div>''',
|
||||
})
|
||||
self.env['ir.model.data'].create({
|
||||
'name': self.layout_template2.name,
|
||||
'model': 'ir.ui.view',
|
||||
'module': 'web',
|
||||
'res_id': self.layout_template2.id,
|
||||
})
|
||||
self.report_layout2 = self.env['report.layout'].create({
|
||||
'view_id': self.layout_template2.id,
|
||||
'name': 'report_%s' % self.layout_template2.name,
|
||||
})
|
||||
|
||||
|
||||
@tagged('document_layout', "post_install", "-at_install")
|
||||
class TestBaseDocumentLayout(TestBaseDocumentLayoutHelpers):
|
||||
# Logo change Tests
|
||||
def test_company_no_color_change_logo(self):
|
||||
"""When neither a logo nor the colors are set
|
||||
The wizard displays the colors of the report layout
|
||||
Changing logo means the colors on the wizard change too
|
||||
Emptying the logo works and doesn't change the colors"""
|
||||
self.company.write({
|
||||
'primary_color': False,
|
||||
'secondary_color': False,
|
||||
'logo': False,
|
||||
'external_report_layout_id': self.env.ref('web.layout_template1').id,
|
||||
'paperformat_id': self.env.ref('base.paperformat_us').id,
|
||||
})
|
||||
default_colors = self.default_colors
|
||||
with Form(self.env['base.document.layout']) as doc_layout:
|
||||
self.assertColors(doc_layout, default_colors)
|
||||
self.assertEqual(doc_layout.company_id, self.company)
|
||||
doc_layout.logo = self.company_imgs['sweden']['img']
|
||||
|
||||
self.assertColors(doc_layout, self.company_imgs['sweden']['colors'])
|
||||
|
||||
doc_layout.logo = ''
|
||||
self.assertColors(doc_layout, self.company_imgs['sweden']['colors'])
|
||||
self.assertEqual(doc_layout.logo, '')
|
||||
|
||||
def test_company_no_color_but_logo_change_logo(self):
|
||||
"""When company colors are not set, but a logo is,
|
||||
the wizard displays the computed colors from the logo"""
|
||||
self.company.write({
|
||||
'primary_color': '#ff0080',
|
||||
'secondary_color': '#00ff00',
|
||||
'logo': self.company_imgs['sweden']['img'],
|
||||
'paperformat_id': self.env.ref('base.paperformat_us').id,
|
||||
})
|
||||
|
||||
with Form(self.env['base.document.layout']) as doc_layout:
|
||||
self.assertColors(doc_layout, self.company)
|
||||
doc_layout.logo = self.company_imgs['odoo']['img']
|
||||
self.assertColors(doc_layout, self.company_imgs['odoo']['colors'])
|
||||
|
||||
def test_company_colors_change_logo(self):
|
||||
"""changes of the logo implies displaying the new computed colors"""
|
||||
self.company.write({
|
||||
'primary_color': '#ff0080',
|
||||
'secondary_color': '#00ff00',
|
||||
'logo': False,
|
||||
'paperformat_id': self.env.ref('base.paperformat_us').id,
|
||||
})
|
||||
|
||||
with Form(self.env['base.document.layout']) as doc_layout:
|
||||
self.assertColors(doc_layout, self.company)
|
||||
doc_layout.logo = self.company_imgs['odoo']['img']
|
||||
self.assertColors(doc_layout, self.company_imgs['odoo']['colors'])
|
||||
|
||||
def test_company_colors_and_logo_change_logo(self):
|
||||
"""The colors of the company may differ from the one the logo computes
|
||||
Opening the wizard in these condition displays the company's colors
|
||||
When the logo changes, colors must change according to the logo"""
|
||||
self.company.write({
|
||||
'primary_color': '#ff0080',
|
||||
'secondary_color': '#00ff00',
|
||||
'logo': self.company_imgs['sweden']['img'],
|
||||
'paperformat_id': self.env.ref('base.paperformat_us').id,
|
||||
})
|
||||
|
||||
with Form(self.env['base.document.layout']) as doc_layout:
|
||||
self.assertColors(doc_layout, self.company)
|
||||
doc_layout.logo = self.company_imgs['odoo']['img']
|
||||
self.assertColors(doc_layout, self.company_imgs['odoo']['colors'])
|
||||
|
||||
# Layout change tests
|
||||
def test_company_colors_reset_colors(self):
|
||||
"""Reset the colors when they differ from the ones originally
|
||||
computed from the company logo"""
|
||||
self.company.write({
|
||||
'primary_color': '#ff0080',
|
||||
'secondary_color': '#00ff00',
|
||||
'logo': self.company_imgs['sweden']['img'],
|
||||
'paperformat_id': self.env.ref('base.paperformat_us').id,
|
||||
})
|
||||
|
||||
with Form(self.env['base.document.layout']) as doc_layout:
|
||||
self.assertColors(doc_layout, self.company)
|
||||
doc_layout.primary_color = doc_layout.logo_primary_color
|
||||
doc_layout.secondary_color = doc_layout.logo_secondary_color
|
||||
self.assertColors(doc_layout, self.company_imgs['sweden']['colors'])
|
||||
|
||||
def test_parse_company_colors_grayscale(self):
|
||||
"""Grayscale images with transparency - make sure the color extraction does not crash"""
|
||||
self.company.write({
|
||||
'primary_color': '#ff0080',
|
||||
'secondary_color': '#00ff00',
|
||||
'paperformat_id': self.env.ref('base.paperformat_us').id,
|
||||
})
|
||||
with Form(self.env['base.document.layout']) as doc_layout:
|
||||
with Image.open(os.path.join(dir_path, 'logo_ci.png'), 'r') as img:
|
||||
base64_img = image_to_base64(img, 'PNG')
|
||||
doc_layout.logo = base64_img
|
||||
self.assertNotEqual(None, doc_layout.primary_color)
|
||||
|
||||
|
||||
# /!\ This case is NOT supported, and probably not supportable
|
||||
# res.partner resizes manu-militari the image it is given
|
||||
# so res.company._get_logo differs from res.partner.[default image]
|
||||
# def test_company_no_colors_default_logo_and_layout_change_layout(self):
|
||||
# """When the default YourCompany logo is set, and no colors are set on company:
|
||||
# change wizard's color according to template"""
|
||||
# self.company.write({
|
||||
# 'primary_color': False,
|
||||
# 'secondary_color': False,
|
||||
# 'external_report_layout_id': self.layout_template1.id,
|
||||
# })
|
||||
# default_colors = self.default_colors
|
||||
# with Form(self.env['base.document.layout']) as doc_layout:
|
||||
# self.assertColors(doc_layout, default_colors)
|
||||
# doc_layout.report_layout_id = self.report_layout2
|
||||
# self.assertColors(doc_layout, self.report_layout2)
|
||||
|
||||
def test_company_details_blank_lines(self):
|
||||
"""Test that the company address is generated dynamically using only the fields that are defined,
|
||||
without leaving any blank lines."""
|
||||
# Make sure there is no blank line in the company details.
|
||||
doc_layout_1 = self.env['base.document.layout'].create({'company_id': self.company.id})
|
||||
self.assertFalse('\n<br>\n' in doc_layout_1.company_details)
|
||||
|
||||
# Make sure that 'street2' (an optional field, initially blank),
|
||||
# appears in the company details when it is defined.
|
||||
self.company.write({'street2': 'street_2_detail'})
|
||||
doc_layout_2 = self.env['base.document.layout'].create({'company_id': self.company.id})
|
||||
self.assertTrue('street_2_detail' in doc_layout_2.company_details)
|
||||
50
odoo-bringout-oca-ocb-web/web/tests/test_click_everywhere.py
Normal file
50
odoo-bringout-oca-ocb-web/web/tests/test_click_everywhere.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import odoo.tests
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@odoo.tests.tagged('click_all', 'post_install', '-at_install', '-standard')
|
||||
class TestMenusAdmin(odoo.tests.HttpCase):
|
||||
allow_end_on_form = True
|
||||
def test_01_click_everywhere_as_admin(self):
|
||||
menus = self.env['ir.ui.menu'].load_menus(False)
|
||||
for app_id in menus['root']['children']:
|
||||
with self.subTest(app=menus[app_id]['name']):
|
||||
_logger.runbot('Testing %s', menus[app_id]['name'])
|
||||
self.browser_js("/web", "odoo.__DEBUG__.services['web.clickEverywhere']('%s');" % menus[app_id]['xmlid'], "odoo.isReady === true", login="admin", timeout=600)
|
||||
self.terminate_browser()
|
||||
|
||||
|
||||
@odoo.tests.tagged('click_all', 'post_install', '-at_install', '-standard')
|
||||
class TestMenusDemo(odoo.tests.HttpCase):
|
||||
allow_end_on_form = True
|
||||
def test_01_click_everywhere_as_demo(self):
|
||||
user_demo = self.env.ref("base.user_demo")
|
||||
menus = self.env['ir.ui.menu'].with_user(user_demo.id).load_menus(False)
|
||||
for app_id in menus['root']['children']:
|
||||
with self.subTest(app=menus[app_id]['name']):
|
||||
_logger.runbot('Testing %s', menus[app_id]['name'])
|
||||
self.browser_js("/web", "odoo.__DEBUG__.services['web.clickEverywhere']('%s');" % menus[app_id]['xmlid'], "odoo.isReady === true", login="demo", timeout=600)
|
||||
self.terminate_browser()
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class TestMenusAdminLight(odoo.tests.HttpCase):
|
||||
allow_end_on_form = True
|
||||
def test_01_click_apps_menus_as_admin(self):
|
||||
self.browser_js("/web", "odoo.__DEBUG__.services['web.clickEverywhere'](undefined, true);", "odoo.isReady === true", login="admin", timeout=120)
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install',)
|
||||
class TestMenusDemoLight(HttpCaseWithUserDemo):
|
||||
allow_end_on_form = True
|
||||
|
||||
def test_01_click_apps_menus_as_demo(self):
|
||||
# If not enabled (like in demo data), landing on website dashboard will redirect to /
|
||||
# and make the test crash
|
||||
group_website_designer = self.env.ref('website.group_website_designer', raise_if_not_found=False)
|
||||
if group_website_designer:
|
||||
self.env.ref('base.group_user').write({"implied_ids": [(4, group_website_designer.id)]})
|
||||
self.browser_js("/web", "odoo.__DEBUG__.services['web.clickEverywhere'](undefined, true);", "odoo.isReady === true", login="demo", timeout=120)
|
||||
119
odoo-bringout-oca-ocb-web/web/tests/test_db_manager.py
Normal file
119
odoo-bringout-oca-ocb-web/web/tests/test_db_manager.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import operator
|
||||
import re
|
||||
import secrets
|
||||
from unittest.mock import patch
|
||||
|
||||
import requests
|
||||
|
||||
import odoo
|
||||
from odoo.tests.common import BaseCase, HttpCase, tagged
|
||||
from odoo.tools import config
|
||||
|
||||
|
||||
class TestDatabaseManager(HttpCase):
|
||||
def test_database_manager(self):
|
||||
if not config['list_db']:
|
||||
return
|
||||
res = self.url_open('/web/database/manager')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
# check that basic existing db actions are present
|
||||
self.assertIn('.o_database_backup', res.text)
|
||||
self.assertIn('.o_database_duplicate', res.text)
|
||||
self.assertIn('.o_database_delete', res.text)
|
||||
|
||||
# check that basic db actions are present
|
||||
self.assertIn('.o_database_create', res.text)
|
||||
self.assertIn('.o_database_restore', res.text)
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install', '-standard', 'database_operations')
|
||||
class TestDatabaseOperations(BaseCase):
|
||||
def setUp(self):
|
||||
self.password = secrets.token_hex()
|
||||
|
||||
# monkey-patch password verification
|
||||
self.verify_admin_password_patcher = patch(
|
||||
'odoo.tools.config.verify_admin_password', self.password.__eq__,
|
||||
)
|
||||
self.startPatcher(self.verify_admin_password_patcher)
|
||||
|
||||
self.db_name = config['db_name']
|
||||
self.assertTrue(self.db_name)
|
||||
|
||||
# monkey-patch db-filter
|
||||
self.addCleanup(operator.setitem, config, 'dbfilter', config['dbfilter'])
|
||||
config['dbfilter'] = self.db_name + '.*'
|
||||
|
||||
self.base_databases = self.list_dbs_filtered()
|
||||
self.session = requests.Session()
|
||||
self.session.get(self.url('/web/database/manager'))
|
||||
|
||||
def tearDown(self):
|
||||
self.assertEqual(
|
||||
self.list_dbs_filtered(),
|
||||
self.base_databases,
|
||||
'No database should have been created or removed at the end of this test',
|
||||
)
|
||||
|
||||
def list_dbs_filtered(self):
|
||||
return set(db for db in odoo.service.db.list_dbs(True) if re.match(config['dbfilter'], db))
|
||||
|
||||
def url(self, path):
|
||||
return HttpCase.base_url() + path
|
||||
|
||||
def assertDbs(self, dbs):
|
||||
self.assertEqual(self.list_dbs_filtered() - self.base_databases, set(dbs))
|
||||
|
||||
def test_database_creation(self):
|
||||
# check verify_admin_password patch
|
||||
self.assertTrue(odoo.tools.config.verify_admin_password(self.password))
|
||||
|
||||
# create a database
|
||||
test_db_name = self.db_name + '-test-database-creation'
|
||||
self.assertNotIn(test_db_name, self.list_dbs_filtered())
|
||||
res = self.session.post(self.url('/web/database/create'), data={
|
||||
'master_pwd': self.password,
|
||||
'name': test_db_name,
|
||||
'login': 'admin',
|
||||
'password': 'admin',
|
||||
'lang': 'en_US',
|
||||
'phone': '',
|
||||
}, allow_redirects=False)
|
||||
self.assertEqual(res.status_code, 303)
|
||||
self.assertIn('/web', res.headers['Location'])
|
||||
self.assertDbs([test_db_name])
|
||||
|
||||
# delete the created database
|
||||
res = self.session.post(self.url('/web/database/drop'), data={
|
||||
'master_pwd': self.password,
|
||||
'name': test_db_name,
|
||||
}, allow_redirects=False)
|
||||
self.assertEqual(res.status_code, 303)
|
||||
self.assertIn('/web/database/manager', res.headers['Location'])
|
||||
self.assertDbs([])
|
||||
|
||||
def test_database_duplicate(self):
|
||||
# duplicate this database
|
||||
test_db_name = self.db_name + '-test-database-duplicate'
|
||||
self.assertNotIn(test_db_name, self.list_dbs_filtered())
|
||||
res = self.session.post(self.url('/web/database/duplicate'), data={
|
||||
'master_pwd': self.password,
|
||||
'name': self.db_name,
|
||||
'new_name': test_db_name,
|
||||
}, allow_redirects=False)
|
||||
self.assertEqual(res.status_code, 303)
|
||||
self.assertIn('/web/database/manager', res.headers['Location'])
|
||||
self.assertDbs([test_db_name])
|
||||
|
||||
# delete the created database
|
||||
res = self.session.post(self.url('/web/database/drop'), data={
|
||||
'master_pwd': self.password,
|
||||
'name': test_db_name,
|
||||
}, allow_redirects=False)
|
||||
self.assertEqual(res.status_code, 303)
|
||||
self.assertIn('/web/database/manager', res.headers['Location'])
|
||||
self.assertDbs([])
|
||||
44
odoo-bringout-oca-ocb-web/web/tests/test_domain.py
Normal file
44
odoo-bringout-oca-ocb-web/web/tests/test_domain.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import json
|
||||
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class DomainTest(HttpCaseWithUserDemo):
|
||||
|
||||
def test_domain_validate(self):
|
||||
self.authenticate("demo", "demo")
|
||||
|
||||
with mute_logger('odoo.http'):
|
||||
resp = self.url_open(
|
||||
'/web/domain/validate',
|
||||
headers={'Content-Type': 'application/json'},
|
||||
data=json.dumps({'params': {'model':'i', 'domain':[]}}),
|
||||
)
|
||||
self.assertEqual(resp.json()['error']['data']['message'], "Invalid model: i")
|
||||
|
||||
resp = self.url_open(
|
||||
'/web/domain/validate',
|
||||
headers={'Content-Type': 'application/json'},
|
||||
data=json.dumps({'params': {'model':'res.users', 'domain':[]}}),
|
||||
)
|
||||
self.assertEqual(resp.json()['result'], True)
|
||||
|
||||
resp = self.url_open(
|
||||
'/web/domain/validate',
|
||||
headers={'Content-Type': 'application/json'},
|
||||
data=json.dumps({'params': {'model':'res.users', 'domain':[('name', 'ilike', 'ad')]}}),
|
||||
)
|
||||
self.assertEqual(resp.json()['result'], True)
|
||||
|
||||
resp = self.url_open(
|
||||
'/web/domain/validate',
|
||||
headers={'Content-Type': 'application/json'},
|
||||
data=json.dumps({'params': {'model':'res.users', 'domain':[('hop')]}}),
|
||||
)
|
||||
self.assertEqual(resp.json()['result'], False)
|
||||
32
odoo-bringout-oca-ocb-web/web/tests/test_health.py
Normal file
32
odoo-bringout-oca-ocb-web/web/tests/test_health.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import psycopg2
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.tests import HttpCase
|
||||
|
||||
|
||||
class TestWebController(HttpCase):
|
||||
def test_health(self):
|
||||
response = self.url_open('/web/health')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
payload = response.json()
|
||||
self.assertEqual(payload['status'], 'pass')
|
||||
self.assertFalse(response.cookies.get('session_id'))
|
||||
|
||||
def test_health_db_server_status(self):
|
||||
response = self.url_open('/web/health?db_server_status=1')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
payload = response.json()
|
||||
self.assertEqual(payload['status'], 'pass')
|
||||
self.assertEqual(payload['db_server_status'], True)
|
||||
self.assertFalse(response.cookies.get('session_id'))
|
||||
|
||||
def _raise_psycopg2_error(*args):
|
||||
raise psycopg2.Error('boom')
|
||||
|
||||
with patch('odoo.sql_db.db_connect', new=_raise_psycopg2_error):
|
||||
response = self.url_open('/web/health?db_server_status=1')
|
||||
self.assertEqual(response.status_code, 500)
|
||||
payload = response.json()
|
||||
self.assertEqual(payload['status'], 'fail')
|
||||
self.assertEqual(payload['db_server_status'], False)
|
||||
159
odoo-bringout-oca-ocb-web/web/tests/test_image.py
Normal file
159
odoo-bringout-oca-ocb-web/web/tests/test_image.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import io
|
||||
import base64
|
||||
|
||||
from PIL import Image
|
||||
from werkzeug.urls import url_unquote_plus
|
||||
|
||||
from odoo.tests.common import HttpCase, tagged
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestImage(HttpCase):
|
||||
def test_01_content_image_resize_placeholder(self):
|
||||
"""The goal of this test is to make sure the placeholder image is
|
||||
resized appropriately depending on the given URL parameters."""
|
||||
|
||||
# CASE: resize placeholder, given size but original ratio is always kept
|
||||
response = self.url_open('/web/image/0/200x150')
|
||||
response.raise_for_status()
|
||||
image = Image.open(io.BytesIO(response.content))
|
||||
self.assertEqual(image.size, (150, 150))
|
||||
|
||||
# CASE: resize placeholder to 128
|
||||
response = self.url_open('/web/image/fake/0/image_128')
|
||||
response.raise_for_status()
|
||||
image = Image.open(io.BytesIO(response.content))
|
||||
self.assertEqual(image.size, (128, 128))
|
||||
|
||||
# CASE: resize placeholder to 256
|
||||
response = self.url_open('/web/image/fake/0/image_256')
|
||||
response.raise_for_status()
|
||||
image = Image.open(io.BytesIO(response.content))
|
||||
self.assertEqual(image.size, (256, 256))
|
||||
|
||||
# CASE: resize placeholder to 1024 (but placeholder image is too small)
|
||||
response = self.url_open('/web/image/fake/0/image_1024')
|
||||
response.raise_for_status()
|
||||
image = Image.open(io.BytesIO(response.content))
|
||||
self.assertEqual(image.size, (256, 256))
|
||||
|
||||
# CASE: no size found, use placeholder original size
|
||||
response = self.url_open('/web/image/fake/0/image_no_size')
|
||||
response.raise_for_status()
|
||||
image = Image.open(io.BytesIO(response.content))
|
||||
self.assertEqual(image.size, (256, 256))
|
||||
|
||||
def test_02_content_image_Etag_304(self):
|
||||
"""This test makes sure that the 304 response is properly returned if the ETag is properly set"""
|
||||
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'datas': b"R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=",
|
||||
'name': 'testEtag.gif',
|
||||
'public': True,
|
||||
'mimetype': 'image/gif',
|
||||
})
|
||||
response = self.url_open('/web/image/%s' % attachment.id, timeout=None)
|
||||
response.raise_for_status()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(base64.b64encode(response.content), attachment.datas)
|
||||
|
||||
etag = response.headers.get('ETag')
|
||||
|
||||
response2 = self.url_open('/web/image/%s' % attachment.id, headers={"If-None-Match": etag})
|
||||
response2.raise_for_status()
|
||||
self.assertEqual(response2.status_code, 304)
|
||||
self.assertEqual(len(response2.content), 0)
|
||||
|
||||
def test_03_web_content_filename(self):
|
||||
"""This test makes sure the Content-Disposition header matches the given filename"""
|
||||
|
||||
att = self.env['ir.attachment'].create({
|
||||
'datas': b'R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=',
|
||||
'name': 'testFilename.gif',
|
||||
'public': True,
|
||||
'mimetype': 'image/gif'
|
||||
})
|
||||
|
||||
# CASE: no filename given
|
||||
res = self.url_open('/web/image/%s/0x0/?download=true' % att.id)
|
||||
res.raise_for_status()
|
||||
self.assertEqual(res.headers['Content-Disposition'], 'attachment; filename=testFilename.gif')
|
||||
|
||||
# CASE: given filename without extension
|
||||
res = self.url_open('/web/image/%s/0x0/custom?download=true' % att.id)
|
||||
res.raise_for_status()
|
||||
self.assertEqual(res.headers['Content-Disposition'], 'attachment; filename=custom.gif')
|
||||
|
||||
# CASE: given filename and extention
|
||||
res = self.url_open('/web/image/%s/0x0/custom.png?download=true' % att.id)
|
||||
res.raise_for_status()
|
||||
self.assertEqual(res.headers['Content-Disposition'], 'attachment; filename=custom.png')
|
||||
|
||||
def test_04_web_content_filename_secure(self):
|
||||
"""This test makes sure the Content-Disposition header matches the given filename"""
|
||||
|
||||
att = self.env['ir.attachment'].create({
|
||||
'datas': b'R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=',
|
||||
'name': """fô☺o-l'éb \n a"!r".gif""",
|
||||
'public': True,
|
||||
'mimetype': 'image/gif',
|
||||
})
|
||||
|
||||
def remove_prefix(text, prefix):
|
||||
if text.startswith(prefix):
|
||||
return text[len(prefix):]
|
||||
return text
|
||||
|
||||
def assert_filenames(
|
||||
url,
|
||||
expected_filename,
|
||||
expected_filename_star='',
|
||||
message=r"File that will be saved on disc should have the original filename without \n and \r",
|
||||
):
|
||||
res = self.url_open(url)
|
||||
res.raise_for_status()
|
||||
if expected_filename_star:
|
||||
inline, filename, filename_star = res.headers['Content-Disposition'].split('; ')
|
||||
else:
|
||||
inline, filename = res.headers['Content-Disposition'].split('; ')
|
||||
filename_star = ''
|
||||
|
||||
filename = remove_prefix(filename, "filename=").strip('"')
|
||||
filename_star = url_unquote_plus(remove_prefix(filename_star, "filename*=UTF-8''").strip('"'))
|
||||
|
||||
self.assertEqual(inline, 'inline')
|
||||
self.assertEqual(filename, expected_filename, message)
|
||||
self.assertEqual(filename_star, expected_filename_star, message)
|
||||
|
||||
assert_filenames(f'/web/image/{att.id}',
|
||||
r"""foo-l'eb _ a\"!r\".gif""",
|
||||
r"""fô☺o-l'éb _ a"!r".gif""",
|
||||
)
|
||||
assert_filenames(f'/web/image/{att.id}/custom_invalid_name\nis-ok.gif',
|
||||
r"""custom_invalid_name_is-ok.gif""",
|
||||
)
|
||||
assert_filenames(f'/web/image/{att.id}/\r\n',
|
||||
r"""__.gif""",
|
||||
)
|
||||
assert_filenames(f'/web/image/{att.id}/你好',
|
||||
r""".gif""",
|
||||
r"""你好.gif""",
|
||||
)
|
||||
assert_filenames(f'/web/image/{att.id}/%E9%9D%A2%E5%9B%BE.gif',
|
||||
r""".gif""",
|
||||
r"""面图.gif""",
|
||||
)
|
||||
assert_filenames(f'/web/image/{att.id}/hindi_नमस्ते.gif',
|
||||
r"""hindi_.gif""",
|
||||
r"""hindi_नमस्ते.gif""",
|
||||
)
|
||||
assert_filenames(f'/web/image/{att.id}/arabic_مرحبا',
|
||||
r"""arabic_.gif""",
|
||||
r"""arabic_مرحبا.gif""",
|
||||
)
|
||||
assert_filenames(f'/web/image/{att.id}/4wzb_!!63148-0-t1.jpg_360x1Q75.jpg_.webp',
|
||||
r"""4wzb_!!63148-0-t1.jpg_360x1Q75.jpg_.webp""",
|
||||
)
|
||||
65
odoo-bringout-oca-ocb-web/web/tests/test_ir_model.py
Normal file
65
odoo-bringout-oca-ocb-web/web/tests/test_ir_model.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import new_test_user
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class IrModelAccessTest(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(IrModelAccessTest, cls).setUpClass()
|
||||
|
||||
cls.env['ir.model.access'].create({
|
||||
'name': "read",
|
||||
'model_id': cls.env['ir.model'].search([("model", "=", "res.company")]).id,
|
||||
'group_id': cls.env.ref("base.group_public").id,
|
||||
'perm_read': False,
|
||||
})
|
||||
|
||||
cls.env['ir.model.access'].create({
|
||||
'name': "read",
|
||||
'model_id': cls.env['ir.model'].search([("model", "=", "res.company")]).id,
|
||||
'group_id': cls.env.ref("base.group_portal").id,
|
||||
'perm_read': True,
|
||||
})
|
||||
|
||||
cls.env['ir.model.access'].create({
|
||||
'name': "read",
|
||||
'model_id': cls.env['ir.model'].search([("model", "=", "res.company")]).id,
|
||||
'group_id': cls.env.ref("base.group_user").id,
|
||||
'perm_read': True,
|
||||
})
|
||||
|
||||
cls.portal_user = new_test_user(
|
||||
cls.env, login="portalDude", groups="base.group_portal"
|
||||
)
|
||||
cls.public_user = new_test_user(
|
||||
cls.env, login="publicDude", groups="base.group_public"
|
||||
)
|
||||
cls.spreadsheet_user = new_test_user(
|
||||
cls.env, login="spreadsheetDude", groups="base.group_user"
|
||||
)
|
||||
|
||||
def test_display_name_for(self):
|
||||
# Internal User with access rights can access the business name
|
||||
result = self.env['ir.model'].with_user(self.spreadsheet_user).display_name_for(["res.company"])
|
||||
self.assertEqual(result, [{"display_name": "Companies", "model": "res.company"}])
|
||||
# external user with access rights cannot access business name
|
||||
result = self.env['ir.model'].with_user(self.portal_user).display_name_for(["res.company"])
|
||||
self.assertEqual(result, [{"display_name": "res.company", "model": "res.company"}])
|
||||
# external user without access rights cannot access business name
|
||||
result = self.env['ir.model'].with_user(self.public_user).display_name_for(["res.company"])
|
||||
self.assertEqual(result, [{"display_name": "res.company", "model": "res.company"}])
|
||||
# admin has all rights
|
||||
result = self.env['ir.model'].display_name_for(["res.company"])
|
||||
self.assertEqual(result, [{"display_name": "Companies", "model": "res.company"}])
|
||||
# non existent model yields same result as a lack of access rights
|
||||
result = self.env['ir.model'].display_name_for(["unexistent"])
|
||||
self.assertEqual(result, [{"display_name": "unexistent", "model": "unexistent"}])
|
||||
# non existent model comes after existent model
|
||||
result = self.env['ir.model'].display_name_for(["res.company", "unexistent"])
|
||||
self.assertEqual(result, [{"display_name": "Companies", "model": "res.company"}, {"display_name": "unexistent", "model": "unexistent"}])
|
||||
30
odoo-bringout-oca-ocb-web/web/tests/test_ir_qweb.py
Normal file
30
odoo-bringout-oca-ocb-web/web/tests/test_ir_qweb.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
from lxml import etree
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
class TestIrQweb(TransactionCase):
|
||||
def test_image_field(self):
|
||||
view = self.env["ir.ui.view"].create({
|
||||
"key": "web.test_qweb",
|
||||
"type": "qweb",
|
||||
"arch": """<t t-name="test_qweb">
|
||||
<span t-field="record.avatar_128" t-options-widget="'image'" t-options-qweb_img_raw_data="is_raw_image" />
|
||||
</t>"""
|
||||
})
|
||||
partner = self.env["res.partner"].create({
|
||||
"name": "test image partner",
|
||||
"image_128": "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAF0lEQVR4nGJxKFrEwMDAxAAGgAAAAP//D+IBWx9K7TUAAAAASUVORK5CYII=",
|
||||
})
|
||||
|
||||
html = view._render_template(view.id, {"is_raw_image": True, "record": partner})
|
||||
tree = etree.fromstring(html)
|
||||
img = tree.find("img")
|
||||
self.assertTrue(img.get("src").startswith("data:image/png;base64"))
|
||||
self.assertEqual(img.get("class"), "img img-fluid")
|
||||
self.assertEqual(img.get("alt"), "test image partner")
|
||||
|
||||
html = view._render_template(view.id, {"is_raw_image": False, "record": partner})
|
||||
tree = etree.fromstring(html)
|
||||
img = tree.find("img")
|
||||
self.assertTrue(img.get("src").startswith("/web/image"))
|
||||
self.assertEqual(img.get("class"), "img img-fluid")
|
||||
self.assertEqual(img.get("alt"), "test image partner")
|
||||
124
odoo-bringout-oca-ocb-web/web/tests/test_js.py
Normal file
124
odoo-bringout-oca-ocb-web/web/tests/test_js.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import re
|
||||
import odoo.tests
|
||||
from werkzeug.urls import url_quote_plus
|
||||
|
||||
RE_ONLY = re.compile(r'QUnit\.(only|debug)\(')
|
||||
|
||||
|
||||
def qunit_error_checker(message):
|
||||
# We don't want to stop qunit if a qunit is breaking.
|
||||
|
||||
# '%s/%s test failed.' case: end message when all tests are finished
|
||||
if 'tests failed.' in message:
|
||||
return True
|
||||
|
||||
# "QUnit test failed" case: one qunit failed. don't stop in this case
|
||||
if "QUnit test failed:" in message:
|
||||
return False
|
||||
|
||||
return True # in other cases, always stop (missing dependency, ...)
|
||||
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class WebsuiteCommon(odoo.tests.HttpCase):
|
||||
def get_filter(self, test_params):
|
||||
positive = []
|
||||
negative = []
|
||||
for sign, param in test_params:
|
||||
filters = param.split(',')
|
||||
for filter in filters:
|
||||
filter = filter.strip()
|
||||
if not filter:
|
||||
continue
|
||||
negate = sign == '-'
|
||||
if filter.startswith('-'):
|
||||
negate = not negate
|
||||
filter = filter[1:]
|
||||
if negate:
|
||||
negative.append(f'({re.escape(filter)}.*)')
|
||||
else:
|
||||
positive.append(f'({re.escape(filter)}.*)')
|
||||
filter = ''
|
||||
if positive or negative:
|
||||
positive_re = '|'.join(positive) or '.*'
|
||||
negative_re = '|'.join(negative)
|
||||
negative_re = f'(?!{negative_re})' if negative_re else ''
|
||||
filter = f'^({negative_re})({positive_re})$'
|
||||
return filter
|
||||
|
||||
def test_get_filter(self):
|
||||
f1 = self.get_filter([('+', 'utils,mail,-utils > bl1,-utils > bl2')])
|
||||
f2 = self.get_filter([('+', 'utils'), ('-', 'utils > bl1,utils > bl2'), ('+', 'mail')])
|
||||
for f in (f1, f2):
|
||||
self.assertRegex('utils', f)
|
||||
self.assertRegex('mail', f)
|
||||
self.assertRegex('utils > something', f)
|
||||
|
||||
self.assertNotRegex('utils > bl1', f)
|
||||
self.assertNotRegex('utils > bl2', f)
|
||||
self.assertNotRegex('web', f)
|
||||
|
||||
f2 = self.get_filter([('+', '-utils > bl1,-utils > bl2')])
|
||||
f3 = self.get_filter([('-', 'utils > bl1,utils > bl2')])
|
||||
for f in (f2, f3):
|
||||
self.assertRegex('utils', f)
|
||||
self.assertRegex('mail', f)
|
||||
self.assertRegex('utils > something', f)
|
||||
self.assertRegex('web', f)
|
||||
|
||||
self.assertNotRegex('utils > bl1', f)
|
||||
self.assertNotRegex('utils > bl2', f)
|
||||
|
||||
def get_filter_param(self):
|
||||
filter_param = ''
|
||||
filter = self.get_filter(self._test_params)
|
||||
if filter:
|
||||
url_filter = url_quote_plus(filter)
|
||||
filter_param = f'&filter=/{url_filter}/'
|
||||
return filter_param
|
||||
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class WebSuite(WebsuiteCommon):
|
||||
|
||||
@odoo.tests.no_retry
|
||||
def test_js(self):
|
||||
filter_param = self.get_filter_param()
|
||||
# webclient desktop test suite
|
||||
self.browser_js('/web/tests?mod=web%s' % filter_param, "", "", login='admin', timeout=1800, error_checker=qunit_error_checker)
|
||||
|
||||
def test_check_suite(self):
|
||||
# verify no js test is using `QUnit.only` as it forbid any other test to be executed
|
||||
self._check_only_call('web.qunit_suite_tests')
|
||||
self._check_only_call('web.qunit_mobile_suite_tests')
|
||||
|
||||
def _check_only_call(self, suite):
|
||||
# As we currently aren't in a request context, we can't render `web.layout`.
|
||||
# redefinied it as a minimal proxy template.
|
||||
self.env.ref('web.layout').write({'arch_db': '<t t-name="web.layout"><head><meta charset="utf-8"/><t t-esc="head"/></head></t>'})
|
||||
|
||||
assets = self.env['ir.qweb']._get_asset_content(suite)[0]
|
||||
if len(assets) == 0:
|
||||
self.fail("No assets found in the given test suite")
|
||||
|
||||
for asset in assets:
|
||||
filename = asset['filename']
|
||||
if not filename or asset['atype'] != 'text/javascript':
|
||||
continue
|
||||
with open(filename, 'rb') as fp:
|
||||
if RE_ONLY.search(fp.read().decode('utf-8')):
|
||||
self.fail("`QUnit.only()` or `QUnit.debug()` used in file %r" % asset['url'])
|
||||
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class MobileWebSuite(WebsuiteCommon):
|
||||
browser_size = '375x667'
|
||||
touch_enabled = True
|
||||
|
||||
def test_mobile_js(self):
|
||||
filter_param = self.get_filter_param()
|
||||
# webclient mobile test suite
|
||||
self.browser_js('/web/tests/mobile?mod=web%s' % filter_param, "", "", login='admin', timeout=1800, error_checker=qunit_error_checker)
|
||||
53
odoo-bringout-oca-ocb-web/web/tests/test_load_menus.py
Normal file
53
odoo-bringout-oca-ocb-web/web/tests/test_load_menus.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
from odoo.tests.common import HttpCase
|
||||
|
||||
class LoadMenusTests(HttpCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.menu = self.env["ir.ui.menu"].create({
|
||||
"name": "test_menu",
|
||||
"parent_id": False,
|
||||
})
|
||||
|
||||
def search(*args, **kwargs):
|
||||
return self.menu
|
||||
|
||||
self.patch(type(self.env["ir.ui.menu"]), "search", search)
|
||||
self.authenticate("admin", "admin")
|
||||
|
||||
def test_load_menus(self):
|
||||
menu_loaded = self.url_open("/web/webclient/load_menus/1234")
|
||||
|
||||
expected = {
|
||||
str(self.menu.id): {
|
||||
"actionID": False,
|
||||
"actionModel": False,
|
||||
"appID": self.menu.id,
|
||||
"children": [],
|
||||
"id": self.menu.id,
|
||||
"name": "test_menu",
|
||||
"webIcon": False,
|
||||
"webIconData": False,
|
||||
"xmlid": ""
|
||||
},
|
||||
"root": {
|
||||
"actionID": False,
|
||||
"actionModel": False,
|
||||
"appID": False,
|
||||
"children": [
|
||||
self.menu.id,
|
||||
],
|
||||
"id": "root",
|
||||
"name": "root",
|
||||
"webIcon": None,
|
||||
"webIconData": None,
|
||||
"xmlid": "",
|
||||
"backgroundImage": None,
|
||||
}
|
||||
}
|
||||
|
||||
self.assertDictEqual(
|
||||
menu_loaded.json(),
|
||||
expected,
|
||||
"load_menus didn't return the expected value"
|
||||
)
|
||||
60
odoo-bringout-oca-ocb-web/web/tests/test_login.py
Normal file
60
odoo-bringout-oca-ocb-web/web/tests/test_login.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import http
|
||||
from odoo.tests.common import get_db_name, HOST, HttpCase, new_test_user, Opener
|
||||
|
||||
|
||||
class TestWebLoginCommon(HttpCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
new_test_user(cls.env, 'internal_user', context={'lang': 'en_US'})
|
||||
new_test_user(cls.env, 'portal_user', groups='base.group_portal')
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.session = http.root.session_store.new()
|
||||
self.session.update(http.get_default_session(), db=get_db_name())
|
||||
self.opener = Opener(self.env.cr)
|
||||
self.opener.cookies.set('session_id', self.session.sid, domain=HOST, path='/')
|
||||
|
||||
def login(self, username, password, csrf_token=None):
|
||||
"""Log in with provided credentials and return response to POST request or raises for status."""
|
||||
res_post = self.url_open('/web/login', data={
|
||||
'login': username,
|
||||
'password': password,
|
||||
'csrf_token':csrf_token or http.Request.csrf_token(self),
|
||||
})
|
||||
res_post.raise_for_status()
|
||||
|
||||
return res_post
|
||||
|
||||
|
||||
class TestWebLogin(TestWebLoginCommon):
|
||||
def test_web_login(self):
|
||||
res_post = self.login('internal_user', 'internal_user')
|
||||
# ensure we are logged-in
|
||||
self.url_open(
|
||||
'/web/session/check',
|
||||
headers={'Content-Type': 'application/json'},
|
||||
data='{}'
|
||||
).raise_for_status()
|
||||
# ensure we end up on the right page for internal users.
|
||||
self.assertEqual(res_post.request.path_url, '/web')
|
||||
|
||||
def test_web_login_external(self):
|
||||
res_post = self.login('portal_user', 'portal_user')
|
||||
# ensure we end up on the right page for external users. Valid without portal installed.
|
||||
self.assertEqual(res_post.request.path_url, '/web/login_successful')
|
||||
|
||||
def test_web_login_bad_xhr(self):
|
||||
# simulate the user downloaded the login form
|
||||
csrf_token = http.Request.csrf_token(self)
|
||||
|
||||
# simulate that the JS sended a bad XHR to a route that is
|
||||
# auth='none' using the same session (e.g. via a service worker)
|
||||
bad_xhr = self.url_open('/web/login_successful', allow_redirects=False)
|
||||
self.assertNotEqual(bad_xhr.status_code, 200)
|
||||
|
||||
# log in using the above form, it should still be valid
|
||||
self.login('internal_user', 'internal_user', csrf_token)
|
||||
54
odoo-bringout-oca-ocb-web/web/tests/test_menu.py
Normal file
54
odoo-bringout-oca-ocb-web/web/tests/test_menu.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo.tests.common import BaseCase
|
||||
from odoo.addons.web.controllers.utils import fix_view_modes
|
||||
|
||||
|
||||
class ActionMungerTest(BaseCase):
|
||||
def test_actual_treeview(self):
|
||||
action = {
|
||||
"views": [[False, "tree"], [False, "form"],
|
||||
[False, "calendar"]],
|
||||
"view_type": "tree",
|
||||
"view_id": False,
|
||||
"view_mode": "tree,form,calendar"
|
||||
}
|
||||
changed = action.copy()
|
||||
del action['view_type']
|
||||
fix_view_modes(changed)
|
||||
|
||||
self.assertEqual(changed, action)
|
||||
|
||||
def test_list_view(self):
|
||||
action = {
|
||||
"views": [[False, "tree"], [False, "form"],
|
||||
[False, "calendar"]],
|
||||
"view_type": "form",
|
||||
"view_id": False,
|
||||
"view_mode": "tree,form,calendar"
|
||||
}
|
||||
fix_view_modes(action)
|
||||
|
||||
self.assertEqual(action, {
|
||||
"views": [[False, "list"], [False, "form"],
|
||||
[False, "calendar"]],
|
||||
"view_id": False,
|
||||
"view_mode": "list,form,calendar"
|
||||
})
|
||||
|
||||
def test_redundant_views(self):
|
||||
|
||||
action = {
|
||||
"views": [[False, "tree"], [False, "form"],
|
||||
[False, "calendar"], [42, "tree"]],
|
||||
"view_type": "form",
|
||||
"view_id": False,
|
||||
"view_mode": "tree,form,calendar"
|
||||
}
|
||||
fix_view_modes(action)
|
||||
|
||||
self.assertEqual(action, {
|
||||
"views": [[False, "list"], [False, "form"],
|
||||
[False, "calendar"], [42, "list"]],
|
||||
"view_id": False,
|
||||
"view_mode": "list,form,calendar"
|
||||
})
|
||||
118
odoo-bringout-oca-ocb-web/web/tests/test_profiler.py
Normal file
118
odoo-bringout-oca-ocb-web/web/tests/test_profiler.py
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.tests.common import HttpCase, tagged
|
||||
|
||||
|
||||
class ProfilingHttpCase(HttpCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
# Trick: we patch db_connect() to make it return the registry; when the
|
||||
# profiler calls cursor() on it, it gets a test cursor (with cls.cr as
|
||||
# its actual cursor), which prevents the profiling data from being
|
||||
# committed for real.
|
||||
cls.patcher = patch('odoo.sql_db.db_connect', return_value=cls.registry)
|
||||
cls.startClassPatcher(cls.patcher)
|
||||
|
||||
def profile_rpc(self, params=None):
|
||||
params = params or {}
|
||||
req = self.url_open(
|
||||
'/web/dataset/call_kw/ir.profile/set_profiling', # use model and method in route has web client does
|
||||
headers={'Content-Type': 'application/json'},
|
||||
data=json.dumps({'params':{
|
||||
'model': 'ir.profile',
|
||||
'method': 'set_profiling',
|
||||
'args': [],
|
||||
'kwargs': params,
|
||||
}})
|
||||
)
|
||||
req.raise_for_status()
|
||||
return req.json()
|
||||
|
||||
@tagged('post_install', '-at_install', 'profiling')
|
||||
class TestProfilingWeb(ProfilingHttpCase):
|
||||
def test_profiling_enabled(self):
|
||||
# since profiling will use a direct connection to the database patch 'db_connect' to ensure we are using the test cursor
|
||||
self.authenticate('admin', 'admin')
|
||||
last_profile = self.env['ir.profile'].search([], limit=1, order='id desc')
|
||||
# Trying to start profiling when not enabled
|
||||
self.env['ir.config_parameter'].set_param('base.profiling_enabled_until', '')
|
||||
res = self.profile_rpc({'profile': 1})
|
||||
self.assertEqual(res['result']['res_model'], 'base.enable.profiling.wizard')
|
||||
self.assertEqual(last_profile, self.env['ir.profile'].search([], limit=1, order='id desc'))
|
||||
|
||||
# Enable profiling and start blank profiling
|
||||
expiration = datetime.datetime.now() + datetime.timedelta(seconds=50)
|
||||
self.env['ir.config_parameter'].set_param('base.profiling_enabled_until', expiration)
|
||||
res = self.profile_rpc({'profile': 1})
|
||||
self.assertTrue(res['result']['session'])
|
||||
self.assertEqual(last_profile, self.env['ir.profile'].search([], limit=1, order='id desc'), "profiling route shouldn't have been profiled")
|
||||
# Profile a page
|
||||
res = self.url_open('/web/speedscope') # profile a light route
|
||||
new_profile = self.env['ir.profile'].search([], limit=1, order='id desc')
|
||||
self.assertNotEqual(last_profile, new_profile, "A new profile should have been created")
|
||||
self.assertEqual(new_profile.name, '/web/speedscope?')
|
||||
|
||||
def test_profile_test_tool(self):
|
||||
with self.profile():
|
||||
self.url_open('/web')
|
||||
|
||||
descriptions = self.env['ir.profile'].search([], order='id desc', limit=3).mapped('name')
|
||||
self.assertEqual(descriptions, [
|
||||
f'test_profile_test_tool uid:{self.env.uid} warm ',
|
||||
f'test_profile_test_tool uid:{self.env.uid} warm /web/login?',
|
||||
f'test_profile_test_tool uid:{self.env.uid} warm /web?',
|
||||
])
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install', 'profiling')
|
||||
class TestProfilingModes(ProfilingHttpCase):
|
||||
def test_profile_collectors(self):
|
||||
expiration = datetime.datetime.now() + datetime.timedelta(seconds=50)
|
||||
self.env['ir.config_parameter'].set_param('base.profiling_enabled_until', expiration)
|
||||
|
||||
self.authenticate('admin', 'admin')
|
||||
res = self.profile_rpc({})
|
||||
self.assertEqual(res['result']['collectors'], None)
|
||||
res = self.profile_rpc({'profile': 1, 'collectors': ['sql', 'traces_async']})
|
||||
self.assertEqual(sorted(res['result']['collectors']), ['sql', 'traces_async'])
|
||||
res = self.profile_rpc({'collectors': ['sql']})
|
||||
self.assertEqual(res['result']['collectors'], ['sql'],)
|
||||
res = self.profile_rpc({'profile': 0})
|
||||
res = self.profile_rpc({'profile': 1})
|
||||
self.assertEqual(res['result']['collectors'], ['sql'],
|
||||
"Enabling and disabling profiling shouldn't have change existing preferences")
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install', 'profiling')
|
||||
class TestProfilingPublic(ProfilingHttpCase):
|
||||
|
||||
def test_public_user_profiling(self):
|
||||
last_profile = self.env['ir.profile'].search([], limit=1, order='id desc')
|
||||
self.env['ir.config_parameter'].set_param('base.profiling_enabled_until', '')
|
||||
self.authenticate(None, None)
|
||||
|
||||
res = self.url_open('/web/set_profiling?profile=1')
|
||||
self.assertEqual(res.status_code, 500)
|
||||
self.assertEqual(res.text, 'error: Profiling is not enabled on this database. Please contact an administrator.')
|
||||
|
||||
expiration = datetime.datetime.now() + datetime.timedelta(seconds=50)
|
||||
self.env['ir.config_parameter'].set_param('base.profiling_enabled_until', expiration)
|
||||
res = self.url_open('/web/set_profiling?profile=1')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
res = res.json()
|
||||
self.assertTrue(res.pop('session'))
|
||||
self.assertEqual(res, {"collectors": ["sql", "traces_async"], "params": {}})
|
||||
self.assertEqual(last_profile, self.env['ir.profile'].search([], limit=1, order='id desc'), "profiling route shouldn't have been profiled")
|
||||
|
||||
res = self.url_open('/web/login') # profile /web/login to avoid redirections of /
|
||||
new_profile = self.env['ir.profile'].search([], limit=1, order='id desc')
|
||||
self.assertNotEqual(last_profile, new_profile, "A route should have been profiled")
|
||||
self.assertEqual(new_profile.name, '/web/login?')
|
||||
158
odoo-bringout-oca-ocb-web/web/tests/test_read_progress_bar.py
Normal file
158
odoo-bringout-oca-ocb-web/web/tests/test_read_progress_bar.py
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo.tests import common
|
||||
|
||||
|
||||
@common.tagged('post_install', '-at_install')
|
||||
class TestReadProgressBar(common.TransactionCase):
|
||||
"""Test for read_progress_bar"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestReadProgressBar, self).setUp()
|
||||
self.Model = self.env['res.partner']
|
||||
|
||||
def test_read_progress_bar_m2m(self):
|
||||
""" Test that read_progress_bar works with m2m field grouping """
|
||||
progressbar = {
|
||||
'field': 'type',
|
||||
'colors': {
|
||||
'contact': 'success', 'private': 'danger', 'other': '200',
|
||||
}
|
||||
}
|
||||
result = self.env['res.partner'].read_progress_bar([], 'category_id', progressbar)
|
||||
# check that it works when grouping by m2m field
|
||||
self.assertTrue(result)
|
||||
# check the null group
|
||||
self.assertIn('False', result)
|
||||
|
||||
def test_week_grouping(self):
|
||||
"""The labels associated to each record in read_progress_bar should match
|
||||
the ones from read_group, even in edge cases like en_US locale on sundays
|
||||
"""
|
||||
context = {"lang": "en_US"}
|
||||
groupby = "date:week"
|
||||
self.Model.create({'date': '2021-05-02', 'name': "testWeekGrouping_first"}) # Sunday
|
||||
self.Model.create({'date': '2021-05-09', 'name': "testWeekGrouping_second"}) # Sunday
|
||||
progress_bar = {
|
||||
'field': 'name',
|
||||
'colors': {
|
||||
"testWeekGrouping_first": 'success',
|
||||
"testWeekGrouping_second": 'danger',
|
||||
}
|
||||
}
|
||||
|
||||
groups = self.Model.with_context(context).read_group(
|
||||
[('name', "like", "testWeekGrouping%")], fields=['date', 'name'], groupby=[groupby])
|
||||
progressbars = self.Model.with_context(context).read_progress_bar(
|
||||
[('name', "like", "testWeekGrouping%")], group_by=groupby, progress_bar=progress_bar)
|
||||
self.assertEqual(len(groups), 2)
|
||||
self.assertEqual(len(progressbars), 2)
|
||||
|
||||
# format the read_progress_bar result to get a dictionary under this format : {record_name: group_name}
|
||||
# original format (after read_progress_bar) is : {group_name: {record_name: count}}
|
||||
pg_groups = {
|
||||
next(record_name for record_name, count in data.items() if count): group_name
|
||||
for group_name, data in progressbars.items()
|
||||
}
|
||||
|
||||
self.assertEqual(groups[0][groupby], pg_groups["testWeekGrouping_first"])
|
||||
self.assertEqual(groups[1][groupby], pg_groups["testWeekGrouping_second"])
|
||||
|
||||
def test_simple(self):
|
||||
model = self.env['ir.model'].create({
|
||||
'model': 'x_progressbar',
|
||||
'name': 'progress_bar',
|
||||
'field_id': [
|
||||
(0, 0, {
|
||||
'field_description': 'Country',
|
||||
'name': 'x_country_id',
|
||||
'ttype': 'many2one',
|
||||
'relation': 'res.country',
|
||||
}),
|
||||
(0, 0, {
|
||||
'field_description': 'Date',
|
||||
'name': 'x_date',
|
||||
'ttype': 'date',
|
||||
}),
|
||||
(0, 0, {
|
||||
'field_description': 'State',
|
||||
'name': 'x_state',
|
||||
'ttype': 'selection',
|
||||
'selection': "[('foo', 'Foo'), ('bar', 'Bar'), ('baz', 'Baz')]",
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
c1, c2, c3 = self.env['res.country'].search([], limit=3)
|
||||
|
||||
self.env['x_progressbar'].create([
|
||||
# week 21
|
||||
{'x_country_id': c1.id, 'x_date': '2021-05-20', 'x_state': 'foo'},
|
||||
{'x_country_id': c1.id, 'x_date': '2021-05-21', 'x_state': 'foo'},
|
||||
{'x_country_id': c1.id, 'x_date': '2021-05-22', 'x_state': 'foo'},
|
||||
{'x_country_id': c1.id, 'x_date': '2021-05-23', 'x_state': 'bar'},
|
||||
# week 22
|
||||
{'x_country_id': c1.id, 'x_date': '2021-05-24', 'x_state': 'baz'},
|
||||
{'x_country_id': c2.id, 'x_date': '2021-05-25', 'x_state': 'foo'},
|
||||
{'x_country_id': c2.id, 'x_date': '2021-05-26', 'x_state': 'bar'},
|
||||
{'x_country_id': c2.id, 'x_date': '2021-05-27', 'x_state': 'bar'},
|
||||
{'x_country_id': c2.id, 'x_date': '2021-05-28', 'x_state': 'baz'},
|
||||
{'x_country_id': c2.id, 'x_date': '2021-05-29', 'x_state': 'baz'},
|
||||
{'x_country_id': c3.id, 'x_date': '2021-05-30', 'x_state': 'foo'},
|
||||
# week 23
|
||||
{'x_country_id': c3.id, 'x_date': '2021-05-31', 'x_state': 'foo'},
|
||||
{'x_country_id': c3.id, 'x_date': '2021-06-01', 'x_state': 'baz'},
|
||||
{'x_country_id': c3.id, 'x_date': '2021-06-02', 'x_state': 'baz'},
|
||||
{'x_country_id': c3.id, 'x_date': '2021-06-03', 'x_state': 'baz'},
|
||||
])
|
||||
|
||||
progress_bar = {
|
||||
'field': 'x_state',
|
||||
'colors': {'foo': 'success', 'bar': 'warning', 'baz': 'danger'},
|
||||
}
|
||||
result = self.env['x_progressbar'].read_progress_bar([], 'x_country_id', progress_bar)
|
||||
self.assertEqual(result, {
|
||||
c1.display_name: {'foo': 3, 'bar': 1, 'baz': 1},
|
||||
c2.display_name: {'foo': 1, 'bar': 2, 'baz': 2},
|
||||
c3.display_name: {'foo': 2, 'bar': 0, 'baz': 3},
|
||||
})
|
||||
|
||||
# check date aggregation and format
|
||||
result = self.env['x_progressbar'].read_progress_bar([], 'x_date:week', progress_bar)
|
||||
self.assertEqual(result, {
|
||||
'W21 2021': {'foo': 3, 'bar': 1, 'baz': 0},
|
||||
'W22 2021': {'foo': 2, 'bar': 2, 'baz': 3},
|
||||
'W23 2021': {'foo': 1, 'bar': 0, 'baz': 3},
|
||||
})
|
||||
|
||||
# add a computed field on model
|
||||
model.write({'field_id': [
|
||||
(0, 0, {
|
||||
'field_description': 'Related State',
|
||||
'name': 'x_state_computed',
|
||||
'ttype': 'selection',
|
||||
'selection': "[('foo', 'Foo'), ('bar', 'Bar'), ('baz', 'Baz')]",
|
||||
'compute': "for rec in self: rec['x_state_computed'] = rec.x_state",
|
||||
'depends': 'x_state',
|
||||
'readonly': True,
|
||||
'store': False,
|
||||
}),
|
||||
]})
|
||||
|
||||
progress_bar = {
|
||||
'field': 'x_state_computed',
|
||||
'colors': {'foo': 'success', 'bar': 'warning', 'baz': 'danger'},
|
||||
}
|
||||
result = self.env['x_progressbar'].read_progress_bar([], 'x_country_id', progress_bar)
|
||||
self.assertEqual(result, {
|
||||
c1.display_name: {'foo': 3, 'bar': 1, 'baz': 1},
|
||||
c2.display_name: {'foo': 1, 'bar': 2, 'baz': 2},
|
||||
c3.display_name: {'foo': 2, 'bar': 0, 'baz': 3},
|
||||
})
|
||||
|
||||
result = self.env['x_progressbar'].read_progress_bar([], 'x_date:week', progress_bar)
|
||||
self.assertEqual(result, {
|
||||
'W21 2021': {'foo': 3, 'bar': 1, 'baz': 0},
|
||||
'W22 2021': {'foo': 2, 'bar': 2, 'baz': 3},
|
||||
'W23 2021': {'foo': 1, 'bar': 0, 'baz': 3},
|
||||
})
|
||||
76
odoo-bringout-oca-ocb-web/web/tests/test_reports.py
Normal file
76
odoo-bringout-oca-ocb-web/web/tests/test_reports.py
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import odoo.tests
|
||||
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
|
||||
|
||||
class TestReports(odoo.tests.HttpCase):
|
||||
def test_report_session_cookie(self):
|
||||
""" Asserts wkhtmltopdf forwards the user session when requesting resources to Odoo, such as images,
|
||||
and that the resource is correctly returned as expected.
|
||||
"""
|
||||
partner_id = self.env.user.partner_id.id
|
||||
img = b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4//8/AAX+Av4N70a4AAAAAElFTkSuQmCC'
|
||||
image = self.env['ir.attachment'].create({
|
||||
'name': 'foo',
|
||||
'res_model': 'res.partner',
|
||||
'res_id': partner_id,
|
||||
'datas': img,
|
||||
})
|
||||
report = self.env['ir.actions.report'].create({
|
||||
'name': 'test report',
|
||||
'report_name': 'base.test_report',
|
||||
'model': 'res.partner',
|
||||
})
|
||||
self.env['ir.ui.view'].create({
|
||||
'type': 'qweb',
|
||||
'name': 'base.test_report',
|
||||
'key': 'base.test_report',
|
||||
'arch': f'''
|
||||
<main>
|
||||
<div class="article" data-oe-model="res.partner" t-att-data-oe-id="docs.id">
|
||||
<img src="/web/image/{image.id}"/>
|
||||
</div>
|
||||
</main>
|
||||
'''
|
||||
})
|
||||
|
||||
result = {}
|
||||
origin_find_record = self.env.registry['ir.binary']._find_record
|
||||
|
||||
def _find_record(self, xmlid=None, res_model='ir.attachment', res_id=None, access_token=None):
|
||||
if res_model == 'ir.attachment' and res_id == image.id:
|
||||
result['uid'] = self.env.uid
|
||||
record = origin_find_record(self, xmlid, res_model, res_id, access_token)
|
||||
result.update({'record_id': record.id, 'data': record.datas})
|
||||
else:
|
||||
record = origin_find_record(self, xmlid, res_model, res_id, access_token)
|
||||
return record
|
||||
|
||||
self.patch(self.env.registry['ir.binary'], '_find_record', _find_record)
|
||||
|
||||
# 1. Request the report as admin, who has access to the image
|
||||
admin = self.env.ref('base.user_admin')
|
||||
report = report.with_user(admin)
|
||||
with MockRequest(report.env) as mock_request:
|
||||
mock_request.session.sid = self.authenticate(admin.login, admin.login).sid
|
||||
report.with_context(force_report_rendering=True)._render_qweb_pdf(report.id, [partner_id])
|
||||
|
||||
self.assertEqual(
|
||||
result.get('uid'), admin.id, 'wkhtmltopdf is not fetching the image as the user printing the report'
|
||||
)
|
||||
self.assertEqual(result.get('record_id'), image.id, 'wkhtmltopdf did not fetch the expected record')
|
||||
self.assertEqual(result.get('data'), img, 'wkhtmltopdf did not fetch the right image content')
|
||||
|
||||
# 2. Request the report as public, who has no acess to the image
|
||||
self.logout()
|
||||
result.clear()
|
||||
public = self.env.ref('base.public_user')
|
||||
report = report.with_user(public)
|
||||
with MockRequest(self.env) as mock_request:
|
||||
report.with_context(force_report_rendering=True)._render_qweb_pdf(report.id, [partner_id])
|
||||
|
||||
self.assertEqual(
|
||||
result.get('uid'), public.id, 'wkhtmltopdf is not fetching the image as the user printing the report'
|
||||
)
|
||||
self.assertEqual(result.get('record_id'), None, 'wkhtmltopdf must not have been allowed to fetch the image')
|
||||
self.assertEqual(result.get('data'), None, 'wkhtmltopdf must not have been allowed to fetch the image')
|
||||
74
odoo-bringout-oca-ocb-web/web/tests/test_session_info.py
Normal file
74
odoo-bringout-oca-ocb-web/web/tests/test_session_info.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import json
|
||||
from uuid import uuid4
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests import common
|
||||
|
||||
|
||||
class TestSessionInfo(common.HttpCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.company_a = cls.env['res.company'].create({'name': "A"})
|
||||
cls.company_b = cls.env['res.company'].create({'name': "B"})
|
||||
cls.company_c = cls.env['res.company'].create({'name': "C"})
|
||||
cls.companies = [cls.company_a, cls.company_b, cls.company_c]
|
||||
|
||||
cls.user_password = "info"
|
||||
cls.user = common.new_test_user(
|
||||
cls.env,
|
||||
"session",
|
||||
email="session@in.fo",
|
||||
password=cls.user_password,
|
||||
tz="UTC")
|
||||
cls.user.write({
|
||||
'company_id': cls.company_a.id,
|
||||
'company_ids': [Command.set([company.id for company in cls.companies])],
|
||||
})
|
||||
|
||||
cls.payload = json.dumps(dict(jsonrpc="2.0", method="call", id=str(uuid4())))
|
||||
cls.headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
def test_session_info(self):
|
||||
""" Checks that the session_info['user_companies'] structure correspond to what is expected """
|
||||
self.authenticate(self.user.login, self.user_password)
|
||||
response = self.url_open("/web/session/get_session_info", data=self.payload, headers=self.headers)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
data = response.json()
|
||||
result = data["result"]
|
||||
|
||||
expected_allowed_companies = {
|
||||
str(company.id): {
|
||||
'id': company.id,
|
||||
'name': company.name,
|
||||
'sequence': company.sequence,
|
||||
} for company in self.companies
|
||||
}
|
||||
expected_user_companies = {
|
||||
'current_company': self.company_a.id,
|
||||
'allowed_companies': expected_allowed_companies,
|
||||
}
|
||||
self.assertEqual(
|
||||
result['user_companies'],
|
||||
expected_user_companies,
|
||||
"The session_info['user_companies'] does not have the expected structure")
|
||||
|
||||
def test_session_modules(self):
|
||||
self.authenticate(self.user.login, self.user_password)
|
||||
response = self.url_open("/web/session/modules", data=self.payload, headers=self.headers)
|
||||
data = response.json()
|
||||
self.assertTrue(isinstance(data['result'], list))
|
||||
|
||||
def test_load_polish_lang(self):
|
||||
# Regression test, making sure languages without thousand separators
|
||||
# work correctly
|
||||
lang_pl = self.env['res.lang']._activate_lang('pl_PL')
|
||||
self.user.lang = lang_pl.code
|
||||
self.authenticate(self.user.login, self.user_password)
|
||||
res = self.url_open('/web')
|
||||
res.raise_for_status()
|
||||
36
odoo-bringout-oca-ocb-web/web/tests/test_web_search_read.py
Normal file
36
odoo-bringout-oca-ocb-web/web/tests/test_web_search_read.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests import common
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
@common.tagged('post_install', '-at_install')
|
||||
class TestWebSearchRead(common.TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.ResCurrency = cls.env['res.currency'].with_context(active_test=False)
|
||||
cls.max = cls.ResCurrency.search_count([])
|
||||
|
||||
def assert_web_search_read(self, expected_length, expected_records_length, expected_search_count_called=True, **kwargs):
|
||||
original_search_count = self.ResCurrency.search_count
|
||||
search_count_called = [False]
|
||||
|
||||
def search_count(obj, *method_args, **method_kwargs):
|
||||
search_count_called[0] = True
|
||||
return original_search_count(*method_args, **method_kwargs)
|
||||
|
||||
with patch('odoo.addons.base.models.res_currency.Currency.search_count', new=search_count):
|
||||
results = self.ResCurrency.web_search_read(domain=[], fields=['id'], **kwargs)
|
||||
|
||||
self.assertEqual(results['length'], expected_length)
|
||||
self.assertEqual(len(results['records']), expected_records_length)
|
||||
self.assertEqual(search_count_called[0], expected_search_count_called)
|
||||
|
||||
def test_web_search_read(self):
|
||||
self.assert_web_search_read(self.max, self.max, expected_search_count_called=False)
|
||||
self.assert_web_search_read(self.max, 2, limit=2)
|
||||
self.assert_web_search_read(self.max, 2, limit=2, offset=10)
|
||||
self.assert_web_search_read(2, 2, limit=2, count_limit=2, expected_search_count_called=False)
|
||||
self.assert_web_search_read(20, 2, limit=2, offset=10, count_limit=20)
|
||||
self.assert_web_search_read(12, 2, limit=2, offset=10, count_limit=12, expected_search_count_called=False)
|
||||
Loading…
Add table
Add a link
Reference in a new issue