Skip to content

Commit 6ac6d58

Browse files
hugobessarvlb
authored andcommitted
Add proper tests
1 parent 2048476 commit 6ac6d58

File tree

6 files changed

+198
-17
lines changed

6 files changed

+198
-17
lines changed

.circleci/config.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ workflows:
66
- base-test:
77
matrix:
88
parameters:
9-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
10-
django-version: ["4.2", "5.0"]
9+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
10+
django-version: ["4.2", "5.0", "5.1", "5.2"]
1111
exclude:
1212
- python-version: "3.8"
1313
django-version: "5.0"
@@ -17,6 +17,10 @@ workflows:
1717
django-version: "5.1"
1818
- python-version: "3.9"
1919
django-version: "5.1"
20+
- python-version: "3.8"
21+
django-version: "5.2"
22+
- python-version: "3.9"
23+
django-version: "5.2"
2024
- coverall:
2125
requires:
2226
- base-test

requirements-dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
coverage==5.5
22
twine==3.4.2
3-
Django==3.2.25
3+
Django==5.2.1
44
django-jinja==2.10.2
55
unittest2==1.1.0
66
wheel==0.38.1

tests/app/tests/test_webpack.py

Lines changed: 181 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
from shutil import rmtree
55
from subprocess import call
66
from threading import Thread
7-
from unittest.mock import Mock
7+
from unittest.mock import Mock, patch
88
from unittest.mock import call as MockCall
9-
from unittest.mock import patch
109

1110
from django.conf import settings
1211
from django.template import Context, Template, engines
@@ -18,9 +17,12 @@
1817
from django_jinja.backend import Template as Jinja2Template
1918
from django_jinja.builtins import DEFAULT_EXTENSIONS
2019

21-
from webpack_loader.exceptions import (WebpackBundleLookupError, WebpackError,
22-
WebpackLoaderBadStatsError,
23-
WebpackLoaderTimeoutError)
20+
from webpack_loader.exceptions import (
21+
WebpackBundleLookupError,
22+
WebpackError,
23+
WebpackLoaderBadStatsError,
24+
WebpackLoaderTimeoutError,
25+
)
2426
from webpack_loader.templatetags.webpack_loader import _WARNING_MESSAGE
2527
from webpack_loader.utils import get_as_tags, get_loader
2628

@@ -239,6 +241,93 @@ def test_integrity(self):
239241
result.rendered_content
240242
)
241243

