Skip to content

Commit 4d9848b

Browse files
benjaminpiliaBenjamin PILIA
andauthored
Bsr refacto admin bootstrap (#799)
* fix unit tests * add integration tests * Update unit coverage badge --------- Co-authored-by: Benjamin PILIA <benjamin.pilia@protonmail.com> Co-authored-by: benjaminpilia <benjaminpilia@users.noreply.github.com>
1 parent 880764b commit 4d9848b

File tree

14 files changed

+342
-388
lines changed

14 files changed

+342
-388
lines changed

.github/badges/coverage.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"schemaVersion":1,"label":"coverage","message":"50.86%","color":"red"}
1+
{"schemaVersion":1,"label":"coverage","message":"50.94%","color":"red"}

api/infrastructure/fastapi/endpoints/admin/providers.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,17 @@
6666
path=EndpointRoute.ADMIN_PROVIDERS,
6767
dependencies=[Security(dependency=get_current_key)],
6868
status_code=201,
69-
responses=get_documentation_responses([
70-
InconsistentModelMaxContextLengthHTTPException,
71-
InconsistentModelVectorSizeHTTPException,
72-
InvalidProviderTypeHTTPException,
73-
ProviderNotReachableHTTPException,
74-
ProviderAlreadyExistsHTTPException,
75-
RouterNotFoundHTTPException,
76-
NotAdminUserHTTPException,
77-
]),
69+
responses=get_documentation_responses(
70+
[
71+
InconsistentModelMaxContextLengthHTTPException,
72+
InconsistentModelVectorSizeHTTPException,
73+
InvalidProviderTypeHTTPException,
74+
ProviderNotReachableHTTPException,
75+
ProviderAlreadyExistsHTTPException,
76+
RouterNotFoundHTTPException,
77+
NotAdminUserHTTPException,
78+
]
79+
),
7880
)
7981
async def create_provider(
8082
body: CreateProviderBody,
@@ -179,15 +181,17 @@ async def delete_provider(
179181
path=EndpointRoute.ADMIN_PROVIDERS + "/{provider_id}",
180182
dependencies=[Security(dependency=get_current_key)],
181183
status_code=201,
182-
responses=get_documentation_responses([
183-
InconsistentModelMaxContextLengthHTTPException,
184-
InconsistentModelVectorSizeHTTPException,
185-
InvalidProviderTypeHTTPException,
186-
ProviderAlreadyExistsHTTPException,
187-
RouterNotFoundHTTPException,
188-
ProviderNotFoundHTTPException,
189-
NotAdminUserHTTPException,
190-
]),
184+
responses=get_documentation_responses(
185+
[
186+
InconsistentModelMaxContextLengthHTTPException,
187+
InconsistentModelVectorSizeHTTPException,
188+
InvalidProviderTypeHTTPException,
189+
ProviderAlreadyExistsHTTPException,
190+
RouterNotFoundHTTPException,
191+
ProviderNotFoundHTTPException,
192+
NotAdminUserHTTPException,
193+
]
194+
),
191195
)
192196
async def update_provider(
193197
provider_id: int = Path(description="The ID of the provider to update."),

api/infrastructure/fastapi/endpoints/admin/routers.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@
5252
path=EndpointRoute.ADMIN_ROUTERS,
5353
dependencies=[Security(dependency=get_current_key)],
5454
status_code=201,
55-
responses=get_documentation_responses([
56-
RouterAliasAlreadyExistsHTTPException,
57-
RouterAlreadyExistsHTTPException,
58-
NotAdminUserHTTPException,
59-
]),
55+
responses=get_documentation_responses(
56+
[
57+
RouterAliasAlreadyExistsHTTPException,
58+
RouterAlreadyExistsHTTPException,
59+
NotAdminUserHTTPException,
60+
]
61+
),
6062
)
6163
async def create_router(
6264
body: CreateRouterBody = Body(description="The router creation request."),
@@ -216,12 +218,14 @@ async def delete_router(
216218
@router.patch(
217219
path=EndpointRoute.ADMIN_ROUTERS + "/{router_id}",
218220
dependencies=[Security(dependency=get_current_key)],
219-
responses=get_documentation_responses([
220-
RouterNotFoundHTTPException,
221-
NotAdminUserHTTPException,
222-
RouterAliasAlreadyExistsHTTPException,
223-
RouterAlreadyExistsHTTPException,
224-
]),
221+
responses=get_documentation_responses(
222+
[
223+
RouterNotFoundHTTPException,
224+
NotAdminUserHTTPException,
225+
RouterAliasAlreadyExistsHTTPException,
226+
RouterAlreadyExistsHTTPException,
227+
]
228+
),
225229
status_code=200,
226230
)
227231
async def update_router(

api/infrastructure/postgres/_postgresrolesrepository.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ async def get_roles(
5858
order_by: Literal["id", "name", "created", "updated"] = "id",
5959
order_direction: Literal["asc", "desc"] = "asc",
6060
) -> list[Role]:
61-
6261
if role_id is None:
6362
# get the unique role IDs with pagination
6463
statement = select(RoleTable.id).offset(offset=offset).limit(limit=limit).order_by(text(f"{order_by} {order_direction}")) # nosemgrep

api/tests/integration/app/__init__.py

Whitespace-only changes.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import pytest
2+
from sqlalchemy import select
3+
4+
from api.domain.role.entities import PermissionType
5+
from api.schemas.core.configuration import Configuration, Dependencies, Settings
6+
from api.sql.models import Permission, User
7+
from api.tests.integration.factories import RoleSQLFactory, UserSQLFactory
8+
from api.utils.lifespan import bootstrap_default_admin
9+
10+
ADMIN_USERNAME = "admin"
11+
ADMIN_PASSWORD = "s3cr3t"
12+
13+
14+
@pytest.fixture
15+
def bootstrap_configuration() -> Configuration:
16+
return Configuration.model_construct(
17+
settings=Settings.model_construct(
18+
auth_default_username=ADMIN_USERNAME,
19+
auth_default_password=ADMIN_PASSWORD,
20+
),
21+
dependencies=Dependencies.model_construct(sentry=None),
22+
)
23+
24+
25+
@pytest.mark.asyncio(loop_scope="session")
26+
class TestBootstrapDefaultAdmin:
27+
async def test_creates_admin_user_and_role_when_no_admin_exists(self, db_session, bootstrap_configuration):
28+
# Act
29+
await bootstrap_default_admin(configuration=bootstrap_configuration, postgres_session=db_session)
30+
await db_session.flush()
31+
32+
# Assert
33+
user = (await db_session.execute(select(User).where(User.email == ADMIN_USERNAME))).scalar_one_or_none()
34+
assert user is not None
35+
assert user.email == ADMIN_USERNAME
36+
37+
permission = (
38+
await db_session.execute(select(Permission).where(Permission.role_id == user.role_id, Permission.permission == PermissionType.ADMIN))
39+
).scalar_one_or_none()
40+
assert permission is not None
41+
42+
async def test_skips_when_admin_user_already_exists(self, db_session, bootstrap_configuration):
43+
# Arrange
44+
UserSQLFactory(admin_user=True)
45+
await db_session.flush()
46+
47+
# Act
48+
await bootstrap_default_admin(configuration=bootstrap_configuration, postgres_session=db_session)
49+
50+
async def test_raises_runtime_error_when_role_name_already_taken(self, db_session, bootstrap_configuration):
51+
# Arrange
52+
role = RoleSQLFactory(name=ADMIN_USERNAME)
53+
await db_session.flush()
54+
55+
# Act & Assert
56+
with pytest.raises(RuntimeError, match=f"'{role.name}' already exists"):
57+
await bootstrap_default_admin(configuration=bootstrap_configuration, postgres_session=db_session)
58+
59+
async def test_raises_runtime_error_when_user_email_already_taken(self, db_session, bootstrap_configuration):
60+
# Arrange
61+
user = UserSQLFactory(regular_user=True, email=ADMIN_USERNAME)
62+
await db_session.flush()
63+
64+
# Act & Assert
65+
with pytest.raises(RuntimeError, match=f"'{user.email}' already exists"):
66+
await bootstrap_default_admin(configuration=bootstrap_configuration, postgres_session=db_session)
67+
68+
69+
if __name__ == "__main__":
70+
pytest.main([__file__, "-v"])
File renamed without changes.

api/tests/integration/postgres/test_postgresproviderrepository.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,7 @@ async def test_update_provider_should_persist_all_updatable_fields(self, reposit
267267

268268
# Act
269269
result = await repository.update_provider(
270-
domain_provider
271-
.with_router_id(router_2.id)
270+
domain_provider.with_router_id(router_2.id)
272271
.with_timeout(120)
273272
.with_model_hosting_zone(ProviderCarbonFootprintZone.USA)
274273
.with_model_total_params(2_000_000)

api/tests/unit/test_utils/test_carbon.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,12 @@ def test_get_carbon_footprint_return_footprint(self, mocker):
4747
mocked_electricity_mix = mocker.patch("api.utils.carbon.electricity_mixes.find_electricity_mix")
4848
mocked_electricity_mix.return_value = SimpleNamespace(adpe=1, pe=2, gwp=3, wue=4)
4949
mocked_compute_llm_impacts = mocker.patch("api.utils.carbon.compute_llm_impacts")
50-
mocked_compute_llm_impacts.return_value = dict_to_namespace({
51-
"energy": {"value": 1},
52-
"gwp": {"value": 3},
53-
})
50+
mocked_compute_llm_impacts.return_value = dict_to_namespace(
51+
{
52+
"energy": {"value": 1},
53+
"gwp": {"value": 3},
54+
}
55+
)
5456
active_params = 1
5557
total_params = 1
5658
model_zone = ProviderCarbonFootprintZone.WOR

api/tests/unit/use_case/admin/roles/test_createroleusecase.py

Lines changed: 42 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -24,58 +24,45 @@ def use_case(role_repository, user_info_repository):
2424
return CreateRoleUseCase(role_repository=role_repository, user_info_repository=user_info_repository)
2525

2626

27-
@pytest.mark.asyncio
28-
async def test_happy_path_returns_success_instance(use_case, role_repository, user_info_repository):
29-
# Arrange
30-
user_info_repository.get_user_info.return_value = UserInfoFactory(admin=True)
31-
role_repository.create_role.return_value = RoleFactory()
32-
command = CreateRoleCommand(user_id=1, name="new_role", permissions=[], limits=[])
33-
34-
# Act
35-
result = await use_case.execute(command)
36-
37-
# Assert
38-
assert isinstance(result, CreateRoleUseCaseSuccess)
39-
40-
41-
@pytest.mark.asyncio
42-
async def test_happy_path_result_contains_role_with_correct_name_and_permissions(use_case, role_repository, user_info_repository):
43-
# Arrange
44-
user_info_repository.get_user_info.return_value = UserInfoFactory(admin=True)
45-
role_repository.create_role.return_value = RoleFactory(name="created_role", permissions=[PermissionType.READ_METRIC])
46-
command = CreateRoleCommand(user_id=1, name="created_role", permissions=[PermissionType.READ_METRIC], limits=[])
47-
48-
# Act
49-
result = await use_case.execute(command)
50-
51-
# Assert
52-
assert result.role.name == "created_role"
53-
assert PermissionType.READ_METRIC in result.role.permissions
54-
55-
56-
@pytest.mark.asyncio
57-
async def test_returns_user_is_not_admin_error_when_user_is_not_admin(use_case, user_info_repository):
58-
# Arrange
59-
user_info_repository.get_user_info.return_value = UserInfoFactory(without_permission=True, limits=[])
60-
command = CreateRoleCommand(user_id=1, name="new_role", permissions=[], limits=[])
61-
62-
# Act
63-
result = await use_case.execute(command)
64-
65-
# Assert
66-
assert isinstance(result, UserIsNotAdminError)
67-
68-
69-
@pytest.mark.asyncio
70-
async def test_returns_role_already_exists_error_when_name_conflicts(use_case, role_repository, user_info_repository):
71-
# Arrange
72-
user_info_repository.get_user_info.return_value = UserInfoFactory(admin=True)
73-
role_repository.create_role.return_value = RoleAlreadyExistsError(name="existing_role")
74-
command = CreateRoleCommand(user_id=1, name="existing_role", permissions=[], limits=[])
75-
76-
# Act
77-
result = await use_case.execute(command)
78-
79-
# Assert
80-
assert isinstance(result, RoleAlreadyExistsError)
81-
assert result.name == "existing_role"
27+
class TestCreateRoleUseCase:
28+
@pytest.mark.asyncio
29+
async def test_should_returns_success_instance(self, use_case, role_repository, user_info_repository):
30+
# Arrange
31+
user_info_repository.get_user_info.return_value = UserInfoFactory(admin=True)
32+
role_repository.create_role.return_value = RoleFactory(name="created_role", permissions=[PermissionType.READ_METRIC])
33+
command = CreateRoleCommand(user_id=1, name="created_role", permissions=[PermissionType.READ_METRIC], limits=[])
34+
35+
# Act
36+
result = await use_case.execute(command)
37+
38+
# Assert
39+
assert isinstance(result, CreateRoleUseCaseSuccess)
40+
41+
assert result.role.name == "created_role"
42+
assert PermissionType.READ_METRIC in result.role.permissions
43+
44+
@pytest.mark.asyncio
45+
async def test_should_return_user_is_not_admin_error_when_user_is_not_admin(self, use_case, user_info_repository):
46+
# Arrange
47+
user_info_repository.get_user_info.return_value = UserInfoFactory(without_permission=True, limits=[])
48+
command = CreateRoleCommand(user_id=1, name="new_role", permissions=[], limits=[])
49+
50+
# Act
51+
result = await use_case.execute(command)
52+
53+
# Assert
54+
assert isinstance(result, UserIsNotAdminError)
55+
56+
@pytest.mark.asyncio
57+
async def test_should_return_role_already_exists_error_when_name_conflicts(self, use_case, role_repository, user_info_repository):
58+
# Arrange
59+
user_info_repository.get_user_info.return_value = UserInfoFactory(admin=True)
60+
role_repository.create_role.return_value = RoleAlreadyExistsError(name="existing_role")
61+
command = CreateRoleCommand(user_id=1, name="existing_role", permissions=[], limits=[])
62+
63+
# Act
64+
result = await use_case.execute(command)
65+
66+
# Assert
67+
assert isinstance(result, RoleAlreadyExistsError)
68+
assert result.name == "existing_role"

0 commit comments

Comments
 (0)