feat: expand platform APIs, sources, and test coverage
Some checks failed
CI/CD Pipeline / Run Tests (pull_request) Successful in 1m53s
CI/CD Pipeline / Telegram Notify Success (push) Has been cancelled
CI/CD Pipeline / Run Tests (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 2m54s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped

This commit is contained in:
2026-03-17 12:56:48 +01:00
parent b505c67968
commit 3d298ce352
101 changed files with 8387 additions and 292 deletions

View File

@@ -117,6 +117,24 @@ class LoginViewTest(APITestCase):
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_login_invalid_payload_returns_400(self):
response = self.client.post(self.login_url, {}, format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("username", response.data)
class LogoutViewTest(APITestCase):
def test_logout_returns_success_message(self):
user = UserFactory.create_user()
tokens = UserService.get_tokens_for_user(user)
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {tokens['access']}")
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"], "Успешный выход")
class CurrentUserViewTest(APITestCase):
"""Tests for CurrentUserView"""
@@ -135,6 +153,8 @@ class CurrentUserViewTest(APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
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)
def test_get_current_user_unauthenticated(self):
@@ -178,6 +198,125 @@ class UserUpdateViewTest(APITestCase):
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_update_user_invalid_returns_400(self):
response = self.client.patch(self.update_url, {"username": ""}, format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("username", response.data)
class AdminUserManagementViewTest(APITestCase):
"""Tests for admin-only user management endpoints."""
def setUp(self):
self.admin = UserFactory.create_user(is_staff=True)
self.user = UserFactory.create_user()
self.tokens = UserService.get_tokens_for_user(self.admin)
self.user_tokens = UserService.get_tokens_for_user(self.user)
self.list_url = reverse("api_v1:user:admin-users")
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {self.tokens['access']}")
def test_admin_can_list_users(self):
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.assertIn(self.admin.username, usernames)
self.assertIn(self.user.username, usernames)
def test_admin_can_create_user_with_role(self):
password = fake.password(length=12, special_chars=False)
payload = {
"email": fake.unique.email(),
"username": fake.unique.user_name(),
"phone": f"+7{fake.numerify('##########')}",
"password": password,
"role": "admin",
"first_name": "Петр",
"last_name": "Петров",
}
response = self.client.post(self.list_url, payload, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
created = User.objects.get(username=payload["username"])
self.assertTrue(created.is_staff)
self.assertEqual(response.data["role"], "admin")
self.assertEqual(created.profile.first_name, "Петр")
def test_admin_can_update_user_and_role(self):
url = reverse("api_v1:user:admin-user-detail", args=[self.user.id])
response = self.client.patch(
url,
{"role": "admin", "first_name": "Иван", "is_verified": True},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.user.refresh_from_db()
self.assertTrue(self.user.is_staff)
self.assertTrue(self.user.is_verified)
self.assertEqual(self.user.profile.first_name, "Иван")
def test_admin_can_get_user_detail(self):
url = reverse("api_v1:user:admin-user-detail", args=[self.user.id])
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["id"], self.user.id)
def test_admin_cannot_patch_self_to_inactive(self):
url = reverse("api_v1:user:admin-user-detail", args=[self.admin.id])
response = self.client.patch(url, {"is_active": False}, format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data["detail"], "Нельзя деактивировать самого себя.")
def test_admin_cannot_patch_self_to_regular_user(self):
url = reverse("api_v1:user:admin-user-detail", args=[self.admin.id])
response = self.client.patch(url, {"role": "user"}, format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
response.data["detail"], "Нельзя снять у себя роль администратора."
)
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")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["profile"]["first_name"], "Админ")
def test_admin_can_deactivate_user(self):
url = reverse("api_v1:user:admin-user-deactivate", args=[self.user.id])
response = self.client.post(url, {}, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.user.refresh_from_db()
self.assertFalse(self.user.is_active)
def test_admin_cannot_deactivate_self(self):
url = reverse("api_v1:user:admin-user-deactivate", args=[self.admin.id])
response = self.client.post(url, {}, format="json")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_regular_user_cannot_access_admin_user_management(self):
self.client.credentials(
HTTP_AUTHORIZATION=f"Bearer {self.user_tokens['access']}"
)
response = self.client.get(self.list_url)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
class ProfileDetailViewTest(APITestCase):
"""Tests for ProfileDetailView"""
@@ -225,6 +364,22 @@ class ProfileDetailViewTest(APITestCase):
# Profile should be created automatically
self.assertTrue(Profile.objects.filter(user=self.user).exists())
def test_update_profile_invalid_returns_400(self):
response = self.client.patch(
self.profile_url,
{"date_of_birth": "not-a-date"},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("date_of_birth", response.data)
def test_get_full_profile_endpoint(self):
response = self.client.get(reverse("api_v1:user:profile_full"))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["id"], self.user.id)
class PasswordChangeViewTest(APITestCase):
"""Tests for PasswordChangeView"""
@@ -316,6 +471,20 @@ class TokenRefreshViewTest(APITestCase):
self.assertTrue("errors" in response.data or "detail" in response.data)
class TokenVerifyViewTest(APITestCase):
def test_verify_access_token_success(self):
user = UserFactory.create_user()
tokens = UserService.get_tokens_for_user(user)
response = self.client.post(
reverse("api_v1:user:token_verify"),
{"token": tokens["access"]},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
class ApiJwtOnlyAuthenticationTest(APITestCase):
"""Tests that API auth flow is JWT-only and not session-cookie based."""