# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from collections import Counter from os import utime import time from odoo import api from odoo.addons.base.ir.ir_qweb import AssetsBundle from odoo.modules.module import get_resource_path from odoo.tests import HttpCase from odoo.tests.common import TransactionCase class TestJavascriptAssetsBundle(TransactionCase): def setUp(self): super(TestJavascriptAssetsBundle, self).setUp() self.jsbundle_xmlid = 'test_assetsbundle.bundle1' self.cssbundle_xmlid = 'test_assetsbundle.bundle2' def _get_asset(self, xmlid, env=None): env = (env or self.env) files, remains = env['ir.qweb']._get_asset_content(xmlid, env.context) return AssetsBundle(xmlid, files, remains, env=env) def _any_ira_for_bundle(self, type): """ Returns all ir.attachments associated to a bundle, regardless of the verion. """ bundle = self.jsbundle_xmlid if type == 'js' else self.cssbundle_xmlid return self.env['ir.attachment'].search([ ('url', '=like', '/web/content/%-%/{0}%.{1}'.format(bundle, type)) ]) def test_01_generation(self): """ Checks that a bundle creates an ir.attachment record when its `js` method is called for the first time. """ self.bundle = self._get_asset(self.jsbundle_xmlid, env=self.env) # there shouldn't be any attachment associated to this bundle self.assertEquals(len(self._any_ira_for_bundle('js')), 0) self.assertEquals(len(self.bundle.get_attachments('js')), 0) # trigger the first generation and, thus, the first save in database self.bundle.js() # there should be one attachment associated to this bundle self.assertEquals(len(self._any_ira_for_bundle('js')), 1) self.assertEquals(len(self.bundle.get_attachments('js')), 1) def test_02_access(self): """ Checks that the bundle's cache is working, i.e. that the bundle creates only one ir.attachment record when rendered multiple times. """ bundle0 = self._get_asset(self.jsbundle_xmlid) bundle0.js() self.assertEquals(len(self._any_ira_for_bundle('js')), 1) version0 = bundle0.version ira0 = self._any_ira_for_bundle('js') date0 = ira0.create_date bundle1 = self._get_asset(self.jsbundle_xmlid) bundle1.js() self.assertEquals(len(self._any_ira_for_bundle('js')), 1) version1 = bundle1.version ira1 = self._any_ira_for_bundle('js') date1 = ira1.create_date self.assertEquals(version0, version1) self.assertEquals(date0, date1) def test_03_date_invalidation(self): """ Checks that a bundle is invalidated when one of its assets' modification date is changed. """ bundle0 = self._get_asset(self.jsbundle_xmlid) bundle0.js() last_modified0 = bundle0.last_modified version0 = bundle0.version path = get_resource_path('test_assetsbundle', 'static', 'src', 'js', 'test_jsfile1.js') utime(path, None) # touch bundle1 = self._get_asset(self.jsbundle_xmlid) bundle1.js() last_modified1 = bundle1.last_modified version1 = bundle1.version self.assertNotEquals(last_modified0, last_modified1) self.assertNotEquals(version0, version1) # check if the previous attachment is correctly cleaned self.assertEquals(len(self._any_ira_for_bundle('js')), 1) def test_04_content_invalidation(self): """ Checks that a bundle is invalidated when its content is modified by adding a file to source. """ bundle0 = self._get_asset(self.jsbundle_xmlid) bundle0.js() files0 = bundle0.files remains0 = bundle0.remains version0 = bundle0.version self.assertEquals(len(self._any_ira_for_bundle('js')), 1) view_arch = """ """ self.env(cr=test_cursor)['ir.ui.view'].create({ 'name': 'test bundle inheritance inline js', 'type': 'qweb', 'arch': view_arch, 'inherit_id': self.browse_ref('test_assetsbundle.bundle1').id, }) self.phantom_js( "/test_assetsbundle/js", "a + b + c + d === 10 ? console.log('ok') : console.log('error')", login="admin", ) class TestAssetsBundleWithIRAMock(TransactionCase): def setUp(self): super(TestAssetsBundleWithIRAMock, self).setUp() self.lessbundle_xmlid = 'test_assetsbundle.bundle3' self.counter = counter = Counter() # patch methods 'create' and 'unlink' of model 'ir.attachment' @api.model def create(self, vals): counter.update(['create']) return create.origin(self, vals) @api.multi def unlink(self): counter.update(['unlink']) return unlink.origin(self) self.env['ir.attachment']._patch_method('create', create) self.addCleanup(self.env['ir.attachment']._revert_method, 'create') self.env['ir.attachment']._patch_method('unlink', unlink) self.addCleanup(self.env['ir.attachment']._revert_method, 'unlink') def _bundle(self, should_create, should_unlink): self.counter.clear() files, remains = self.env['ir.qweb']._get_asset_content(self.lessbundle_xmlid, {}) asset = AssetsBundle(self.lessbundle_xmlid, files, remains, env=self.env) asset.to_html(debug='assets') self.assertEquals(self.counter['create'], int(should_create)) self.assertEquals(self.counter['unlink'], int(should_unlink)) def test_01_debug_mode_assets(self): """ Checks that the ir.attachments records created for compiled less assets in debug mode are correctly invalidated. """ # Compile for the first time self._bundle(True, False) # Compile a second time, without changes self._bundle(False, False) # Touch the file and compile a third time path = get_resource_path('test_assetsbundle', 'static', 'src', 'less', 'test_lessfile1.less') t = time.time() + 5 utime(path, (t, t)) # touch self._bundle(True, True) # Because we are in the same transaction since the beginning of the test, the first asset # created and the second one have the same write_date, but the file's last modified date # has really been modified. If we do not update the write_date to a posterior date, we are # not able to reproduce the case where we compile this bundle again without changing # anything. self.cr.execute("update ir_attachment set write_date=clock_timestamp() + interval '10 seconds' where id = (select max(id) from ir_attachment)") # Compile a fourth time, without changes self._bundle(False, False)