Align frontend API contracts

This commit is contained in:
2026-03-22 13:21:02 +01:00
parent 0da5b4abe2
commit e639b3c792
35 changed files with 1362 additions and 205 deletions

View File

@@ -80,23 +80,23 @@ class ProfileModelTest(TestCase):
"""Test OneToOne relationship with User"""
self.assertIsNotNone(self.profile.user)
def test_profile_first_name_optional(self):
"""Test first_name field is optional"""
def test_profile_first_name_required(self):
"""Test first_name field is required and stored without NULL."""
field = self.profile._meta.get_field("first_name")
self.assertTrue(field.blank)
self.assertTrue(field.null)
self.assertFalse(field.blank)
self.assertFalse(field.null)
def test_profile_last_name_optional(self):
"""Test last_name field is optional"""
def test_profile_last_name_required(self):
"""Test last_name field is required and stored without NULL."""
field = self.profile._meta.get_field("last_name")
self.assertTrue(field.blank)
self.assertTrue(field.null)
self.assertFalse(field.blank)
self.assertFalse(field.null)
def test_profile_middle_name_optional(self):
"""Test middle_name field is optional"""
def test_profile_middle_name_optional_but_not_null(self):
"""Test middle_name remains optional but stored without NULL."""
field = self.profile._meta.get_field("middle_name")
self.assertTrue(field.blank)
self.assertTrue(field.null)
self.assertFalse(field.null)
def test_profile_bio_optional(self):
"""Test bio field is optional"""
@@ -126,7 +126,7 @@ class ProfileModelTest(TestCase):
self.profile.middle_name = middle_name
self.profile.last_name = last_name
self.assertEqual(
self.profile.full_name, f"{first_name} {middle_name} {last_name}"
self.profile.full_name, f"{last_name} {first_name} {middle_name}"
)
# Test with only first name

View File

@@ -33,6 +33,9 @@ class UserRegistrationSerializerTest(TestCase):
"password": self.password,
"password_confirm": self.password,
"phone": f"+7{fake.numerify('##########')}",
"first_name": fake.first_name(),
"middle_name": fake.first_name(),
"last_name": fake.last_name(),
}
def test_valid_registration_data(self):
@@ -95,6 +98,20 @@ class UserRegistrationSerializerTest(TestCase):
self.assertEqual(user.email, self.user_data["email"])
self.assertEqual(user.username, self.user_data["username"])
self.assertTrue(user.check_password(self.user_data["password"]))
self.assertEqual(user.profile.first_name, self.user_data["first_name"])
self.assertEqual(user.profile.middle_name, self.user_data["middle_name"])
self.assertEqual(user.profile.last_name, self.user_data["last_name"])
def test_registration_requires_first_and_last_name(self):
data = self.user_data.copy()
data.pop("first_name")
data.pop("last_name")
serializer = UserRegistrationSerializer(data=data)
self.assertFalse(serializer.is_valid())
self.assertIn("first_name", serializer.errors)
self.assertIn("last_name", serializer.errors)
class UserSerializerTest(TestCase):
@@ -217,6 +234,7 @@ class AdminUserUpdateSerializerTest(TestCase):
"is_active": False,
"first_name": fake.first_name(),
"middle_name": fake.first_name(),
"last_name": fake.last_name(),
},
partial=True,
)

View File

@@ -57,6 +57,18 @@ class UserServiceTest(TestCase):
self.assertEqual(UserService.get_user_role(user), UserService.ROLE_ADMIN)
self.assertTrue(user.groups.filter(name=UserService.ROLE_ADMIN).exists())
def test_create_user_with_profile_names(self):
user = UserService.create_user(
**self.user_data,
first_name="Иван",
middle_name="Иванович",
last_name="Иванов",
)
self.assertEqual(user.profile.first_name, "Иван")
self.assertEqual(user.profile.middle_name, "Иванович")
self.assertEqual(user.profile.last_name, "Иванов")
def test_get_user_by_email_found(self):
"""Test getting user by existing email"""
found_user = UserService.get_user_by_email(self.user.email)
@@ -191,8 +203,8 @@ class UserServiceTest(TestCase):
queryset = UserService.get_filtered_users_queryset(ordering="first_name")
ids = list(queryset.values_list("id", flat=True)[:2])
self.assertEqual(ids, [second.id, first.id])
ids = list(queryset.values_list("id", flat=True))
self.assertLess(ids.index(second.id), ids.index(first.id))
def test_get_user_capabilities_for_admin(self):
"""Test admin capabilities set."""