244+
def test_integrity_with_crosorigin_empty(self):
245+
self.compile_bundles('webpack.config.integrity.js')
246+
247+
loader = get_loader(DEFAULT_CONFIG)
248+
with patch.dict(loader.config, {'INTEGRITY': True, 'CROSSORIGIN': ''}):
249+
view = TemplateView.as_view(template_name='single.html')
250+
request = self.factory.get('/')
251+
request.META['HTTP_HOST'] = 'crossorigen-custom-static-host.com'
252+
result = view(request)
253+
254+
self.assertIn((
255+
'<script src="http://custom-static-host.com/main.js" '
256+
'integrity="sha256-Yk6uAc7SoE41LSNc9zTBxij8YhVqBIIuRpLCaTyqrlQ= '
257+
'sha384-cwtz5c2CaEK8Q8ZeraWgf3qo7eO5jUDE8XMo00QTUCcbmF/fLuDtQFm8'
258+
'g4Jh9R5D sha512-s9uhbJTCZv4WfH/F81fgS6B6XNhOuH21Xouv5XPp35WlFR7'
259+
'ykkIafUG8cma4vbEfheH1NVbjsON5BHm8U13I4g==" '
260+
'crossorigin ></script>'
261+
), result.rendered_content)
262+
self.assertIn((
263+
'<link href="http://custom-static-host.com/main.css" '
264+
'rel="stylesheet" '
265+
'integrity="sha256-cYWwRvS04/VsttQYx4BalKYrBDuw5t8vKFhWB/LKX30= '
266+
'sha384-V/UxbrsEy8BK5nd+sBlN31Emmq/WdDDdI01UR8wKIFkIr6vEaT5YRaeL'
267+
'MfLcAQvS sha512-aigPxglXDA33t9s5i0vRap5b7dFwyp7cSN6x8rOXrPpCTMu'
268+
'bOR7qTFpmTIa8z9B0wtXxbSheBPNCEURBHKLQPw==" '
269+
'crossorigin />'),
270+
result.rendered_content
271+
)
272+
273+
def test_integrity_with_crosorigin_anonymous(self):
274+
self.compile_bundles('webpack.config.integrity.js')
275+
276+
loader = get_loader(DEFAULT_CONFIG)
277+
with patch.dict(loader.config, {'INTEGRITY': True, 'CROSSORIGIN': 'anonymous'}):
278+
view = TemplateView.as_view(template_name='single.html')
279+
request = self.factory.get('/')
280+
request.META['HTTP_HOST'] = 'crossorigen-custom-static-host.com'
281+
result = view(request)
282+
283+
self.assertIn((
284+
'<script src="http://custom-static-host.com/main.js" '
285+
'integrity="sha256-Yk6uAc7SoE41LSNc9zTBxij8YhVqBIIuRpLCaTyqrlQ= '
286+
'sha384-cwtz5c2CaEK8Q8ZeraWgf3qo7eO5jUDE8XMo00QTUCcbmF/fLuDtQFm8'
287+
'g4Jh9R5D sha512-s9uhbJTCZv4WfH/F81fgS6B6XNhOuH21Xouv5XPp35WlFR7'
288+
'ykkIafUG8cma4vbEfheH1NVbjsON5BHm8U13I4g==" '
289+
'crossorigin="anonymous" ></script>'
290+
), result.rendered_content)
291+
self.assertIn((
292+
'<link href="http://custom-static-host.com/main.css" '
293+
'rel="stylesheet" '
294+
'integrity="sha256-cYWwRvS04/VsttQYx4BalKYrBDuw5t8vKFhWB/LKX30= '
295+
'sha384-V/UxbrsEy8BK5nd+sBlN31Emmq/WdDDdI01UR8wKIFkIr6vEaT5YRaeL'
296+
'MfLcAQvS sha512-aigPxglXDA33t9s5i0vRap5b7dFwyp7cSN6x8rOXrPpCTMu'
297+
'bOR7qTFpmTIa8z9B0wtXxbSheBPNCEURBHKLQPw==" '
298+
'crossorigin="anonymous" />'),
299+
result.rendered_content
300+
)
301+
302+
def test_integrity_with_crosorigin_use_credentials(self):
303+
self.compile_bundles('webpack.config.integrity.js')
304+
305+
loader = get_loader(DEFAULT_CONFIG)
306+
with patch.dict(loader.config, {'INTEGRITY': True, 'CROSSORIGIN': 'use-credentials'}):
307+
view = TemplateView.as_view(template_name='single.html')
308+
request = self.factory.get('/')
309+
request.META['HTTP_HOST'] = 'crossorigen-custom-static-host.com'
310+
result = view(request)
311+
312+
self.assertIn((
313+
'<script src="http://custom-static-host.com/main.js" '
314+
'integrity="sha256-Yk6uAc7SoE41LSNc9zTBxij8YhVqBIIuRpLCaTyqrlQ= '
315+
'sha384-cwtz5c2CaEK8Q8ZeraWgf3qo7eO5jUDE8XMo00QTUCcbmF/fLuDtQFm8'
316+
'g4Jh9R5D sha512-s9uhbJTCZv4WfH/F81fgS6B6XNhOuH21Xouv5XPp35WlFR7'
317+
'ykkIafUG8cma4vbEfheH1NVbjsON5BHm8U13I4g==" '
318+
'crossorigin="use-credentials" ></script>'
319+
), result.rendered_content)
320+
self.assertIn((
321+
'<link href="http://custom-static-host.com/main.css" '
322+
'rel="stylesheet" '
323+
'integrity="sha256-cYWwRvS04/VsttQYx4BalKYrBDuw5t8vKFhWB/LKX30= '
324+
'sha384-V/UxbrsEy8BK5nd+sBlN31Emmq/WdDDdI01UR8wKIFkIr6vEaT5YRaeL'
325+
'MfLcAQvS sha512-aigPxglXDA33t9s5i0vRap5b7dFwyp7cSN6x8rOXrPpCTMu'
326+
'bOR7qTFpmTIa8z9B0wtXxbSheBPNCEURBHKLQPw==" '
327+
'crossorigin="use-credentials" />'),
328+
result.rendered_content
329+
)
330+
242331
def test_integrity_missing_config(self):
243332
self.compile_bundles('webpack.config.integrity.js')
244333

