zrb 1.0.0b8__py3-none-any.whl → 1.0.0b10__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.
Files changed (81) hide show
  1. zrb/__main__.py +3 -0
  2. zrb/builtin/project/add/fastapp/fastapp_task.py +1 -0
  3. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/.coveragerc +11 -0
  4. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/.gitignore +4 -0
  5. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/add_column_task.py +4 -4
  6. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/config.py +5 -0
  7. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_task.py +108 -1
  8. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +67 -4
  9. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/my_entity_service.py +5 -5
  10. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/schema/my_entity.py +1 -0
  11. 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
  12. 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
  13. 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
  14. 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
  15. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/gateway_subroute.py +57 -13
  16. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/input.py +8 -0
  17. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +2 -2
  18. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py +6 -1
  19. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/module_task_definition.py +10 -6
  20. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task.py +65 -14
  21. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task_util.py +106 -0
  22. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/util.py +6 -86
  23. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_db_repository.py +27 -11
  24. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_service.py +140 -51
  25. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/error.py +15 -0
  26. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/parser.py +1 -1
  27. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +22 -4
  28. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client.py +21 -0
  29. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_auth_tables.py +106 -61
  30. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/8ed025bcc845_create_permissions.py +69 -0
  31. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration_metadata.py +3 -4
  32. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/route.py +15 -14
  33. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/permission_service.py +4 -4
  34. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_db_repository.py +24 -5
  35. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service.py +14 -12
  36. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_db_repository.py +134 -97
  37. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository.py +28 -11
  38. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service.py +215 -13
  39. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service_factory.py +30 -2
  40. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +216 -41
  41. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/auth.py +57 -0
  42. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/requirements.txt +7 -1
  43. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/permission.py +2 -0
  44. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/role.py +13 -12
  45. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +64 -12
  46. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/_util/access_token.py +19 -0
  47. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_create_permission.py +59 -0
  48. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_delete_permission.py +68 -0
  49. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_read_permission.py +71 -0
  50. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_update_permission.py +66 -0
  51. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/test_user_session.py +195 -0
  52. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_health_and_readiness.py +28 -0
  53. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_homepage.py +17 -0
  54. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_not_found_error.py +16 -0
  55. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test.sh +7 -0
  56. zrb/task/base_task.py +10 -10
  57. zrb/task/cmd_task.py +2 -5
  58. zrb/util/cmd/command.py +39 -48
  59. zrb/util/codemod/modification_mode.py +3 -0
  60. zrb/util/codemod/modify_class.py +58 -0
  61. zrb/util/codemod/modify_class_parent.py +68 -0
  62. zrb/util/codemod/modify_class_property.py +128 -0
  63. zrb/util/codemod/modify_dict.py +75 -0
  64. zrb/util/codemod/modify_function.py +65 -0
  65. zrb/util/codemod/modify_function_call.py +68 -0
  66. zrb/util/codemod/modify_method.py +88 -0
  67. zrb/util/codemod/{prepend_code_to_module.py → modify_module.py} +2 -3
  68. zrb/util/file.py +3 -2
  69. {zrb-1.0.0b8.dist-info → zrb-1.0.0b10.dist-info}/METADATA +2 -1
  70. {zrb-1.0.0b8.dist-info → zrb-1.0.0b10.dist-info}/RECORD +72 -55
  71. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/migrate.py +0 -3
  72. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/session.py +0 -48
  73. zrb/util/codemod/append_code_to_class.py +0 -35
  74. zrb/util/codemod/append_code_to_function.py +0 -38
  75. zrb/util/codemod/append_code_to_method.py +0 -55
  76. zrb/util/codemod/append_key_to_dict.py +0 -51
  77. zrb/util/codemod/append_param_to_function_call.py +0 -39
  78. zrb/util/codemod/prepend_parent_to_class.py +0 -38
  79. zrb/util/codemod/prepend_property_to_class.py +0 -55
  80. {zrb-1.0.0b8.dist-info → zrb-1.0.0b10.dist-info}/WHEEL +0 -0
  81. {zrb-1.0.0b8.dist-info → zrb-1.0.0b10.dist-info}/entry_points.txt +0 -0
@@ -9,6 +9,7 @@ from sqlmodel import Field, SQLModel
9
9
 
10
10
  class UserBase(SQLModel):
11
11
  username: str
12
+ active: bool
12
13
 
13
14
 
14
15
  class UserCreate(UserBase):
@@ -23,7 +24,7 @@ class UserCreateWithAudit(UserCreate):
23
24
 
24
25
 
25
26
  class UserCreateWithRoles(UserCreate):
