FlaskTester 4.0__tar.gz → 4.2__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.
- {flasktester-4.0 → flasktester-4.2}/FlaskTester.egg-info/PKG-INFO +3 -2
- {flasktester-4.0 → flasktester-4.2}/FlaskTester.egg-info/requires.txt +1 -0
- {flasktester-4.0 → flasktester-4.2}/FlaskTester.py +45 -1
- {flasktester-4.0 → flasktester-4.2}/PKG-INFO +3 -2
- {flasktester-4.0 → flasktester-4.2}/README.md +1 -1
- {flasktester-4.0 → flasktester-4.2}/pyproject.toml +9 -2
- {flasktester-4.0 → flasktester-4.2}/tests/test_app.py +45 -0
- {flasktester-4.0 → flasktester-4.2}/FlaskTester.egg-info/SOURCES.txt +0 -0
- {flasktester-4.0 → flasktester-4.2}/FlaskTester.egg-info/dependency_links.txt +0 -0
- {flasktester-4.0 → flasktester-4.2}/FlaskTester.egg-info/top_level.txt +0 -0
- {flasktester-4.0 → flasktester-4.2}/LICENSE +0 -0
- {flasktester-4.0 → flasktester-4.2}/MANIFEST.in +0 -0
- {flasktester-4.0 → flasktester-4.2}/setup.cfg +0 -0
- {flasktester-4.0 → flasktester-4.2}/tests/test_app2.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: FlaskTester
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.2
|
|
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"
|
|
@@ -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
|
|
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
|
|
@@ -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
|
|
|
@@ -346,17 +348,59 @@ class Client:
|
|
|
346
348
|
cookies.update(kwargs["cookies"])
|
|
347
349
|
del kwargs["cookies"]
|
|
348
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)):
|
|
382
|
+
# FIXME bool seems KO
|
|
383
|
+
pass
|
|
384
|
+
elif isinstance(val, (list, dict)):
|
|
385
|
+
data_param[name] = json.dumps(val)
|
|
386
|
+
elif "model_dump_json" in val.__dir__() and callable(val.model_dump_json):
|
|
387
|
+
data_param[name] = val.model_dump_json()
|
|
388
|
+
else:
|
|
389
|
+
data_param[name] = json.dumps(dataclasses.asdict(val))
|
|
390
|
+
|
|
349
391
|
self._auth.setAuth(login, kwargs, cookies, auth=auth)
|
|
350
392
|
res = self._request(method, path, cookies, **kwargs) # type: ignore
|
|
351
393
|
|
|
352
394
|
# check status
|
|
353
395
|
if status is not None:
|
|
354
396
|
if res.status_code != status: # show error before aborting
|
|
397
|
+
# FIXME what if the useful part is at the end?
|
|
355
398
|
_pytestFail(f"bad {status} result: {res.status_code} {res.text[:512]}...")
|
|
356
399
|
|
|
357
400
|
# check content
|
|
358
401
|
if content is not None:
|
|
359
402
|
if not re.search(content, res.text, re.DOTALL):
|
|
403
|
+
# FIXME what if the useful part is at the end?
|
|
360
404
|
_pytestFail(f"cannot find {content} in {res.text[:512]}...")
|
|
361
405
|
|
|
362
406
|
return res
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: FlaskTester
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.2
|
|
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"
|
|
@@ -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
|
|
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
|
|
@@ -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
|
|
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 = "4.
|
|
7
|
+
version = "4.2"
|
|
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 = [
|
|
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)
|
|
@@ -338,3 +339,47 @@ def test_client_fixture():
|
|
|
338
339
|
# reset env
|
|
339
340
|
if app:
|
|
340
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
|
+
# simple types 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
|
|
374
|
+
assert api.get("/t0", 200, json={"t": True}).json == True
|
|
375
|
+
assert api.get("/t0", 200, json={"t": False}).json == False
|
|
376
|
+
# non working, bad JsonData conversion
|
|
377
|
+
api.get("/t0", 400, data={"t": True})
|
|
378
|
+
api.get("/t0", 400, data={"t": False})
|
|
379
|
+
assert api.get("/t0", 200, json={"t": 2345}).json == 2345
|
|
380
|
+
assert api.get("/t0", 200, data={"t": 5432}).json == 5432
|
|
381
|
+
assert api.get("/t0", 200, json={"t": 23.45}).json == 23.45
|
|
382
|
+
assert api.get("/t0", 200, data={"t": 54.32}).json == 54.32
|
|
383
|
+
assert api.get("/t0", 200, json={"t": "Susie"}).json == "Susie"
|
|
384
|
+
api.get("/t0", 400, data={"t": "Susie"}) # raw str is not JSON
|
|
385
|
+
assert api.get("/t0", 200, data={"t": "\"Susie\""}).json == "Susie"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|