2025-01-06 10:57:38 +07:00
# 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
2025-03-04 12:23:19 +07:00
website_id = self . env [ ' website ' ] . search ( [ ] , limit = 1 , order = ' id asc ' ) . id
2025-01-06 10:57:38 +07:00
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 ' ,
)