FlaskTester 5.0__tar.gz → 6.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,16 +1,15 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: FlaskTester
3
- Version: 5.0
3
+ Version: 6.0
4
4
  Summary: Pytest fixtures for Flask internal and external authenticated tests
5
5
  Author-email: Fabien Coelho <flask.tester@coelho.net>
6
- License: CC0
6
+ License-Expression: CC0-1.0
7
7
  Project-URL: repository, https://github.com/zx80/flask-tester
8
8
  Project-URL: documentation, https://zx80.github.io/flask-tester/
9
9
  Project-URL: issues, https://github.com/zx80/flask-tester/issues
10
10
  Project-URL: package, https://pypi.org/project/FlaskTester/
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Developers
13
- Classifier: License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
14
13
  Classifier: Programming Language :: Python
15
14
  Classifier: Environment :: Web Environment
16
15
  Classifier: Framework :: Flask
@@ -26,6 +25,7 @@ Requires-Dist: pyright; extra == "dev"
26
25
  Requires-Dist: ruff; extra == "dev"
27
26
  Requires-Dist: coverage; extra == "dev"
28
27
  Requires-Dist: pymarkdownlnt!=0.9.5; extra == "dev"
28
+ Requires-Dist: flake8; extra == "dev"
29
29
  Requires-Dist: types-flask; extra == "dev"
30
30
  Requires-Dist: types-requests; extra == "dev"
31
31
  Requires-Dist: FlaskSimpleAuth>=35.0; extra == "dev"
@@ -41,6 +41,7 @@ Provides-Extra: pub
41
41
  Requires-Dist: build; extra == "pub"
42
42
  Requires-Dist: twine; extra == "pub"
43
43
  Requires-Dist: wheel; extra == "pub"
44
+ Dynamic: license-file
44
45
 
45
46
  # FlaskTester - Pytest fixtures for Flask internal and external authenticated tests
46
47
 
@@ -7,6 +7,4 @@ FlaskTester.egg-info/PKG-INFO
7
7
  FlaskTester.egg-info/SOURCES.txt
8
8
  FlaskTester.egg-info/dependency_links.txt
9
9
  FlaskTester.egg-info/requires.txt
10
- FlaskTester.egg-info/top_level.txt
11
- tests/test_app.py
12
- tests/test_app2.py
10
+ FlaskTester.egg-info/top_level.txt
@@ -7,6 +7,7 @@ pyright
7
7
  ruff
8
8
  coverage
9
9
  pymarkdownlnt!=0.9.5
10
+ flake8
10
11
  types-flask
11
12
  types-requests
12
13
  FlaskSimpleAuth>=35.0
@@ -7,7 +7,8 @@ import os
7
7
  import io
8
8
  import re
9
9
  # Python >= 3.11: Self
10
- from typing import Any, Callable
10
+ from typing import Any
11
+ from collections.abc import Callable
11
12
  import importlib
12
13
  import logging
13
14
  import pytest # for explicit fail calls, see _pytestFail
@@ -90,23 +91,26 @@ class Authenticator:
90
91
  _AUTH_SCHEMES.update(_TOKEN_SCHEMES)
91
92
  _AUTH_SCHEMES.update(_PASS_SCHEMES)
92
93
 
94
+ # should not be mutable
95
+ _AUTH_DEFAULT_SCHEMES = ["bearer", "basic", "param", "none"]
96
+
93
97
  # authenticator login/pass hook
94
98
  _AuthHook = Callable[[str, str|None], None]
95
99
 
96
100
  def __init__(self,
97
- allow: list[str] = ["bearer", "basic", "param", "none"],
98
- # parameter names for "basic" and "param"
99
- user: str = "USER",
100
- pwd: str = "PASS",
101
- # parameter name for "fake"
102
- login: str = "LOGIN",
103
- # 4 token options
104
- bearer: str = "Bearer",
105
- header: str = "Auth",
106
- cookie: str = "auth",
107
- tparam: str = "AUTH",
108
- ptype: str = "data",
109
- ):
101
+ allow: list[str] = _AUTH_DEFAULT_SCHEMES,
102
+ # parameter names for "basic" and "param"
103
+ user: str = "USER",
104
+ pwd: str = "PASS",
105
+ # parameter name for "fake"
106
+ login: str = "LOGIN",
107
+ # 4 token options
108
+ bearer: str = "Bearer",
109
+ header: str = "Auth",
110
+ cookie: str = "auth",
111
+ tparam: str = "AUTH",
112
+ ptype: str = "data",
113
+ ):
110
114
 
