1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import re
import odoo.tests
from odoo.tools import config
@odoo.tests.common.tagged('post_install', '-at_install')
class TestWebsiteAssets(odoo.tests.HttpCase):
def test_01_multi_domain_assets_generation(self):
Website = self.env['website']
Attachment = self.env['ir.attachment']
# Create an additional website to ensure it works in multi-website setup
Website.create({'name': 'Second Website'})
# Simulate single website DBs: make sure other website do not interfer
# (We can't delete those, constraint will most likely be raised)
[w.write({'domain': f'inactive-{w.id}.test'}) for w in Website.search([])]
# Don't use HOST, hardcode it so it doesn't get changed one day and make
# the test useless
domain_1 = f"http://127.0.0.1:{self.http_port()}"
domain_2 = f"http://localhost:{self.http_port()}"
Website.browse(1).domain = domain_1
self.authenticate('admin', 'admin')
self.env['web_editor.assets'].with_context(website_id=1).make_scss_customization(
'/website/static/src/scss/options/colors/user_color_palette.scss',
{"o-cc1-bg": "'400'"},
)
def get_last_backend_asset_attach_id():
return Attachment.search([
('name', '=', 'web.assets_backend.min.js'),
], order="id desc", limit=1).id
def check_asset():
self.assertEqual(last_backend_asset_attach_id, get_last_backend_asset_attach_id())
last_backend_asset_attach_id = get_last_backend_asset_attach_id()
# The first call will generate the assets and populate the cache and
# take ~100 SQL Queries (~cold state).
# Any later call to `/web`, regardless of the domain, will take only
# ~10 SQL Queries (hot state).
# Without the calls the `check_asset()` (which would raise early and
# would not call other `url_open()`) and before the fix coming with this
# test, here is the logs:
# "GET /web HTTP/1.1" 200 - 222 0.135 3.840 <-- 222 Queries, ~4s
# "GET /web HTTP/1.1" 200 - 181 0.101 3.692 <-- 181 Queries, ~4s
# "GET /web HTTP/1.1" 200 - 215 0.121 3.704 <-- 215 Queries, ~4s
# "GET /web HTTP/1.1" 200 - 181 0.100 3.616 <-- 181 Queries, ~4s
# After the fix, here is the logs:
# "GET /web HTTP/1.1" 200 - 101 0.043 0.353 <-- 101 Queries, ~0.3s
# "GET /web HTTP/1.1" 200 - 11 0.004 0.007 <-- 11 Queries, ~10ms
# "GET /web HTTP/1.1" 200 - 11 0.003 0.005 <-- 11 Queries, ~10ms
# "GET /web HTTP/1.1" 200 - 11 0.003 0.008 <-- 11 Queries, ~10ms
self.url_open(domain_1 + '/odoo')
check_asset()
self.url_open(domain_2 + '/odoo')
check_asset()
self.url_open(domain_1 + '/odoo')
check_asset()
self.url_open(domain_2 + '/odoo')
check_asset()
self.url_open(domain_1 + '/odoo')
check_asset()
def test_02_t_cache_invalidation(self):
self.authenticate(None, None)
page = self.url_open('/').text # add to cache
public_assets_links = re.findall(r'(/web/assets/\d+/\w{7}/web.assets_frontend\..+)"/>', page)
self.assertTrue(public_assets_links)
self.authenticate('admin', 'admin')
page = self.url_open('/').text
admin_assets_links = re.findall(r'(/web/assets/\d+/\w{7}/web.assets_frontend\..+)"/>', page)
self.assertTrue(admin_assets_links)
self.assertEqual(public_assets_links, admin_assets_links)
snippets = self.env['ir.asset'].search([
('path', '=like', 'website/static/src/snippets/s_social_media/000.scss'), # arbitrary, a unused css one that doesn't make the page fail when archived.
('bundle', '=', 'web.assets_frontend'),
])
self.assertTrue(snippets)
write_dates = snippets.mapped('write_date')
snippets.write({'active': False})
snippets.flush_recordset()
self.assertNotEqual(write_dates, snippets.mapped('write_date'))
page = self.url_open('/').text
new_admin_assets_links = re.findall(r'(/web/assets/\d+/\w{7}/web.assets_frontend\..+)"/>', page)
self.assertTrue(new_admin_assets_links)
self.assertEqual(public_assets_links, admin_assets_links)
self.assertNotEqual(new_admin_assets_links, admin_assets_links, "we expect a change since ir_assets were written")
self.authenticate(None, None)
page = self.url_open('/').text
new_public_assets_links = re.findall(r'(/web/assets/\d+/\w{7}/web.assets_frontend\..+)"/>', page)
self.assertEqual(new_admin_assets_links, new_public_assets_links, "t-cache should have been invalidated for public user too")
def test_invalid_unlink(self):
self.env['ir.attachment'].search([('url', '=like', '/web/assets/%')]).unlink()
asset_bundle_xmlid = 'web.assets_frontend'
website_default = self.env['website'].search([], limit=1)
code = b"document.body.dataset.hello = 'world';"
attach = self.env['ir.attachment'].create({
'name': 'EditorExtension.css',
'mimetype': 'text/css',
'raw': code,
})
custom_url = '/_custom/web/content/%s/%s' % (attach.id, attach.name)
attach.url = custom_url
self.env['ir.asset'].create({
'name': 'EditorExtension',
'bundle': asset_bundle_xmlid,
'path': custom_url,
'website_id': website_default.id,
})
website_bundle = self.env['ir.qweb']._get_asset_bundle(asset_bundle_xmlid, assets_params={'website_id': website_default.id})
self.assertIn(custom_url, [f['url'] for f in website_bundle.files])
base_website_css_version = website_bundle.get_version('css')
no_website_bundle = self.env['ir.qweb']._get_asset_bundle(asset_bundle_xmlid)
self.assertNotIn(custom_url, [f['url'] for f in no_website_bundle.files])
self.assertNotEqual(no_website_bundle.get_version('css'), base_website_css_version)
website_attach = website_bundle.css()
self.assertTrue(website_attach.exists())
no_website_bundle.css()
self.assertTrue(website_attach.exists(), 'attachment for website should still exist after generating attachment for no website')
@odoo.tests.tagged('-at_install', 'post_install')
class TestWebAssets(odoo.tests.HttpCase):
def test_assets_url_validation(self):
website_id = self.env['website'].search([], limit=1, order='id desc').id
with odoo.tools.mute_logger('odoo.addons.web.controllers.binary'):
self.assertEqual(
self.url_open(f'/web/assets/{website_id}/debug/hello/web.assets_frontend.css', allow_redirects=False).status_code,
404,
"unexpected direction extra",
)
self.assertEqual(
self.url_open(f'/web/assets/{website_id}/debug/web.assets_f_ontend.js', allow_redirects=False).status_code,
404,
"bundle name contains `_` and should be escaped wildcard",
)
self.assertEqual(
self.url_open(f'/web/assets/{website_id}/debug/web.assets_frontend.rtl.js', allow_redirects=False).status_code,
404,
"js cannot have `rtl` has extra",
)
self.assertEqual(
self.url_open(f'/web/assets/{website_id}/debug/web.assets_frontend.rtl.js', allow_redirects=False).status_code,
404,
"js cannot have `rtl` has extra",
)
self.assertEqual(
self.url_open(f'/web/{website_id+1}/assets/debug/web.assets_frontend.css', allow_redirects=False).status_code,
404,
"website_id does not exist",
)
self.assertEqual(
self.url_open(f'/web/assets/{website_id}/debug/web.assets_frontend.aa.css', allow_redirects=False).status_code,
404,
"invalid direction",
)
self.assertEqual(
self.url_open(f'/web/assets/{website_id}/any/web.assets_frontend.min.rtl.css', allow_redirects=False).status_code,
404,
"min and direction inverted",
)
self.assertEqual(
self.url_open(f'/web/assets/{website_id}/any/web.assets_frontend.js', allow_redirects=False).status_code,
404,
"missing min in non debug mode",
)
self.assertEqual(
self.url_open('/web/assets/debug/web.assets_frontend.css', allow_redirects=False).status_code,
200,
)
self.assertEqual(
self.url_open('/web/assets/debug/web.assets_frontend.js', allow_redirects=False).status_code,
200,
)
self.assertEqual(
self.url_open('/web/assets/debug/web.assets_frontend.rtl.css', allow_redirects=False).status_code,
200,
)
self.assertEqual(
self.url_open(f'/web/assets/{website_id}/debug/web.assets_frontend.css', allow_redirects=False).status_code,
200,
)
self.assertEqual(
self.url_open(f'/web/assets/{website_id}/debug/web.assets_frontend.rtl.css', allow_redirects=False).status_code,
200,
)
self.assertEqual(
self.url_open(f'/web/assets/{website_id}/debug/web.assets_frontend.js', allow_redirects=False).status_code,
200,
)
self.assertEqual(
self.url_open(f'/web/assets/{website_id}/any/web.assets_frontend.rtl.min.css', allow_redirects=False).status_code,
200,
)
self.assertEqual(
self.url_open(f'/web/assets/{website_id}/any/web.assets_frontend.min.css', allow_redirects=False).status_code,
200,
)
self.assertEqual(
self.url_open(f'/web/assets/{website_id}/any/web.assets_frontend.min.js', allow_redirects=False).status_code,
200,
)
# redirect urls
invalid_version = '1234567'
self.assertEqual(
self.url_open(f'/web/assets/{website_id}/{invalid_version}/web.assets_frontend.min.css', allow_redirects=False).headers['location'].split('/assets/')[1],
self.env['ir.qweb']._get_asset_bundle('web.assets_frontend', assets_params={'website_id': website_id}).get_link('css').split('/assets/')[1],
)
def test_ensure_correct_website_asset(self):
# when searching for an attachment, if the unique a wildcard, we want to ensute that we don't match a website one when seraching a no website one.
# this test should also wheck that the clean_attachement does not erase a website_attachement after generating a base attachment
website_id = self.env['website'].search([], limit=1, order='id desc').id
unique = self.env['ir.qweb']._get_asset_bundle('web.assets_frontend').get_version('js')
base_url = self.env['ir.asset']._get_asset_bundle_url('web.assets_frontend.min.js', '%', {})
base_url_versioned = self.env['ir.asset']._get_asset_bundle_url('web.assets_frontend.min.js', unique, {})
website_url = self.env['ir.asset']._get_asset_bundle_url('web.assets_frontend.min.js', '%', {'website_id': website_id})
# we expect the unique to be the same in this case, but there is no garantee
website_url_versioned = self.env['ir.asset']._get_asset_bundle_url('web.assets_frontend.min.js', unique, {'website_id': website_id})
self.env['ir.attachment'].search([('url', '=like', '%web.assets_frontend.min.js')]).unlink()
# generate website assets
self.assertEqual(self.url_open(website_url, allow_redirects=False).status_code, 200)
self.assertEqual(
self.env['ir.attachment'].search([('url', '=like', '%web.assets_frontend.min.js')]).mapped('url'),
[website_url_versioned],
'Only the website asset is expected to be present',
)
# generate base assets
with self.assertLogs() as logs:
self.assertEqual(self.url_open(base_url, allow_redirects=False).status_code, 200)
self.assertEqual(
f'Found a similar attachment for /web/assets/{unique}/web.assets_frontend.min.js, copying from /web/assets/{website_id}/{unique}/web.assets_frontend.min.js',
logs.records[0].message,
'The attachment was expected to be linked to an existing one')
self.assertEqual(
self.env['ir.attachment'].search([('url', '=like', '%web.assets_frontend.min.js')]).mapped('url'),
[base_url_versioned, website_url_versioned],
'base asset is expected to be present',
)
|