Skip to content

Commit 49601a2

Browse files
Add some more tests (#97)
* Bump deps * Fix Dockerfile * Update migrations * Add factories, fixtures, create user tests * Add set user password tests * Add grant admin tests * Add revoke admin tests * Add activate user tests * Add deactivate user tests * Add list users tests
1 parent b7caf01 commit 49601a2

18 files changed

Lines changed: 1102 additions & 255 deletions

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ COPY pyproject.toml uv.lock README.md ./
1818
RUN if [ "${ENVIRONMENT}" = "prod" ]; then \
1919
uv sync --frozen --no-cache --no-dev --no-install-project; \
2020
else \
21-
uv sync --frozen --dev --no-install-project; \
21+
uv sync --frozen --no-cache --dev --no-install-project; \
2222
fi
2323

2424
COPY . .
2525

2626
RUN if [ "${ENVIRONMENT}" = "prod" ]; then \
2727
uv sync --frozen --no-cache --no-dev; \
2828
else \
29-
uv sync --frozen --dev; \
29+
uv sync --frozen --no-cache --dev; \
3030
fi
3131

3232
RUN groupadd -r runner

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ uvicorn app.main.run:make_app --host 0.0.0.0 --port 8000 --reload
2929
```
3030
Full API access:
3131
- create user via sign up
32-
- set its role to `SUPER_ADMIN` manually in DB
32+
- set its role to `super_admin` manually in DB
3333
- log in as super admin
3434

3535
Stop

pyproject.toml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,33 @@ dependencies = [
1313
"alembic-postgresql-enum==1.10.0",
1414
"bcrypt==5.0.0",
1515
"dishka==1.9.1",
16-
"fastapi==0.133.1",
16+
"fastapi==0.135.3",
1717
"fastapi-error-map==0.9.10",
1818
"psycopg[binary]==3.3.3",
1919
"pydantic-settings==2.13.1",
20-
"pyjwt[crypto]==2.11.0",
21-
"sqlalchemy[mypy]==2.0.47",
20+
"pyjwt[crypto]==2.12.1",
21+
"sqlalchemy[mypy]==2.0.48",
2222
"uuid-utils==0.14.1",
23-
"uvicorn==0.41.0",
23+
"uvicorn==0.42.0",
2424
]
2525

2626
[dependency-groups]
2727
dev = [
2828
"asgi-lifespan==2.1.0",
29-
"coverage==7.13.4",
30-
"deptry==0.24.0",
29+
"coverage==7.13.5",
30+
"deptry==0.25.1",
3131
"httpx==0.28.1",
32-
"import-linter==2.10",
32+
"import-linter==2.11",
3333
"line-profiler==5.0.2",
34-
"mypy==1.19.1",
34+
"mypy==1.20.0",
3535
"pip-audit==2.10.0",
3636
"pre-commit==4.5.1",
3737
"pytest==9.0.2",
3838
"pytest-asyncio==1.3.0",
39-
"pytest-cov==7.0.0",
40-
"ruff==0.15.4",
39+
"pytest-cov==7.1.0",
40+
"ruff==0.15.8",
4141
"slotscheck==0.19.1",
42-
"tombi==0.7.33",
42+
"tombi==0.9.13",
4343
]
4444

4545
[build-system]

src/app/infrastructure/persistence_sqla/alembic/versions/2026-03-02_221518_users.py renamed to src/app/infrastructure/persistence_sqla/alembic/versions/2026-04-01_222815_users.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"""users
22
3-
Revision ID: c64b121a3428
3+
Revision ID: 0e6c649ac887
44
Revises:
5-
Create Date: 2026-03-02 22:15:18.425263
5+
Create Date: 2026-04-01 22:28:15.224058
66
77
"""
88

@@ -13,7 +13,7 @@
1313

1414

1515
# revision identifiers, used by Alembic.
16-
revision: str = "c64b121a3428"
16+
revision: str = "0e6c649ac887"
1717
down_revision: Union[str, Sequence[str], None] = None
1818
branch_labels: Union[str, Sequence[str], None] = None
1919
depends_on: Union[str, Sequence[str], None] = None
@@ -26,7 +26,7 @@ def upgrade() -> None:
2626
sa.Column("id", sa.UUID(), nullable=False),
2727
sa.Column("username", sa.String(length=20), nullable=False),
2828
sa.Column("password_hash", sa.LargeBinary(), nullable=False),
29-
sa.Column("role", sa.Enum("SUPER_ADMIN", "ADMIN", "USER", name="user_role", native_enum=False), nullable=False),
29+
sa.Column("role", sa.Enum("super_admin", "admin", "user", name="user_role", native_enum=False), nullable=False),
3030
sa.Column("is_active", sa.Boolean(), nullable=False),
3131
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
3232
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),

src/app/infrastructure/persistence_sqla/alembic/versions/2026-03-02_230628_auth_sessions.py renamed to src/app/infrastructure/persistence_sqla/alembic/versions/2026-04-01_223011_auth_sessions.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"""auth_sessions
22
3-
Revision ID: 7b50faaefa7c
4-
Revises: c64b121a3428
5-
Create Date: 2026-03-02 23:06:28.995808
3+
Revision ID: c025baa8044e
4+
Revises: 0e6c649ac887
5+
Create Date: 2026-04-01 22:30:11.002095
66
77
"""
88

@@ -13,8 +13,8 @@
1313

1414

1515
# revision identifiers, used by Alembic.
16-
revision: str = "7b50faaefa7c"
17-
down_revision: Union[str, Sequence[str], None] = "c64b121a3428"
16+
revision: str = "c025baa8044e"
17+
down_revision: Union[str, Sequence[str], None] = "0e6c649ac887"
1818
branch_labels: Union[str, Sequence[str], None] = None
1919
depends_on: Union[str, Sequence[str], None] = None
2020

src/app/infrastructure/persistence_sqla/mappings/user.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from enum import StrEnum
2+
13
from sqlalchemy import UUID, Boolean, Column, DateTime, Enum, LargeBinary, String, Table
24
from sqlalchemy.orm import composite
35

@@ -7,6 +9,12 @@
79
from app.core.common.value_objects.utc_datetime import UtcDatetime
810
from app.infrastructure.persistence_sqla.registry import mapper_registry
911

12+
13+
def get_strenum_values(enum_cls: type[StrEnum]) -> list[str]:
14+
"""Return member values instead of member names for SQLAlchemy Enum storage."""
15+
return [e.value for e in enum_cls]
16+
17+
1018
users_table = Table(
1119
"users",
1220
mapper_registry.metadata,
@@ -20,6 +28,7 @@
2028
name="user_role",
2129
native_enum=False,
2230
validate_strings=True,
31+
values_callable=get_strenum_values,
2332
),
2433
nullable=False,
2534
),

tests/integration/with_infra/factories.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,25 @@ async def create_user_with_password(
7474
role=role,
7575
is_active=is_active,
7676
)
77+
78+
79+
async def create_super_admin_with_password(
80+
user_service: UserService,
81+
*,
82+
raw_user_id: uuid.UUID | None = None,
83+
raw_username: str | None = None,
84+
raw_password: str | None = None,
85+
is_active: bool = True,
86+
raw_now: datetime | None = None,
87+
) -> User:
88+
"""System role is not assignable via UserService; create as USER, then promote."""
89+
user = await create_user_with_password(
90+
user_service,
91+
raw_user_id=raw_user_id,
92+
raw_username=raw_username,
93+
raw_password=raw_password,
94+
is_active=is_active,
95+
raw_now=raw_now,
96+
)
97+
user.role = UserRole.SUPER_ADMIN
98+
return user

tests/integration/with_infra/users/__init__.py

Whitespace-only changes.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import httpx
2+
import pytest
3+
from sqlalchemy.ext.asyncio import AsyncSession
4+
5+
from app.core.common.entities.types_ import UserRole
6+
from app.core.common.entities.user import User
7+
from app.core.common.services.user import UserService
8+
from tests.integration.with_infra.authentication import authenticate
9+
from tests.integration.with_infra.factories import (
10+
create_raw_password,
11+
create_super_admin_with_password,
12+
create_user_with_password,
13+
)
14+
15+
16+
@pytest.fixture
17+
async def it_admin(
18+
it_client: httpx.AsyncClient,
19+
it_session: AsyncSession,
20+
it_user_service: UserService,
21+
) -> User:
22+
password = create_raw_password()
23+
admin = await create_user_with_password(it_user_service, raw_password=password, role=UserRole.ADMIN)
24+
it_session.add(admin)
25+
await it_session.commit()
26+
await authenticate(it_client, admin.username.value, password)
27+
return admin
28+
29+
30+
@pytest.fixture
31+
async def it_super_admin(
32+
it_client: httpx.AsyncClient,
33+
it_session: AsyncSession,
34+
it_user_service: UserService,
35+
) -> User:
36+
password = create_raw_password()
37+
super_admin = await create_super_admin_with_password(it_user_service, raw_password=password)
38+
it_session.add(super_admin)
39+
await it_session.commit()
40+
await authenticate(it_client, super_admin.username.value, password)
41+
return super_admin
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from typing import Final
2+
3+
USERS_ENDPOINT: Final[str] = "/api/v1/users/"

0 commit comments

Comments
 (0)