FlaskTester 3.6__tar.gz → 4.1__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: FlaskTester
3
- Version: 3.6
3
+ Version: 4.1
4
4
  Summary: Pytest fixtures for Flask internal and external authenticated tests
5
5
  Author-email: Fabien Coelho <flask.tester@coelho.net>
6
6
  License: CC0
@@ -34,6 +34,7 @@ Requires-Dist: types-flask; extra == "dev"
34
34
  Requires-Dist: types-requests; extra == "dev"
35
35
  Requires-Dist: FlaskSimpleAuth>=30.0; extra == "dev"
36
36
  Requires-Dist: passlib; extra == "dev"
37
+ Requires-Dist: pydantic; extra == "dev"
37
38
  Provides-Extra: doc
38
39
  Requires-Dist: sphinx; extra == "doc"
39
40
  Requires-Dist: sphinx_rtd_theme; extra == "doc"
@@ -49,7 +50,7 @@ either with internal Flask tests (aka `test_client`) or external tests (with
49
50
  authentication and per-user cookies.
50
51
 
51
52
  Only one set of tests is needed, switching from internal to external is
52
- achieved through environment variables.
53
+ achieved by setting an environment variable.
53
54
 
54
55
  ![Status](https://github.com/zx80/flask-tester/actions/workflows/package.yml/badge.svg?branch=main&style=flat)
55
56
  ![Tests](https://img.shields.io/badge/tests-14%20✓-success)
@@ -123,7 +124,7 @@ The above test runs with [`tests/app.py`](tests/app.py)
123
124
  [Flask](https://flask.palletsprojects.com/)
124
125
  REST application back-end with password and token authentication based on
125
126
  [FlaskSimpleAuth](https://pypi.org/project/FlaskSimpleAuth/).
126
- The code uses _25_ lines of Python for implementing
127
+ The code uses _23_ lines of Python for implementing
127
128
  password (basic and parameters) and token authentications,
128
129
  admin group authorization, and routes for
129
130
  token generation (2), identity tests (2) and an incredible open cookie-based
@@ -15,6 +15,7 @@ types-flask
15
15
  types-requests
16
16
  FlaskSimpleAuth>=30.0
17
17
  passlib
18
+ pydantic
18
19
 
19
20
  [doc]
20
21
  sphinx
@@ -9,7 +9,9 @@ import re
9
9
  from typing import Any
10
10
  import importlib
11
11
  import logging
12
- import pytest
12
+ import pytest # for explicit fail calls, see _pytestFail
13
+ import json
14
+ import dataclasses
13
15
 
14
16
  log = logging.getLogger("flask_tester")
15
17
 
@@ -29,12 +31,15 @@ class _AssertError(FlaskTesterError):
29
31
  pass
30
32
 
31
33
 
32
- def _raiseError(msg: str):
33
- """Undocumented switch for FlaskTester own tests."""
34
+ def _pytestFail(msg: str):
35
+ """Undocumented switch for FlaskTester own tests.
36
+
37
+ A pytest test cannot check an expected pytest failure without failing.
38
+ """
34
39
  log.error(msg)
35
40
  if "FLASK_TESTER_TESTING" in os.environ:
36
41
  raise _AssertError(msg)
37
- else: # pragma: no cover # cannot cover an expected pytest failure!
42
+ else: # pragma: no cover
38
43
  pytest.fail(msg)
39
44
 
40
45
 
@@ -54,24 +59,24 @@ class Authenticator:
54
59
 
55
60
  Constructor parameters:
56
61
 
57
- - ``allow``: list of allowed schemes.
58
- default is ``["bearer", "basic", "param", "none"]``.
59
- - ``user``: parameter for user on ``param`` password authentication,
60
- default is ``USER``.
61
- - ``pwd``: parameter for password on ``param`` password authentication,
62
- default is ``PASS``.
63
- - ``login``: parameter for user on ``fake`` authentication,
64
- default is ``LOGIN``.
65
- - ``bearer``: name of bearer scheme for token,
66
- default is ``Bearer``.
67
- - ``header``: name of header for token,
68
- default is ``Auth``.
69
- - ``cookie``: name of cookie for token,
70
- default is ``auth``.
71
- - ``tparam``: name of parameter for token,
72
- default is ``AUTH``.
73
- - ``ptype``: default parameter type, either ``data`` or ``json``,
74
- default is ``data``.
62
+ :param allow: List of allowed schemes.
63
+ defaults to ``["bearer", "basic", "param", "none"]``.
64
+ :param user: Parameter for user on ``param`` password authentication,
65
+ defaults to ``"USER"``.
66
+ :param pwd: Parameter for password on ``param`` password authentication,
67
+ defaults to ``"PASS"``.
68
+ :param login: Parameter for user on ``fake`` authentication,
69
+ defaults to ``"LOGIN"``.
70
+ :param bearer: Name of bearer scheme for token,
71
+ defaults to ``"Bearer"``.
72
+ :param header: Name of header for token,
73
+ defaults to ``"Auth"``.
74
+ :param cookie: Name of cookie for token,
75
+ defaults to ``"auth"``.
76
+ :param tparam: Name of parameter for token,
77
+ defaults to ``"AUTH"``.
78
+ :param ptype: Default parameter type, either *data* or *json*,
79
+ defaults to ``"data"``.
75
80
 
76
81
  Note: default values are consistent with `FlaskSimpleAuth <https://pypi.org/project/FlaskSimpleAuth/>`_.
77
82
  """
@@ -182,12 +187,12 @@ class Authenticator:
182
187
  def setAuth(self, login: str|None, kwargs: dict[str, Any], cookies: dict[str, str], auth: str|None = None):
183
188
  """Set request authentication.
184
189
 
185
- - ``login``: login target, None means no authentication.
186
- - ``kwargs``: request parameters.
187
- - ``cookies``: request cookies.
188
- - ``auth``: authentication method, default is None.
190
+ :param login: Login target, *None* means no authentication.
191
+ :param kwargs: Request parameters to modify.
192
+ :param cookies: Request cookies to modify.
193
+ :param auth: Authentication method, default is *None*.
189
194
 
190
- The default behavior is to try allowed schemes: tokens first,
195
+ The default behavior is to try allowed schemes: token first,
191
196
  then password, then fake.
192
197
  """
193
198
 
@@ -251,9 +256,9 @@ class Authenticator:
251
256
  class RequestFlaskResponse:
252
257
  """Wrapper to return a Flask-looking response from a request response.
253
258
 
254
- This only work for simple responses.
259
+ This only works for simple responses.
255
260
 
256
- Available attributes:
261
+ Available public attributes:
257
262
 
258
263
  - ``status_code``: integer status code.
259
264
  - ``data``: body as bytes.
@@ -265,7 +270,7 @@ class RequestFlaskResponse:
265
270
 
266
271
  Constructor parameter:
267
272
 
268
- - ``response``: from request.
273
+ :param response: Response from request.
269
274
  """
270
275
 
271
276
  def __init__(self, response):
@@ -289,8 +294,8 @@ class Client:
289
294
 
290
295
  Constructor parameters:
291
296
 
292
- - ``auth``: authenticator.
293
- - ``default_login``: if ``login`` is not set.
297
+ :param auth: Authenticator.
298
+ :param default_login: When ``login`` is not set.
294
299
  """
295
300
 
296
301
  def __init__(self, auth: Authenticator, default_login: str|None = None):
@@ -320,16 +325,16 @@ class Client:
320
325
 
321
326
  Mandatory parameters:
322
327
 
323
- - ``method``: HTTP method ("GET", "POST", "PATCH", "DELETE"…).
324
- - ``path``: local path under the base URL.
328
+ :param method: HTTP method ("GET", "POST", "PATCH", "DELETE"…).
329
+ :param path: Local path under the base URL.
325
330
 
326
331
  Optional parameters:
327
332
 
328
- - ``status``: expected HTTP status, *None* to skip status check.
329
- - ``content``: regular expression for response body, *None* to skip content check.
330
- - ``login``: authenticated user, use **explicit** *None* to skip.
331
- - ``auth``: authentication scheme to use instead of default behavior.
332
- - ``**kwargs``: more request parameters (headers, data, json…).
333
+ :param status: Expected HTTP status, *None* to skip status check.
334
+ :param content: Regular expression for response body, *None* to skip content check.
335
+ :param login: Authenticated user, use **explicit** *None* to skip default.
336
+ :param auth: Authentication scheme to use instead of default behavior.
337
+ :param **kwargs: More request parameters (headers, data, json…).
333
338
  """
334
339
 
335
340
  if "login" in kwargs:
@@ -343,54 +348,89 @@ class Client:
343
348
  cookies.update(kwargs["cookies"])
344
349
  del kwargs["cookies"]
345
350
 
351
+ # FIXME allow or forbid?
352
+ # if "json" in kwargs and "data" in kwargs:
353
+ # log.warning("mix of json and data parameters in request")
354
+
355
+ # convert json parameters
356
+ if "json" in kwargs:
357
+ json_param = kwargs["json"]
358
+ assert isinstance(json_param, dict)
359
+ for name in list(json_param.keys()):
360
+ val = json_param[name]
361
+ if val is None:
362
+ pass
363
+ elif isinstance(val, (bool, int, float, str, tuple, list, dict)):
364
+ pass
365
+ elif "model_dump" in val.__dir__() and callable(val.model_dump):
366
+ # probably pydantic
367
+ json_param[name] = val.model_dump()
368
+ else: # pydantic or standard dataclasses?
369
+ json_param[name] = dataclasses.asdict(val)
370
+
371
+ # convert data parameters
372
+ if "data" in kwargs:
373
+ data_param = kwargs["data"]
374
+ assert isinstance(data_param, dict)
375
+ for name in list(data_param.keys()):
376
+ val = data_param[name]
377
+ if val is None:
378
+ data_param[name] = "null"
379
+ elif isinstance(val, (io.IOBase, tuple)):
380
+ pass # file parameters?
381
+ elif isinstance(val, (bool, int, float, str, list, dict)):
382
+ data_param[name] = json.dumps(val)
383
+ elif "model_dump_json" in val.__dir__() and callable(val.model_dump_json):
384
+ data_param[name] = val.model_dump_json()
385
+ else:
386
+ data_param[name] = json.dumps(dataclasses.asdict(val))
387
+
346
388
  self._auth.setAuth(login, kwargs, cookies, auth=auth)
347
389
  res = self._request(method, path, cookies, **kwargs) # type: ignore
348
390
 
349
391
  # check status
350
392
  if status is not None:
351
393
  if res.status_code != status: # show error before aborting
352
- _raiseError(f"bad {status} result: {res.status_code} {res.text[:512]}...")
394
+ # FIXME what if the useful part is at the end?
395
+ _pytestFail(f"bad {status} result: {res.status_code} {res.text[:512]}...")
353
396
 
354
397
  # check content
355
398
  if content is not None:
356
399
  if not re.search(content, res.text, re.DOTALL):
357
- _raiseError(f"cannot find {content} in {res.text[:512]}...")
400
+ # FIXME what if the useful part is at the end?
401
+ _pytestFail(f"cannot find {content} in {res.text[:512]}...")
358
402
 
359
403
  return res
360
404
 
361
405
  def get(self, path: str, status: int|None = None, content: str|None = None, **kwargs):
362
- """HTTP GET request, see ``request`` parameters."""
406
+ """HTTP GET request, see `Client.request`."""
363
407
  return self.request("GET", path, status=status, content=content, **kwargs)
364
408
 
365
409
  def post(self, path: str, status: int|None = None, content: str|None = None, **kwargs):
366
- """HTTP POST request, see ``request`` parameters."""
410
+ """HTTP POST request, see `Client.request`."""
367
411
  return self.request("POST", path, status=status, content=content, **kwargs)
368
412
 
369
413
  def put(self, path: str, status: int|None = None, content: str|None = None, **kwargs):
370
- """HTTP PUT request, see ``request`` parameters."""
414
+ """HTTP PUT request, see `Client.request`."""
371
415
  return self.request("PUT", path, status=status, content=content, **kwargs)
372
416
 
373
417
  def patch(self, path: str, status: int|None = None, content: str|None = None, **kwargs):
374
- """HTTP PATCH request, see ``request`` parameters."""
418
+ """HTTP PATCH request, see `Client.request`."""
375
419
  return self.request("PATCH", path, status=status, content=content, **kwargs)
376
420
 
377
421
  def delete(self, path: str, status: int|None = None, content: str|None = None, **kwargs):
378
- """HTTP DELETE request, see ``request`` parameters."""
422
+ """HTTP DELETE request, see `Client.request`."""
379
423
  return self.request("DELETE", path, status=status, content=content, **kwargs)
380
424
 
381
- def check(self, method: str, path: str, status: int, content: str|None = None, **kwargs):
382
- """Deprecated, use ``request`` or HTTP-method-specific methods."""
383
- return self.request(method, path, status=status, content=content, **kwargs)
384
-
385
425
 
386
426
  class RequestClient(Client):
387
427
  """Request-based test provider.
388
428
 
389
429
  Constructor parameters:
390
430
 
391
- - ``auth``: authenticator.
392
- - ``base_url``: target server.
393
- - ``default_login``: if ``login`` is not set.
431
+ :param auth: Authenticator.
432
+ :param base_url: Target server.
433
+ :param default_login: When ``login`` is not set.
394
434
  """
395
435
 
396
436
  def __init__(self, auth: Authenticator, base_url: str, default_login=None):
@@ -434,9 +474,9 @@ class FlaskClient(Client):
434
474
 
435
475
  Constructor parameters:
436
476
 
437
- - ``auth``: authenticator.
438
- - ``client``: Flask actual ``test_client``.
439
- - ``default_login``: if ``login`` is not set.
477
+ :param auth: Authenticator.
478
+ :param client: Flask actual ``test_client``.
479
+ :param default_login: When ``login`` is not set.
440
480
 
441
481
  Note: this client handles `cookies`.
442
482
  """
@@ -499,29 +539,29 @@ def ft_authenticator():
499
539
  Environment variables:
500
540
 
501
541
  - ``FLASK_TESTER_LOG_LEVEL``: package log level in
502
- ``DEBUG INFO WARNING ERROR CRITICAL NOSET``.
503
- Default is ``NOTSET``.
542
+ ``DEBUG INFO WARNING ERROR CRITICAL NOSET``,
543
+ defaults to ``NOTSET``.
504
544
  - ``FLASK_TESTER_ALLOW``: allowed space-separated authentication schemes, in
505
- ``basic param bearer header cookie tparam fake none``.
506
- Default is ``bearer basic param none``.
507
- - ``FLASK_TESTER_USER``: user login parameter for ``param`` authentication.
508
- Default is ``USER``.
509
- - ``FLASK_TESTER_PASS``: user password parameter for ``param`` authentication.
510
- Default is ``PASS``.
511
- - ``FLASK_TESTER_LOGIN``: user login parameter for ``fake`` authentication.
512
- Default is ``LOGIN``.
513
- - ``FLASK_TESTER_BEARER``: bearer name for *token* authentication.
514
- Default is ``Bearer``.
515
- - ``FLASK_TESTER_HEADER``: header name for *token* authentication.
516
- Default is ``Auth``.
517
- - ``FLASK_TESTER_COOKIE``: cookie name for *token* authentication.
518
- Default is ``auth``.
519
- - ``FLASK_TESTER_TPARAM``: parameter for *token* authentication.
520
- Default is ``AUTH``.
521
- - ``FLASK_TESTER_PTYPE``: default parameter type, ``data`` or ``json``.
522
- Default is ``data``.
523
- - ``FLASK_TESTER_AUTH``: initial comma-separated list of *login:password*.
524
- Default is not set.
545
+ ``basic param bearer header cookie tparam fake none``,
546
+ defaults to ``bearer basic param none``.
547
+ - ``FLASK_TESTER_USER``: user login parameter for ``param`` authentication,
548
+ defaults to ``USER``.
549
+ - ``FLASK_TESTER_PASS``: user password parameter for ``param`` authentication,
550
+ defaults to ``PASS``.
551
+ - ``FLASK_TESTER_LOGIN``: user login parameter for ``fake`` authentication,
552
+ defaults to ``LOGIN``.
553
+ - ``FLASK_TESTER_BEARER``: bearer name for *token* authentication,
554
+ defaults to ``Bearer``.
555
+ - ``FLASK_TESTER_HEADER``: header name for *token* authentication,
556
+ defaults to ``Auth``.
557
+ - ``FLASK_TESTER_COOKIE``: cookie name for *token* authentication,
558
+ defaults to ``auth``.
559
+ - ``FLASK_TESTER_TPARAM``: parameter for *token* authentication,
560
+ defaults to ``AUTH``.
561
+ - ``FLASK_TESTER_PTYPE``: default parameter type, ``data`` or ``json``,
562
+ defaults to ``data``.
563
+ - ``FLASK_TESTER_AUTH``: initial comma-separated list of *login:password*,
564
+ defaults to not set.
525
565
  """
526
566
 
527
567
  yield _ft_authenticator()
@@ -532,28 +572,30 @@ def _ft_client(authenticator):
532
572
  default_login = os.environ.get("FLASK_TESTER_DEFAULT", None)
533
573
  client: Client
534
574
 
535
- app_def = os.environ.get("FLASK_TESTER_URL", "app")
536
- app_def = os.environ.get("FLASK_TESTER_APP", app_def)
575
+ test_app = os.environ.get("FLASK_TESTER_APP", "app")
537
576
 
538
- if app_def.startswith("http://") or app_def.startswith("https://"):
539
- client = RequestClient(authenticator, app_def, default_login)
577
+ if test_app.startswith("http://") or test_app.startswith("https://"):
578
+ client = RequestClient(authenticator, test_app, default_login)
540
579
  else:
541
580
  # load app package
542
- pkg_name, app = app_def, None
543
- app_names = ["app", "application", "create_app", "make_app"]
544
- if ":" in pkg_name: # override defaults
545
- pkg_name, app_name = pkg_name.split(":", 1)
581
+ if ":" in test_app: # override defaults
582
+ pkg_name, app_name = test_app.split(":", 1)
546
583
  app_names = [app_name]
584
+ else:
585
+ pkg_name = test_app
586
+ app_names = ["app", "application", "create_app", "make_app"]
547
587
  pkg = importlib.import_module(pkg_name)
548
588
  # find app in package
589
+ app = None
549
590
  for name in app_names:
550
591
  if hasattr(pkg, name):
551
592
  app = getattr(pkg, name)
552
593
  if callable(app) and not hasattr(app, "test_client"):
553
594
  app = app()
554
595
  break
596
+ # none found
555
597
  if not app:
556
- raise FlaskTesterError(f"cannot find Flask app in {pkg_name}")
598
+ raise FlaskTesterError(f"cannot find Flask app in {pkg_name} ({test_app})")
557
599
  client = FlaskClient(authenticator, app.test_client(), default_login)
558
600
 
559
601
  return client
@@ -562,7 +604,7 @@ def _ft_client(authenticator):
562
604
  def ft_client(ft_authenticator):
563
605
  """Pytest Fixture: ft_client.
564
606
 
565
- Target environment variable:
607
+ Mandatory target environment variable:
566
608
 
567
609
  - ``FLASK_TESTER_APP``: find the Flask application, eg
568
610
  ``app:create_app`` for an internal test, or
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: FlaskTester
3
- Version: 3.6
3
+ Version: 4.1
4
4
  Summary: Pytest fixtures for Flask internal and external authenticated tests
5
5
  Author-email: Fabien Coelho <flask.tester@coelho.net>
6
6
  License: CC0
@@ -34,6 +34,7 @@ Requires-Dist: types-flask; extra == "dev"
34
34
  Requires-Dist: types-requests; extra == "dev"
35
35
  Requires-Dist: FlaskSimpleAuth>=30.0; extra == "dev"
36
36
  Requires-Dist: passlib; extra == "dev"
37
+ Requires-Dist: pydantic; extra == "dev"
37
38
  Provides-Extra: doc
38
39
  Requires-Dist: sphinx; extra == "doc"
39
40
  Requires-Dist: sphinx_rtd_theme; extra == "doc"
@@ -49,7 +50,7 @@ either with internal Flask tests (aka `test_client`) or external tests (with
49
50
  authentication and per-user cookies.
50
51
 
51
52
  Only one set of tests is needed, switching from internal to external is
52
- achieved through environment variables.
53
+ achieved by setting an environment variable.
53
54
 
54
55
  ![Status](https://github.com/zx80/flask-tester/actions/workflows/package.yml/badge.svg?branch=main&style=flat)
55
56
  ![Tests](https://img.shields.io/badge/tests-14%20✓-success)
@@ -123,7 +124,7 @@ The above test runs with [`tests/app.py`](tests/app.py)
123
124
  [Flask](https://flask.palletsprojects.com/)
124
125
  REST application back-end with password and token authentication based on
125
126
  [FlaskSimpleAuth](https://pypi.org/project/FlaskSimpleAuth/).
126
- The code uses _25_ lines of Python for implementing
127
+ The code uses _23_ lines of Python for implementing
127
128
  password (basic and parameters) and token authentications,
128
129
  admin group authorization, and routes for
129
130
  token generation (2), identity tests (2) and an incredible open cookie-based
@@ -6,7 +6,7 @@ either with internal Flask tests (aka `test_client`) or external tests (with
6
6
  authentication and per-user cookies.
7
7
 
8
8
  Only one set of tests is needed, switching from internal to external is
9
- achieved through environment variables.
9
+ achieved by setting an environment variable.
10
10
 
11
11
  ![Status](https://github.com/zx80/flask-tester/actions/workflows/package.yml/badge.svg?branch=main&style=flat)
12
12
  ![Tests](https://img.shields.io/badge/tests-14%20✓-success)
@@ -80,7 +80,7 @@ The above test runs with [`tests/app.py`](tests/app.py)
80
80
  [Flask](https://flask.palletsprojects.com/)
81
81
  REST application back-end with password and token authentication based on
82
82
  [FlaskSimpleAuth](https://pypi.org/project/FlaskSimpleAuth/).
83
- The code uses _25_ lines of Python for implementing
83
+ The code uses _23_ lines of Python for implementing
84
84
  password (basic and parameters) and token authentications,
85
85
  admin group authorization, and routes for
86
86
  token generation (2), identity tests (2) and an incredible open cookie-based
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "FlaskTester"
7
- version = "3.6"
7
+ version = "4.1"
8
8
  authors = [ { name = "Fabien Coelho", email = "flask.tester@coelho.net" } ]
9
9
  description = "Pytest fixtures for Flask internal and external authenticated tests"
10
10
  readme = "README.md"
@@ -33,7 +33,14 @@ package = "https://pypi.org/project/FlaskTester/"
33
33
 
34
34
  [project.optional-dependencies]
35
35
  # various dev tools
36
- dev = ["mypy", "pyright", "ruff", "coverage", "pymarkdownlnt!=0.9.5", "build", "twine", "wheel", "types-flask", "types-requests", "FlaskSimpleAuth>=30.0", "passlib"]
36
+ dev = [
37
+ # static checks…
38
+ "mypy", "pyright", "ruff", "coverage", "pymarkdownlnt!=0.9.5",
39
+ # packaging
40
+ "build", "twine", "wheel",
41
+ # tests
42
+ "types-flask", "types-requests", "FlaskSimpleAuth>=30.0", "passlib", "pydantic"
43
+ ]
37
44
  # documentation generation
38
45
  doc = ["sphinx", "sphinx_rtd_theme", "sphinx-autoapi", "sphinx-lint", "myst_parser"]
39
46
 
@@ -8,6 +8,7 @@ import http.server as htsv
8
8
  import threading
9
9
  import io
10
10
  import logging
11
+ import model
11
12
 
12
13
  logging.basicConfig(level=logging.INFO)
13
14
  # logging.basicConfig(level=logging.DEBUG)
@@ -22,7 +23,7 @@ os.environ.update(
22
23
 
23
24
  def test_sanity():
24
25
  # must provide url or package of Flask application to test
25
- assert "FLASK_TESTER_URL" in os.environ or "FLASK_TESTER_APP" in os.environ
26
+ assert "FLASK_TESTER_APP" in os.environ
26
27
  # log.debug(f"TEST_SEED={os.environ.get('TEST_SEED')}")
27
28
 
28
29
  # example from README.md
@@ -296,7 +297,7 @@ def test_request_client():
296
297
  try:
297
298
  client = ft.RequestClient(ft.Authenticator(), "http://localhost:8888")
298
299
  client.get("/", 200)
299
- client.check("GET", "/", 200)
300
+ client.request("GET", "/", 200)
300
301
  hello = io.BytesIO(b"hello world")
301
302
  client.post("/", 501, data={"hello": hello})
302
303
  hello = io.BytesIO(b"hello world")
@@ -309,10 +310,6 @@ def test_client_fixture():
309
310
  # ft_client coverage
310
311
  auth = ft._ft_authenticator()
311
312
  # check and save env
312
- url = None
313
- if "FLASK_TESTER_URL" in os.environ: # pragma: no cover
314
- url = os.environ["FLASK_TESTER_URL"]
315
- del os.environ["FLASK_TESTER_URL"]
316
313
  app = None
317
314
  if "FLASK_TESTER_APP" in os.environ:
318
315
  app = os.environ["FLASK_TESTER_APP"]
@@ -320,10 +317,10 @@ def test_client_fixture():
320
317
  # no url nor app, defaults to "app"
321
318
  init = ft._ft_client(auth)
322
319
  # url
323
- os.environ["FLASK_TESTER_URL"] = "http://localhost:5000"
320
+ os.environ["FLASK_TESTER_APP"] = "http://localhost:5000"
324
321
  init = ft._ft_client(auth)
325
322
  assert isinstance(init, ft.RequestClient)
326
- del os.environ["FLASK_TESTER_URL"]
323
+ del os.environ["FLASK_TESTER_APP"]
327
324
  # bad package
328
325
  os.environ["FLASK_TESTER_APP"] = "no_such_package"
329
326
  try:
@@ -340,7 +337,37 @@ def test_client_fixture():
340
337
  assert True, "expected error raised"
341
338
  del os.environ["FLASK_TESTER_APP"]
342
339
  # reset env
343
- if url: # pragma: no cover
344
- os.environ["FLASK_TESTER_URL"] = url
345
340
  if app:
346
341
  os.environ["FLASK_TESTER_APP"] = app
342
+
343
+ def test_classes(api):
344
+
345
+ def thing_eq(ta, tb):
346
+ tb = model.Thing1(**tb) if isinstance(tb, dict) else tb
347
+ return ta.tid == tb.tid and ta.name == tb.name and ta.owner == tb.owner
348
+
349
+ # Things
350
+ t0 = {"tid": 0, "name": "zero", "owner": "Rosalyn"}
351
+ t1 = model.Thing1(tid=1, name="one", owner="Susie")
352
+ t2 = model.Thing2(tid=2, name="two", owner="Calvin")
353
+ t3 = model.Thing3(tid=3, name="three", owner="Hobbes")
354
+
355
+ n = 0
356
+ # check all combinations
357
+ for path in ["/t0", "/t1", "/t2", "/t3"]:
358
+ for param in [t0, t1, t2, t3]:
359
+ for tclass in [model.Thing1, model.Thing2, model.Thing3]:
360
+ for method in ["GET", "POST"]:
361
+ for mode in ["data", "json"]:
362
+ n +=1
363
+ parameter = {mode: {"t": param}}
364
+ res = api.request(method, path, 200, **parameter)
365
+ assert res.is_json
366
+ json = res.json
367
+ assert isinstance(json, dict)
368
+ assert thing_eq(tclass(**json), param)
369
+ assert n == 192
370
+
371
+ # null translation
372
+ assert api.get("/t0", 200, json={"t": None}).json is None
373
+ assert api.get("/t0", 200, data={"t": None}).json is None
@@ -0,0 +1,28 @@
1
+ import os
2
+ import pytest
3
+ from FlaskTester import ft_client, ft_authenticator
4
+ import secret
5
+
6
+ os.environ.update(FLASK_TESTER_ALLOW="basic param none")
7
+
8
+ @pytest.fixture
9
+ def app(ft_client):
10
+ ft_client.setPass("calvin", secret.PASSES["calvin"])
11
+ ft_client.setCookie("calvin", "lang", "en")
12
+ ft_client.setPass("hobbes", secret.PASSES["hobbes"])
13
+ ft_client.setCookie("hobbes", "lang", "fr")
14
+ yield ft_client
15
+
16
+ def test_something(app):
17
+ # requires an authentication
18
+ app.get("/authenticated", 401, login=None)
19
+ app.get("/authenticated", 200, "Hello", login="calvin")
20
+ app.get("/authenticated", 200, "Bonjour", login="hobbes")
21
+ # only allowed to calvin
22
+ app.get("/only-admin", 401, login=None)
23
+ app.get("/only-admin", 200, "administrateur", login="calvin")
24
+ app.get("/only-admin", 403, "not in group", login="hobbes")
25
+ # no authentication required, but depends on lang
26
+ app.get("/open", 200, "Hello", login="calvin", auth="none")
27
+ app.get("/open", 200, "Bonjour", login="hobbes", auth="none")
28
+ app.get("/open", 200, "Guten Tag", login=None)
@@ -1,28 +0,0 @@
1
- import os
2
- import pytest
3
- from FlaskTester import ft_client, ft_authenticator
4
-
5
- os.environ.update(FLASK_TESTER_ALLOW="basic param none")
6
-
7
- @pytest.fixture
8
- def app(ft_client):
9
- ft_client.setPass("calvin", "clv-pw")
10
- ft_client.setCookie("calvin", "lang", "en")
11
- ft_client.setPass("hobbes", "hbs-pw")
12
- ft_client.setCookie("hobbes", "lang", "fr")
13
- yield ft_client
14
-
15
- def test_something(app):
16
- # requires an authentication
17
- app.get("/authenticated", 401, login=None)
18
- res = app.get("/authenticated", 200, login="calvin")
19
- assert "Hello" in res.text
20
- app.get("/authenticated", 200, "Bonjour", login="hobbes")
21
- # only allowed to calvin
22
- app.get("/only-calvin", 401, login=None)
23
- app.get("/only-calvin", 200, login="calvin")
24
- app.get("/only-calvin", 403, login="hobbes")
25
- # no authentication required, but depends on lang
26
- res = app.get("/no-auth", 200, login="calvin", auth="none")
27
- assert "Hello" in res.text
28
- app.get("/no-auth", 200, "Bonjour", login="hobbes", auth="none")
File without changes
File without changes
File without changes