@@ -857,3 +946,90 @@ def test_get_as_tags_direct_usage(self):
857946
self.assertEqual(tags[0], asset_vendor)
858947
self.assertEqual(tags[1], asset_app1)
859948
self.assertEqual(tags[2], asset_app2)
949+
950+
def test_get_url_to_tag_dict_with_nonce(self):
951+
"""Test the get_as_url_to_tag_dict function with nonce attribute handling."""
952+
# Setup FakeWebpackLoader with CSP_NONCE enabled
953+
954+
with self.settings(
955+
WEBPACK_LOADER={
956+
"DEFAULT": {
957+
"CSP_NONCE": True,
958+
},
959+
}
960+
):
961+
from webpack_loader.utils import get_as_url_to_tag_dict, get_loader
962+
963+
self.compile_bundles('webpack.config.simple.js')
964+
965+
# Use default config but enable CSP_NONCE
966+
loader = get_loader(DEFAULT_CONFIG)
967+
original_config = loader.config.copy()
968+
try:
969+
# Test with CSP_NONCE enabled
970+
loader.config['CSP_NONCE'] = True
971+
972+
# Create a request with csp_nonce
973+
request = self.factory.get('/')
974+
request.csp_nonce = "test-nonce-123"
975+
976+
# Get tag dict with nonce enabled
977+
tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request)
978+
979+
# Verify nonce is in the tag
980+
self.assertIn('nonce="test-nonce-123"', tag_dict['/static/webpack_bundles/main.js'])
981+
982+
# Test with existing nonce in attrs - should not duplicate
983+
tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='nonce="existing-nonce"', request=request)
984+
self.assertIn('nonce="existing-nonce"', tag_dict['/static/webpack_bundles/main.js'])
985+
self.assertNotIn('nonce="test-nonce-123"', tag_dict['/static/webpack_bundles/main.js'])
986+
987+
# Test without request - should not have nonce and should emit warning
988+
tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=None)
989+
self.assertNotIn('nonce=', tag_dict['/static/webpack_bundles/main.js'])
990+
991+
# Test with request but no csp_nonce attribute - should not have nonce and should emit warning
992+
request_without_nonce = self.factory.get('/')
993+
tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request_without_nonce)
994+
self.assertNotIn('nonce=', tag_dict['/static/webpack_bundles/main.js'])
995+
996+
# Test with CSP_NONCE disabled - should not have nonce
997+
loader.config['CSP_NONCE'] = False
998+
tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request)
999+
self.assertNotIn('nonce=', tag_dict['/static/webpack_bundles/main.js'])
1000+
1001+
finally:
1002+
# Restore original config
1003+
loader.config = original_config
1004+
1005+
def test_get_url_to_tag_dict_with_different_extensions(self):
1006+
"""Test the get_as_url_to_tag_dict function with different file extensions."""
1007+
1008+
1009+
with self.settings(
1010+
WEBPACK_LOADER={
1011+
"DEFAULT": {
1012+
"CSP_NONCE": True,
1013+
},
1014+
}
1015+
):
1016+
from webpack_loader.utils import get_as_url_to_tag_dict
1017+
self.compile_bundles('webpack.config.simple.js')
1018+
1019+
# Create a request with csp_nonce
1020+
request = self.factory.get('/')
1021+
request.csp_nonce = "test-nonce-123"
1022+
1023+
# Test with different extensions
1024+
1025+
# JavaScript file
1026+
tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request)
1027+
self.assertIn('<script src="/static/webpack_bundles/main.js"',
1028+
tag_dict['/static/webpack_bundles/main.js'])
1029+
self.assertIn('nonce="test-nonce-123"', tag_dict['/static/webpack_bundles/main.js'])
1030+
1031+
# CSS file
1032+
tag_dict = get_as_url_to_tag_dict('main', extension='css', attrs='', request=request)
1033+
self.assertIn('<link href="/static/webpack_bundles/main.css" rel="stylesheet"',
1034+
tag_dict['/static/webpack_bundles/main.css'])
1035+
self.assertIn('nonce="test-nonce-123"', tag_dict['/static/webpack_bundles/main.css'])

tests/webpack.config.integrity.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module.exports = {
1313
context: __dirname,
1414
entry: './assets/js/index',
1515
output: {
16+
publicPath: 'http://custom-static-host.com/',
1617
path: path.resolve('./assets/django_webpack_loader_bundles/'),
1718
filename: "[name].js"
1819
},

webpack_loader/config.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,16 @@
2828
}
2929
}
3030

31-
user_config = getattr(settings, 'WEBPACK_LOADER', DEFAULT_CONFIG)
3231

33-
user_config = dict(
34-
(name, dict(DEFAULT_CONFIG['DEFAULT'], **cfg))
35-
for name, cfg in user_config.items()
36-
)
3732

38-
for entry in user_config.values():
39-
entry['ignores'] = [re.compile(I) for I in entry['IGNORE']]
33+
def load_config(name):
34+
user_config = getattr(settings, 'WEBPACK_LOADER', DEFAULT_CONFIG)
4035

36+
user_config = dict(
37+
(name, dict(DEFAULT_CONFIG['DEFAULT'], **cfg))
38+
for name, cfg in user_config.items()
39+
)
4140

42-
def load_config(name):
41+
for entry in user_config.values():
42+
entry['ignores'] = [re.compile(I) for I in entry['IGNORE']]
4343
return user_config[name]

webpack_loader/loaders.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
from django.http.request import HttpRequest
1313

1414
from .exceptions import (
15+
WebpackBundleLookupError,
1516
WebpackError,
1617
WebpackLoaderBadStatsError,
1718
WebpackLoaderTimeoutError,
18-
WebpackBundleLookupError,
1919
)
2020

2121
_CROSSORIGIN_NO_REQUEST = (

0 commit comments

Comments
 (0)