helpers.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. # -*- coding: utf-8 -*-
  2. """
  3. flask.testsuite.helpers
  4. ~~~~~~~~~~~~~~~~~~~~~~~
  5. Various helpers.
  6. :copyright: (c) 2011 by Armin Ronacher.
  7. :license: BSD, see LICENSE for more details.
  8. """
  9. import os
  10. import flask
  11. import unittest
  12. from logging import StreamHandler
  13. from flask.testsuite import FlaskTestCase, catch_warnings, catch_stderr
  14. from werkzeug.http import parse_cache_control_header, parse_options_header
  15. from flask._compat import StringIO, text_type
  16. def has_encoding(name):
  17. try:
  18. import codecs
  19. codecs.lookup(name)
  20. return True
  21. except LookupError:
  22. return False
  23. class JSONTestCase(FlaskTestCase):
  24. def test_json_bad_requests(self):
  25. app = flask.Flask(__name__)
  26. @app.route('/json', methods=['POST'])
  27. def return_json():
  28. return flask.jsonify(foo=text_type(flask.request.get_json()))
  29. c = app.test_client()
  30. rv = c.post('/json', data='malformed', content_type='application/json')
  31. self.assert_equal(rv.status_code, 400)
  32. def test_json_body_encoding(self):
  33. app = flask.Flask(__name__)
  34. app.testing = True
  35. @app.route('/')
  36. def index():
  37. return flask.request.get_json()
  38. c = app.test_client()
  39. resp = c.get('/', data=u'"Hällo Wörld"'.encode('iso-8859-15'),
  40. content_type='application/json; charset=iso-8859-15')
  41. self.assert_equal(resp.data, u'Hällo Wörld'.encode('utf-8'))
  42. def test_jsonify(self):
  43. d = dict(a=23, b=42, c=[1, 2, 3])
  44. app = flask.Flask(__name__)
  45. @app.route('/kw')
  46. def return_kwargs():
  47. return flask.jsonify(**d)
  48. @app.route('/dict')
  49. def return_dict():
  50. return flask.jsonify(d)
  51. c = app.test_client()
  52. for url in '/kw', '/dict':
  53. rv = c.get(url)
  54. self.assert_equal(rv.mimetype, 'application/json')
  55. self.assert_equal(flask.json.loads(rv.data), d)
  56. def test_json_as_unicode(self):
  57. app = flask.Flask(__name__)
  58. app.config['JSON_AS_ASCII'] = True
  59. with app.app_context():
  60. rv = flask.json.dumps(u'\N{SNOWMAN}')
  61. self.assert_equal(rv, '"\\u2603"')
  62. app.config['JSON_AS_ASCII'] = False
  63. with app.app_context():
  64. rv = flask.json.dumps(u'\N{SNOWMAN}')
  65. self.assert_equal(rv, u'"\u2603"')
  66. def test_json_attr(self):
  67. app = flask.Flask(__name__)
  68. @app.route('/add', methods=['POST'])
  69. def add():
  70. json = flask.request.get_json()
  71. return text_type(json['a'] + json['b'])
  72. c = app.test_client()
  73. rv = c.post('/add', data=flask.json.dumps({'a': 1, 'b': 2}),
  74. content_type='application/json')
  75. self.assert_equal(rv.data, b'3')
  76. def test_template_escaping(self):
  77. app = flask.Flask(__name__)
  78. render = flask.render_template_string
  79. with app.test_request_context():
  80. rv = flask.json.htmlsafe_dumps('</script>')
  81. self.assert_equal(rv, u'"\\u003c/script\\u003e"')
  82. self.assert_equal(type(rv), text_type)
  83. rv = render('{{ "</script>"|tojson }}')
  84. self.assert_equal(rv, '"\\u003c/script\\u003e"')
  85. rv = render('{{ "<\0/script>"|tojson }}')
  86. self.assert_equal(rv, '"\\u003c\\u0000/script\\u003e"')
  87. rv = render('{{ "<!--<script>"|tojson }}')
  88. self.assert_equal(rv, '"\\u003c!--\\u003cscript\\u003e"')
  89. rv = render('{{ "&"|tojson }}')
  90. self.assert_equal(rv, '"\\u0026"')
  91. rv = render('{{ "\'"|tojson }}')
  92. self.assert_equal(rv, '"\\u0027"')
  93. rv = render("<a ng-data='{{ data|tojson }}'></a>",
  94. data={'x': ["foo", "bar", "baz'"]})
  95. self.assert_equal(rv,
  96. '<a ng-data=\'{"x": ["foo", "bar", "baz\\u0027"]}\'></a>')
  97. def test_json_customization(self):
  98. class X(object):
  99. def __init__(self, val):
  100. self.val = val
  101. class MyEncoder(flask.json.JSONEncoder):
  102. def default(self, o):
  103. if isinstance(o, X):
  104. return '<%d>' % o.val
  105. return flask.json.JSONEncoder.default(self, o)
  106. class MyDecoder(flask.json.JSONDecoder):
  107. def __init__(self, *args, **kwargs):
  108. kwargs.setdefault('object_hook', self.object_hook)
  109. flask.json.JSONDecoder.__init__(self, *args, **kwargs)
  110. def object_hook(self, obj):
  111. if len(obj) == 1 and '_foo' in obj:
  112. return X(obj['_foo'])
  113. return obj
  114. app = flask.Flask(__name__)
  115. app.testing = True
  116. app.json_encoder = MyEncoder
  117. app.json_decoder = MyDecoder
  118. @app.route('/', methods=['POST'])
  119. def index():
  120. return flask.json.dumps(flask.request.get_json()['x'])
  121. c = app.test_client()
  122. rv = c.post('/', data=flask.json.dumps({
  123. 'x': {'_foo': 42}
  124. }), content_type='application/json')
  125. self.assertEqual(rv.data, b'"<42>"')
  126. def test_modified_url_encoding(self):
  127. class ModifiedRequest(flask.Request):
  128. url_charset = 'euc-kr'
  129. app = flask.Flask(__name__)
  130. app.testing = True
  131. app.request_class = ModifiedRequest
  132. app.url_map.charset = 'euc-kr'
  133. @app.route('/')
  134. def index():
  135. return flask.request.args['foo']
  136. rv = app.test_client().get(u'/?foo=정상처리'.encode('euc-kr'))
  137. self.assert_equal(rv.status_code, 200)
  138. self.assert_equal(rv.data, u'정상처리'.encode('utf-8'))
  139. if not has_encoding('euc-kr'):
  140. test_modified_url_encoding = None
  141. def test_json_key_sorting(self):
  142. app = flask.Flask(__name__)
  143. app.testing = True
  144. self.assert_equal(app.config['JSON_SORT_KEYS'], True)
  145. d = dict.fromkeys(range(20), 'foo')
  146. @app.route('/')
  147. def index():
  148. return flask.jsonify(values=d)
  149. c = app.test_client()
  150. rv = c.get('/')
  151. lines = [x.strip() for x in rv.data.strip().decode('utf-8').splitlines()]
  152. self.assert_equal(lines, [
  153. '{',
  154. '"values": {',
  155. '"0": "foo",',
  156. '"1": "foo",',
  157. '"2": "foo",',
  158. '"3": "foo",',
  159. '"4": "foo",',
  160. '"5": "foo",',
  161. '"6": "foo",',
  162. '"7": "foo",',
  163. '"8": "foo",',
  164. '"9": "foo",',
  165. '"10": "foo",',
  166. '"11": "foo",',
  167. '"12": "foo",',
  168. '"13": "foo",',
  169. '"14": "foo",',
  170. '"15": "foo",',
  171. '"16": "foo",',
  172. '"17": "foo",',
  173. '"18": "foo",',
  174. '"19": "foo"',
  175. '}',
  176. '}'
  177. ])
  178. class SendfileTestCase(FlaskTestCase):
  179. def test_send_file_regular(self):
  180. app = flask.Flask(__name__)
  181. with app.test_request_context():
  182. rv = flask.send_file('static/index.html')
  183. self.assert_true(rv.direct_passthrough)
  184. self.assert_equal(rv.mimetype, 'text/html')
  185. with app.open_resource('static/index.html') as f:
  186. rv.direct_passthrough = False
  187. self.assert_equal(rv.data, f.read())
  188. rv.close()
  189. def test_send_file_xsendfile(self):
  190. app = flask.Flask(__name__)
  191. app.use_x_sendfile = True
  192. with app.test_request_context():
  193. rv = flask.send_file('static/index.html')
  194. self.assert_true(rv.direct_passthrough)
  195. self.assert_in('x-sendfile', rv.headers)
  196. self.assert_equal(rv.headers['x-sendfile'],
  197. os.path.join(app.root_path, 'static/index.html'))
  198. self.assert_equal(rv.mimetype, 'text/html')
  199. rv.close()
  200. def test_send_file_object(self):
  201. app = flask.Flask(__name__)
  202. with catch_warnings() as captured:
  203. with app.test_request_context():
  204. f = open(os.path.join(app.root_path, 'static/index.html'))
  205. rv = flask.send_file(f)
  206. rv.direct_passthrough = False
  207. with app.open_resource('static/index.html') as f:
  208. self.assert_equal(rv.data, f.read())
  209. self.assert_equal(rv.mimetype, 'text/html')
  210. rv.close()
  211. # mimetypes + etag
  212. self.assert_equal(len(captured), 2)
  213. app.use_x_sendfile = True
  214. with catch_warnings() as captured:
  215. with app.test_request_context():
  216. f = open(os.path.join(app.root_path, 'static/index.html'))
  217. rv = flask.send_file(f)
  218. self.assert_equal(rv.mimetype, 'text/html')
  219. self.assert_in('x-sendfile', rv.headers)
  220. self.assert_equal(rv.headers['x-sendfile'],
  221. os.path.join(app.root_path, 'static/index.html'))
  222. rv.close()
  223. # mimetypes + etag
  224. self.assert_equal(len(captured), 2)
  225. app.use_x_sendfile = False
  226. with app.test_request_context():
  227. with catch_warnings() as captured:
  228. f = StringIO('Test')
  229. rv = flask.send_file(f)
  230. rv.direct_passthrough = False
  231. self.assert_equal(rv.data, b'Test')
  232. self.assert_equal(rv.mimetype, 'application/octet-stream')
  233. rv.close()
  234. # etags
  235. self.assert_equal(len(captured), 1)
  236. with catch_warnings() as captured:
  237. f = StringIO('Test')
  238. rv = flask.send_file(f, mimetype='text/plain')
  239. rv.direct_passthrough = False
  240. self.assert_equal(rv.data, b'Test')
  241. self.assert_equal(rv.mimetype, 'text/plain')
  242. rv.close()
  243. # etags
  244. self.assert_equal(len(captured), 1)
  245. app.use_x_sendfile = True
  246. with catch_warnings() as captured:
  247. with app.test_request_context():
  248. f = StringIO('Test')
  249. rv = flask.send_file(f)
  250. self.assert_not_in('x-sendfile', rv.headers)
  251. rv.close()
  252. # etags
  253. self.assert_equal(len(captured), 1)
  254. def test_attachment(self):
  255. app = flask.Flask(__name__)
  256. with catch_warnings() as captured:
  257. with app.test_request_context():
  258. f = open(os.path.join(app.root_path, 'static/index.html'))
  259. rv = flask.send_file(f, as_attachment=True)
  260. value, options = parse_options_header(rv.headers['Content-Disposition'])
  261. self.assert_equal(value, 'attachment')
  262. rv.close()
  263. # mimetypes + etag
  264. self.assert_equal(len(captured), 2)
  265. with app.test_request_context():
  266. self.assert_equal(options['filename'], 'index.html')
  267. rv = flask.send_file('static/index.html', as_attachment=True)
  268. value, options = parse_options_header(rv.headers['Content-Disposition'])
  269. self.assert_equal(value, 'attachment')
  270. self.assert_equal(options['filename'], 'index.html')
  271. rv.close()
  272. with app.test_request_context():
  273. rv = flask.send_file(StringIO('Test'), as_attachment=True,
  274. attachment_filename='index.txt',
  275. add_etags=False)
  276. self.assert_equal(rv.mimetype, 'text/plain')
  277. value, options = parse_options_header(rv.headers['Content-Disposition'])
  278. self.assert_equal(value, 'attachment')
  279. self.assert_equal(options['filename'], 'index.txt')
  280. rv.close()
  281. def test_static_file(self):
  282. app = flask.Flask(__name__)
  283. # default cache timeout is 12 hours
  284. with app.test_request_context():
  285. # Test with static file handler.
  286. rv = app.send_static_file('index.html')
  287. cc = parse_cache_control_header(rv.headers['Cache-Control'])
  288. self.assert_equal(cc.max_age, 12 * 60 * 60)
  289. rv.close()
  290. # Test again with direct use of send_file utility.
  291. rv = flask.send_file('static/index.html')
  292. cc = parse_cache_control_header(rv.headers['Cache-Control'])
  293. self.assert_equal(cc.max_age, 12 * 60 * 60)
  294. rv.close()
  295. app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3600
  296. with app.test_request_context():
  297. # Test with static file handler.
  298. rv = app.send_static_file('index.html')
  299. cc = parse_cache_control_header(rv.headers['Cache-Control'])
  300. self.assert_equal(cc.max_age, 3600)
  301. rv.close()
  302. # Test again with direct use of send_file utility.
  303. rv = flask.send_file('static/index.html')
  304. cc = parse_cache_control_header(rv.headers['Cache-Control'])
  305. self.assert_equal(cc.max_age, 3600)
  306. rv.close()
  307. class StaticFileApp(flask.Flask):
  308. def get_send_file_max_age(self, filename):
  309. return 10
  310. app = StaticFileApp(__name__)
  311. with app.test_request_context():
  312. # Test with static file handler.
  313. rv = app.send_static_file('index.html')
  314. cc = parse_cache_control_header(rv.headers['Cache-Control'])
  315. self.assert_equal(cc.max_age, 10)
  316. rv.close()
  317. # Test again with direct use of send_file utility.
  318. rv = flask.send_file('static/index.html')
  319. cc = parse_cache_control_header(rv.headers['Cache-Control'])
  320. self.assert_equal(cc.max_age, 10)
  321. rv.close()
  322. class LoggingTestCase(FlaskTestCase):
  323. def test_logger_cache(self):
  324. app = flask.Flask(__name__)
  325. logger1 = app.logger
  326. self.assert_true(app.logger is logger1)
  327. self.assert_equal(logger1.name, __name__)
  328. app.logger_name = __name__ + '/test_logger_cache'
  329. self.assert_true(app.logger is not logger1)
  330. def test_debug_log(self):
  331. app = flask.Flask(__name__)
  332. app.debug = True
  333. @app.route('/')
  334. def index():
  335. app.logger.warning('the standard library is dead')
  336. app.logger.debug('this is a debug statement')
  337. return ''
  338. @app.route('/exc')
  339. def exc():
  340. 1 // 0
  341. with app.test_client() as c:
  342. with catch_stderr() as err:
  343. c.get('/')
  344. out = err.getvalue()
  345. self.assert_in('WARNING in helpers [', out)
  346. self.assert_in(os.path.basename(__file__.rsplit('.', 1)[0] + '.py'), out)
  347. self.assert_in('the standard library is dead', out)
  348. self.assert_in('this is a debug statement', out)
  349. with catch_stderr() as err:
  350. try:
  351. c.get('/exc')
  352. except ZeroDivisionError:
  353. pass
  354. else:
  355. self.assert_true(False, 'debug log ate the exception')
  356. def test_debug_log_override(self):
  357. app = flask.Flask(__name__)
  358. app.debug = True
  359. app.logger_name = 'flask_tests/test_debug_log_override'
  360. app.logger.level = 10
  361. self.assert_equal(app.logger.level, 10)
  362. def test_exception_logging(self):
  363. out = StringIO()
  364. app = flask.Flask(__name__)
  365. app.logger_name = 'flask_tests/test_exception_logging'
  366. app.logger.addHandler(StreamHandler(out))
  367. @app.route('/')
  368. def index():
  369. 1 // 0
  370. rv = app.test_client().get('/')
  371. self.assert_equal(rv.status_code, 500)
  372. self.assert_in(b'Internal Server Error', rv.data)
  373. err = out.getvalue()
  374. self.assert_in('Exception on / [GET]', err)
  375. self.assert_in('Traceback (most recent call last):', err)
  376. self.assert_in('1 // 0', err)
  377. self.assert_in('ZeroDivisionError:', err)
  378. def test_processor_exceptions(self):
  379. app = flask.Flask(__name__)
  380. @app.before_request
  381. def before_request():
  382. if trigger == 'before':
  383. 1 // 0
  384. @app.after_request
  385. def after_request(response):
  386. if trigger == 'after':
  387. 1 // 0
  388. return response
  389. @app.route('/')
  390. def index():
  391. return 'Foo'
  392. @app.errorhandler(500)
  393. def internal_server_error(e):
  394. return 'Hello Server Error', 500
  395. for trigger in 'before', 'after':
  396. rv = app.test_client().get('/')
  397. self.assert_equal(rv.status_code, 500)
  398. self.assert_equal(rv.data, b'Hello Server Error')
  399. def test_url_for_with_anchor(self):
  400. app = flask.Flask(__name__)
  401. @app.route('/')
  402. def index():
  403. return '42'
  404. with app.test_request_context():
  405. self.assert_equal(flask.url_for('index', _anchor='x y'),
  406. '/#x%20y')
  407. def test_url_for_with_scheme(self):
  408. app = flask.Flask(__name__)
  409. @app.route('/')
  410. def index():
  411. return '42'
  412. with app.test_request_context():
  413. self.assert_equal(flask.url_for('index',
  414. _external=True,
  415. _scheme='https'),
  416. 'https://localhost/')
  417. def test_url_for_with_scheme_not_external(self):
  418. app = flask.Flask(__name__)
  419. @app.route('/')
  420. def index():
  421. return '42'
  422. with app.test_request_context():
  423. self.assert_raises(ValueError,
  424. flask.url_for,
  425. 'index',
  426. _scheme='https')
  427. def test_url_with_method(self):
  428. from flask.views import MethodView
  429. app = flask.Flask(__name__)
  430. class MyView(MethodView):
  431. def get(self, id=None):
  432. if id is None:
  433. return 'List'
  434. return 'Get %d' % id
  435. def post(self):
  436. return 'Create'
  437. myview = MyView.as_view('myview')
  438. app.add_url_rule('/myview/', methods=['GET'],
  439. view_func=myview)
  440. app.add_url_rule('/myview/<int:id>', methods=['GET'],
  441. view_func=myview)
  442. app.add_url_rule('/myview/create', methods=['POST'],
  443. view_func=myview)
  444. with app.test_request_context():
  445. self.assert_equal(flask.url_for('myview', _method='GET'),
  446. '/myview/')
  447. self.assert_equal(flask.url_for('myview', id=42, _method='GET'),
  448. '/myview/42')
  449. self.assert_equal(flask.url_for('myview', _method='POST'),
  450. '/myview/create')
  451. class NoImportsTestCase(FlaskTestCase):
  452. """Test Flasks are created without import.
  453. Avoiding ``__import__`` helps create Flask instances where there are errors
  454. at import time. Those runtime errors will be apparent to the user soon
  455. enough, but tools which build Flask instances meta-programmatically benefit
  456. from a Flask which does not ``__import__``. Instead of importing to
  457. retrieve file paths or metadata on a module or package, use the pkgutil and
  458. imp modules in the Python standard library.
  459. """
  460. def test_name_with_import_error(self):
  461. try:
  462. flask.Flask('importerror')
  463. except NotImplementedError:
  464. self.fail('Flask(import_name) is importing import_name.')
  465. class StreamingTestCase(FlaskTestCase):
  466. def test_streaming_with_context(self):
  467. app = flask.Flask(__name__)
  468. app.testing = True
  469. @app.route('/')
  470. def index():
  471. def generate():
  472. yield 'Hello '
  473. yield flask.request.args['name']
  474. yield '!'
  475. return flask.Response(flask.stream_with_context(generate()))
  476. c = app.test_client()
  477. rv = c.get('/?name=World')
  478. self.assertEqual(rv.data, b'Hello World!')
  479. def test_streaming_with_context_as_decorator(self):
  480. app = flask.Flask(__name__)
  481. app.testing = True
  482. @app.route('/')
  483. def index():
  484. @flask.stream_with_context
  485. def generate():
  486. yield 'Hello '
  487. yield flask.request.args['name']
  488. yield '!'
  489. return flask.Response(generate())
  490. c = app.test_client()
  491. rv = c.get('/?name=World')
  492. self.assertEqual(rv.data, b'Hello World!')
  493. def test_streaming_with_context_and_custom_close(self):
  494. app = flask.Flask(__name__)
  495. app.testing = True
  496. called = []
  497. class Wrapper(object):
  498. def __init__(self, gen):
  499. self._gen = gen
  500. def __iter__(self):
  501. return self
  502. def close(self):
  503. called.append(42)
  504. def __next__(self):
  505. return next(self._gen)
  506. next = __next__
  507. @app.route('/')
  508. def index():
  509. def generate():
  510. yield 'Hello '
  511. yield flask.request.args['name']
  512. yield '!'
  513. return flask.Response(flask.stream_with_context(
  514. Wrapper(generate())))
  515. c = app.test_client()
  516. rv = c.get('/?name=World')
  517. self.assertEqual(rv.data, b'Hello World!')
  518. self.assertEqual(called, [42])
  519. def suite():
  520. suite = unittest.TestSuite()
  521. if flask.json_available:
  522. suite.addTest(unittest.makeSuite(JSONTestCase))
  523. suite.addTest(unittest.makeSuite(SendfileTestCase))
  524. suite.addTest(unittest.makeSuite(LoggingTestCase))
  525. suite.addTest(unittest.makeSuite(NoImportsTestCase))
  526. suite.addTest(unittest.makeSuite(StreamingTestCase))
  527. return suite