26
- role_ids: list[str] | None = None
27
+ role_names: list[str] | None = None
27
28
 
28
29
  def with_audit(self, created_by: str) -> "UserCreateWithRolesAndAudit":
29
30
  return UserCreateWithRolesAndAudit(**self.model_dump(), created_by=created_by)
@@ -33,18 +34,21 @@ class UserCreateWithRolesAndAudit(UserCreateWithRoles):
33
34
  created_by: str
34
35
 
35
36
  def get_user_create_with_audit(self) -> UserCreateWithAudit:
36
- data = {key: val for key, val in self.model_dump().items() if key != "role_ids"}
37
+ data = {
38
+ key: val for key, val in self.model_dump().items() if key != "role_names"
39
+ }
37
40
  return UserCreateWithAudit(**data)
38
41
 
39
- def get_role_ids(self) -> list[str]:
40
- if self.role_ids is None:
42
+ def get_role_names(self) -> list[str]:
43
+ if self.role_names is None:
41
44
  return []
42
- return self.role_ids
45
+ return self.role_names
43
46
 
44
47
 
45
48
  class UserUpdate(SQLModel):
46
49
  username: str | None = None
47
50
  password: str | None = None
51
+ active: bool | None = None
48
52
 
49
53
  def with_audit(self, updated_by: str) -> "UserUpdateWithAudit":
50
54
  return UserUpdateWithAudit(**self.model_dump(), updated_by=updated_by)
@@ -55,7 +59,7 @@ class UserUpdateWithAudit(UserUpdate):
55
59
 
56
60
 
57
61
  class UserUpdateWithRoles(UserUpdate):
58
- role_ids: list[str] | None = None
62
+ role_names: list[str] | None = None
59
63
 
60
64
  def with_audit(self, updated_by: str) -> "UserUpdateWithRolesAndAudit":
61
65
  return UserUpdateWithRolesAndAudit(**self.model_dump(), updated_by=updated_by)
@@ -65,19 +69,32 @@ class UserUpdateWithRolesAndAudit(UserUpdateWithRoles):
65
69
  updated_by: str
66
70
 
67
71
  def get_user_update_with_audit(self) -> UserUpdateWithAudit:
68
- data = {key: val for key, val in self.model_dump().items() if key != "role_ids"}
72
+ data = {
73
+ key: val for key, val in self.model_dump().items() if key != "role_names"
74
+ }
69
75
  return UserUpdateWithAudit(**data)
70
76
 
71
- def get_role_ids(self) -> list[str]:
72
- if self.role_ids is None:
77
+ def get_role_names(self) -> list[str]:
78
+ if self.role_names is None:
73
79
  return []
74
- return self.role_ids
80
+ return self.role_names
75
81
 
76
82
 
77
83
  class UserResponse(UserBase):
78
84
  id: str
79
- roles: list[Role]
80
- permissions: list[Permission]
85
+ role_names: list[str]
86
+ permission_names: list[str]
87
+
88
+
89
+ class AuthUserResponse(UserResponse):
90
+ is_super_user: bool
91
+ is_guest: bool
92
+
93
+ def has_permission(self, permission_name: str):
94
+ return self.is_super_user or permission_name in self.permission_names
95
+
96
+ def has_role(self, role_name: str):
97
+ return self.is_super_user or role_name in self.role_names
81
98
 
82
99
 
83
100
  class MultipleUserResponse(BaseModel):
@@ -85,7 +102,30 @@ class MultipleUserResponse(BaseModel):
85
102
  count: int
86
103
 
87
104
 
105
+ class UserCredentials(SQLModel):
106
+ username: str
107
+ password: str
108
+
109
+
110
+ class UserTokenData(SQLModel):
111
+ access_token: str
112
+ refresh_token: str
113
+ access_token_expired_at: datetime.datetime
114
+ refresh_token_expired_at: datetime.datetime
115
+
116
+
117
+ class UserSessionResponse(SQLModel):
118
+ id: str
119
+ user_id: str
120
+ access_token: str
121
+ refresh_token: str
122
+ token_type: str
123
+ access_token_expired_at: datetime.datetime
124
+ refresh_token_expired_at: datetime.datetime
125
+
126
+
88
127
  class User(SQLModel, table=True):
128
+ __tablename__ = "users"
89
129
  id: str = Field(default_factory=lambda: ulid.new().str, primary_key=True)
90
130
  created_at: datetime.datetime = Field(index=True)
91
131
  created_by: str = Field(index=True)
@@ -93,11 +133,23 @@ class User(SQLModel, table=True):
93
133
  updated_by: str | None = Field(index=True)
94
134
  username: str = Field(index=True, unique=True)
