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

Roundup Issue Tracker: http://roundup-tracker.org/