View File

@@ -7,6 +7,7 @@ from django.urls import reverse
from faker import Faker
from rest_framework import status
from rest_framework.test import APIClient, APITestCase
from rest_framework_simplejwt.token_blacklist.models import BlacklistedToken
from .factories import ProfileFactory, UserFactory
@@ -26,6 +27,9 @@ class RegisterViewTest(APITestCase):
"password": self.password,
"password_confirm": self.password,
"phone": f"+7{fake.numerify('##########')}",
"first_name": fake.first_name(),
"middle_name": fake.first_name(),
"last_name": fake.last_name(),
}
def test_register_success(self):
@@ -39,7 +43,9 @@ class RegisterViewTest(APITestCase):
self.assertIn("access", response.data["tokens"])
# Verify user was created
self.assertTrue(User.objects.filter(email=self.user_data["email"]).exists())
created_user = User.objects.get(email=self.user_data["email"])
self.assertEqual(created_user.profile.first_name, self.user_data["first_name"])
self.assertEqual(created_user.profile.last_name, self.user_data["last_name"])
def test_register_passwords_do_not_match(self):
"""Test registration fails when passwords don't match"""
@@ -77,6 +83,17 @@ class RegisterViewTest(APITestCase):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("password", response.data)
def test_register_requires_first_and_last_name(self):
data = self.user_data.copy()
data.pop("first_name")
data.pop("last_name")
response = self.client.post(self.register_url, data, format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("first_name", response.data)
self.assertIn("last_name", response.data)
class LoginViewTest(APITestCase):
"""Tests for LoginView"""
@@ -125,7 +142,7 @@ class LoginViewTest(APITestCase):
class LogoutViewTest(APITestCase):
def test_logout_returns_success_message(self):
def test_logout_blacklists_refresh_tokens_and_returns_empty_payload(self):
user = UserFactory.create_user()
tokens = UserService.get_tokens_for_user(user)
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {tokens['access']}")
@@ -133,7 +150,8 @@ class LogoutViewTest(APITestCase):
response = self.client.post(reverse("api_v1:user:logout"), {}, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["message"], "Успешный выход")
self.assertEqual(response.data, {})
self.assertEqual(BlacklistedToken.objects.filter(token__user=user).count(), 1)
class CurrentUserViewTest(APITestCase):
@@ -151,11 +169,26 @@ class CurrentUserViewTest(APITestCase):
response = self.client.get(self.current_user_url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
set(response.data.keys()),
{
"id",
"username",
"email",
"phone",
"is_active",
"role",
"role_label",
"profile",
},
)
self.assertEqual(response.data["id"], self.user.id)
self.assertEqual(response.data["email"], self.user.email)
self.assertEqual(response.data["role"], "user")
self.assertFalse(response.data["capabilities"]["can_refresh_dashboard"])
self.assertIn("profile", response.data)
self.assertEqual(
set(response.data["profile"].keys()),
{"first_name", "middle_name", "last_name", "full_name"},
)
def test_get_current_user_unauthenticated(self):
"""Test getting current user when unauthenticated"""
@@ -220,7 +253,24 @@ class AdminUserManagementViewTest(APITestCase):
response = self.client.get(self.list_url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
usernames = {item["username"] for item in response.data}
self.assertEqual(
set(response.data["results"][0].keys()),
{
"id",
"username",
"email",
"phone",
"is_active",
"role",
"role_label",
"profile",
},
)
self.assertEqual(
set(response.data["results"][0]["profile"].keys()),
{"first_name", "middle_name", "last_name", "full_name"},
)
usernames = {item["username"] for item in response.data["results"]}
self.assertIn(self.admin.username, usernames)
self.assertIn(self.user.username, usernames)
@@ -237,7 +287,7 @@ class AdminUserManagementViewTest(APITestCase):
response = self.client.get(self.list_url, {"search": "Петрович"})
self.assertEqual(response.status_code, status.HTTP_200_OK)
usernames = [item["username"] for item in response.data]
usernames = [item["username"] for item in response.data["results"]]
self.assertEqual(usernames, [self.user.username])
def test_admin_can_order_users(self):
@@ -249,7 +299,7 @@ class AdminUserManagementViewTest(APITestCase):
response = self.client.get(self.list_url, {"ordering": "first_name"})
self.assertEqual(response.status_code, status.HTTP_200_OK)
ordered_ids = [item["id"] for item in response.data]
ordered_ids = [item["id"] for item in response.data["results"]]
self.assertLess(ordered_ids.index(second.id), ordered_ids.index(first.id))
def test_admin_can_create_user_with_role(self):
@@ -270,6 +320,18 @@ class AdminUserManagementViewTest(APITestCase):
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
created = User.objects.get(username=payload["username"])
self.assertTrue(created.is_staff)
self.assertEqual(
set(response.data.keys()),
{
"id",
"username",
"email",
"phone",
"role",
"role_label",
"is_active",
},
)
self.assertEqual(response.data["role"], "admin")
self.assertEqual(created.profile.first_name, "Петр")
self.assertEqual(created.profile.middle_name, "Петрович")
@@ -283,6 +345,7 @@ class AdminUserManagementViewTest(APITestCase):
"role": "admin",
"first_name": "Иван",
"middle_name": "Иванович",
"last_name": "Иванов",
"is_verified": True,
},
format="json",
@@ -292,6 +355,18 @@ class AdminUserManagementViewTest(APITestCase):
self.user.refresh_from_db()
self.assertTrue(self.user.is_staff)
self.assertTrue(self.user.is_verified)
self.assertEqual(
set(response.data.keys()),
{
"id",
"username",
"email",
"phone",
"role",
"role_label",
"is_active",
},
)
self.assertEqual(self.user.profile.first_name, "Иван")
self.assertEqual(self.user.profile.middle_name, "Иванович")
@@ -302,6 +377,10 @@ class AdminUserManagementViewTest(APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["id"], self.user.id)
self.assertEqual(
set(response.data["profile"].keys()),
{"first_name", "middle_name", "last_name", "full_name"},
)
def test_admin_cannot_patch_self_to_inactive(self):
url = reverse("api_v1:user:admin-user-detail", args=[self.admin.id])
@@ -324,10 +403,15 @@ class AdminUserManagementViewTest(APITestCase):
def test_admin_can_patch_self_with_safe_fields(self):
url = reverse("api_v1:user:admin-user-detail", args=[self.admin.id])
response = self.client.patch(url, {"first_name": "Админ"}, format="json")
response = self.client.patch(
url,
{"first_name": "Админ", "last_name": "Админов"},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["profile"]["first_name"], "Админ")
self.assertEqual(response.data["role"], "admin")
self.assertNotIn("profile", response.data)
def test_admin_can_deactivate_user(self):
url = reverse("api_v1:user:admin-user-deactivate", args=[self.user.id])
@@ -348,6 +432,18 @@ class AdminUserManagementViewTest(APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.user.refresh_from_db()
self.assertTrue(self.user.is_active)
self.assertEqual(
set(response.data.keys()),
{
"id",
"username",
"email",
"phone",
"role",
"role_label",
"is_active",
},
)
def test_admin_cannot_deactivate_self(self):
url = reverse("api_v1:user:admin-user-deactivate", args=[self.admin.id])