Mercurial > p > roundup > code
comparison test/test_cgi.py @ 7836:219fc5804345
issue2551270 - Better templating support for JavaScript
Add (templating) utils.readfile(file, optional=False) and
utils.expandfile(file, token_dict=None, optional=False). Allows
reading an external file (e.g. JavaScript) and inserting it using
tal:contents or equivalent jinja function. expandfile allows setting
a dictionary and tokens in the file of the form "%(token_name)s"
will be replaced in the file with the values from the dict.
See method doc blocks or reference.txt for more info.
Also reordered table in references.txt to be case sensitive
alphabetic. Added a paragraph on using python's help() to get
method/function/... documention blocks.
in templating.py _find method. Added explicit return None calls to all
code paths. Also added internationalization method to the
TemplatingUtils class. Fixed use of 'property' hiding python builtin
of same name.
Added tests for new TemplatingUtils framework to use for testing existing
utils.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Tue, 26 Mar 2024 14:15:46 -0400 |
| parents | 978285986b2c |
| children | e90be54708e9 |
comparison
equal
deleted
inserted
replaced
| 7835:c6fcc8ba478a | 7836:219fc5804345 |
|---|---|
| 18 from roundup.cgi import client, actions, exceptions | 18 from roundup.cgi import client, actions, exceptions |
| 19 from roundup.cgi.exceptions import FormError, NotFound, Redirect | 19 from roundup.cgi.exceptions import FormError, NotFound, Redirect |
| 20 from roundup.exceptions import UsageError, Reject | 20 from roundup.exceptions import UsageError, Reject |
| 21 from roundup.cgi.templating import HTMLItem, HTMLRequest, NoTemplate | 21 from roundup.cgi.templating import HTMLItem, HTMLRequest, NoTemplate |
| 22 from roundup.cgi.templating import HTMLProperty, _HTMLItem, anti_csrf_nonce | 22 from roundup.cgi.templating import HTMLProperty, _HTMLItem, anti_csrf_nonce |
| 23 from roundup.cgi.templating import TemplatingUtils | |
| 23 from roundup.cgi.form_parser import FormParser | 24 from roundup.cgi.form_parser import FormParser |
| 24 from roundup import init, instance, password, hyperdb, date | 25 from roundup import init, instance, password, hyperdb, date |
| 25 from roundup.anypy.strings import u2s, b2s, s2b | 26 from roundup.anypy.strings import u2s, b2s, s2b |
| 26 from roundup.test.tx_Source_detector import init as tx_Source_init | 27 from roundup.test.tx_Source_detector import init as tx_Source_init |
| 27 | 28 |
| 2993 | 2994 |
| 2994 # template check works | 2995 # template check works |
| 2995 r = t.selectTemplate("user", "subdir/item") | 2996 r = t.selectTemplate("user", "subdir/item") |
| 2996 self.assertEqual("subdir/user.item", r) | 2997 self.assertEqual("subdir/user.item", r) |
| 2997 | 2998 |
| 2999 class TemplateUtilsTestCase(unittest.TestCase): | |
| 3000 ''' Test various TemplateUtils | |
| 3001 ''' | |
| 3002 def setUp(self): | |
| 3003 self.dirname = '_test_template' | |
| 3004 # set up and open a tracker | |
| 3005 self.instance = setupTracker(self.dirname) | |
| 3006 | |
| 3007 # open the database | |
| 3008 self.db = self.instance.open('admin') | |
| 3009 self.db.tx_Source = "web" | |
| 3010 self.db.user.create(username='Chef', address='chef@bork.bork.bork', | |
| 3011 realname='Bork, Chef', roles='User') | |
| 3012 self.db.user.create(username='mary', address='mary@test.test', | |
| 3013 roles='User', realname='Contrary, Mary') | |
| 3014 self.db.post_init() | |
| 3015 | |
| 3016 def tearDown(self): | |
| 3017 self.db.close() | |
| 3018 try: | |
| 3019 shutil.rmtree(self.dirname) | |
| 3020 except OSError as error: | |
| 3021 if error.errno not in (errno.ENOENT, errno.ESRCH): raise | |
| 3022 | |
| 3023 @pytest.fixture(autouse=True) | |
| 3024 def inject_fixtures(self, caplog): | |
| 3025 self._caplog = caplog | |
| 3026 | |
| 3027 def testReadfile(self): | |
| 3028 # create new files in html dir | |
| 3029 testfiles = [ | |
| 3030 { "name": "file_to_read.js", | |
| 3031 "content": ('hello world'), | |
| 3032 }, | |
| 3033 { # for future test expanding TAL | |
| 3034 "name": "_generic.readfile_success.html", | |
| 3035 "content": ( | |
| 3036 | |
| 3037 '''<span tal:content="python:utils.readfile(''' | |
| 3038 """'example.js')"></span>""" ), | |
| 3039 }, | |
| 3040 ] | |
| 3041 | |
| 3042 for file_spec in testfiles: | |
| 3043 file_path = "%s/html/%s" % (self.dirname, file_spec['name']) | |
| 3044 with open(file_path, "w") as f: | |
| 3045 f.write(file_spec['content']) | |
| 3046 | |
| 3047 # get the client instance The form is needed to initialize, | |
| 3048 # but not used since I call selectTemplate directly. | |
| 3049 t = client.Client(self.instance, "user", | |
| 3050 {'PATH_INFO':'/user', 'REQUEST_METHOD':'POST'}, | |
| 3051 form=db_test_base.makeForm({"@template": "readfile_success"})) | |
| 3052 | |
| 3053 tu = TemplatingUtils(t) | |
| 3054 # testcase 1 - file exists | |
| 3055 r = tu.readfile("file_to_read.js") | |
| 3056 self.assertEqual(r, 'hello world') | |
| 3057 r = None | |
| 3058 | |
| 3059 # testcase 2 - file does not exist | |
| 3060 with self.assertRaises(NoTemplate) as e: | |
| 3061 r = tu.readfile("no_file_to_read.js") | |
| 3062 | |
| 3063 self.assertEqual( | |
| 3064 e.exception.args[0], | |
| 3065 "Unable to read or expand file 'no_file_to_read.js' " | |
| 3066 "in template 'home'.") | |
| 3067 r = None | |
| 3068 | |
| 3069 # testcase 3 - file does not exist - optional = True | |
| 3070 r = tu.readfile("no_file_to_read.js", optional=True) | |
| 3071 self.assertEqual(r, '') | |
| 3072 | |
| 3073 # make sure a created template is found | |
| 3074 # note that the extension is not included just the basename | |
| 3075 self.assertEqual("_generic.readfile_success", | |
| 3076 t.selectTemplate("", "readfile_success")) | |
| 3077 | |
| 3078 | |
| 3079 | |
| 3080 def testExpandfile(self): | |
| 3081 # test for templates in subdirectories | |
| 3082 | |
| 3083 # make the directory | |
| 3084 subdir = self.dirname + "/html/subdir" | |
| 3085 os.mkdir(subdir) | |
| 3086 | |
| 3087 # create new files in html dir | |
| 3088 testfiles = [ | |
| 3089 { "name": "file_to_read.js", | |
| 3090 "content": ('hello world'), | |
| 3091 }, | |
| 3092 { "name": "file_no_content.js", | |
| 3093 "content": '', | |
| 3094 }, | |
| 3095 { "name": "file_to_expand.js", | |
| 3096 "content": ('hello world %(base)s'), | |
| 3097 }, | |
| 3098 { "name": "file_with_broken_expand_type.js", | |
| 3099 "content": ('hello world %(base)'), | |
| 3100 }, | |
| 3101 { "name": "file_with_odd_token.js", | |
| 3102 "content": ('hello world %(base)s, %(No,token)s'), | |
| 3103 }, | |
| 3104 { "name": "file_with_missing.js", | |
| 3105 "content": ('hello world %(base)s, %(idontexist)s'), | |
| 3106 }, | |
| 3107 { "name": "subdir/file_to_read.js", | |
| 3108 "content": ('hello world from subdir'), | |
| 3109 }, | |
| 3110 { # for future test expanding TAL | |
| 3111 "name": "_generic.expandfile_success.html", | |
| 3112 "content": ( | |
| 3113 | |
| 3114 '''<span tal:content="python:utils.expandfile(''' | |
| 3115 """'example.js', { 'No Token': "NT", | |
| 3116 "dict_token': 'DT'})"></span>""" ), | |
| 3117 }, | |
| 3118 ] | |
| 3119 | |
| 3120 for file_spec in testfiles: | |
| 3121 file_path = "%s/html/%s" % (self.dirname, file_spec['name']) | |
| 3122 with open(file_path, "w") as f: | |
| 3123 f.write(file_spec['content']) | |
| 3124 | |
| 3125 # get the client instance The form is needed to initialize, | |
| 3126 # but not used since I call selectTemplate directly. | |
| 3127 t = client.Client(self.instance, "user", | |
| 3128 {'PATH_INFO':'/user', 'REQUEST_METHOD':'POST'}, | |
| 3129 form=db_test_base.makeForm({"@template": "readfile_success"})) | |
| 3130 | |
| 3131 t.db = MockNull() | |
| 3132 t.db.config = MockNull() | |
| 3133 t.db.config.TRACKER_WEB = '_tracker_template' | |
| 3134 tu = TemplatingUtils(t) | |
| 3135 | |
| 3136 # testcase 1 - file exists | |
| 3137 r = tu.expandfile("file_to_read.js") | |
| 3138 self.assertEqual(r, 'hello world') | |
| 3139 r = None | |
| 3140 | |
| 3141 # testcase 2 - file does not exist | |
| 3142 with self.assertRaises(NoTemplate) as e: | |
| 3143 r = tu.expandfile("no_file_to_read.js") | |
| 3144 | |
| 3145 self.assertEqual( | |
| 3146 e.exception.args[0], | |
| 3147 "Unable to read or expand file 'no_file_to_read.js' " | |
| 3148 "in template 'home'.") | |
| 3149 r = None | |
| 3150 | |
| 3151 # testcase 3 - file does not exist - optional = True | |
| 3152 r = tu.expandfile("no_file_to_read.js", optional=True) | |
| 3153 self.assertEqual(r, '') | |
| 3154 r = None | |
| 3155 | |
| 3156 # testcase 4 - file is empty | |
| 3157 r = tu.expandfile("file_no_content.js") | |
| 3158 self.assertEqual(r, '') | |
| 3159 r = None | |
| 3160 | |
| 3161 # testcase 5 - behave like readfile (values = None) | |
| 3162 r = tu.expandfile("file_to_expand.js") | |
| 3163 self.assertEqual(r, "hello world %(base)s") | |
| 3164 r = None | |
| 3165 | |
| 3166 # testcase 6 - expand predefined | |
| 3167 r = tu.expandfile("file_to_expand.js", {}) | |
| 3168 self.assertEqual(r, "hello world _tracker_template") | |
| 3169 r = None | |
| 3170 | |
| 3171 # testcase 7 - missing trailing type specifier | |
| 3172 r = tu.expandfile("file_with_broken_expand_type.js", {}) | |
| 3173 | |
| 3174 self.assertEqual(r, "") | |
| 3175 | |
| 3176 # self._caplog.record_tuples[0] - without line breaks | |
| 3177 # ('roundup.template', 40, "Found an incorrect token when | |
| 3178 # expandfile applied string subsitution on | |
| 3179 # '/home/roundup/_test_template/html/file_with_broken_expand_type.js. | |
| 3180 # ValueError('incomplete format') was raised. Check the format | |
| 3181 # of your named conversion specifiers." | |
| 3182 | |
| 3183 # name used for logging | |
| 3184 self.assertEqual(self._caplog.record_tuples[0][0], 'roundup.template') | |
| 3185 # severity ERROR = 40 | |
| 3186 self.assertEqual(self._caplog.record_tuples[0][1], 40, | |
| 3187 msg="logging level != 40 (ERROR)") | |
| 3188 # message. It includes a full path to the problem file, so Regex | |
| 3189 # match the changable filename directory | |
| 3190 self.assertRegex(self._caplog.record_tuples[0][2], ( | |
| 3191 r"^Found an incorrect token when expandfile applied " | |
| 3192 r"string subsitution on " | |
| 3193 r"'[^']*/_test_template/html/file_with_broken_expand_type.js'. " | |
| 3194 r"ValueError\('incomplete format'\) was raised. Check the format " | |
| 3195 r"of your named conversion specifiers.")) | |
| 3196 self._caplog.clear() | |
| 3197 r = None | |
| 3198 | |
| 3199 # testcase 8 - odd token. Apparently names are not identifiers | |
| 3200 r = tu.expandfile("file_with_odd_token.js", {'No,token': 'NT'}) | |
| 3201 | |
| 3202 self.assertEqual(r, "hello world _tracker_template, NT") | |
| 3203 | |
| 3204 # self._caplog.record_tuples[0] - without line breaks | |
| 3205 # ('roundup.template', 40, "Found an incorrect token when | |
| 3206 # expandfile applied string subsitution on | |
| 3207 # '/home/roundup/_test_template/html/file_with_broken_expand_type.js. | |
| 3208 # ValueError('incomplete format') was raised. Check the format | |
| 3209 # of your named conversion specifiers." | |
| 3210 | |
| 3211 # no logs should be present | |
| 3212 self.assertEqual(self._caplog.text, '') | |
| 3213 r = None | |
| 3214 | |
| 3215 # testcase 9 - key missing from values | |
| 3216 r = tu.expandfile("file_with_missing.js", {}) | |
| 3217 | |
| 3218 self.assertEqual(r, "") | |
| 3219 | |
| 3220 # self._caplog.record_tuples[0] - without line breaks | |
| 3221 # ('roundup.template', 40, "Found an incorrect token when | |
| 3222 # expandfile applied string subsitution on | |
| 3223 # '/home/roundup/_test_template/html/file_with_broken_expand_type.js. | |
| 3224 # ValueError('incomplete format') was raised. Check the format | |
| 3225 # of your named conversion specifiers." | |
| 3226 | |
| 3227 # name used for logging | |
| 3228 self.assertEqual(self._caplog.record_tuples[0][0], 'roundup.template') | |
| 3229 # severity ERROR = 40 | |
| 3230 self.assertEqual(self._caplog.record_tuples[0][1], 40, | |
| 3231 msg="logging level != 40 (ERROR)") | |
| 3232 # message. It includes a full path to the problem file, so Regex | |
| 3233 # match the changable filename directory | |
| 3234 self.assertRegex(self._caplog.record_tuples[0][2], ( | |
| 3235 r"When running " | |
| 3236 r"expandfile\('[^']*/_test_template/html/file_with_missing.js'\) " | |
| 3237 r"in 'home' there was no value for token: 'idontexist'.")) | |
| 3238 self._caplog.clear() | |
| 3239 r = None | |
| 3240 | |
| 3241 # testcase 10 - set key missing from values in test 8 | |
| 3242 r = tu.expandfile("file_with_missing.js", {'idontexist': 'I do exist'}) | |
| 3243 | |
| 3244 self.assertEqual(r, "hello world _tracker_template, I do exist") | |
| 3245 | |
| 3246 # no logging | |
| 3247 self.assertEqual(self._caplog.text, '') | |
| 3248 self._caplog.clear() | |
| 3249 | |
| 3250 # testcase 11 - file exists in subdir | |
| 3251 r = tu.expandfile("subdir/file_to_read.js") | |
| 3252 self.assertEqual(r, 'hello world from subdir') | |
| 3253 r = None | |
| 3254 | |
| 3255 | |
| 3256 # make sure a created template is found | |
| 3257 # note that the extension is not included just the basename | |
| 3258 self.assertEqual("_generic.expandfile_success", | |
| 3259 t.selectTemplate("", "expandfile_success")) | |
| 3260 | |
| 3261 | |
| 2998 class SqliteNativeFtsCgiTest(unittest.TestCase, testFtsQuery, testCsvExport): | 3262 class SqliteNativeFtsCgiTest(unittest.TestCase, testFtsQuery, testCsvExport): |
| 2999 """All of the rest of the tests use anydbm as the backend. | 3263 """All of the rest of the tests use anydbm as the backend. |
| 3000 In addtion to normal fts test, this class tests renderError | 3264 In addtion to normal fts test, this class tests renderError |
| 3001 when renderContext fails. | 3265 when renderContext fails. |
| 3002 Triggering this error requires the native-fts backend for | 3266 Triggering this error requires the native-fts backend for |