111
115
  self._has_pass, self._has_token = False, False
112
116
  for auth in allow:
@@ -139,8 +143,7 @@ class Authenticator:
139
143
  def _set(self, key: str, val: str|None, store: dict[str, str]):
140
144
  """Set a key/value in a directory, with None for delete."""
141
145
  if val is None:
142
- if key in store:
143
- del store[key]
146
+ store.pop(key, None)
144
147
  else:
145
148
  assert isinstance(val, str)
146
149
  store[key] = val
@@ -315,6 +318,7 @@ class Client:
315
318
  AuthHook = Callable[[Any, str, str|None], None] # type: ignore
316
319
 
317
320
  def __init__(self, auth: Authenticator, default_login: str|None = None):
321
+ self._calls = 0
318
322
  self._auth = auth
319
323
  self._cookies: dict[str, dict[str, str]] = {} # login -> name -> value
320
324
  self._default_login = default_login
@@ -357,6 +361,8 @@ class Client:
357
361
  :param **kwargs: More request parameters (headers, data, json…).
358
362
  """
359
363
 
364
+ self._calls += 1
365
+
360
366
  if "login" in kwargs:
361
367
  login = kwargs["login"]
362
368
  del kwargs["login"]
@@ -387,7 +393,7 @@ class Client:
387
393
  elif "model_dump" in val.__dir__() and callable(val.model_dump):
388
394
  # probably pydantic
389
395
  json_param[name] = val.model_dump()
390
- else: # pydantic or standard dataclasses?
396
+ else: # pydantic or standard dataclasses?
391
397
  json_param[name] = dataclasses.asdict(val)
392
398
 
393
399
  # convert data parameters to simple strings
@@ -415,16 +421,14 @@ class Client:
415
421
  res = self._request(method, path, cookies, **kwargs) # type: ignore
416
422
 
417
423
  # check status
418
- if status is not None:
419
- if res.status_code != status: # show error before aborting
420
- # FIXME what if the useful part is at the end?
421
- _pytestFail(f"bad {status} result: {res.status_code} {res.text[:512]}...")
424
+ if status is not None and res.status_code != status: # show error before aborting
425
+ # FIXME what if the useful part is at the end?
426
+ _pytestFail(f"bad {status} result: {res.status_code} {res.text[:512]}...")
422
427
 
423
428
  # check content
424
- if content is not None:
425
- if not re.search(content, res.text, re.DOTALL):
426
- # FIXME what if the useful part is at the end?
427
- _pytestFail(f"cannot find {content} in {res.text[:512]}...")
429
+ if content is not None and not re.search(content, res.text, re.DOTALL):
430
+ # FIXME what if the useful part is at the end?
431
+ _pytestFail(f"cannot find {content} in {res.text[:512]}...")
428
432
 
429
433
  return res
430
434
 
@@ -448,6 +452,12 @@ class Client:
448
452
  """HTTP DELETE request, see `Client.request`."""
449
453
  return self.request("DELETE", path, status=status, content=content, **kwargs)
450
454
 
455
+ def __str__(self):
456
+ return f"client calls: {self._calls}"
457
+
458
+ def __del__(self):
459
+ log.info(f"ending, {self}")
460
+
451
461
 
452
462
  class RequestClient(Client):
453
463
  """Request-based test provider.
@@ -526,6 +536,7 @@ class FlaskClient(Client):
526
536
 
527
537
  return self._client.open(method=method, path=path, **kwargs)
528
538
 
539
+
529
540
  def _ft_authenticator():
530
541
  """Fixture implementation, separated for testing purposes."""
531
542
 
@@ -559,6 +570,7 @@ def _ft_authenticator():
559
570
 
