chore(): test and validation improvements

This commit is contained in:
ITQ
2026-02-13 10:39:11 +03:00
parent 9c45d9883e
commit 94d99c72e6
12 changed files with 96 additions and 177 deletions
+5 -44
View File
@@ -37,7 +37,7 @@ router = Router(tags=["users"])
@require_admin
def list_users(
request: HttpRequest,
role: str | None = None,
role: UserRole | None = None,
is_active: bool | None = None, # noqa: FBT001
search: str | None = None,
limit: int = 50,
@@ -54,7 +54,7 @@ def list_users(
@router.post(
"",
response={201: UserOut},
response={HTTPStatus.CREATED: UserOut},
auth=jwt_bearer,
summary="Create user",
description=(
@@ -66,25 +66,7 @@ def create_user(
request: HttpRequest,
payload: UserCreateIn,
) -> tuple[HTTPStatus, UserOut]:
valid_roles = {choice[0] for choice in UserRole.choices}
if payload.role not in valid_roles:
raise ValidationError(
{
"role": (
f"Invalid role '{payload.role}'. "
f"Must be one of: {', '.join(sorted(valid_roles))}"
)
}
)
user = user_create(
username=payload.username,
email=payload.email,
password=payload.password,
role=payload.role,
first_name=payload.first_name,
last_name=payload.last_name,
)
user = user_create(**payload.model_dump())
return HTTPStatus.CREATED, UserOut.model_validate(user)
@@ -123,34 +105,13 @@ def update_user(
) -> tuple[HTTPStatus, UserOut]:
user = get_object_or_404(User, pk=user_id)
if payload.role is not None:
valid_roles = {choice[0] for choice in UserRole.choices}
if payload.role not in valid_roles:
raise ValidationError(
{
"role": (
f"Invalid role '{payload.role}'. "
f"Must be one of: {', '.join(sorted(valid_roles))}"
)
}
)
updated_user = user_update(
user=user,
username=payload.username,
email=payload.email,
password=payload.password,
role=payload.role,
is_active=payload.is_active,
first_name=payload.first_name,
last_name=payload.last_name,
)
updated_user = user_update(user=user, **payload.model_dump())
return HTTPStatus.OK, UserOut.model_validate(updated_user)
@router.delete(
"/{user_id}",
response={204: None},
response={HTTPStatus.NO_CONTENT: None},
auth=jwt_bearer,
summary="Delete user",
description="Permanently delete a user. Admin only.",
+28 -87
View File
@@ -1,14 +1,11 @@
from typing import ClassVar
from ninja import ModelSchema, Schema
from pydantic import Field
from apps.users.models import User
from apps.users.models import User, UserRole
class UserOut(ModelSchema):
is_active: bool
class Meta:
model = User
fields: ClassVar[tuple[str, ...]] = (
@@ -18,97 +15,41 @@ class UserOut(ModelSchema):
User.role.field.name,
User.first_name.field.name,
User.last_name.field.name,
User._meta.get_field("is_active").name,
User.is_active.field.name,
)
class UserCreateIn(Schema):
username: str = Field(
...,
min_length=1,
max_length=150,
description="Unique username for the new account.",
)
email: str = Field(
...,
min_length=1,
max_length=254,
description="Email address.",
)
password: str = Field(
...,
min_length=8,
max_length=128,
description="Account password (min 8 characters).",
)
role: str = Field(
"viewer",
description=(
"Platform role to assign. "
"One of: admin, experimenter, approver, viewer."
),
)
first_name: str = Field(
"",
max_length=150,
description="First name.",
)
last_name: str = Field(
"",
max_length=150,
description="Last name.",
)
class UserCreateIn(ModelSchema):
role: UserRole = UserRole.VIEWER
class Meta:
model = User
fields: ClassVar[tuple[str, ...]] = (
User.username.field.name,
User.email.field.name,
User.password.field.name,
User.role.field.name,
)
class UserUpdateIn(Schema):
username: str | None = Field(
None,
min_length=1,
max_length=150,
description="New username.",
)
email: str | None = Field(
None,
min_length=1,
max_length=254,
description="New email address.",
)
password: str | None = Field(
None,
min_length=8,
max_length=128,
description="New password (min 8 characters).",
)
role: str | None = Field(
None,
description=(
"New platform role. One of: admin, experimenter, approver, viewer."
),
)
first_name: str | None = Field(
None,
max_length=150,
description="New first name.",
)
last_name: str | None = Field(
None,
max_length=150,
description="New last name.",
)
is_active: bool | None = Field(
None,
description="Set active/inactive status.",
)
class UserUpdateIn(ModelSchema):
username: str | None = None
email: str | None = None
first_name: str | None = None
last_name: str | None = None
class Meta:
model = User
fields: ClassVar[tuple[str, ...]] = (
User.username.field.name,
User.email.field.name,
User.first_name.field.name,
User.last_name.field.name,
)
class UserRoleAssignIn(Schema):
role: str = Field(
...,
description=(
"Platform role to assign. "
"One of: admin, experimenter, approver, viewer."
),
)
role: UserRole
class UserListOut(Schema):
@@ -30,7 +30,7 @@ class UsersAPIDeleteAssignRoleTest(BaseUsersAPITest):
),
HTTP_AUTHORIZATION=self.admin_auth,
)
self.assertIn(resp.status_code, [422, 400])
self.assertEqual(resp.status_code, 422)
def test_delete_user_viewer_denied(self) -> None:
resp = self.client.delete(
@@ -71,7 +71,7 @@ class UsersAPIDeleteAssignRoleTest(BaseUsersAPITest):
content_type="application/json",
HTTP_AUTHORIZATION=self.admin_auth,
)
self.assertIn(resp.status_code, [422, 400])
self.assertEqual(resp.status_code, 422)
def test_assign_role_viewer_denied(self) -> None:
resp = self.client.post(
@@ -92,7 +92,7 @@ class UsersAPIListCreateTest(BaseUsersAPITest):
content_type="application/json",
HTTP_AUTHORIZATION=self.admin_auth,
)
self.assertIn(resp.status_code, [422, 400])
self.assertEqual(resp.status_code, 422)
def test_create_user_viewer_denied(self) -> None:
resp = self.client.post(
@@ -122,4 +122,4 @@ class UsersAPIListCreateTest(BaseUsersAPITest):
content_type="application/json",
HTTP_AUTHORIZATION=self.admin_auth,
)
self.assertIn(resp.status_code, [409, 422, 400, 500])
self.assertEqual(resp.status_code, 409)
@@ -34,16 +34,13 @@ class UsersAPIReadUpdateTest(BaseUsersAPITest):
reverse(
"api-1:update_user", kwargs={"user_id": str(self.viewer.pk)}
),
data=json.dumps(
{"username": "renamed_viewer", "role": "approver"}
),
data=json.dumps({"username": "renamed_viewer"}),
content_type="application/json",
HTTP_AUTHORIZATION=self.admin_auth,
)
self.assertEqual(resp.status_code, 200)
data = resp.json()
self.assertEqual(data["username"], "renamed_viewer")
self.assertEqual(data["role"], "approver")
def test_update_user_partial(self) -> None:
original_role = self.viewer.role
@@ -59,17 +56,6 @@ class UsersAPIReadUpdateTest(BaseUsersAPITest):
data = resp.json()
self.assertEqual(data["role"], original_role)
def test_update_user_invalid_role(self) -> None:
resp = self.client.patch(
reverse(
"api-1:update_user", kwargs={"user_id": str(self.viewer.pk)}
),
data=json.dumps({"role": "superadmin"}),
content_type="application/json",
HTTP_AUTHORIZATION=self.admin_auth,
)
self.assertIn(resp.status_code, [422, 400])
def test_update_user_viewer_denied(self) -> None:
resp = self.client.patch(
reverse(