tigrbl-tests 0.3.2__py3-none-any.whl → 0.3.3__py3-none-any.whl
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.
- tests/i9n/test_tigrbl_api_app_usage_uvicorn.py +98 -0
- tests/i9n/test_tigrbl_api_usage_uvicorn.py +105 -0
- tests/i9n/test_tigrbl_api_uvicorn.py +67 -0
- tests/i9n/test_tigrbl_app_include_api_uvicorn.py +149 -0
- tests/i9n/test_tigrbl_app_multi_api_uvicorn.py +80 -0
- tests/i9n/test_tigrbl_app_usage_uvicorn.py +103 -0
- tests/i9n/test_tigrbl_app_uvicorn.py +58 -0
- tests/unit/test_initialize_async_task.py +27 -0
- tests/unit/test_initialize_mixed_engines.py +41 -0
- tests/unit/test_initialize_task_schedule.py +27 -0
- tests/unit/test_spec_app.py +1 -1
- tests/unit/test_table_collect_spec.py +1 -1
- tests/unit/test_table_namespace_init.py +27 -0
- tests/unit/test_table_namespace_isolation.py +43 -0
- tests/unit/test_tigrbl_api_app_configuration.py +83 -0
- tests/unit/test_tigrbl_api_app_instantiation.py +42 -0
- tests/unit/test_tigrbl_api_app_subclass_definition.py +38 -0
- tests/unit/test_tigrbl_api_configuration.py +47 -0
- tests/unit/test_tigrbl_api_instantiation.py +37 -0
- tests/unit/test_tigrbl_api_subclass_definition.py +37 -0
- tests/unit/test_tigrbl_app_configuration.py +47 -0
- tests/unit/test_tigrbl_app_instantiation.py +37 -0
- tests/unit/test_tigrbl_app_subclass_definition.py +37 -0
- {tigrbl_tests-0.3.2.dist-info → tigrbl_tests-0.3.3.dist-info}/METADATA +16 -1
- {tigrbl_tests-0.3.2.dist-info → tigrbl_tests-0.3.3.dist-info}/RECORD +27 -6
- {tigrbl_tests-0.3.2.dist-info → tigrbl_tests-0.3.3.dist-info}/WHEEL +0 -0
- {tigrbl_tests-0.3.2.dist-info → tigrbl_tests-0.3.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
import pytest_asyncio
|
|
4
|
+
from fastapi import Security
|
|
5
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
6
|
+
|
|
7
|
+
from tigrbl import Base, TigrblApi, TigrblApp
|
|
8
|
+
from tigrbl.engine.shortcuts import mem
|
|
9
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
10
|
+
from tigrbl.specs import F, IO, S, acol
|
|
11
|
+
from tigrbl.types import Mapped, String
|
|
12
|
+
|
|
13
|
+
from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
bearer = HTTPBearer()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def auth_dependency(
|
|
20
|
+
credentials: HTTPAuthorizationCredentials = Security(bearer),
|
|
21
|
+
) -> HTTPAuthorizationCredentials:
|
|
22
|
+
return credentials
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Kappa(Base, GUIDPk):
|
|
26
|
+
__tablename__ = "kappa_api_app_usage"
|
|
27
|
+
__allow_unmapped__ = True
|
|
28
|
+
|
|
29
|
+
name: Mapped[str] = acol(
|
|
30
|
+
storage=S(type_=String, nullable=False),
|
|
31
|
+
field=F(py_type=str),
|
|
32
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class KappaApi(TigrblApi):
|
|
39
|
+
MODELS = (Kappa,)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest_asyncio.fixture()
|
|
43
|
+
async def running_api_app():
|
|
44
|
+
api = KappaApi(engine=mem(async_=False))
|
|
45
|
+
api.set_auth(authn=auth_dependency, allow_anon=False)
|
|
46
|
+
api.include_models([Kappa])
|
|
47
|
+
api.initialize()
|
|
48
|
+
|
|
49
|
+
class KappaApp(TigrblApp):
|
|
50
|
+
APIS = (api,)
|
|
51
|
+
|
|
52
|
+
app = KappaApp(engine=mem(async_=False))
|
|
53
|
+
app.include_router(api)
|
|
54
|
+
|
|
55
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
56
|
+
try:
|
|
57
|
+
yield base_url
|
|
58
|
+
finally:
|
|
59
|
+
await stop_uvicorn_server(server, task)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.mark.i9n
|
|
63
|
+
@pytest.mark.asyncio
|
|
64
|
+
async def test_tigrbl_api_app_deploys_and_serves_openapi(running_api_app) -> None:
|
|
65
|
+
base_url = running_api_app
|
|
66
|
+
|
|
67
|
+
async with httpx.AsyncClient() as client:
|
|
68
|
+
openapi_resp = await client.get(f"{base_url}/openapi.json")
|
|
69
|
+
|
|
70
|
+
assert openapi_resp.status_code == 200
|
|
71
|
+
openapi = openapi_resp.json()
|
|
72
|
+
paths = openapi["paths"]
|
|
73
|
+
|
|
74
|
+
assert "/kappa" in paths
|
|
75
|
+
assert "/kappa/{item_id}" in paths
|
|
76
|
+
assert {"get", "post", "delete"}.issubset(paths["/kappa"])
|
|
77
|
+
assert {"get", "patch", "put", "delete"}.issubset(paths["/kappa/{item_id}"])
|
|
78
|
+
|
|
79
|
+
security_schemes = openapi.get("components", {}).get("securitySchemes", {})
|
|
80
|
+
assert "HTTPBearer" in security_schemes
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@pytest.mark.i9n
|
|
84
|
+
@pytest.mark.asyncio
|
|
85
|
+
async def test_tigrbl_api_app_handles_authenticated_request(running_api_app) -> None:
|
|
86
|
+
base_url = running_api_app
|
|
87
|
+
headers = {"Authorization": "Bearer demo"}
|
|
88
|
+
|
|
89
|
+
async with httpx.AsyncClient() as client:
|
|
90
|
+
resp = await client.post(
|
|
91
|
+
f"{base_url}/kappa",
|
|
92
|
+
json={"name": "Kappa"},
|
|
93
|
+
headers=headers,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
assert resp.status_code == 201
|
|
97
|
+
payload = resp.json()
|
|
98
|
+
assert payload["name"] == "Kappa"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
import pytest_asyncio
|
|
4
|
+
from fastapi import Security
|
|
5
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
6
|
+
|
|
7
|
+
from tigrbl import Base, TigrblApi
|
|
8
|
+
from tigrbl.engine.shortcuts import mem
|
|
9
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
10
|
+
from tigrbl.specs import F, IO, S, acol
|
|
11
|
+
from tigrbl.types import App, Mapped, String
|
|
12
|
+
|
|
13
|
+
from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
bearer = HTTPBearer()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def auth_dependency(
|
|
20
|
+
credentials: HTTPAuthorizationCredentials = Security(bearer),
|
|
21
|
+
) -> HTTPAuthorizationCredentials:
|
|
22
|
+
return credentials
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Alpha(Base, GUIDPk):
|
|
26
|
+
__tablename__ = "alpha_api_usage"
|
|
27
|
+
__allow_unmapped__ = True
|
|
28
|
+
|
|
29
|
+
name: Mapped[str] = acol(
|
|
30
|
+
storage=S(type_=String, nullable=False),
|
|
31
|
+
field=F(py_type=str),
|
|
32
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Beta(Base, GUIDPk):
|
|
39
|
+
__tablename__ = "beta_api_usage"
|
|
40
|
+
__allow_unmapped__ = True
|
|
41
|
+
|
|
42
|
+
name: Mapped[str] = acol(
|
|
43
|
+
storage=S(type_=String, nullable=False),
|
|
44
|
+
field=F(py_type=str),
|
|
45
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest_asyncio.fixture()
|
|
52
|
+
async def running_api():
|
|
53
|
+
app = App()
|
|
54
|
+
api = TigrblApi(engine=mem(async_=False))
|
|
55
|
+
api.set_auth(authn=auth_dependency, allow_anon=False)
|
|
56
|
+
api.include_models([Alpha, Beta])
|
|
57
|
+
api.initialize()
|
|
58
|
+
app.include_router(api)
|
|
59
|
+
|
|
60
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
61
|
+
try:
|
|
62
|
+
yield base_url
|
|
63
|
+
finally:
|
|
64
|
+
await stop_uvicorn_server(server, task)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest.mark.i9n
|
|
68
|
+
@pytest.mark.asyncio
|
|
69
|
+
async def test_tigrbl_api_deploys_and_serves_openapi(running_api) -> None:
|
|
70
|
+
base_url = running_api
|
|
71
|
+
|
|
72
|
+
async with httpx.AsyncClient() as client:
|
|
73
|
+
openapi_resp = await client.get(f"{base_url}/openapi.json")
|
|
74
|
+
|
|
75
|
+
assert openapi_resp.status_code == 200
|
|
76
|
+
openapi = openapi_resp.json()
|
|
77
|
+
paths = openapi["paths"]
|
|
78
|
+
|
|
79
|
+
assert "/alpha" in paths
|
|
80
|
+
assert "/alpha/{item_id}" in paths
|
|
81
|
+
assert "/beta" in paths
|
|
82
|
+
assert "/beta/{item_id}" in paths
|
|
83
|
+
assert {"get", "post", "delete"}.issubset(paths["/alpha"])
|
|
84
|
+
assert {"get", "patch", "put", "delete"}.issubset(paths["/alpha/{item_id}"])
|
|
85
|
+
|
|
86
|
+
security_schemes = openapi.get("components", {}).get("securitySchemes", {})
|
|
87
|
+
assert "HTTPBearer" in security_schemes
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@pytest.mark.i9n
|
|
91
|
+
@pytest.mark.asyncio
|
|
92
|
+
async def test_tigrbl_api_handles_authenticated_request(running_api) -> None:
|
|
93
|
+
base_url = running_api
|
|
94
|
+
headers = {"Authorization": "Bearer demo"}
|
|
95
|
+
|
|
96
|
+
async with httpx.AsyncClient() as client:
|
|
97
|
+
resp = await client.post(
|
|
98
|
+
f"{base_url}/alpha",
|
|
99
|
+
json={"name": "Alpha"},
|
|
100
|
+
headers=headers,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
assert resp.status_code == 201
|
|
104
|
+
payload = resp.json()
|
|
105
|
+
assert payload["name"] == "Alpha"
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
import pytest_asyncio
|
|
4
|
+
|
|
5
|
+
from tigrbl import Base, TigrblApi
|
|
6
|
+
from tigrbl.engine.shortcuts import mem
|
|
7
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
8
|
+
from tigrbl.specs import F, IO, S, acol
|
|
9
|
+
from tigrbl.types import App, Mapped, String
|
|
10
|
+
|
|
11
|
+
from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Gadget(Base, GUIDPk):
|
|
15
|
+
__tablename__ = "gadgets_api"
|
|
16
|
+
__resource__ = "gadget"
|
|
17
|
+
|
|
18
|
+
name: Mapped[str] = acol(
|
|
19
|
+
storage=S(String, nullable=False),
|
|
20
|
+
field=F(py_type=str),
|
|
21
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest_asyncio.fixture()
|
|
26
|
+
async def running_api_app():
|
|
27
|
+
api = TigrblApi(
|
|
28
|
+
engine=mem(async_=False),
|
|
29
|
+
models=[Gadget],
|
|
30
|
+
prefix="/gadgets",
|
|
31
|
+
system_prefix="/diagnostics",
|
|
32
|
+
)
|
|
33
|
+
await api.initialize()
|
|
34
|
+
|
|
35
|
+
app = App()
|
|
36
|
+
app.include_router(api.router)
|
|
37
|
+
api.attach_diagnostics(app=app)
|
|
38
|
+
|
|
39
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
40
|
+
try:
|
|
41
|
+
yield base_url
|
|
42
|
+
finally:
|
|
43
|
+
await stop_uvicorn_server(server, task)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.i9n
|
|
47
|
+
@pytest.mark.asyncio
|
|
48
|
+
async def test_tigrbl_api_create_gadget(running_api_app):
|
|
49
|
+
async with httpx.AsyncClient() as client:
|
|
50
|
+
response = await client.post(
|
|
51
|
+
f"{running_api_app}/gadgets/gadget", json={"name": "gyro"}
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
assert response.status_code == 201
|
|
55
|
+
payload = response.json()
|
|
56
|
+
assert payload["name"] == "gyro"
|
|
57
|
+
assert "id" in payload
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.mark.i9n
|
|
61
|
+
@pytest.mark.asyncio
|
|
62
|
+
async def test_tigrbl_api_healthz(running_api_app):
|
|
63
|
+
async with httpx.AsyncClient() as client:
|
|
64
|
+
response = await client.get(f"{running_api_app}/diagnostics/healthz")
|
|
65
|
+
|
|
66
|
+
assert response.status_code == 200
|
|
67
|
+
assert response.json()["ok"] is True
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
import pytest_asyncio
|
|
4
|
+
|
|
5
|
+
from tigrbl import Base, TigrblApi, TigrblApp
|
|
6
|
+
from tigrbl.engine.shortcuts import mem
|
|
7
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
8
|
+
from tigrbl.specs import F, IO, S, acol
|
|
9
|
+
from tigrbl.types import Mapped, String
|
|
10
|
+
|
|
11
|
+
from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AlphaWidget(Base, GUIDPk):
|
|
15
|
+
__tablename__ = "alpha_widgets_alt"
|
|
16
|
+
__resource__ = "alpha-widget"
|
|
17
|
+
|
|
18
|
+
name: Mapped[str] = acol(
|
|
19
|
+
storage=S(String, nullable=False),
|
|
20
|
+
field=F(py_type=str),
|
|
21
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BetaWidget(Base, GUIDPk):
|
|
26
|
+
__tablename__ = "beta_widgets_alt"
|
|
27
|
+
__resource__ = "beta-widget"
|
|
28
|
+
|
|
29
|
+
name: Mapped[str] = acol(
|
|
30
|
+
storage=S(String, nullable=False),
|
|
31
|
+
field=F(py_type=str),
|
|
32
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ZetaWidget(Base, GUIDPk):
|
|
37
|
+
__tablename__ = "zeta_widgets_alt"
|
|
38
|
+
__resource__ = "zeta-widget"
|
|
39
|
+
|
|
40
|
+
name: Mapped[str] = acol(
|
|
41
|
+
storage=S(String, nullable=False),
|
|
42
|
+
field=F(py_type=str),
|
|
43
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest_asyncio.fixture()
|
|
48
|
+
async def running_app_with_apis():
|
|
49
|
+
engine = mem(async_=False)
|
|
50
|
+
alpha_api = TigrblApi(engine=engine, models=[AlphaWidget], prefix="/alpha")
|
|
51
|
+
beta_api = TigrblApi(engine=engine)
|
|
52
|
+
beta_api.include_model(BetaWidget)
|
|
53
|
+
zeta_api = TigrblApi(engine=engine)
|
|
54
|
+
zeta_api.include_model(ZetaWidget)
|
|
55
|
+
|
|
56
|
+
app = TigrblApp(engine=engine, apis=[alpha_api, (zeta_api, "/zeta")])
|
|
57
|
+
app.include_api(beta_api, prefix="/beta")
|
|
58
|
+
await app.initialize()
|
|
59
|
+
|
|
60
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
61
|
+
try:
|
|
62
|
+
yield base_url
|
|
63
|
+
finally:
|
|
64
|
+
await stop_uvicorn_server(server, task)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest_asyncio.fixture()
|
|
68
|
+
async def running_app_with_api_router():
|
|
69
|
+
engine = mem(async_=False)
|
|
70
|
+
alpha_api = TigrblApi(engine=engine)
|
|
71
|
+
alpha_api.include_model(AlphaWidget)
|
|
72
|
+
beta_api = TigrblApi(engine=engine)
|
|
73
|
+
beta_api.include_model(BetaWidget)
|
|
74
|
+
|
|
75
|
+
app = TigrblApp(engine=engine, apis=[alpha_api])
|
|
76
|
+
app.include_api(beta_api, prefix="/beta")
|
|
77
|
+
app.include_api(alpha_api.router, prefix="/alpha")
|
|
78
|
+
await app.initialize()
|
|
79
|
+
|
|
80
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
81
|
+
try:
|
|
82
|
+
yield base_url
|
|
83
|
+
finally:
|
|
84
|
+
await stop_uvicorn_server(server, task)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.mark.i9n
|
|
88
|
+
@pytest.mark.asyncio
|
|
89
|
+
async def test_tigrbl_app_api_list_alpha(running_app_with_apis):
|
|
90
|
+
async with httpx.AsyncClient() as client:
|
|
91
|
+
response = await client.post(
|
|
92
|
+
f"{running_app_with_apis}/alpha/alpha-widget", json={"name": "ace"}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
assert response.status_code == 201
|
|
96
|
+
payload = response.json()
|
|
97
|
+
assert payload["name"] == "ace"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@pytest.mark.i9n
|
|
101
|
+
@pytest.mark.asyncio
|
|
102
|
+
async def test_tigrbl_app_include_api_beta(running_app_with_apis):
|
|
103
|
+
async with httpx.AsyncClient() as client:
|
|
104
|
+
response = await client.post(
|
|
105
|
+
f"{running_app_with_apis}/beta/beta-widget", json={"name": "bolt"}
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
assert response.status_code == 201
|
|
109
|
+
payload = response.json()
|
|
110
|
+
assert payload["name"] == "bolt"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@pytest.mark.i9n
|
|
114
|
+
@pytest.mark.asyncio
|
|
115
|
+
async def test_tigrbl_app_api_list_zeta(running_app_with_apis):
|
|
116
|
+
async with httpx.AsyncClient() as client:
|
|
117
|
+
response = await client.post(
|
|
118
|
+
f"{running_app_with_apis}/zeta/zeta-widget", json={"name": "zen"}
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
assert response.status_code == 201
|
|
122
|
+
payload = response.json()
|
|
123
|
+
assert payload["name"] == "zen"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@pytest.mark.i9n
|
|
127
|
+
@pytest.mark.asyncio
|
|
128
|
+
async def test_tigrbl_app_include_api_router_alpha(running_app_with_api_router):
|
|
129
|
+
async with httpx.AsyncClient() as client:
|
|
130
|
+
response = await client.post(
|
|
131
|
+
f"{running_app_with_api_router}/alpha/alpha-widget", json={"name": "ace"}
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
assert response.status_code == 201
|
|
135
|
+
payload = response.json()
|
|
136
|
+
assert payload["name"] == "ace"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@pytest.mark.i9n
|
|
140
|
+
@pytest.mark.asyncio
|
|
141
|
+
async def test_tigrbl_app_include_api_router_beta(running_app_with_api_router):
|
|
142
|
+
async with httpx.AsyncClient() as client:
|
|
143
|
+
response = await client.post(
|
|
144
|
+
f"{running_app_with_api_router}/beta/beta-widget", json={"name": "bolt"}
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
assert response.status_code == 201
|
|
148
|
+
payload = response.json()
|
|
149
|
+
assert payload["name"] == "bolt"
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
import pytest_asyncio
|
|
4
|
+
|
|
5
|
+
from tigrbl import Base, TigrblApi, TigrblApp
|
|
6
|
+
from tigrbl.engine.shortcuts import mem
|
|
7
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
8
|
+
from tigrbl.specs import F, IO, S, acol
|
|
9
|
+
from tigrbl.types import Mapped, String
|
|
10
|
+
|
|
11
|
+
from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AlphaWidget(Base, GUIDPk):
|
|
15
|
+
__tablename__ = "alpha_widgets"
|
|
16
|
+
__resource__ = "alpha-widget"
|
|
17
|
+
|
|
18
|
+
name: Mapped[str] = acol(
|
|
19
|
+
storage=S(String, nullable=False),
|
|
20
|
+
field=F(py_type=str),
|
|
21
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BetaWidget(Base, GUIDPk):
|
|
26
|
+
__tablename__ = "beta_widgets"
|
|
27
|
+
__resource__ = "beta-widget"
|
|
28
|
+
|
|
29
|
+
name: Mapped[str] = acol(
|
|
30
|
+
storage=S(String, nullable=False),
|
|
31
|
+
field=F(py_type=str),
|
|
32
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest_asyncio.fixture()
|
|
37
|
+
async def running_multi_api_app():
|
|
38
|
+
engine = mem(async_=False)
|
|
39
|
+
alpha_api = TigrblApi(engine=engine)
|
|
40
|
+
alpha_api.include_model(AlphaWidget)
|
|
41
|
+
|
|
42
|
+
beta_api = TigrblApi(engine=engine)
|
|
43
|
+
beta_api.include_model(BetaWidget)
|
|
44
|
+
|
|
45
|
+
app = TigrblApp(engine=engine, apis=[alpha_api])
|
|
46
|
+
app.include_router(beta_api, prefix="/beta")
|
|
47
|
+
app.include_router(alpha_api.router, prefix="/alpha")
|
|
48
|
+
await app.initialize()
|
|
49
|
+
|
|
50
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
51
|
+
try:
|
|
52
|
+
yield base_url
|
|
53
|
+
finally:
|
|
54
|
+
await stop_uvicorn_server(server, task)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.mark.i9n
|
|
58
|
+
@pytest.mark.asyncio
|
|
59
|
+
async def test_tigrbl_app_routes_alpha_api(running_multi_api_app):
|
|
60
|
+
async with httpx.AsyncClient() as client:
|
|
61
|
+
response = await client.post(
|
|
62
|
+
f"{running_multi_api_app}/alpha/alpha-widget", json={"name": "ace"}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
assert response.status_code == 201
|
|
66
|
+
payload = response.json()
|
|
67
|
+
assert payload["name"] == "ace"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.i9n
|
|
71
|
+
@pytest.mark.asyncio
|
|
72
|
+
async def test_tigrbl_app_routes_beta_api(running_multi_api_app):
|
|
73
|
+
async with httpx.AsyncClient() as client:
|
|
74
|
+
response = await client.post(
|
|
75
|
+
f"{running_multi_api_app}/beta/beta-widget", json={"name": "bolt"}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
assert response.status_code == 201
|
|
79
|
+
payload = response.json()
|
|
80
|
+
assert payload["name"] == "bolt"
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
import pytest_asyncio
|
|
4
|
+
from fastapi import Security
|
|
5
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
6
|
+
|
|
7
|
+
from tigrbl import Base, TigrblApp
|
|
8
|
+
from tigrbl.engine.shortcuts import mem
|
|
9
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
10
|
+
from tigrbl.specs import F, IO, S, acol
|
|
11
|
+
from tigrbl.types import Mapped, String
|
|
12
|
+
|
|
13
|
+
from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
bearer = HTTPBearer()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def auth_dependency(
|
|
20
|
+
credentials: HTTPAuthorizationCredentials = Security(bearer),
|
|
21
|
+
) -> HTTPAuthorizationCredentials:
|
|
22
|
+
return credentials
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Gamma(Base, GUIDPk):
|
|
26
|
+
__tablename__ = "gamma_app_usage"
|
|
27
|
+
__allow_unmapped__ = True
|
|
28
|
+
|
|
29
|
+
name: Mapped[str] = acol(
|
|
30
|
+
storage=S(type_=String, nullable=False),
|
|
31
|
+
field=F(py_type=str),
|
|
32
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Delta(Base, GUIDPk):
|
|
39
|
+
__tablename__ = "delta_app_usage"
|
|
40
|
+
__allow_unmapped__ = True
|
|
41
|
+
|
|
42
|
+
name: Mapped[str] = acol(
|
|
43
|
+
storage=S(type_=String, nullable=False),
|
|
44
|
+
field=F(py_type=str),
|
|
45
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest_asyncio.fixture()
|
|
52
|
+
async def running_app():
|
|
53
|
+
app = TigrblApp(engine=mem(async_=False))
|
|
54
|
+
app.set_auth(authn=auth_dependency, allow_anon=False)
|
|
55
|
+
app.include_models([Gamma, Delta])
|
|
56
|
+
app.initialize()
|
|
57
|
+
|
|
58
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
59
|
+
try:
|
|
60
|
+
yield base_url
|
|
61
|
+
finally:
|
|
62
|
+
await stop_uvicorn_server(server, task)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@pytest.mark.i9n
|
|
66
|
+
@pytest.mark.asyncio
|
|
67
|
+
async def test_tigrbl_app_deploys_and_serves_openapi(running_app) -> None:
|
|
68
|
+
base_url = running_app
|
|
69
|
+
|
|
70
|
+
async with httpx.AsyncClient() as client:
|
|
71
|
+
openapi_resp = await client.get(f"{base_url}/openapi.json")
|
|
72
|
+
|
|
73
|
+
assert openapi_resp.status_code == 200
|
|
74
|
+
openapi = openapi_resp.json()
|
|
75
|
+
paths = openapi["paths"]
|
|
76
|
+
|
|
77
|
+
assert "/gamma" in paths
|
|
78
|
+
assert "/gamma/{item_id}" in paths
|
|
79
|
+
assert "/delta" in paths
|
|
80
|
+
assert "/delta/{item_id}" in paths
|
|
81
|
+
assert {"get", "post", "delete"}.issubset(paths["/gamma"])
|
|
82
|
+
assert {"get", "patch", "put", "delete"}.issubset(paths["/gamma/{item_id}"])
|
|
83
|
+
|
|
84
|
+
security_schemes = openapi.get("components", {}).get("securitySchemes", {})
|
|
85
|
+
assert "HTTPBearer" in security_schemes
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@pytest.mark.i9n
|
|
89
|
+
@pytest.mark.asyncio
|
|
90
|
+
async def test_tigrbl_app_handles_authenticated_request(running_app) -> None:
|
|
91
|
+
base_url = running_app
|
|
92
|
+
headers = {"Authorization": "Bearer demo"}
|
|
93
|
+
|
|
94
|
+
async with httpx.AsyncClient() as client:
|
|
95
|
+
resp = await client.post(
|
|
96
|
+
f"{base_url}/gamma",
|
|
97
|
+
json={"name": "Gamma"},
|
|
98
|
+
headers=headers,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
assert resp.status_code == 201
|
|
102
|
+
payload = resp.json()
|
|
103
|
+
assert payload["name"] == "Gamma"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
import pytest_asyncio
|
|
4
|
+
|
|
5
|
+
from tigrbl import Base, TigrblApp
|
|
6
|
+
from tigrbl.engine.shortcuts import mem
|
|
7
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
8
|
+
from tigrbl.specs import F, IO, S, acol
|
|
9
|
+
from tigrbl.types import Mapped, String
|
|
10
|
+
|
|
11
|
+
from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Widget(Base, GUIDPk):
|
|
15
|
+
__tablename__ = "widgets_app"
|
|
16
|
+
__resource__ = "widget"
|
|
17
|
+
|
|
18
|
+
name: Mapped[str] = acol(
|
|
19
|
+
storage=S(String, nullable=False),
|
|
20
|
+
field=F(py_type=str),
|
|
21
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest_asyncio.fixture()
|
|
26
|
+
async def running_app():
|
|
27
|
+
app = TigrblApp(engine=mem(async_=False))
|
|
28
|
+
app.include_model(Widget)
|
|
29
|
+
app.attach_diagnostics()
|
|
30
|
+
await app.initialize()
|
|
31
|
+
|
|
32
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
33
|
+
try:
|
|
34
|
+
yield base_url
|
|
35
|
+
finally:
|
|
36
|
+
await stop_uvicorn_server(server, task)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.mark.i9n
|
|
40
|
+
@pytest.mark.asyncio
|
|
41
|
+
async def test_tigrbl_app_create_widget(running_app):
|
|
42
|
+
async with httpx.AsyncClient() as client:
|
|
43
|
+
response = await client.post(f"{running_app}/widget", json={"name": "alpha"})
|
|
44
|
+
|
|
45
|
+
assert response.status_code == 201
|
|
46
|
+
payload = response.json()
|
|
47
|
+
assert payload["name"] == "alpha"
|
|
48
|
+
assert "id" in payload
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.i9n
|
|
52
|
+
@pytest.mark.asyncio
|
|
53
|
+
async def test_tigrbl_app_healthz(running_app):
|
|
54
|
+
async with httpx.AsyncClient() as client:
|
|
55
|
+
response = await client.get(f"{running_app}/system/healthz")
|
|
56
|
+
|
|
57
|
+
assert response.status_code == 200
|
|
58
|
+
assert response.json()["ok"] is True
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from sqlalchemy import Column, Integer
|
|
5
|
+
|
|
6
|
+
from tigrbl import Base, TigrblApp
|
|
7
|
+
from tigrbl.engine.shortcuts import mem
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Widget(Base):
|
|
11
|
+
__tablename__ = "widgets_async_task"
|
|
12
|
+
|
|
13
|
+
id = Column(Integer, primary_key=True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.asyncio
|
|
17
|
+
async def test_initialize_returns_task_for_async_engine():
|
|
18
|
+
app = TigrblApp(engine=mem())
|
|
19
|
+
app.include_model(Widget, prefix="")
|
|
20
|
+
|
|
21
|
+
result = app.initialize()
|
|
22
|
+
|
|
23
|
+
assert isinstance(result, asyncio.Task)
|
|
24
|
+
|
|
25
|
+
await result
|
|
26
|
+
|
|
27
|
+
assert getattr(app, "_ddl_executed", False) is True
|