zrb 1.0.0b9__py3-none-any.whl → 1.1.0__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.
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/.coveragerc +11 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/.gitignore +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/add_column_task.py +99 -55
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/add_column_util.py +301 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/config.py +5 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_task.py +131 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +128 -5
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/gateway/view/content/my-module/my-entity.html +297 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_create_my_entity.py +53 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_delete_my_entity.py +62 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_read_my_entity.py +65 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_update_my_entity.py +61 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/gateway_subroute.py +81 -13
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/navigation_config_file.py +8 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_task.py +8 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +42 -3
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py +8 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/module_task_definition.py +10 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/navigation_config_file.py +6 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task.py +56 -12
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task_util.py +10 -4
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_service.py +136 -52
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/parser.py +3 -3
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/view.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +19 -8
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_auth_tables.py +46 -43
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/8ed025bcc845_create_permissions.py +69 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_db_repository.py +5 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service.py +16 -21
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/config/navigation.py +39 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/route.py +52 -11
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/schema/navigation.py +95 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +277 -44
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/auth.py +66 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/view.py +33 -8
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/permission.html +311 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/role.html +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/user.html +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/error.html +4 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/login.html +67 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/logout.html +49 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/common/util.js +160 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/crud/style.css +14 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/crud/util.js +94 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/pico-style.css +23 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/script.js +44 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/style.css +102 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/template/default.html +73 -18
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/requirements.txt +6 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/permission.py +1 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +9 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/_util/access_token.py +19 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_create_permission.py +59 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_delete_permission.py +68 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_read_permission.py +71 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_update_permission.py +66 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/test_user_session.py +195 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_health_and_readiness.py +28 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_homepage.py +15 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_not_found_error.py +16 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test.sh +7 -0
- zrb/runner/web_route/refresh_token_api_route.py +1 -1
- zrb/runner/web_route/static/refresh-token.template.js +9 -0
- zrb/runner/web_route/static/static_route.py +1 -1
- zrb/task/base_task.py +10 -10
- zrb/util/codemod/modification_mode.py +3 -0
- zrb/util/codemod/modify_class.py +58 -0
- zrb/util/codemod/modify_class_parent.py +68 -0
- zrb/util/codemod/modify_class_property.py +128 -0
- zrb/util/codemod/modify_dict.py +75 -0
- zrb/util/codemod/modify_function.py +65 -0
- zrb/util/codemod/modify_function_call.py +68 -0
- zrb/util/codemod/modify_method.py +88 -0
- zrb/util/codemod/{prepend_code_to_module.py → modify_module.py} +2 -3
- zrb/util/file.py +3 -2
- zrb/util/load.py +13 -7
- {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/METADATA +2 -2
- {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/RECORD +80 -46
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/migrate.py +0 -3
- zrb/util/codemod/append_code_to_class.py +0 -35
- zrb/util/codemod/append_code_to_function.py +0 -38
- zrb/util/codemod/append_code_to_method.py +0 -55
- zrb/util/codemod/append_key_to_dict.py +0 -51
- zrb/util/codemod/append_param_to_function_call.py +0 -39
- zrb/util/codemod/prepend_parent_to_class.py +0 -38
- zrb/util/codemod/prepend_property_to_class.py +0 -55
- {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/WHEEL +0 -0
- {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
from fastapi.testclient import TestClient
|
2
|
+
from my_app_name.main import app
|
3
|
+
from my_app_name.test._util.access_token import get_admin_access_token
|
4
|
+
|
5
|
+
|
6
|
+
def test_update_permission():
|
7
|
+
client = TestClient(app, base_url="http://localhost")
|
8
|
+
access_token = get_admin_access_token()
|
9
|
+
new_permission_data = {
|
10
|
+
"name": "to-be-updated-permission",
|
11
|
+
"description": "to-be-updated-permission-description",
|
12
|
+
}
|
13
|
+
insert_response = client.post(
|
14
|
+
"/api/v1/permissions",
|
15
|
+
json=new_permission_data,
|
16
|
+
headers={"Authorization": f"Bearer {access_token}"},
|
17
|
+
)
|
18
|
+
assert insert_response.status_code == 200
|
19
|
+
id = insert_response.json().get("id")
|
20
|
+
# updating
|
21
|
+
updated_permission_data = {
|
22
|
+
"name": "updated-permission",
|
23
|
+
"description": "updated-permission-description",
|
24
|
+
}
|
25
|
+
response = client.put(
|
26
|
+
f"/api/v1/permissions/{id}",
|
27
|
+
json=updated_permission_data,
|
28
|
+
headers={"Authorization": f"Bearer {access_token}"},
|
29
|
+
)
|
30
|
+
assert response.status_code == 200
|
31
|
+
response_data = response.json()
|
32
|
+
assert response_data.get("id") == id
|
33
|
+
assert response_data.get("name") == "updated-permission"
|
34
|
+
assert response_data.get("description") == "updated-permission-description"
|
35
|
+
|
36
|
+
|
37
|
+
def test_update_permission_bulk():
|
38
|
+
client = TestClient(app, base_url="http://localhost")
|
39
|
+
access_token = get_admin_access_token()
|
40
|
+
new_first_permission_data = {
|
41
|
+
"name": "to-be-updated-permission-bulk-1",
|
42
|
+
"description": "to-be-updated-permission-bulk-description-1",
|
43
|
+
}
|
44
|
+
insert_response = client.post(
|
45
|
+
"/api/v1/permissions/bulk",
|
46
|
+
json=[new_first_permission_data],
|
47
|
+
headers={"Authorization": f"Bearer {access_token}"},
|
48
|
+
)
|
49
|
+
assert insert_response.status_code == 200
|
50
|
+
ids = [row["id"] for row in insert_response.json()]
|
51
|
+
# updating (we only test with one data)
|
52
|
+
updated_permission_data = {"description": "updated-permission-description"}
|
53
|
+
response = client.put(
|
54
|
+
f"/api/v1/permissions/bulk",
|
55
|
+
json={
|
56
|
+
"permission_ids": ids,
|
57
|
+
"data": updated_permission_data,
|
58
|
+
},
|
59
|
+
headers={"Authorization": f"Bearer {access_token}"},
|
60
|
+
)
|
61
|
+
assert response.status_code == 200
|
62
|
+
response_data = response.json()
|
63
|
+
assert len(response_data) == 1
|
64
|
+
response_data[0].get("id") == ids[0]
|
65
|
+
response_data[0].get("name") == new_first_permission_data["name"]
|
66
|
+
response_data[0].get("description") == new_first_permission_data["description"]
|
@@ -0,0 +1,195 @@
|
|
1
|
+
import time
|
2
|
+
from typing import Annotated
|
3
|
+
|
4
|
+
from fastapi import Depends
|
5
|
+
from fastapi.testclient import TestClient
|
6
|
+
from my_app_name.config import (
|
7
|
+
APP_AUTH_ACCESS_TOKEN_COOKIE_NAME,
|
8
|
+
APP_AUTH_GUEST_USER,
|
9
|
+
APP_AUTH_GUEST_USER_PERMISSIONS,
|
10
|
+
APP_AUTH_REFRESH_TOKEN_COOKIE_NAME,
|
11
|
+
APP_AUTH_SUPER_USER,
|
12
|
+
APP_AUTH_SUPER_USER_PASSWORD,
|
13
|
+
)
|
14
|
+
from my_app_name.main import app
|
15
|
+
from my_app_name.module.gateway.util.auth import get_current_user
|
16
|
+
from my_app_name.schema.user import AuthUserResponse
|
17
|
+
|
18
|
+
|
19
|
+
@app.get("/test/current-user", response_model=AuthUserResponse)
|
20
|
+
async def serve_get_current_user(
|
21
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
22
|
+
) -> AuthUserResponse:
|
23
|
+
return current_user
|
24
|
+
|
25
|
+
|
26
|
+
def test_create_invalid_user_session():
|
27
|
+
client = TestClient(app, base_url="http://localhost")
|
28
|
+
response = client.post(
|
29
|
+
"/api/v1/user-sessions",
|
30
|
+
data={
|
31
|
+
"username": "nonExistingUserFromSevenKingdom",
|
32
|
+
"password": "nonExistingSecurity",
|
33
|
+
},
|
34
|
+
)
|
35
|
+
assert response.status_code == 401
|
36
|
+
|
37
|
+
|
38
|
+
def test_get_guest_user():
|
39
|
+
client = TestClient(app, base_url="http://localhost")
|
40
|
+
# Fetch current user
|
41
|
+
response = client.get("/test/current-user")
|
42
|
+
assert response.status_code == 200
|
43
|
+
user_data = response.json()
|
44
|
+
assert user_data.get("username") == APP_AUTH_GUEST_USER
|
45
|
+
assert not user_data.get("is_super_user")
|
46
|
+
assert user_data.get("is_guest")
|
47
|
+
assert user_data.get("permission_names") == APP_AUTH_GUEST_USER_PERMISSIONS
|
48
|
+
|
49
|
+
|
50
|
+
def test_create_admin_user_session():
|
51
|
+
client = TestClient(app, base_url="http://localhost")
|
52
|
+
# Create new admin user session and check the response
|
53
|
+
session_response = client.post(
|
54
|
+
"/api/v1/user-sessions",
|
55
|
+
data={
|
56
|
+
"username": APP_AUTH_SUPER_USER,
|
57
|
+
"password": APP_AUTH_SUPER_USER_PASSWORD,
|
58
|
+
},
|
59
|
+
)
|
60
|
+
assert session_response.status_code == 200
|
61
|
+
session_data = session_response.json()
|
62
|
+
assert session_data.get("user_id") == APP_AUTH_SUPER_USER
|
63
|
+
assert session_data.get("token_type") == "bearer"
|
64
|
+
assert "access_token" in session_data
|
65
|
+
assert "access_token_expired_at" in session_data
|
66
|
+
assert "refresh_token" in session_data
|
67
|
+
assert "refresh_token_expired_at" in session_data
|
68
|
+
assert session_response.cookies.get(
|
69
|
+
APP_AUTH_ACCESS_TOKEN_COOKIE_NAME
|
70
|
+
) == session_data.get("access_token")
|
71
|
+
assert session_response.cookies.get(
|
72
|
+
APP_AUTH_REFRESH_TOKEN_COOKIE_NAME
|
73
|
+
) == session_data.get("refresh_token")
|
74
|
+
|
75
|
+
|
76
|
+
def test_get_admin_user_with_bearer():
|
77
|
+
client = TestClient(app, base_url="http://localhost")
|
78
|
+
session_response = client.post(
|
79
|
+
"/api/v1/user-sessions",
|
80
|
+
data={
|
81
|
+
"username": APP_AUTH_SUPER_USER,
|
82
|
+
"password": APP_AUTH_SUPER_USER_PASSWORD,
|
83
|
+
},
|
84
|
+
)
|
85
|
+
assert session_response.status_code == 200
|
86
|
+
access_token = session_response.json()["access_token"]
|
87
|
+
# Fetch current user with bearer token
|
88
|
+
response = client.get(
|
89
|
+
"/test/current-user", headers={"Authorization": f"Bearer {access_token}"}
|
90
|
+
)
|
91
|
+
assert response.status_code == 200
|
92
|
+
user_data = response.json()
|
93
|
+
assert user_data.get("username") == APP_AUTH_SUPER_USER
|
94
|
+
assert user_data.get("is_super_user")
|
95
|
+
assert not user_data.get("is_guest")
|
96
|
+
|
97
|
+
|
98
|
+
def test_get_admin_user_with_cookie():
|
99
|
+
login_client = TestClient(app, base_url="http://localhost")
|
100
|
+
session_response = login_client.post(
|
101
|
+
"/api/v1/user-sessions",
|
102
|
+
data={
|
103
|
+
"username": APP_AUTH_SUPER_USER,
|
104
|
+
"password": APP_AUTH_SUPER_USER_PASSWORD,
|
105
|
+
},
|
106
|
+
)
|
107
|
+
assert session_response.status_code == 200
|
108
|
+
# Fetch current user with cookies
|
109
|
+
cookies = {
|
110
|
+
APP_AUTH_ACCESS_TOKEN_COOKIE_NAME: session_response.cookies.get(
|
111
|
+
APP_AUTH_ACCESS_TOKEN_COOKIE_NAME
|
112
|
+
)
|
113
|
+
}
|
114
|
+
# re-initiate client with cookies
|
115
|
+
client = TestClient(app, base_url="http://localhost", cookies=cookies)
|
116
|
+
response = client.get("/test/current-user")
|
117
|
+
assert response.status_code == 200
|
118
|
+
user_data = response.json()
|
119
|
+
assert user_data.get("username") == APP_AUTH_SUPER_USER
|
120
|
+
assert user_data.get("is_super_user")
|
121
|
+
assert not user_data.get("is_guest")
|
122
|
+
|
123
|
+
|
124
|
+
def test_update_user_session():
|
125
|
+
login_client = TestClient(app, base_url="http://localhost")
|
126
|
+
old_session_response = login_client.post(
|
127
|
+
"/api/v1/user-sessions",
|
128
|
+
data={
|
129
|
+
"username": APP_AUTH_SUPER_USER,
|
130
|
+
"password": APP_AUTH_SUPER_USER_PASSWORD,
|
131
|
+
},
|
132
|
+
)
|
133
|
+
assert old_session_response.status_code == 200
|
134
|
+
old_session_data = old_session_response.json()
|
135
|
+
# re-initiate client with cookies and delete user session
|
136
|
+
client = TestClient(app, base_url="http://localhost")
|
137
|
+
time.sleep(1)
|
138
|
+
new_session_response = client.put(
|
139
|
+
"/api/v1/user-sessions",
|
140
|
+
params={"refresh_token": old_session_data.get("refresh_token")},
|
141
|
+
)
|
142
|
+
assert new_session_response.status_code == 200
|
143
|
+
new_session_data = new_session_response.json()
|
144
|
+
# Cookies and response should match
|
145
|
+
assert new_session_response.cookies.get(
|
146
|
+
APP_AUTH_ACCESS_TOKEN_COOKIE_NAME
|
147
|
+
) == new_session_data.get("access_token")
|
148
|
+
assert new_session_response.cookies.get(
|
149
|
+
APP_AUTH_REFRESH_TOKEN_COOKIE_NAME
|
150
|
+
) == new_session_data.get("refresh_token")
|
151
|
+
# New session should has longer TTL than old session
|
152
|
+
assert old_session_data.get("access_token") != new_session_data.get("access_token")
|
153
|
+
assert old_session_data.get("access_token_expired_at") < new_session_data.get(
|
154
|
+
"access_token_expired_at"
|
155
|
+
)
|
156
|
+
assert old_session_data.get("refresh_token") != new_session_data.get(
|
157
|
+
"refresh_token"
|
158
|
+
)
|
159
|
+
assert old_session_data.get("refresh_token_expired_at") < new_session_data.get(
|
160
|
+
"refresh_token_expired_at"
|
161
|
+
)
|
162
|
+
|
163
|
+
|
164
|
+
def test_delete_user_session():
|
165
|
+
login_client = TestClient(app, base_url="http://localhost")
|
166
|
+
session_response = login_client.post(
|
167
|
+
"/api/v1/user-sessions",
|
168
|
+
data={
|
169
|
+
"username": APP_AUTH_SUPER_USER,
|
170
|
+
"password": APP_AUTH_SUPER_USER_PASSWORD,
|
171
|
+
},
|
172
|
+
)
|
173
|
+
assert session_response.status_code == 200
|
174
|
+
# Initiate cookies that should be deleted when user session is deleted.
|
175
|
+
cookies = {
|
176
|
+
APP_AUTH_ACCESS_TOKEN_COOKIE_NAME: session_response.cookies.get(
|
177
|
+
APP_AUTH_ACCESS_TOKEN_COOKIE_NAME
|
178
|
+
),
|
179
|
+
APP_AUTH_REFRESH_TOKEN_COOKIE_NAME: session_response.cookies.get(
|
180
|
+
APP_AUTH_REFRESH_TOKEN_COOKIE_NAME
|
181
|
+
),
|
182
|
+
}
|
183
|
+
# re-initiate client with cookies and delete user session
|
184
|
+
client = TestClient(app, base_url="http://localhost", cookies=cookies)
|
185
|
+
response = client.delete(
|
186
|
+
"/api/v1/user-sessions",
|
187
|
+
params={
|
188
|
+
"refresh_token": session_response.cookies.get(
|
189
|
+
APP_AUTH_REFRESH_TOKEN_COOKIE_NAME
|
190
|
+
),
|
191
|
+
},
|
192
|
+
)
|
193
|
+
assert response.status_code == 200
|
194
|
+
assert response.cookies.get(APP_AUTH_ACCESS_TOKEN_COOKIE_NAME, None) is None
|
195
|
+
assert response.cookies.get(APP_AUTH_REFRESH_TOKEN_COOKIE_NAME, None) is None
|
zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_health_and_readiness.py
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
from fastapi.testclient import TestClient
|
2
|
+
from my_app_name.main import app
|
3
|
+
|
4
|
+
|
5
|
+
def test_get_health():
|
6
|
+
client = TestClient(app, base_url="http://localhost")
|
7
|
+
response = client.get("/health")
|
8
|
+
assert response.status_code == 200
|
9
|
+
assert response.json() == {"message": "ok"}
|
10
|
+
|
11
|
+
|
12
|
+
def test_head_health():
|
13
|
+
client = TestClient(app, base_url="http://localhost")
|
14
|
+
response = client.head("/health")
|
15
|
+
assert response.status_code == 200
|
16
|
+
|
17
|
+
|
18
|
+
def test_get_readiness():
|
19
|
+
client = TestClient(app, base_url="http://localhost")
|
20
|
+
response = client.get("/readiness")
|
21
|
+
assert response.status_code == 200
|
22
|
+
assert response.json() == {"message": "ok"}
|
23
|
+
|
24
|
+
|
25
|
+
def test_head_readiness():
|
26
|
+
client = TestClient(app, base_url="http://localhost")
|
27
|
+
response = client.head("/readiness")
|
28
|
+
assert response.status_code == 200
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
from fastapi.testclient import TestClient
|
4
|
+
from my_app_name.main import app
|
5
|
+
from my_app_name.module.gateway.util.view import render_content
|
6
|
+
|
7
|
+
|
8
|
+
def test_homepage():
|
9
|
+
client = TestClient(app, base_url="http://localhost")
|
10
|
+
response = client.get("/")
|
11
|
+
assert response.status_code == 200
|
12
|
+
view_path = os.path.join(
|
13
|
+
os.path.dirname(os.path.dirname(__file__)), "module", "gateway", "view"
|
14
|
+
)
|
15
|
+
assert response.text == render_content("homepage.html").body.decode("utf-8")
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
from fastapi.testclient import TestClient
|
4
|
+
from my_app_name.main import app
|
5
|
+
from my_app_name.module.gateway.util.view import render_error
|
6
|
+
|
7
|
+
|
8
|
+
def test_not_found_error():
|
9
|
+
client = TestClient(app, base_url="http://localhost")
|
10
|
+
response = client.get(
|
11
|
+
"/holly-grail/not-found/philosopher-stone/not-found/hope/inexist"
|
12
|
+
)
|
13
|
+
assert response.status_code == 404
|
14
|
+
assert response.text == render_error(
|
15
|
+
error_message="Not found", status_code=404
|
16
|
+
).body.decode("utf-8")
|
@@ -30,7 +30,7 @@ def serve_refresh_token_api(app: "FastAPI", web_config: WebConfig) -> None:
|
|
30
30
|
# If we still don't have a refresh token, raise an exception
|
31
31
|
if not refresh_token:
|
32
32
|
return JSONResponse(
|
33
|
-
content={"detail": "Refresh token not provided"}, status_code=
|
33
|
+
content={"detail": "Refresh token not provided"}, status_code=401
|
34
34
|
)
|
35
35
|
# Get token
|
36
36
|
new_token = regenerate_tokens(web_config, refresh_token)
|
@@ -1,6 +1,10 @@
|
|
1
1
|
function refreshAuthToken(){
|
2
2
|
const refreshUrl = "/api/v1/refresh-token";
|
3
|
+
let shouldRefresh = true;
|
3
4
|
async function refresh() {
|
5
|
+
if (!shouldRefresh) {
|
6
|
+
return;
|
7
|
+
}
|
4
8
|
try {
|
5
9
|
const response = await fetch(refreshUrl, {
|
6
10
|
method: "POST",
|
@@ -9,6 +13,11 @@ function refreshAuthToken(){
|
|
9
13
|
});
|
10
14
|
|
11
15
|
if (!response.ok) {
|
16
|
+
if (response.status === 401 || response.status === 403) {
|
17
|
+
console.warn("Skipping token refresh, authentication required.");
|
18
|
+
shouldRefresh = false;
|
19
|
+
return;
|
20
|
+
}
|
12
21
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
13
22
|
}
|
14
23
|
console.log("Token refreshed successfully");
|
@@ -30,7 +30,7 @@ def serve_static_resources(app: "FastAPI", web_config: WebConfig) -> None:
|
|
30
30
|
async def refresh_token_js():
|
31
31
|
return PlainTextResponse(
|
32
32
|
content=_get_refresh_token_js(
|
33
|
-
60 * web_config.
|
33
|
+
60 * web_config.access_token_expire_minutes / 3
|
34
34
|
),
|
35
35
|
media_type="application/javascript",
|
36
36
|
)
|
zrb/task/base_task.py
CHANGED
@@ -295,16 +295,16 @@ class BaseTask(AnyTask):
|
|
295
295
|
async def exec_root_tasks(self, session: AnySession):
|
296
296
|
session.set_main_task(self)
|
297
297
|
session.state_logger.write(session.as_state_log())
|
298
|
-
log_state = asyncio.create_task(self._log_session_state(session))
|
299
|
-
root_tasks = [
|
300
|
-
task
|
301
|
-
for task in session.get_root_tasks(self)
|
302
|
-
if session.is_allowed_to_run(task)
|
303
|
-
]
|
304
|
-
root_task_coros = [
|
305
|
-
run_async(root_task.exec_chain(session)) for root_task in root_tasks
|
306
|
-
]
|
307
298
|
try:
|
299
|
+
log_state = asyncio.create_task(self._log_session_state(session))
|
300
|
+
root_tasks = [
|
301
|
+
task
|
302
|
+
for task in session.get_root_tasks(self)
|
303
|
+
if session.is_allowed_to_run(task)
|
304
|
+
]
|
305
|
+
root_task_coros = [
|
306
|
+
run_async(root_task.exec_chain(session)) for root_task in root_tasks
|
307
|
+
]
|
308
308
|
await asyncio.gather(*root_task_coros)
|
309
309
|
await session.wait_deferred()
|
310
310
|
session.terminate()
|
@@ -312,7 +312,7 @@ class BaseTask(AnyTask):
|
|
312
312
|
return session.final_result
|
313
313
|
except IndexError:
|
314
314
|
return None
|
315
|
-
except asyncio.CancelledError:
|
315
|
+
except (asyncio.CancelledError, KeyboardInterrupt):
|
316
316
|
ctx = self.get_ctx(session)
|
317
317
|
ctx.log_info("Session terminated")
|
318
318
|
finally:
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import libcst as cst
|
2
|
+
|
3
|
+
from zrb.util.codemod.modification_mode import APPEND, PREPEND, REPLACE
|
4
|
+
|
5
|
+
|
6
|
+
def replace_class_code(original_code: str, class_name: str, new_code: str) -> str:
|
7
|
+
return _modify_code(original_code, class_name, new_code, REPLACE)
|
8
|
+
|
9
|
+
|
10
|
+
def prepend_code_to_class(original_code: str, class_name: str, new_code: str) -> str:
|
11
|
+
return _modify_code(original_code, class_name, new_code, PREPEND)
|
12
|
+
|
13
|
+
|
14
|
+
def append_code_to_class(original_code: str, class_name: str, new_code: str) -> str:
|
15
|
+
return _modify_code(original_code, class_name, new_code, APPEND)
|
16
|
+
|
17
|
+
|
18
|
+
def _modify_code(original_code: str, class_name: str, new_code: str, mode: int) -> str:
|
19
|
+
# Parse the original code into a module
|
20
|
+
module = cst.parse_module(original_code)
|
21
|
+
# Initialize transformer with the class name and method code
|
22
|
+
transformer = _ClassCodeModifier(class_name, new_code, mode)
|
23
|
+
# Apply the transformation
|
24
|
+
modified_module = module.visit(transformer)
|
25
|
+
# Check if the class was found
|
26
|
+
if not transformer.class_found:
|
27
|
+
raise ValueError(f"Class {class_name} not found in the provided code.")
|
28
|
+
# Return the modified code
|
29
|
+
return modified_module.code
|
30
|
+
|
31
|
+
|
32
|
+
class _ClassCodeModifier(cst.CSTTransformer):
|
33
|
+
def __init__(self, class_name: str, new_code: str, mode: int):
|
34
|
+
self.class_name = class_name
|
35
|
+
self.new_code = cst.parse_module(new_code).body
|
36
|
+
self.class_found = False
|
37
|
+
self.mode = mode
|
38
|
+
|
39
|
+
def leave_ClassDef(
|
40
|
+
self, original_node: cst.ClassDef, updated_node: cst.ClassDef
|
41
|
+
) -> cst.ClassDef:
|
42
|
+
# Check if this is the target class
|
43
|
+
if original_node.name.value == self.class_name:
|
44
|
+
self.class_found = True
|
45
|
+
if self.mode == REPLACE:
|
46
|
+
new_body = updated_node.body.with_changes(body=tuple(self.new_code))
|
47
|
+
return updated_node.with_changes(body=new_body)
|
48
|
+
if self.mode == PREPEND:
|
49
|
+
new_body = updated_node.body.with_changes(
|
50
|
+
body=tuple(self.new_code) + updated_node.body.body
|
51
|
+
)
|
52
|
+
return updated_node.with_changes(body=new_body)
|
53
|
+
if self.mode == APPEND:
|
54
|
+
new_body = updated_node.body.with_changes(
|
55
|
+
body=updated_node.body.body + tuple(self.new_code)
|
56
|
+
)
|
57
|
+
return updated_node.with_changes(body=new_body)
|
58
|
+
return updated_node
|
@@ -0,0 +1,68 @@
|
|
1
|
+
import libcst as cst
|
2
|
+
|
3
|
+
from zrb.util.codemod.modification_mode import APPEND, PREPEND, REPLACE
|
4
|
+
|
5
|
+
|
6
|
+
def replace_parent_class(
|
7
|
+
original_code: str, class_name: str, parent_class_name: str
|
8
|
+
) -> str:
|
9
|
+
return _modify_parent_class(original_code, class_name, parent_class_name, REPLACE)
|
10
|
+
|
11
|
+
|
12
|
+
def append_parent_class(
|
13
|
+
original_code: str, class_name: str, parent_class_name: str
|
14
|
+
) -> str:
|
15
|
+
return _modify_parent_class(original_code, class_name, parent_class_name, APPEND)
|
16
|
+
|
17
|
+
|
18
|
+
def prepend_parent_class(
|
19
|
+
original_code: str, class_name: str, parent_class_name: str
|
20
|
+
) -> str:
|
21
|
+
return _modify_parent_class(original_code, class_name, parent_class_name, PREPEND)
|
22
|
+
|
23
|
+
|
24
|
+
def _modify_parent_class(
|
25
|
+
original_code: str, class_name: str, parent_class_name: str, mode: int
|
26
|
+
) -> str:
|
27
|
+
# Parse the original code into a module
|
28
|
+
module = cst.parse_module(original_code)
|
29
|
+
# Initialize transformer with the class name and parent class name
|
30
|
+
transformer = _ParentClassAdder(class_name, parent_class_name, mode)
|
31
|
+
# Apply the transformation
|
32
|
+
modified_module = module.visit(transformer)
|
33
|
+
# Check if the class was found
|
34
|
+
if not transformer.class_found:
|
35
|
+
raise ValueError(f"Class {class_name} not found in the provided code.")
|
36
|
+
# Return the modified code
|
37
|
+
return modified_module.code
|
38
|
+
|
39
|
+
|
40
|
+
class _ParentClassAdder(cst.CSTTransformer):
|
41
|
+
def __init__(self, class_name: str, parent_class_name: str, mode: int):
|
42
|
+
self.class_name = class_name
|
43
|
+
self.parent_class_name = parent_class_name
|
44
|
+
self.class_found = False
|
45
|
+
self.mode = mode
|
46
|
+
|
47
|
+
def leave_ClassDef(
|
48
|
+
self, original_node: cst.ClassDef, updated_node: cst.ClassDef
|
49
|
+
) -> cst.ClassDef:
|
50
|
+
# Check if this is the target class
|
51
|
+
if original_node.name.value == self.class_name:
|
52
|
+
self.class_found = True
|
53
|
+
if self.mode == REPLACE:
|
54
|
+
new_bases = (cst.Arg(value=cst.Name(self.parent_class_name)),)
|
55
|
+
return updated_node.with_changes(bases=new_bases)
|
56
|
+
if self.mode == PREPEND:
|
57
|
+
new_bases = (
|
58
|
+
cst.Arg(value=cst.Name(self.parent_class_name)),
|
59
|
+
*updated_node.bases,
|
60
|
+
)
|
61
|
+
return updated_node.with_changes(bases=new_bases)
|
62
|
+
if self.mode == APPEND:
|
63
|
+
new_bases = (
|
64
|
+
*updated_node.bases,
|
65
|
+
cst.Arg(value=cst.Name(self.parent_class_name)),
|
66
|
+
)
|
67
|
+
return updated_node.with_changes(bases=new_bases)
|
68
|
+
return updated_node
|
@@ -0,0 +1,128 @@
|
|
1
|
+
import libcst as cst
|
2
|
+
|
3
|
+
from zrb.util.codemod.modification_mode import APPEND, PREPEND
|
4
|
+
|
5
|
+
|
6
|
+
def append_property_to_class(
|
7
|
+
original_code: str,
|
8
|
+
class_name: str,
|
9
|
+
property_name: str,
|
10
|
+
annotation: str,
|
11
|
+
default_value: str,
|
12
|
+
) -> str:
|
13
|
+
return _modify_class_property(
|
14
|
+
original_code, class_name, property_name, annotation, default_value, APPEND
|
15
|
+
)
|
16
|
+
|
17
|
+
|
18
|
+
def prepend_property_to_class(
|
19
|
+
original_code: str,
|
20
|
+
class_name: str,
|
21
|
+
property_name: str,
|
22
|
+
annotation: str,
|
23
|
+
default_value: str,
|
24
|
+
) -> str:
|
25
|
+
return _modify_class_property(
|
26
|
+
original_code, class_name, property_name, annotation, default_value, PREPEND
|
27
|
+
)
|
28
|
+
|
29
|
+
|
30
|
+
def _modify_class_property(
|
31
|
+
original_code: str,
|
32
|
+
class_name: str,
|
33
|
+
property_name: str,
|
34
|
+
annotation: str,
|
35
|
+
default_value: str,
|
36
|
+
mode: int,
|
37
|
+
) -> str:
|
38
|
+
# Parse the original code into a module
|
39
|
+
module = cst.parse_module(original_code)
|
40
|
+
# Initialize transformer with the class name, property name, annotation, and default value
|
41
|
+
transformer = _ClassPropertyModifier(
|
42
|
+
class_name, property_name, annotation, default_value, mode
|
43
|
+
)
|
44
|
+
# Apply the transformation
|
45
|
+
modified_module = module.visit(transformer)
|
46
|
+
# Check if the class was found
|
47
|
+
if not transformer.class_found:
|
48
|
+
raise ValueError(f"Class {class_name} not found in the provided code.")
|
49
|
+
# Return the modified code
|
50
|
+
return modified_module.code
|
51
|
+
|
52
|
+
|
53
|
+
class _ClassPropertyModifier(cst.CSTTransformer):
|
54
|
+
def __init__(
|
55
|
+
self,
|
56
|
+
class_name: str,
|
57
|
+
property_name: str,
|
58
|
+
annotation: str,
|
59
|
+
default_value: str,
|
60
|
+
mode: int,
|
61
|
+
):
|
62
|
+
self.class_name = class_name
|
63
|
+
self.property_name = property_name
|
64
|
+
self.annotation = cst.Annotation(cst.parse_expression(annotation))
|
65
|
+
self.default_value = cst.parse_expression(default_value)
|
66
|
+
self.class_found = False
|
67
|
+
self.mode = mode
|
68
|
+
|
69
|
+
def leave_ClassDef(
|
70
|
+
self, original_node: cst.ClassDef, updated_node: cst.ClassDef
|
71
|
+
) -> cst.ClassDef:
|
72
|
+
# Check if this is the target class
|
73
|
+
if original_node.name.value == self.class_name:
|
74
|
+
self.class_found = True
|
75
|
+
# Create the annotated property with a default value
|
76
|
+
new_property = cst.SimpleStatementLine(
|
77
|
+
body=[
|
78
|
+
cst.AnnAssign(
|
79
|
+
target=cst.Name(self.property_name),
|
80
|
+
annotation=self.annotation,
|
81
|
+
value=self.default_value,
|
82
|
+
)
|
83
|
+
]
|
84
|
+
)
|
85
|
+
if self.mode == PREPEND:
|
86
|
+
new_body = cst.IndentedBlock(
|
87
|
+
body=(new_property,) + updated_node.body.body
|
88
|
+
)
|
89
|
+
return updated_node.with_changes(body=new_body)
|
90
|
+
if self.mode == APPEND:
|
91
|
+
# Identify properties and methods
|
92
|
+
properties = []
|
93
|
+
methods = []
|
94
|
+
for stmt in updated_node.body.body:
|
95
|
+
if isinstance(stmt, cst.SimpleStatementLine) and isinstance(
|
96
|
+
stmt.body[0], (cst.AnnAssign, cst.Assign)
|
97
|
+
):
|
98
|
+
properties.append(stmt)
|
99
|
+
elif isinstance(stmt, cst.FunctionDef):
|
100
|
+
methods.append(stmt)
|
101
|
+
if properties:
|
102
|
+
# Class has properties
|
103
|
+
last_property_index = updated_node.body.body.index(properties[-1])
|
104
|
+
new_body = cst.IndentedBlock(
|
105
|
+
body=(
|
106
|
+
updated_node.body.body[: last_property_index + 1]
|
107
|
+
+ (new_property,)
|
108
|
+
+ updated_node.body.body[last_property_index + 1 :]
|
109
|
+
)
|
110
|
+
)
|
111
|
+
return updated_node.with_changes(body=new_body)
|
112
|
+
if methods:
|
113
|
+
# Class doesn't have properties but has methods
|
114
|
+
first_method_index = updated_node.body.body.index(methods[0])
|
115
|
+
new_body = cst.IndentedBlock(
|
116
|
+
body=(
|
117
|
+
updated_node.body.body[:first_method_index]
|
118
|
+
+ (new_property,)
|
119
|
+
+ updated_node.body.body[first_method_index:]
|
120
|
+
)
|
121
|
+
)
|
122
|
+
return updated_node.with_changes(body=new_body)
|
123
|
+
# Class is empty, add add the bottom
|
124
|
+
new_body = cst.IndentedBlock(
|
125
|
+
body=updated_node.body.body + (new_property,)
|
126
|
+
)
|
127
|
+
return updated_node.with_changes(body=new_body)
|
128
|
+
return updated_node
|