560
571
  return auth
561
572
 
573
+
562
574
  @pytest.fixture
563
575
  def ft_authenticator():
564
576
  """Pytest Fixture: ft_authenticator.
@@ -593,6 +605,7 @@ def ft_authenticator():
593
605
 
594
606
  yield _ft_authenticator()
595
607
 
608
+
596
609
  def _ft_client(authenticator):
597
610
  """Fixture implementation separated for testing."""
598
611
 
@@ -601,7 +614,7 @@ def _ft_client(authenticator):
601
614
 
602
615
  test_app = os.environ.get("FLASK_TESTER_APP", "app")
603
616
 
604
- if test_app.startswith("http://") or test_app.startswith("https://"):
617
+ if test_app.startswith(("http://", "https://")):
605
618
  client = RequestClient(authenticator, test_app, default_login)
606
619
  else:
607
620
  # load app package
@@ -627,6 +640,7 @@ def _ft_client(authenticator):
627
640
 
628
641
  return client
629
642
 
643
+
630
644
  @pytest.fixture
631
645
  def ft_client(ft_authenticator):
632
646
  """Pytest Fixture: ft_client.
@@ -0,0 +1 @@
1
+ exclude docs/* tests/* */Makefile
@@ -1,16 +1,15 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: FlaskTester
3
- Version: 5.0
3
+ Version: 6.0
4
4
  Summary: Pytest fixtures for Flask internal and external authenticated tests
5
5
  Author-email: Fabien Coelho <flask.tester@coelho.net>
6
- License: CC0
6
+ License-Expression: CC0-1.0
7
7
  Project-URL: repository, https://github.com/zx80/flask-tester
8
8
  Project-URL: documentation, https://zx80.github.io/flask-tester/
9
9
  Project-URL: issues, https://github.com/zx80/flask-tester/issues
10
10
  Project-URL: package, https://pypi.org/project/FlaskTester/
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Developers
13
- Classifier: License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
14
13
  Classifier: Programming Language :: Python
15
14
  Classifier: Environment :: Web Environment
16
15
  Classifier: Framework :: Flask
@@ -26,6 +25,7 @@ Requires-Dist: pyright; extra == "dev"
26
25
  Requires-Dist: ruff; extra == "dev"
27
26
  Requires-Dist: coverage; extra == "dev"
28
27
  Requires-Dist: pymarkdownlnt!=0.9.5; extra == "dev"
28
+ Requires-Dist: flake8; extra == "dev"
29
29
  Requires-Dist: types-flask; extra == "dev"
30
30
  Requires-Dist: types-requests; extra == "dev"
31
31
  Requires-Dist: FlaskSimpleAuth>=35.0; extra == "dev"
@@ -41,6 +41,7 @@ Provides-Extra: pub
41
41
  Requires-Dist: build; extra == "pub"
42
42
  Requires-Dist: twine; extra == "pub"
43
43
  Requires-Dist: wheel; extra == "pub"
44
+ Dynamic: license-file
44
45
 
45
46
  # FlaskTester - Pytest fixtures for Flask internal and external authenticated tests
46
47
 
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "FlaskTester"
7
- version = "5.0"
7
+ version = "6.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"
11
- license = { text = "CC0" }
11
+ license = "CC0-1.0"
12
12
  requires-python = ">=3.10"