95
135
  password: str
136
+ active: bool = Field(index=True)
96
137
 
97
138
 
98
139
  class UserRole(SQLModel, table=True):
140
+ __tablename__ = "user_roles"
99
141
  id: str = Field(default_factory=lambda: ulid.new().str, primary_key=True)
100
142
  user_id: str = Field(index=True)
101
143
  role_id: str = Field(index=True)
102
144
  created_at: datetime.datetime | None
103
145
  created_by: str | None
146
+
147
+
148
+ class UserSession(SQLModel, table=True):
149
+ __tablename__ = "user_sessions"
150
+ id: str = Field(default_factory=lambda: ulid.new().str, primary_key=True)
151
+ user_id: str = Field(index=True)
152
+ access_token: str = Field(index=True)
153
+ refresh_token: str = Field(index=True)
154
+ access_token_expired_at: datetime.datetime = Field(index=True)
155
+ refresh_token_expired_at: datetime.datetime = Field(index=True)
@@ -0,0 +1,19 @@
1
+ import time
2
+
3
+ from fastapi.testclient import TestClient
4
+ from my_app_name.config import APP_AUTH_SUPER_USER, APP_AUTH_SUPER_USER_PASSWORD
5
+ from my_app_name.main import app
6
+
7
+
8
+ def get_admin_access_token():
9
+ client = TestClient(app, base_url="http://localhost")
10
+ # Create new admin user session and check the response
11
+ session_response = client.post(
12
+ "/api/v1/user-sessions",
13
+ data={
14
+ "username": APP_AUTH_SUPER_USER,
15
+ "password": APP_AUTH_SUPER_USER_PASSWORD,
16
+ },
17
+ )
18
+ session_data = session_response.json()
19
+ return session_data.get("access_token")
@@ -0,0 +1,59 @@
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_create_permission():
7
+ client = TestClient(app, base_url="http://localhost")
8
+ access_token = get_admin_access_token()
9
+ new_permission_data = {
10
+ "name": "new-permission",
11
+ "description": "new-permission-description",
12
+ }
13
+ response = client.post(
14
+ "/api/v1/permissions",
15
+ json=new_permission_data,
16
+ headers={"Authorization": f"Bearer {access_token}"},
17
+ )
18
+ assert response.status_code == 200
19
+ response_data = response.json()
20
+ assert response_data.get("id") is not None
21
+ assert response_data.get("id") != ""
22
+ assert response_data.get("name") == "new-permission"
23
+ assert response_data.get("description") == "new-permission-description"
24
+
25
+
26
+ def test_create_permission_bulk():
27
+ client = TestClient(app, base_url="http://localhost")
28
+ access_token = get_admin_access_token()
29
+ new_first_permission_data = {
30
+ "name": "new-permission-bulk-1",
31
+ "description": "new-permission-bulk-description-1",
32
+ }
33
+ new_second_permission_data = {
34
+ "name": "new-permission-bulk-2",
35
+ "description": "new-permission-bulk-description-2",
36
+ }
37
+ new_permission_data = [new_first_permission_data, new_second_permission_data]
38
+ response = client.post(
39
+ "/api/v1/permissions/bulk",
40
+ json=new_permission_data,
41
+ headers={"Authorization": f"Bearer {access_token}"},
42
+ )
43
+ assert response.status_code == 200
44
+ response_data = response.json()
45
+ assert len(response_data) == 2
46
+ # Id should not be empty
47
+ assert response_data[0] is not None
48
+ assert response_data[0] != ""
49
+ assert response_data[1] is not None
50
+ assert response_data[1] != ""
51
+ # Data should match
52
+ assert new_first_permission_data["name"] in [row["name"] for row in response_data]
53
+ assert new_second_permission_data["name"] in [row["name"] for row in response_data]
54
+ assert new_first_permission_data["description"] in [
55
+ row["description"] for row in response_data
56
+ ]
57
+ assert new_second_permission_data["description"] in [
58
+ row["description"] for row in response_data
59
+ ]
@@ -0,0 +1,68 @@
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_delete_permission():
7
+ client = TestClient(app, base_url="http://localhost")
8
+ access_token = get_admin_access_token()
9
+ new_permission_data = {
10
+ "name": "to-be-deleted-permission",
11
+ "description": "to-be-deleted-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
+ # deleting
21
+ response = client.delete(
22
+ f"/api/v1/permissions/{id}", headers={"Authorization": f"Bearer {access_token}"}
23
+ )
24
+ assert response.status_code == 200
25
+ response_data = response.json()
26
+ assert response_data.get("id") == id
27
+ assert response_data.get("name") == "to-be-deleted-permission"
28
+ assert response_data.get("description") == "to-be-deleted-permission-description"
29
+
30
+
31
+ def test_delete_permission_bulk():
32
+ client = TestClient(app, base_url="http://localhost")
33
+ access_token = get_admin_access_token()
34
+ new_first_permission_data = {
35
+ "name": "to-be-deleted-permission-bulk-1",
36
+ "description": "to-be-deleted-permission-bulk-description-1",
37
+ }
38
+ new_second_permission_data = {
39
+ "name": "to-be-deleted-permission-bulk-2",
40
+ "description": "to-be-deleted-permission-bulk-description-2",
41
+ }
42
+ new_permission_data = [new_first_permission_data, new_second_permission_data]
43
+ insert_response = client.post(
44
+ "/api/v1/permissions/bulk",
45
+ json=new_permission_data,
46
+ headers={"Authorization": f"Bearer {access_token}"},
47
+ )
48
+ assert insert_response.status_code == 200
49
+ ids = [row["id"] for row in insert_response.json()]
50
+ # deleting (use client.request since client.delete doesn't support json param)
51
+ response = client.request(
52
+ "DELETE",
53
+ f"/api/v1/permissions/bulk",
54
+ json=ids,
55
+ headers={"Authorization": f"Bearer {access_token}"},
56
+ )
57
+ assert response.status_code == 200
58
+ response_data = response.json()
59
+ # Data should match
60
+ assert len([row["id"] for row in response_data if row["id"] in ids]) == 2
61
+ assert new_first_permission_data["name"] in [row["name"] for row in response_data]
62
+ assert new_second_permission_data["name"] in [row["name"] for row in response_data]
63
+ assert new_first_permission_data["description"] in [
64
+ row["description"] for row in response_data
65
+ ]
66
+ assert new_second_permission_data["description"] in [
67
+ row["description"] for row in response_data
68
+ ]
@@ -0,0 +1,71 @@
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_read_permission_by_id():
7
+ client = TestClient(app, base_url="http://localhost")
8
+ access_token = get_admin_access_token()
9
+ new_permission_data = {
10
+ "name": "to-be-read-permission",
11
+ "description": "to-be-read-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
+ # fetching
21
+ response = client.get(
22
+ f"/api/v1/permissions/{id}", headers={"Authorization": f"Bearer {access_token}"}
23
+ )
24
+ assert response.status_code == 200
25
+ response_data = response.json()
26
+ assert response_data.get("id") == id
27
+ assert response_data.get("name") == "to-be-read-permission"
28
+ assert response_data.get("description") == "to-be-read-permission-description"
29
+
30
+
31
+ def test_read_permission_bulk():
32
+ client = TestClient(app, base_url="http://localhost")
33
+ access_token = get_admin_access_token()
34
+ new_first_permission_data = {
35
+ "name": "to-be-read-permission-bulk-1",
36
+ "description": "to-be-read-permission-bulk-description-1",
37
+ }
38
+ new_second_permission_data = {
39
+ "name": "to-be-read-permission-bulk-2",
40
+ "description": "to-be-read-permission-bulk-description-2",
41
+ }
42
+ new_permission_data = [new_first_permission_data, new_second_permission_data]
43
+ insert_response = client.post(
44
+ "/api/v1/permissions/bulk",
45
+ json=new_permission_data,
46
+ headers={"Authorization": f"Bearer {access_token}"},
47
+ )
48
+ assert insert_response.status_code == 200
49
+ ids = [row["id"] for row in insert_response.json()]
50
+ # fetching
51
+ response = client.get(
52
+ f"/api/v1/permissions",
53
+ params={
54
+ "filter": "name:like:to-be-read-permission-bulk-%",
55
+ },
56
+ headers={"Authorization": f"Bearer {access_token}"},
57
+ )
58
+ assert response.status_code == 200
59
+ response_data_count = response.json()["count"]
60
+ assert response_data_count == 2
61
+ response_data = response.json()["data"]
62
+ # Data should match
63
+ assert len([row["id"] for row in response_data if row["id"] in ids]) == 2
64
+ assert new_first_permission_data["name"] in [row["name"] for row in response_data]
65
+ assert new_second_permission_data["name"] in [row["name"] for row in response_data]
66
+ assert new_first_permission_data["description"] in [
67
+ row["description"] for row in response_data
68
+ ]
69
+ assert new_second_permission_data["description"] in [
70
+ row["description"] for row in response_data
71
+ ]
@@ -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
@@ -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,17 @@
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
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(
16
+ os.path.join(view_path, "content", "homepage.html")
17
+ ).body.decode("utf-8")