cherrypy-foundation 1.0.0__py3-none-any.whl → 1.0.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. cherrypy_foundation/components/ColorModes.jinja +4 -5
  2. cherrypy_foundation/components/Datatable.jinja +2 -2
  3. cherrypy_foundation/components/Datatable.js +2 -2
  4. cherrypy_foundation/components/Field.jinja +6 -16
  5. cherrypy_foundation/components/Fields.jinja +2 -0
  6. cherrypy_foundation/components/Typeahead.css +1 -6
  7. cherrypy_foundation/components/Typeahead.jinja +2 -2
  8. cherrypy_foundation/components/__init__.py +2 -2
  9. cherrypy_foundation/components/tests/test_static.py +1 -1
  10. cherrypy_foundation/error_page.py +17 -20
  11. cherrypy_foundation/flash.py +15 -17
  12. cherrypy_foundation/form.py +2 -2
  13. cherrypy_foundation/logging.py +2 -2
  14. cherrypy_foundation/passwd.py +2 -2
  15. cherrypy_foundation/plugins/db.py +9 -35
  16. cherrypy_foundation/plugins/ldap.py +38 -46
  17. cherrypy_foundation/plugins/restapi.py +1 -1
  18. cherrypy_foundation/plugins/scheduler.py +84 -208
  19. cherrypy_foundation/plugins/smtp.py +46 -78
  20. cherrypy_foundation/plugins/tests/test_db.py +4 -4
  21. cherrypy_foundation/plugins/tests/test_ldap.py +3 -76
  22. cherrypy_foundation/plugins/tests/test_scheduler.py +50 -58
  23. cherrypy_foundation/plugins/tests/test_smtp.py +7 -40
  24. cherrypy_foundation/tests/__init__.py +0 -72
  25. cherrypy_foundation/tests/test_error_page.py +1 -7
  26. cherrypy_foundation/tests/test_passwd.py +2 -2
  27. cherrypy_foundation/tools/auth.py +38 -59
  28. cherrypy_foundation/tools/auth_mfa.py +88 -89
  29. cherrypy_foundation/tools/errors.py +27 -0
  30. cherrypy_foundation/tools/i18n.py +153 -246
  31. cherrypy_foundation/tools/jinja2.py +13 -29
  32. cherrypy_foundation/tools/ratelimit.py +27 -37
  33. cherrypy_foundation/tools/secure_headers.py +5 -1
  34. cherrypy_foundation/tools/sessions_timeout.py +21 -23
  35. cherrypy_foundation/tools/tests/locales/en/LC_MESSAGES/messages.mo +0 -0
  36. cherrypy_foundation/tools/tests/locales/{de → en}/LC_MESSAGES/messages.po +2 -2
  37. cherrypy_foundation/tools/tests/test_auth.py +4 -21
  38. cherrypy_foundation/tools/tests/test_i18n.py +6 -81
  39. cherrypy_foundation/tools/tests/test_ratelimit.py +2 -2
  40. cherrypy_foundation/url.py +25 -25
  41. cherrypy_foundation/widgets.py +2 -2
  42. cherrypy_foundation-1.0.0a1.dist-info/METADATA +42 -0
  43. {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/RECORD +46 -65
  44. {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/WHEEL +1 -1
  45. cherrypy_foundation/components/Flash.jinja +0 -13
  46. cherrypy_foundation/components/LocaleSelection.jinja +0 -13
  47. cherrypy_foundation/components/LocaleSelection.js +0 -26
  48. cherrypy_foundation/plugins/tests/test_scheduler_db.py +0 -107
  49. cherrypy_foundation/sessions.py +0 -93
  50. cherrypy_foundation/tests/templates/test_flash.html +0 -9
  51. cherrypy_foundation/tests/templates/test_form.html +0 -16
  52. cherrypy_foundation/tests/templates/test_url.html +0 -15
  53. cherrypy_foundation/tests/test_flash.py +0 -61
  54. cherrypy_foundation/tests/test_form.py +0 -148
  55. cherrypy_foundation/tests/test_logging.py +0 -78
  56. cherrypy_foundation/tests/test_sessions.py +0 -89
  57. cherrypy_foundation/tests/test_url.py +0 -161
  58. cherrypy_foundation/tools/tests/components/Button.jinja +0 -2
  59. cherrypy_foundation/tools/tests/locales/de/LC_MESSAGES/messages.mo +0 -0
  60. cherrypy_foundation/tools/tests/templates/test_jinja2.html +0 -11
  61. cherrypy_foundation/tools/tests/templates/test_jinjax.html +0 -9
  62. cherrypy_foundation/tools/tests/templates/test_jinjax_i18n.html +0 -22
  63. cherrypy_foundation/tools/tests/test_auth_mfa.py +0 -369
  64. cherrypy_foundation/tools/tests/test_jinja2.py +0 -153
  65. cherrypy_foundation/tools/tests/test_secure_headers.py +0 -200
  66. cherrypy_foundation-1.0.0.dist-info/METADATA +0 -71
  67. {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/licenses/LICENSE.md +0 -0
  68. {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/top_level.txt +0 -0
@@ -1,369 +0,0 @@
1
- # CherryPy
2
- # Copyright (C) 2026 IKUS Software
3
- #
4
- # This program is free software: you can redistribute it and/or modify
5
- # it under the terms of the GNU General Public License as published by
6
- # the Free Software Foundation, either version 3 of the License, or
7
- # (at your option) any later version.
8
- #
9
- # This program is distributed in the hope that it will be useful,
10
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- # GNU General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU General Public License
15
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
-
17
- import datetime
18
- from collections import namedtuple
19
- from urllib.parse import urlencode
20
-
21
- import cherrypy
22
- from cherrypy.lib.sessions import RamSession
23
- from cherrypy.test import helper
24
-
25
- from cherrypy_foundation.sessions import session_lock
26
-
27
- from ..auth import AUTH_LAST_PASSWORD_AT
28
- from ..auth_mfa import (
29
- MFA_CODE_TIME,
30
- MFA_DEFAULT_CODE_TIMEOUT,
31
- MFA_DEFAULT_TRUST_DURATION,
32
- MFA_TRUSTED_IP_LIST,
33
- MFA_USER_KEY,
34
- MFA_VERIFICATION_TIME,
35
- )
36
- from ..sessions_timeout import SESSION_PERSISTENT
37
-
38
- User = namedtuple('User', 'id,username,password,email,mfa', defaults=[False])
39
-
40
- users = {
41
- User(2, 'myuser', 'changeme', 'myuser@example.com', False),
42
- User(3, 'mfauser', 'changeme', 'mfauser@example.com', True),
43
- User(4, 'noemail', 'changeme', '', True),
44
- }
45
-
46
-
47
- def checkpassword(username, password):
48
- for u in users:
49
- if u.username == username and u.password == password:
50
- return True
51
- return False
52
-
53
-
54
- def user_lookup_func(login, user_info):
55
- for u in users:
56
- if u.username == login:
57
- return u.id, u
58
- return None
59
-
60
-
61
- def user_from_key_func(userkey):
62
- for u in users:
63
- if u.id == userkey:
64
- return u
65
- return None
66
-
67
-
68
- @cherrypy.tools.sessions(locking='explicit')
69
- @cherrypy.tools.auth(
70
- user_lookup_func=user_lookup_func,
71
- user_from_key_func=user_from_key_func,
72
- checkpassword=checkpassword,
73
- )
74
- @cherrypy.tools.auth_mfa(
75
- mfa_enabled=lambda: hasattr(cherrypy.serving.request, 'currentuser') and cherrypy.request.currentuser.mfa
76
- )
77
- class Root:
78
-
79
- @cherrypy.expose
80
- def index(self):
81
- return "OK"
82
-
83
- @cherrypy.expose()
84
- def login(self, username=None, password=None):
85
- if cherrypy.serving.request.method == 'POST' and username and password:
86
- userobj = cherrypy.tools.auth.login_with_credentials(username, password)
87
- if userobj:
88
- raise cherrypy.tools.auth.redirect_to_original_url()
89
- else:
90
- return "invalid credentials"
91
- return "login"
92
-
93
- @cherrypy.expose()
94
- def mfa(self, code=None, resend_code=None, persistent=False):
95
- html = "mfa\n"
96
- if cherrypy.serving.request.method == 'POST' and code:
97
- if cherrypy.tools.auth_mfa.verify_code(code=code, persistent=persistent):
98
- raise cherrypy.tools.auth.redirect_to_original_url()
99
- else:
100
- html += "<p>invalid verification code</p>\n"
101
- # Send verification code if previous code expired.
102
- # Or when requested by user.
103
- if (resend_code and cherrypy.serving.request.method == 'POST') or cherrypy.tools.auth_mfa.is_code_expired():
104
- code = cherrypy.tools.auth_mfa.generate_code()
105
- # Here the code should be send by email, SMS or any other means.
106
- # For our test we store it in the session.
107
- with session_lock() as session:
108
- session['code'] = code
109
- html += "<p>new verification code sent to your email</p>\n"
110
- return html
111
-
112
-
113
- class AuthManagerMfaTest(helper.CPWebCase):
114
- interactive = False
115
- # Authenticated by default.
116
- login = True
117
-
118
- @classmethod
119
- def setup_server(cls):
120
- cherrypy.tree.mount(Root(), '/')
121
-
122
- def getPage(self, *args, **kwargs):
123
- """
124
- This implementation keep track of session cookies.
125
- """
126
- headers = kwargs.pop('headers', [])
127
- if hasattr(self, 'cookies') and self.cookies:
128
- headers.extend(self.cookies)
129
- return helper.CPWebCase.getPage(self, *args, headers=headers, **kwargs)
130
-
131
- def _login(self, username, password):
132
- self.getPage('/login/', method='POST', body=urlencode({'username': username, 'password': password}))
133
- self.assertStatus(303)
134
- self.assertHeaderItemValue('Location', f'http://{self.HOST}:{self.PORT}/')
135
-
136
- @property
137
- def _session_id(self):
138
- if hasattr(self, 'cookies') and self.cookies:
139
- for unused, value in self.cookies:
140
- for part in value.split(';'):
141
- key, unused, value = part.partition('=')
142
- if key == 'session_id':
143
- return value
144
-
145
- def _get_code(self):
146
- # Query MFA page to generate a code
147
- self.getPage("/mfa/")
148
- self.assertStatus(200)
149
- self.assertInBody("new verification code sent to your email")
150
- # Extract code from user session for testing only.
151
- session = RamSession.cache[self._session_id][0]
152
- return session['code']
153
-
154
- def test_get_without_login(self):
155
- # Given the user is not authenticated.
156
- # When requesting /mfa/
157
- self.getPage("/mfa/")
158
- # Then user is redirected to /login/
159
- self.assertStatus(303)
160
- self.assertHeaderItemValue('Location', f'http://{self.HOST}:{self.PORT}/login/')
161
-
162
- def test_get_with_mfa_disabled(self):
163
- # Given an authenticated user with MFA Disable
164
- self._login('myuser', 'changeme')
165
- # When requesting /mfa/ page
166
- self.getPage("/mfa/")
167
- # Then user is redirected to root page
168
- self.assertStatus(303)
169
- self.assertHeaderItemValue('Location', f'http://{self.HOST}:{self.PORT}/')
170
- # Then index is enabled.
171
- self.getPage("/")
172
- self.assertStatus(200)
173
- self.assertInBody('OK')
174
-
175
- def test_get_with_trusted(self):
176
- # Given an authenticated user with MFA Disable
177
- self._login('mfauser', 'changeme')
178
- # Given an authenticated user with MFA enabled and already verified
179
- session = RamSession.cache[self._session_id][0]
180
- session[MFA_USER_KEY] = 3
181
- session[MFA_VERIFICATION_TIME] = datetime.datetime.now()
182
- session[MFA_TRUSTED_IP_LIST] = ['127.0.0.1']
183
-
184
- # When requesting /mfa/ page when we are already trusted
185
- self.getPage("/mfa/")
186
- # Then user is redirected to root page
187
- self.assertStatus(303)
188
- self.assertHeaderItemValue('Location', f'http://{self.HOST}:{self.PORT}/')
189
-
190
- def test_get_with_trusted_expired(self):
191
- # Given an authenticated user with MFA enabled and already verified
192
- self._login('mfauser', 'changeme')
193
- session = RamSession.cache[self._session_id][0]
194
- session[MFA_USER_KEY] = 3
195
- session[MFA_VERIFICATION_TIME] = datetime.datetime.now() - datetime.timedelta(minutes=60)
196
-
197
- # When requesting /mfa/ page
198
- self.getPage("/mfa/")
199
- self.assertStatus(200)
200
- # Then a verification code is send to the user
201
- self.assertInBody("new verification code sent to your email")
202
-
203
- def test_get_with_trusted_different_ip(self):
204
- # Given an authenticated user with MFA enabled and already verified
205
- self._login('mfauser', 'changeme')
206
- session = RamSession.cache[self._session_id][0]
207
- session[MFA_USER_KEY] = 3
208
- session[MFA_VERIFICATION_TIME] = datetime.datetime.now()
209
-
210
- # When requesting /mfa/ page from a different ip
211
- self.getPage("/mfa/", headers=[('X-Forwarded-For', '10.255.14.23')])
212
- self.assertStatus(200)
213
- # Then a verification code is send to the user
214
- self.assertInBody("new verification code sent to your email")
215
-
216
- def test_get_without_verified(self):
217
- # Given an authenticated user With MFA enabled
218
- self._login('mfauser', 'changeme')
219
- # When requesting /mfa/ page
220
- self.getPage("/mfa/")
221
- self.assertStatus(200)
222
- # Then a verification code is send to the user
223
- self.assertInBody("new verification code sent to your email")
224
-
225
- def test_verify_code_valid(self):
226
- prev_session_id = self._session_id
227
- # Given an authenticated user With MFA enabled
228
- self._login('mfauser', 'changeme')
229
- code = self._get_code()
230
- # When sending a valid verification code
231
- self.getPage("/mfa/", method='POST', body=urlencode({'code': code}))
232
- # Then a new session_id is generated
233
- self.assertNotEqual(prev_session_id, self._session_id)
234
- # Then user is redirected to root page
235
- self.assertStatus(303)
236
- self.assertHeaderItemValue('Location', f'http://{self.HOST}:{self.PORT}/')
237
- # Then user has access
238
- self.getPage("/")
239
- self.assertStatus(200)
240
-
241
- def test_verify_code_invalid(self):
242
- # Given an authenticated user With MFA enabled
243
- # When sending an invalid verification code
244
- self.getPage("/mfa/", method='POST', body=urlencode({'code': '1234567'}))
245
- # Then user is redirected to login page
246
- self.assertStatus(303)
247
- self.assertHeaderItemValue('Location', f'http://{self.HOST}:{self.PORT}/login/')
248
-
249
- def test_verify_code_expired(self):
250
- # Given an authenticated user With MFA enabled
251
- self._login('mfauser', 'changeme')
252
- code = self._get_code()
253
- # When sending a valid verification code that expired
254
- session = RamSession.cache[self._session_id][0]
255
-
256
- session[MFA_CODE_TIME] = datetime.datetime.now() - datetime.timedelta(minutes=MFA_DEFAULT_CODE_TIMEOUT + 1)
257
-
258
- self.getPage("/mfa/", method='POST', body=urlencode({'code': code}))
259
- # Then a new code get generated.
260
- self.assertStatus(200)
261
- self.assertInBody("invalid verification code")
262
-
263
- def test_verify_code_invalid_after_3_tentative(self):
264
- # Given an authenticated user With MFA
265
- self._login('mfauser', 'changeme')
266
- code = self._get_code()
267
- # When user enter an invalid verification code 3 times
268
- self.getPage("/mfa/", method='POST', body=urlencode({'code': '1234567'}))
269
- self.assertStatus(200)
270
- self.getPage("/mfa/", method='POST', body=urlencode({'code': '1234567'}))
271
- self.assertStatus(200)
272
- self.getPage("/mfa/", method='POST', body=urlencode({'code': '1234567'}))
273
- # Then an error get displayed to the user
274
- self.assertStatus(200)
275
- self.assertInBody("invalid verification code")
276
- # Then a new code get send to the user.
277
- self.assertInBody("new verification code sent to your email")
278
- session = RamSession.cache[self._session_id][0]
279
- new_code = session['code']
280
- self.assertNotEqual(code, new_code)
281
-
282
- def test_resend_code(self):
283
- # Given an authenticated user With MFA enabled with an existing code
284
- self._login('mfauser', 'changeme')
285
- code = self._get_code()
286
- # When user request a new code
287
- self.getPage("/mfa/", method='POST', body=urlencode({'resend_code': '1'}))
288
- # Then A success message is displayedto the user.
289
- self.assertInBody("new verification code sent to your email")
290
- session = RamSession.cache[self._session_id][0]
291
- new_code = session['code']
292
- self.assertNotEqual(code, new_code)
293
-
294
- def test_redirect_to_original_url(self):
295
- # Given an authenticated user
296
- self._login('mfauser', 'changeme')
297
- # When querying a page that required mfa
298
- self.getPage('/prefs/general')
299
- # Then user is redirected to mfa page
300
- self.assertStatus(303)
301
- self.assertHeaderItemValue('Location', f'http://{self.HOST}:{self.PORT}/mfa/')
302
- # When providing verification code
303
- code = self._get_code()
304
- self.getPage("/mfa/", method='POST', body=urlencode({'code': code}))
305
- # Then user is redirected to original url
306
- self.assertStatus(303)
307
- self.assertHeaderItemValue('Location', f'http://{self.HOST}:{self.PORT}/prefs/general')
308
-
309
- def test_login_persistent_when_login_timeout(self):
310
- prev_session_id = self._session_id
311
- # Given a user authenticated with MFA with "persistent"
312
- self._login('mfauser', 'changeme')
313
- code = self._get_code()
314
- self.getPage("/mfa/", method='POST', body=urlencode({'code': code, 'persistent': '1'}))
315
- self.assertStatus(303)
316
- self.getPage("/")
317
- self.assertStatus(200)
318
- self.assertNotEqual(prev_session_id, self._session_id)
319
- session = RamSession.cache[self._session_id][0]
320
- self.assertTrue(session[SESSION_PERSISTENT])
321
- # When the re-auth time expired (after 15 min)
322
- session[AUTH_LAST_PASSWORD_AT] = datetime.datetime.now() - datetime.timedelta(minutes=60, seconds=1)
323
-
324
- # Then next query redirect user to /login/ page (by mfa)
325
- self.getPage("/")
326
- self.assertStatus(303)
327
- self.assertHeaderItemValue('Location', f'http://{self.HOST}:{self.PORT}/login/')
328
- prev_session_id = self._session_id
329
- # When user enter valid username password
330
- self.getPage("/login/", method='POST', body=urlencode({'username': 'mfauser', 'password': 'changeme'}))
331
- # Then user is redirected to original url without need to pass MFA again.
332
- self.assertStatus(303)
333
- self.assertHeaderItemValue('Location', f'http://{self.HOST}:{self.PORT}/')
334
- self.assertNotEqual(prev_session_id, self._session_id)
335
- self.getPage("/")
336
- self.assertStatus(200)
337
- self.assertInBody('OK')
338
-
339
- def test_login_persistent_when_mfa_timeout(self):
340
- prev_session_id = self._session_id
341
- # Given a user authenticated with MFA with "persistent"
342
- self._login('mfauser', 'changeme')
343
- code = self._get_code()
344
- self.getPage("/mfa/", method='POST', body=urlencode({'code': code, 'persistent': '1'}))
345
- self.assertStatus(303)
346
- self.getPage("/")
347
- self.assertStatus(200)
348
- self.assertNotEqual(prev_session_id, self._session_id)
349
- session = RamSession.cache[self._session_id][0]
350
-
351
- self.assertTrue(session[SESSION_PERSISTENT])
352
- # When the mfa verification timeout (after 15 min)
353
- session[MFA_VERIFICATION_TIME] = datetime.datetime.now() - datetime.timedelta(
354
- minutes=MFA_DEFAULT_TRUST_DURATION, seconds=1
355
- )
356
-
357
- # Then next query redirect user to mfa page
358
- self.getPage("/prefs/general")
359
- self.assertStatus(303)
360
- self.assertHeaderItemValue('Location', f'http://{self.HOST}:{self.PORT}/mfa/')
361
- # When user enter valid code
362
- code = self._get_code()
363
- self.getPage("/mfa/", method='POST', body=urlencode({'code': code, 'persistent': '1'}))
364
- # Then user is redirected to original page.
365
- self.assertStatus(303)
366
- self.assertHeaderItemValue('Location', f'http://{self.HOST}:{self.PORT}/prefs/general')
367
- self.getPage("/")
368
- self.assertStatus(200)
369
- self.assertInBody('OK')
@@ -1,153 +0,0 @@
1
- # Cherrypy-foundation
2
- # Copyright (C) 2026 IKUS Software
3
- #
4
- # This program is free software: you can redistribute it and/or modify
5
- # it under the terms of the GNU General Public License as published by
6
- # the Free Software Foundation, either version 3 of the License, or
7
- # (at your option) any later version.
8
- #
9
- # This program is distributed in the hope that it will be useful,
10
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- # GNU General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU General Public License
15
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
- import importlib
17
- from datetime import datetime, timezone
18
- from unittest import skipUnless
19
-
20
- import cherrypy
21
- from cherrypy.test import helper
22
- from parameterized import parameterized
23
-
24
- from cherrypy_foundation.components import StaticMiddleware
25
- from cherrypy_foundation.tests import SeleniumUnitTest
26
- from cherrypy_foundation.url import url_for
27
-
28
- from .. import i18n # noqa
29
- from .. import jinja2 # noqa
30
-
31
- HAS_JINJAX = importlib.util.find_spec("jinjax") is not None
32
-
33
- env = cherrypy.tools.jinja2.create_env(
34
- package_name=__package__,
35
- globals={
36
- 'const1': 'STATIC VALUE',
37
- },
38
- )
39
-
40
-
41
- def extra_processor():
42
- return {'var2': 'bar'}
43
-
44
-
45
- @cherrypy.tools.i18n(on=False)
46
- @cherrypy.tools.sessions(on=False)
47
- class Static:
48
-
49
- components = StaticMiddleware()
50
-
51
-
52
- @cherrypy.tools.jinja2(on=False, env=env, extra_processor=extra_processor)
53
- class Root:
54
-
55
- static = Static()
56
-
57
- @cherrypy.expose
58
- @cherrypy.tools.jinja2(template='test_jinja2.html')
59
- def index(self):
60
- return {'var1': 'test-jinja2'}
61
-
62
- @cherrypy.expose
63
- @cherrypy.tools.jinja2(template='test_jinjax.html')
64
- def jinjax(self):
65
- return {'var1': 'test-jinjax'}
66
-
67
- @cherrypy.expose
68
- @cherrypy.tools.jinja2(template='test_jinjax_i18n.html')
69
- @cherrypy.tools.i18n(
70
- default='fr',
71
- default_timezone='America/Toronto',
72
- mo_dir=importlib.resources.files(__package__) / 'locales',
73
- domain='messages',
74
- cookie_name='locale', # For LocaleSelection
75
- )
76
- def localized(self):
77
- return {
78
- 'my_datetime': datetime(year=2025, month=11, day=26, hour=11, minute=16, tzinfo=timezone.utc),
79
- 'my_date': datetime(year=2025, month=12, day=22, hour=14, minute=8, tzinfo=timezone.utc),
80
- }
81
-
82
-
83
- class Jinja2Test(helper.CPWebCase, SeleniumUnitTest):
84
- default_lang = None
85
- interactive = False
86
-
87
- @classmethod
88
- def setup_server(cls):
89
- cherrypy.tree.mount(Root(), '/')
90
-
91
- def test_get_page(self):
92
- # Given a page render using jinja2
93
- # When querying the page
94
- self.getPage("/")
95
- # Then the page return without error
96
- self.assertStatus(200)
97
- # Then the page is render dynamically using page context
98
- self.assertInBody('test-jinja2')
99
- self.assertInBody('bar')
100
- self.assertInBody('STATIC VALUE')
101
-
102
- @skipUnless(HAS_JINJAX, reason='Required jinjax')
103
- def test_get_page_jinjax(self):
104
- # Given a page render using jinjax
105
- # When querying the page
106
- self.getPage("/jinjax")
107
- # Then the page return without error
108
- self.assertStatus(200)
109
- # Then the page is render dynamically using page context
110
- self.assertInBody('<a class="btn btn-primary" href="http://example.com">foo</a>')
111
-
112
- @skipUnless(HAS_JINJAX, reason='Required jinjax')
113
- @parameterized.expand(
114
- [
115
- ('server_default', {}, 'fr'),
116
- ('accept_lang_fr', {'Accept-Language': 'fr'}, 'fr'),
117
- ('accept_lang_en', {'Accept-Language': 'en'}, 'en'),
118
- ]
119
- )
120
- def test_get_page_i18n(self, _name, headers, expected_lang):
121
- # Given a localized page render with jinja2
122
- # When querying the page
123
- self.getPage("/localized", headers=list(headers.items()))
124
- # Then the page return without error
125
- self.assertStatus(200)
126
- # Then the page is render dynamically using page context
127
- if expected_lang == 'fr':
128
- self.assertInBody('lang="fr"')
129
- self.assertInBody('français')
130
- self.assertInBody('Du texte à traduire')
131
- self.assertInBody('mercredi 26 novembre 2025, 06:16:00 heure normale de l’Est nord-américain')
132
- self.assertInBody('lundi, décembre 22, 2025')
133
- else:
134
- self.assertInBody('lang="en"')
135
- self.assertInBody('English')
136
- self.assertInBody('Some text to translate')
137
- self.assertInBody('Wednesday, November 26, 2025, 6:16:00\u202fAM Eastern Standard Time')
138
- self.assertInBody('Monday, December 22, 2025')
139
-
140
- @skipUnless(HAS_JINJAX, reason='Required jinjax')
141
- def test_get_page_i18n_selenium(self):
142
- # Given a localized page render with jinja2
143
- with self.selenium() as driver:
144
- # When querying the page
145
- driver.get(url_for('localized'))
146
- # Then page load without error in english (enforced chronium lang)
147
- self.assertFalse(driver.get_log('browser'))
148
- self.assertEqual('en_US', driver.find_element('css selector', 'html').get_attribute('lang'))
149
- # When user select a language
150
- btn = driver.find_element('css selector', 'button[data-locale=fr]')
151
- btn.click()
152
- # Then page is reloaded with in French.
153
- self.assertEqual('fr', driver.find_element('css selector', 'html').get_attribute('lang'))