13
13
  dependencies = [
14
14
  "requests",
@@ -17,7 +17,6 @@ dependencies = [
17
17
  classifiers = [
18
18
  "Development Status :: 5 - Production/Stable",
19
19
  "Intended Audience :: Developers",
20
- "License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication",
21
20
  "Programming Language :: Python",
22
21
  "Environment :: Web Environment",
23
22
  "Framework :: Flask",
@@ -34,7 +33,7 @@ package = "https://pypi.org/project/FlaskTester/"
34
33
  # various dev tools
35
34
  dev = [
36
35
  # static checks…
37
- "mypy", "pyright", "ruff", "coverage", "pymarkdownlnt!=0.9.5",
36
+ "mypy", "pyright", "ruff", "coverage", "pymarkdownlnt!=0.9.5", "flake8",
38
37
  # tests
39
38
  "types-flask", "types-requests", "FlaskSimpleAuth>=35.0", "bcrypt", "pydantic"
40
39
  ]
@@ -48,3 +47,7 @@ py-modules = [ "FlaskTester" ]
48
47
  [tool.black]
49
48
  line-length = 100
50
49
  target-version = ["py310"]
50
+
51
+ [tool.ruff.lint]
52
+ # ruff rules are what they are, but they are not always right
53
+ ignore = ["BLE001", "B006", "I001", "SIM114", "RUF012", "PIE790"]
@@ -1 +0,0 @@
1
- exclude docs/* */Makefile
@@ -1,392 +0,0 @@
1
- import os
2
- import pytest
3
- import FlaskSimpleAuth as fsa
4
- import FlaskTester as ft
5
- from FlaskTester import ft_client, ft_authenticator
6
- import secret
7
- import http.server as htsv
8
- import threading
9
- import io
10
- import logging
11
- import model
12
-
13
- logging.basicConfig(level=logging.INFO)
14
- # logging.basicConfig(level=logging.DEBUG)
15
- log = logging.getLogger("test")
16
-
17
- # set authn for ft_authenticator
18
- os.environ.update(
19
- FLASK_TESTER_TESTING="*",
20
- FLASK_TESTER_ALLOW="bearer basic param none",
21
- FLASK_TESTER_AUTH=",".join(f"{l}:{p}" for l, p in secret.PASSES.items()),
22
- )
23
-
24
- def test_sanity():
25
- # must provide url or package of Flask application to test
26
- assert "FLASK_TESTER_APP" in os.environ
27
- # log.debug(f"TEST_SEED={os.environ.get('TEST_SEED')}")
28
-
29
- # example from README.md
30
- def authHook(api, user: str, pwd: str|None):
31
- if pwd is not None: # get token
32
- try:
33
- res = api.get("/login", login=user, auth="basic", status=200)
34
- api.setToken(user, res.json["token"])
35
- except ft.FlaskTesterError as e: # pragma: no cover
36
- log.warning(f"error: {e}")
37
- else: # cleanup
38
- api.setToken(user, None)
39
-
40
- @pytest.fixture
41
- def app(ft_client):
42
- # hook when adding login/passwords
43
- ft_client.setHook(authHook)
44
- # Calvin authentication
45
- ft_client.setPass("calvin", secret.PASSES["calvin"])
46
- ft_client.setCookie("hobbes", "lang", "fr")
47
- # Hobbes authentication
48
- ft_client.setPass("hobbes", secret.PASSES["hobbes"])
49
- ft_client.setCookie("calvin", "lang", "en")
50
- # return working client
51
- yield ft_client
52
- # cleanup
53
- ft_client.setPass("calvin", None)
54
- ft_client.setPass("hobbes", None)
55
-
56
- def test_app_admin(app): # GET /admin
57
- app.get("/admin", login=None, status=401)
58
- for auth in ["bearer", "basic", "param"]:
59
- res = app.get("/admin", login="calvin", auth=auth, status=200)
60
- assert res.json["user"] == "calvin" and res.json["isadmin"]
61
- res = app.get("/admin", login="hobbes", auth=auth, status=403)
62
- assert 'not in group "ADMIN"' in res.text
63
-
64
- def test_app_who_am_i(app): # GET /who-am-i
65
- app.get("/who-am-i", login=None, status=401)
66
- for auth in ["bearer", "basic", "param"]:
67
- res = app.get("/who-am-i", login="calvin", auth=auth, status=200)
68
- assert res.json["lang"] == "en" and res.json["isadmin"]
69
- res = app.get("/who-am-i", login="hobbes", auth=auth, status=200)
70
- assert res.json["lang"] == "fr" and not res.json["isadmin"]
71
-
72
- @pytest.fixture
73
- def api(ft_client):
74
- # set a default
75
- ft_client._default_login = "calvin"
76
- # bad password and token
77
- ft_client.setPass("moe", None)
78
- ft_client.setToken("moe", None)
79
- ft_client.setPass("moe", "bad password")
80
- ft_client.setToken("moe", "bad token")
81
- # set language cookies
82
- ft_client.setCookie("calvin", "lang", "en")
83
- ft_client.setCookie("hobbes", "lang", "fr")
84
- # get valid tokens using password authn on /login
85
- res = ft_client.get("/login", login="calvin", auth="basic", status=200)
86
- assert res.json["user"] == "calvin"
87
- ft_client._auth.setToken("calvin", res.json["token"])
88
- res = ft_client.post("/login", login="hobbes", auth="param", status=201, data={})
89
- assert res.json["user"] == "hobbes"
90
- ft_client._auth.setToken("hobbes", res.json["token"])
91
- res = ft_client.post("/login", login="susie", auth="param", status=201, json={})
92
- assert res.json["user"] == "susie"
93
- ft_client._auth.setToken("susie", res.json["token"])
94
- ft_client.get("/login", login="calvin", auth="none", status=401)
95
- # check that token auth and cookie is ok
96
- res = ft_client.get("/who-am-i", login="calvin", status=200, auth="bearer")
97
- assert res.json["user"] == "calvin" and res.json["lang"] == "en"
98
- res = ft_client.get("/who-am-i", login="hobbes", status=200, auth="bearer")
99
- assert res.json["user"] == "hobbes" and res.json["lang"] == "fr"
100
- res = ft_client.get("/who-am-i", login="susie", status=200, auth="bearer")
101
- assert res.json["user"] == "susie" and res.json["lang"] is None
102
- # with defaults
103
- res = ft_client.get("/who-am-i", auth="basic", status=200)
104
- assert res.json["user"] == "calvin"
105
- res = ft_client.get("/who-am-i", auth="param", status=200)
106
- assert res.json["user"] == "calvin"
107
- res = ft_client.get("/who-am-i", auth="bearer", status=200)
108
- assert res.json["user"] == "calvin"
109
- res = ft_client.get("/who-am-i", status=200)
110
- assert res.json["user"] == "calvin"
111
- # ready for testing routes
112
- yield ft_client
113
-
114
- def test_admin(api):
115
- # check authentication schemes
116
- for auth in (None, "basic", "param", "bearer"):
117
- api.get("/admin", status=200, login="calvin", auth=auth)
118
- api.get("/admin", 200, login="susie", auth=auth)
119
- api.get("/admin", 403, login="hobbes", auth=auth)
120
- api.get("/admin", 401, login="moe", auth=auth)
121
- api.get("/admin", 401, login=None, auth=auth)
122
- # test status error
123
- try:
124
- api.get("/admin", status=599, login="calvin", auth=auth)
125
- pytest.fail("assert on status must fail") # pragma: no cover
126
- except ft._AssertError as e:
127
- assert "200" in str(e)
128
- # test content error
129
- try:
130
- api.get("/admin", status=200, login="calvin", auth=auth, content="NOT THERE")
131
- pytest.fail("assert on content must fail") # pragma: no cover
132
- except ft._AssertError as e:
133
- assert "NOT THERE" in str(e)
134
-
135
- def test_errors(api):
136
- # these schemes are not allowed
137
- for scheme in ("header", "cookie", "fake", "tparam"):
138
- try:
139
- api.get("/login", login="calvin", auth=scheme)
140
- pytest.fail("must raise an exception") # pragma: no cover
141
- except ft.AuthError as e:
142
- assert "auth is not allowed" in str(e)
143
- try:
144
- api.get("/login", login="calvin", auth="foobla")
145
- pytest.fail("must raise an exception") # pragma: no cover
146
- except ft.AuthError as e:
147
- assert "unexpected auth" in str(e)
148
-
149
- def test_methods(api):
150
- res = api.get("/who-am-i", login="susie", status=200, cookies={"lang": "it"})
151
- assert res.json["lang"] == "it"
152
- api.post("/who-am-i", login="hobbes", status=405)
153
- api.put("/who-am-i", login="hobbes", status=405)
154
- api.patch("/who-am-i", login="hobbes", status=405)
155
- api.delete("/who-am-i", login="hobbes", status=405)
156
-
157
- def test_hello(api):
158
- res = api.get("/hello", login="calvin", auth="none", status=200)
159
- assert res.json["lang"] == "en" and res.json["hello"] == "Hi"
160
- assert res.headers["FSA-User"] == "None (None)"
161
- res = api.get("/hello", login="hobbes", auth="none", status=200)
162
- assert res.json["lang"] == "fr" and res.json["hello"] == "Salut"
163
- assert res.headers["FSA-User"] == "None (None)"
164
- res = api.get("/hello", login="susie", auth="none", status=200, cookies={"lang": "it"})
165
- assert res.json["lang"] == "it" and res.json["hello"] == "Ciao"
166
- assert res.headers["FSA-User"] == "None (None)"
167
- res = api.get("/hello", login="moe", auth="none", status=200)
168
- assert res.json["lang"] == "en" and res.json["hello"] == "Hi"
169
- assert res.headers["FSA-User"] == "None (None)"
170
-
171
- def test_authenticator_token():
172
- # all token carriers
173
- auth = ft.Authenticator(allow=["bearer", "header", "tparam", "cookie"])
174
- auth.setToken("calvin", "clv-token")
175
- auth.setToken("susie", "ss-token")
176
- auth.setToken("hobbes", "hbs-token")
177
- auth.setToken("moe", "m-token")
178
- auth.setCookie("calvin", "what", "clv-cookie")
179
- kwargs, cookies = {}, {}
180
- auth.setAuth("calvin", kwargs, cookies, auth="bearer")
181
- assert kwargs["headers"]["Authorization"] == "Bearer clv-token"
182
- assert cookies["what"] == "clv-cookie"
183
- kwargs, cookies = {}, {}
184
- auth.setAuth("susie", kwargs, cookies, auth="header")
185
- assert kwargs["headers"]["Auth"] == "ss-token"
186
- assert not cookies
187
- kwargs, cookies = {"data": {}}, {}
188
- auth.setAuth("hobbes", kwargs, cookies, auth="tparam")
189
- assert kwargs["data"]["AUTH"] == "hbs-token"
190
- assert not cookies
191
- kwargs, cookies = {"json": {}}, {}
192
- auth.setAuth("hobbes", kwargs, cookies, auth="tparam")
193
- assert kwargs["json"]["AUTH"] == "hbs-token"
194
- assert not cookies
195
- kwargs, cookies = {}, {}
196
- auth.setAuth("moe", kwargs, cookies, auth="cookie")
197
- assert not kwargs
198
- assert cookies["auth"] == "m-token"
199
- # dad does not have a token
200
- try:
201
- kwargs, cookies = {}, {}
202
- auth.setAuth("dad", kwargs, cookies)
203
- pytest.fail("must raise an error") # pragma: no cover
204
- except ft.FlaskTesterError:
205
- assert True, "error raised"
206
- # rosalyn as a password, but no password carrier is allowed
207
- try:
208
- auth.setPass("rosalyn", "rsln-pass")
209
- pytest.fail("must raise an error") # pragma: no cover
210
- except ft.AuthError:
211
- assert True, "error raised"
212
- # force to trigger later errors
213
- auth._has_pass = True
214
- auth.setPass("rosalyn", "rsln-pass")
215
- try:
216
- kwargs, cookies = {}, {}
217
- auth.setAuth("rosalyn", kwargs, cookies)
218
- pytest.fail("must raise an error") # pragma: no cover
219
- except ft.FlaskTesterError:
220
- assert True, "error raised"
221
-
222
- def test_authenticator_password():
223
- # all password carriers plus fake
224
- auth = ft.Authenticator(allow=["basic", "param", "fake"])
225
- auth.setPass("calvin", "clv-pass")
226
- auth.setPass("hobbes", "hbs-pass")
227
- auth.setPass("moe", "m-pass")
228
- auth.setPass("rosalyn", "rsln-pass")
229
- auth.setCookie("moe", "hello", "world!")
230
- kwargs, cookies = {}, {}
231
- auth.setAuth("calvin", kwargs, cookies, auth="basic")
232
- assert kwargs["auth"] == ("calvin", "clv-pass")
233
- assert not cookies
234
- kwargs, cookies = {"data": {}}, {}
235
- auth.setAuth("hobbes", kwargs, cookies, auth="param")
236
- assert kwargs["data"]["USER"] == "hobbes" and kwargs["data"]["PASS"] == "hbs-pass"
237
- assert not cookies
238
- kwargs, cookies = {"json": {}}, {}
239
- auth.setAuth("moe", kwargs, cookies, auth="param")
240
- assert kwargs["json"]["USER"] == "moe" and kwargs["json"]["PASS"] == "m-pass"
241
- assert cookies["hello"] == "world!"
242
- kwargs, cookies = {"data": {}}, {}
243
- auth.setAuth("rosalyn", kwargs, cookies, auth="fake")
244
- assert kwargs["data"]["LOGIN"] == "rosalyn"
245
- assert not cookies
246
- kwargs, cookies = {"json": {}}, {}
247
- auth.setAuth("hobbes", kwargs, cookies, auth="fake")
248
- assert kwargs["json"]["LOGIN"] == "hobbes"
249
- assert not cookies
250
- # susie as a token, but no token carrier is allowed
251
- try:
252
- auth.setToken("susie", "ss-token")
253
- pytest.fail("must raise an error") # pragma: no cover
254
- except ft.FlaskTesterError:
255
- assert True, "error raised"
256
- # force to trigger later error
257
- auth._has_token = True
258
- auth.setToken("susie", "ss-token")
259
- try:
260
- kwargs, cookies = {}, {}
261
- auth.setAuth("susie", kwargs, cookies)
262
- pytest.fail("must raise an error") # pragma: no cover
263
- except ft.FlaskTesterError:
264
- assert True, "error raised"
265
-
266
- def test_request_flask_response():
267
-
268
- class RequestResponse:
269
- """Local class for testing RequestFlaskResponse."""
270
-
271
- def __init__(self, is_json: bool):
272
- self._is_json = is_json
273
- self.status_code = 200
274
- self.content = b"hello world!"
275
- self.text = "hello world!"
276
- self.headers = {"Server": "test/0.1"}
277
- self.cookies = {}
278
-
279
- def json(self):
280
- if self._is_json:
281
- return {"hello": "world!"}
282
- else:
283
- raise Exception("not json!")
284
-
285
- jres = ft.RequestFlaskResponse(RequestResponse(True))
286
- assert jres.is_json and jres.json is not None
287
-
288
- xres = ft.RequestFlaskResponse(RequestResponse(False))
289
- assert not xres.is_json and xres.json is None
290
-
291
- def test_client():
292
- # abstract class for coverage
293
- client = ft.Client(ft.Authenticator())
294
- try:
295
- client._request("GET", "/", {})
296
- pytest.fail("must raise an error") # pragma: no cover
297
- except NotImplementedError:
298
- assert True, "expected error raised"
299
-
300
- def test_request_client():
301
- # start a tmp server on port 8888 for URL client coverage
302
- httpd = htsv.HTTPServer(("", 8888), htsv.SimpleHTTPRequestHandler)
303
- thread = threading.Thread(target = lambda: httpd.serve_forever())
304
- thread.start()
305
- try:
306
- client = ft.RequestClient(ft.Authenticator(), "http://localhost:8888")
307
- client.get("/", 200)
308
- client.request("GET", "/", 200)
309
- hello = io.BytesIO(b"hello world")
310
- client.post("/", 501, data={"hello": hello})
311
- hello = io.BytesIO(b"hello world")
312
- client.post("/", 501, data={"hello": (hello, "hello.txt", "text/plain")})
313
- client.post("/", 501, data={"hello": "world!"})
314
- finally:
315
- httpd.shutdown()
316
-
317
- def test_client_fixture():
318
- # ft_client coverage
319
- auth = ft._ft_authenticator()
320
- # check and save env
321
- app = None
322
- if "FLASK_TESTER_APP" in os.environ:
323
- app = os.environ["FLASK_TESTER_APP"]
324
- del os.environ["FLASK_TESTER_APP"]
325
- # no url nor app, defaults to "app"
326
- init = ft._ft_client(auth)
327
- # url
328
- os.environ["FLASK_TESTER_APP"] = "http://localhost:5000"
329
- init = ft._ft_client(auth)
330
- assert isinstance(init, ft.RequestClient)
331
- del os.environ["FLASK_TESTER_APP"]
332
- # bad package
333
- os.environ["FLASK_TESTER_APP"] = "no_such_package"
334
- try:
335
- init = ft._ft_client(auth)
336
- pytest.fail("must fail on missing package") # pragma: no cover
337
- except ModuleNotFoundError:
338
- assert True, "expected error raised"
339
- # bad name
340
- os.environ["FLASK_TESTER_APP"] = "app:no_such_app"
341
- try:
342
- init = ft._ft_client(auth)
343
- pytest.fail("must fail on missing package") # pragma: no cover
344
- except ft.FlaskTesterError:
345
- assert True, "expected error raised"
346
- del os.environ["FLASK_TESTER_APP"]
347
- # reset env
348
- if app:
349
- os.environ["FLASK_TESTER_APP"] = app
350
-
351
- def test_classes(api):
352
-
353
- def thing_eq(ta, tb):
354
- ta = model.Thing1(**ta) if isinstance(ta, dict) else ta
355
- tb = model.Thing1(**tb) if isinstance(tb, dict) else tb
356
- return ta.tid == tb.tid and ta.name == tb.name and ta.owner == tb.owner
357
-
358
- # Things
359
- t0 = {"tid": 0, "name": "zero", "owner": "Rosalyn"}
360
- t1 = model.Thing1(tid=1, name="one", owner="Susie")
361
- t2 = model.Thing2(tid=2, name="two", owner="Calvin")
362
- t3 = model.Thing3(tid=3, name="three", owner="Hobbes")
363
-
364
- n = 0
365
- # check all combinations
366
- for path in ["/t0", "/t1", "/t2", "/t3"]:
367
- for param in [t0, t1, t2, t3]:
368
- for tclass in [dict, model.Thing1, model.Thing2, model.Thing3]:
369
- for method in ["GET", "POST"]:
370
- for mode in ["data", "json"]:
371
- n +=1
372
- parameter = {mode: {"t": param}}
373
- res = api.request(method, path, 200, **parameter)
374
- assert res.is_json and isinstance(res.json, dict)
375
- assert thing_eq(tclass(**res.json), param)
376
- assert n == 256
377
-
378
- # simple types translation
379
- assert api.get("/t0", 200, json={"t": None}).json is None
380
- assert api.get("/t0", 200, data={"t": None}).json is None
381
- assert api.get("/t0", 200, json={"t": True}).json == True
382
- assert api.get("/t0", 200, json={"t": False}).json == False
383
- # non working, bad JsonData conversion
384
- api.get("/t0", 400, data={"t": True})
385
- api.get("/t0", 400, data={"t": False})
386
- assert api.get("/t0", 200, json={"t": 2345}).json == 2345
387
- assert api.get("/t0", 200, data={"t": 5432}).json == 5432
388
- assert api.get("/t0", 200, json={"t": 23.45}).json == 23.45
389
- assert api.get("/t0", 200, data={"t": 54.32}).json == 54.32
390
- assert api.get("/t0", 200, json={"t": "Susie"}).json == "Susie"
391
- api.get("/t0", 400, data={"t": "Susie"}) # raw str is not JSON
392
- assert api.get("/t0", 200, data={"t": "\"Susie\""}).json == "Susie"
@@ -1,37 +0,0 @@
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)
29
-
30
- def test_params(app):
31
- res = app.get("/add", 200, data={"i": 39, "j": 3}, login=None)
32
- assert res.is_json and res.json["sum"] == 42
33
- res = app.get("/add", 200, json={"i": 35, "j": 7}, login=None)
34
- assert res.is_json and res.json["sum"] == 42
35
- # mixing data/json is okay for FlaskTester
36
- res = app.get("/add", 200, data={"i": 30}, json={"j": 12}, login=None)
37
- assert res.is_json and res.json["sum"] == 42
File without changes
File without changes
File without changes