FlaskTester 3.6__tar.gz → 4.0__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.0
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
@@ -49,7 +49,7 @@ either with internal Flask tests (aka `test_client`) or external tests (with
49
49
  authentication and per-user cookies.
50
50
 
51
51
  Only one set of tests is needed, switching from internal to external is
52
- achieved through environment variables.
52
+ achieved by setting an environment variable.
53
53
 
54
54
  ![Status](https://github.com/zx80/flask-tester/actions/workflows/package.yml/badge.svg?branch=main&style=flat)
55
55
  ![Tests](https://img.shields.io/badge/tests-14%20✓-success)
@@ -29,12 +29,15 @@ class _AssertError(FlaskTesterError):
29
29
  pass
30
30
 
31
31
 
32
- def _raiseError(msg: str):
33
- """Undocumented switch for FlaskTester own tests."""
32
+ def _pytestFail(msg: str):
33
+ """Undocumented switch for FlaskTester own tests.
34
+
35
+ A pytest test cannot check an expected pytest failure without failing.
36
+ """
34
37
  log.error(msg)
35
38
  if "FLASK_TESTER_TESTING" in os.environ:
36
39
  raise _AssertError(msg)
37
- else: # pragma: no cover # cannot cover an expected pytest failure!
40
+ else: # pragma: no cover
38
41
  pytest.fail(msg)
39
42
 
40
43
 
@@ -54,24 +57,24 @@ class Authenticator:
54
57
 
55
58
  Constructor parameters:
56
59
 
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``.
60
+ :param allow: List of allowed schemes.
61
+ defaults to ``["bearer", "basic", "param", "none"]``.
62
+ :param user: Parameter for user on ``param`` password authentication,
63
+ defaults to ``"USER"``.
64
+ :param pwd: Parameter for password on ``param`` password authentication,
65
+ defaults to ``"PASS"``.
66
+ :param login: Parameter for user on ``fake`` authentication,
67
+ defaults to ``"LOGIN"``.
68
+ :param bearer: Name of bearer scheme for token,
69
+ defaults to ``"Bearer"``.
70
+ :param header: Name of header for token,
71
+ defaults to ``"Auth"``.
72
+ :param cookie: Name of cookie for token,
73
+ defaults to ``"auth"``.
74
+ :param tparam: Name of parameter for token,
75
+ defaults to ``"AUTH"``.
76
+ :param ptype: Default parameter type, either *data* or *json*,
77
+ defaults to ``"data"``.
75
78
 
76
79
  Note: default values are consistent with `FlaskSimpleAuth <https://pypi.org/project/FlaskSimpleAuth/>`_.
77
80
  """
@@ -182,12 +185,12 @@ class Authenticator:
182
185
  def setAuth(self, login: str|None, kwargs: dict[str, Any], cookies: dict[str, str], auth: str|None = None):
183
186
  """Set request authentication.
184
187
 
185
- - ``login``: login target, None means no authentication.
186
- - ``kwargs``: request parameters.
187
- - ``cookies``: request cookies.
188
- - ``auth``: authentication method, default is None.
188
+ :param login: Login target, *None* means no authentication.
189
+ :param kwargs: Request parameters to modify.
190
+ :param cookies: Request cookies to modify.
191
+ :param auth: Authentication method, default is *None*.
189
192
 
190
- The default behavior is to try allowed schemes: tokens first,
193
+ The default behavior is to try allowed schemes: token first,
191
194
  then password, then fake.
192
195
  """
193
196
 
@@ -251,9 +254,9 @@ class Authenticator:
251
254
  class RequestFlaskResponse:
252
255
  """Wrapper to return a Flask-looking response from a request response.
253
256
 
254
- This only work for simple responses.
257
+ This only works for simple responses.
255
258
 
256
- Available attributes:
259
+ Available public attributes:
257
260
 
258
261
  - ``status_code``: integer status code.
259
262
  - ``data``: body as bytes.
@@ -265,7 +268,7 @@ class RequestFlaskResponse:
265
268
 
266
269
  Constructor parameter:
267
270
 
268
- - ``response``: from request.
271
+ :param response: Response from request.
269
272
  """
270
273
 
271
274
  def __init__(self, response):
@@ -289,8 +292,8 @@ class Client:
289
292
 
290
293
  Constructor parameters:
291
294
 
292
- - ``auth``: authenticator.
293
- - ``default_login``: if ``login`` is not set.
295
+ :param auth: Authenticator.
296
+ :param default_login: When ``login`` is not set.
294
297
  """
295
298
 
296
299
  def __init__(self, auth: Authenticator, default_login: str|None = None):
@@ -320,16 +323,16 @@ class Client:
320
323
 
321
324
  Mandatory parameters:
322
325
 
323
- - ``method``: HTTP method ("GET", "POST", "PATCH", "DELETE"…).
324
- - ``path``: local path under the base URL.
326
+ :param method: HTTP method ("GET", "POST", "PATCH", "DELETE"…).
327
+ :param path: Local path under the base URL.
325
328
 
326
329
  Optional parameters:
327
330
 
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…).
331
+ :param status: Expected HTTP status, *None* to skip status check.
332
+ :param content: Regular expression for response body, *None* to skip content check.
333
+ :param login: Authenticated user, use **explicit** *None* to skip default.
334
+ :param auth: Authentication scheme to use instead of default behavior.
335
+ :param **kwargs: More request parameters (headers, data, json…).
333
336
  """
334
337
 
335
338
  if "login" in kwargs:
@@ -349,48 +352,44 @@ class Client:
349
352
  # check status
350
353
  if status is not None:
351
354
  if res.status_code != status: # show error before aborting
352
- _raiseError(f"bad {status} result: {res.status_code} {res.text[:512]}...")
355
+ _pytestFail(f"bad {status} result: {res.status_code} {res.text[:512]}...")
353
356
 
354
357
  # check content
355
358
  if content is not None:
356
359
  if not re.search(content, res.text, re.DOTALL):
357
- _raiseError(f"cannot find {content} in {res.text[:512]}...")
360
+ _pytestFail(f"cannot find {content} in {res.text[:512]}...")
358
361
 
359
362
  return res
360
363
 
361
364
  def get(self, path: str, status: int|None = None, content: str|None = None, **kwargs):
362
- """HTTP GET request, see ``request`` parameters."""
365
+ """HTTP GET request, see `Client.request`."""
363
366
  return self.request("GET", path, status=status, content=content, **kwargs)
364
367
 
365
368
  def post(self, path: str, status: int|None = None, content: str|None = None, **kwargs):
366
- """HTTP POST request, see ``request`` parameters."""
369
+ """HTTP POST request, see `Client.request`."""
367
370
  return self.request("POST", path, status=status, content=content, **kwargs)
368
371
 
369
372
  def put(self, path: str, status: int|None = None, content: str|None = None, **kwargs):
370
- """HTTP PUT request, see ``request`` parameters."""
373
+ """HTTP PUT request, see `Client.request`."""
371
374
  return self.request("PUT", path, status=status, content=content, **kwargs)
372
375
 
373
376
  def patch(self, path: str, status: int|None = None, content: str|None = None, **kwargs):
374
- """HTTP PATCH request, see ``request`` parameters."""
377
+ """HTTP PATCH request, see `Client.request`."""
375
378
  return self.request("PATCH", path, status=status, content=content, **kwargs)
376
379
 
377
380
  def delete(self, path: str, status: int|None = None, content: str|None = None, **kwargs):
378
- """HTTP DELETE request, see ``request`` parameters."""
381
+ """HTTP DELETE request, see `Client.request`."""
379
382
  return self.request("DELETE", path, status=status, content=content, **kwargs)
380
383
 
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
384
 
386
385
  class RequestClient(Client):
387
386
  """Request-based test provider.
388
387
 
389
388
  Constructor parameters:
390
389
 
391
- - ``auth``: authenticator.
392
- - ``base_url``: target server.
393
- - ``default_login``: if ``login`` is not set.
390
+ :param auth: Authenticator.
391
+ :param base_url: Target server.
392
+ :param default_login: When ``login`` is not set.
394
393
  """
395
394
 
396
395
  def __init__(self, auth: Authenticator, base_url: str, default_login=None):
@@ -434,9 +433,9 @@ class FlaskClient(Client):
434
433
 
435
434
  Constructor parameters:
436
435
 
437
- - ``auth``: authenticator.
438
- - ``client``: Flask actual ``test_client``.
439
- - ``default_login``: if ``login`` is not set.
436
+ :param auth: Authenticator.
437
+ :param client: Flask actual ``test_client``.
438
+ :param default_login: When ``login`` is not set.
440
439
 
441
440
  Note: this client handles `cookies`.
442
441
  """
@@ -499,29 +498,29 @@ def ft_authenticator():
499
498
  Environment variables:
500
499
 
501
500
  - ``FLASK_TESTER_LOG_LEVEL``: package log level in
502
- ``DEBUG INFO WARNING ERROR CRITICAL NOSET``.
503
- Default is ``NOTSET``.
501
+ ``DEBUG INFO WARNING ERROR CRITICAL NOSET``,
502
+ defaults to ``NOTSET``.
504
503
  - ``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.
504
+ ``basic param bearer header cookie tparam fake none``,
505
+ defaults to ``bearer basic param none``.
506
+ - ``FLASK_TESTER_USER``: user login parameter for ``param`` authentication,
507
+ defaults to ``USER``.
508
+ - ``FLASK_TESTER_PASS``: user password parameter for ``param`` authentication,
509
+ defaults to ``PASS``.
510
+ - ``FLASK_TESTER_LOGIN``: user login parameter for ``fake`` authentication,
511
+ defaults to ``LOGIN``.
512
+ - ``FLASK_TESTER_BEARER``: bearer name for *token* authentication,
513
+ defaults to ``Bearer``.
514
+ - ``FLASK_TESTER_HEADER``: header name for *token* authentication,
515
+ defaults to ``Auth``.
516
+ - ``FLASK_TESTER_COOKIE``: cookie name for *token* authentication,
517
+ defaults to ``auth``.
518
+ - ``FLASK_TESTER_TPARAM``: parameter for *token* authentication,
519
+ defaults to ``AUTH``.
520
+ - ``FLASK_TESTER_PTYPE``: default parameter type, ``data`` or ``json``,
521
+ defaults to ``data``.
522
+ - ``FLASK_TESTER_AUTH``: initial comma-separated list of *login:password*,
523
+ defaults to not set.
525
524
  """
526
525
 
527
526
  yield _ft_authenticator()
@@ -532,28 +531,30 @@ def _ft_client(authenticator):
532
531
  default_login = os.environ.get("FLASK_TESTER_DEFAULT", None)
533
532
  client: Client
534
533
 
535
- app_def = os.environ.get("FLASK_TESTER_URL", "app")
536
- app_def = os.environ.get("FLASK_TESTER_APP", app_def)
534
+ test_app = os.environ.get("FLASK_TESTER_APP", "app")
537
535
 
538
- if app_def.startswith("http://") or app_def.startswith("https://"):
539
- client = RequestClient(authenticator, app_def, default_login)
536
+ if test_app.startswith("http://") or test_app.startswith("https://"):
537
+ client = RequestClient(authenticator, test_app, default_login)
540
538
  else:
541
539
  # 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)
540
+ if ":" in test_app: # override defaults
541
+ pkg_name, app_name = test_app.split(":", 1)
546
542
  app_names = [app_name]
543
+ else:
544
+ pkg_name = test_app
545
+ app_names = ["app", "application", "create_app", "make_app"]
547
546
  pkg = importlib.import_module(pkg_name)
548
547
  # find app in package
548
+ app = None
549
549
  for name in app_names:
550
550
  if hasattr(pkg, name):
551
551
  app = getattr(pkg, name)
552
552
  if callable(app) and not hasattr(app, "test_client"):
553
553
  app = app()
554
554
  break
555
+ # none found
555
556
  if not app:
556
- raise FlaskTesterError(f"cannot find Flask app in {pkg_name}")
557
+ raise FlaskTesterError(f"cannot find Flask app in {pkg_name} ({test_app})")
557
558
  client = FlaskClient(authenticator, app.test_client(), default_login)
558
559
 
559
560
  return client
@@ -562,7 +563,7 @@ def _ft_client(authenticator):
562
563
  def ft_client(ft_authenticator):
563
564
  """Pytest Fixture: ft_client.
564
565
 
565
- Target environment variable:
566
+ Mandatory target environment variable:
566
567
 
567
568
  - ``FLASK_TESTER_APP``: find the Flask application, eg
568
569
  ``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.0
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
@@ -49,7 +49,7 @@ either with internal Flask tests (aka `test_client`) or external tests (with
49
49
  authentication and per-user cookies.
50
50
 
51
51
  Only one set of tests is needed, switching from internal to external is
52
- achieved through environment variables.
52
+ achieved by setting an environment variable.
53
53
 
54
54
  ![Status](https://github.com/zx80/flask-tester/actions/workflows/package.yml/badge.svg?branch=main&style=flat)
55
55
  ![Tests](https://img.shields.io/badge/tests-14%20✓-success)
@@ -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)
@@ -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.0"
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"
@@ -22,7 +22,7 @@ os.environ.update(
22
22
 
23
23
  def test_sanity():
24
24
  # 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
25
+ assert "FLASK_TESTER_APP" in os.environ
26
26
  # log.debug(f"TEST_SEED={os.environ.get('TEST_SEED')}")
27
27
 
28
28
  # example from README.md
@@ -296,7 +296,7 @@ def test_request_client():
296
296
  try:
297
297
  client = ft.RequestClient(ft.Authenticator(), "http://localhost:8888")
298
298
  client.get("/", 200)
299
- client.check("GET", "/", 200)
299
+ client.request("GET", "/", 200)
300
300
  hello = io.BytesIO(b"hello world")
301
301
  client.post("/", 501, data={"hello": hello})
302
302
  hello = io.BytesIO(b"hello world")
@@ -309,10 +309,6 @@ def test_client_fixture():
309
309
  # ft_client coverage
310
310
  auth = ft._ft_authenticator()
311
311
  # 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
312
  app = None
317
313
  if "FLASK_TESTER_APP" in os.environ:
318
314
  app = os.environ["FLASK_TESTER_APP"]
@@ -320,10 +316,10 @@ def test_client_fixture():
320
316
  # no url nor app, defaults to "app"
321
317
  init = ft._ft_client(auth)
322
318
  # url
323
- os.environ["FLASK_TESTER_URL"] = "http://localhost:5000"
319
+ os.environ["FLASK_TESTER_APP"] = "http://localhost:5000"
324
320
  init = ft._ft_client(auth)
325
321
  assert isinstance(init, ft.RequestClient)
326
- del os.environ["FLASK_TESTER_URL"]
322
+ del os.environ["FLASK_TESTER_APP"]
327
323
  # bad package
328
324
  os.environ["FLASK_TESTER_APP"] = "no_such_package"
329
325
  try:
@@ -340,7 +336,5 @@ def test_client_fixture():
340
336
  assert True, "expected error raised"
341
337
  del os.environ["FLASK_TESTER_APP"]
342
338
  # reset env
343
- if url: # pragma: no cover
344
- os.environ["FLASK_TESTER_URL"] = url
345
339
  if app:
346
340
  os.environ["FLASK_TESTER_APP"] = app
@@ -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