feat(backend): added auth, reviews, users modules

also provided tests
This commit is contained in:
ITQ
2026-02-12 20:48:29 +03:00
parent cb9692089f
commit 613c99dce2
60 changed files with 5101 additions and 127 deletions
+24
View File
@@ -0,0 +1,24 @@
from apps.users.auth.jwt import create_access_token
from apps.users.models import User, UserRole
from apps.users.services import user_create
def _make_user(
username="testuser",
email="test@lotty.local",
password="testpass123", # noqa: S107
role=UserRole.VIEWER,
**kwargs,
) -> User:
return user_create(
username=username,
email=email,
password=password,
role=role,
**kwargs,
)
def _auth_header(user) -> str:
token: str = create_access_token(user.pk, user.role)
return f"Bearer {token}"
+116
View File
@@ -0,0 +1,116 @@
import uuid
from datetime import timedelta
from typing import Any
from django.core.handlers.wsgi import WSGIRequest
from django.test import RequestFactory, TestCase
from apps.users.auth.bearer import JWTBearer
from apps.users.auth.jwt import (
TokenError,
create_access_token,
create_refresh_token,
create_token_pair,
decode_access_token,
decode_refresh_token,
decode_token,
)
from apps.users.models import User, UserRole
from ._helpers import _make_user
class JWTCreateTest(TestCase):
def test_create_access_token(self) -> None:
token: str = create_access_token(uuid.uuid4(), "admin")
self.assertIsInstance(token, str)
self.assertTrue(len(token) > 0)
def test_create_refresh_token(self) -> None:
token: str = create_refresh_token(uuid.uuid4())
self.assertIsInstance(token, str)
def test_create_token_pair(self) -> None:
pair: dict[str, str] = create_token_pair(uuid.uuid4(), "viewer")
self.assertIn("access", pair)
self.assertIn("refresh", pair)
class JWTDecodeTest(TestCase):
def setUp(self) -> None:
self.uid: uuid.UUID = uuid.uuid4()
def test_decode_access_token(self) -> None:
token: str = create_access_token(self.uid, "experimenter")
payload: dict[str, Any] = decode_access_token(token)
self.assertEqual(payload["sub"], str(self.uid))
self.assertEqual(payload["role"], "experimenter")
self.assertEqual(payload["type"], "access")
def test_decode_refresh_token(self) -> None:
token: str = create_refresh_token(self.uid)
payload: dict[str, Any] = decode_refresh_token(token)
self.assertEqual(payload["sub"], str(self.uid))
self.assertEqual(payload["type"], "refresh")
def test_decode_wrong_type_raises(self) -> None:
token: str = create_refresh_token(self.uid)
with self.assertRaises(TokenError):
decode_access_token(token)
def test_decode_expired_token_raises(self) -> None:
token: str = create_access_token(
self.uid, "admin", lifetime=timedelta(seconds=-1)
)
with self.assertRaises(TokenError):
decode_access_token(token)
def test_decode_invalid_token_raises(self) -> None:
with self.assertRaises(TokenError):
decode_token("not.a.jwt")
def test_extra_claims(self) -> None:
token: str = create_access_token(
self.uid, "admin", extra_claims={"org": "lotty"}
)
payload: dict[str, Any] = decode_access_token(token)
self.assertEqual(payload["org"], "lotty")
class JWTBearerTest(TestCase):
def setUp(self) -> None:
self.bearer = JWTBearer()
self.user: User = _make_user(
username="bearer_user",
email="bearer@x.com",
role=UserRole.ADMIN,
)
def test_valid_token_returns_user(self) -> None:
token: str = create_access_token(self.user.pk, self.user.role)
request: WSGIRequest = RequestFactory().get("/")
result: User | None = self.bearer.authenticate(request, token)
self.assertEqual(result, self.user)
def test_invalid_token_returns_none(self) -> None:
request: WSGIRequest = RequestFactory().get("/")
result: User | None = self.bearer.authenticate(request, "garbage")
self.assertIsNone(result)
def test_nonexistent_user_returns_none(self) -> None:
token: str = create_access_token(uuid.uuid4(), "admin")
request: WSGIRequest = RequestFactory().get("/")
result: User | None = self.bearer.authenticate(request, token)
self.assertIsNone(result)
def test_inactive_user_returns_none(self) -> None:
self.user.is_active = False
self.user.save()
token: str = create_access_token(self.user.pk, self.user.role)
request: WSGIRequest = RequestFactory().get("/")
result: User | None = self.bearer.authenticate(request, token)
self.assertIsNone(result)
@@ -0,0 +1,56 @@
import uuid
from django.test import TestCase
from apps.users.models import User, UserRole
from ._helpers import _make_user
class UserRoleChoicesTest(TestCase):
def test_choices_count(self) -> None:
self.assertEqual(len(UserRole.choices), 4)
def test_choice_values(self) -> None:
values = {c[0] for c in UserRole.choices}
self.assertEqual(
values, {"admin", "experimenter", "approver", "viewer"}
)
class UserModelTest(TestCase):
def test_default_role_is_viewer(self) -> None:
user: User = _make_user()
self.assertEqual(user.role, UserRole.VIEWER)
def test_role_properties(self) -> None:
admin: User = _make_user(
username="a", email="a@x.com", role=UserRole.ADMIN
)
self.assertTrue(admin.is_admin_role)
self.assertFalse(admin.is_experimenter)
self.assertFalse(admin.is_approver)
self.assertFalse(admin.is_viewer)
exp: User = _make_user(
username="e", email="e@x.com", role=UserRole.EXPERIMENTER
)
self.assertTrue(exp.is_experimenter)
appr: User = _make_user(
username="ap", email="ap@x.com", role=UserRole.APPROVER
)
self.assertTrue(appr.is_approver)
viewer: User = _make_user(
username="v", email="v@x.com", role=UserRole.VIEWER
)
self.assertTrue(viewer.is_viewer)
def test_uuid_primary_key(self) -> None:
user: User = _make_user()
self.assertIsInstance(user.pk, uuid.UUID)
def test_str_representation(self) -> None:
user: User = _make_user(username="hello")
self.assertEqual(str(user), "hello")
@@ -0,0 +1,44 @@
from django.core.handlers.wsgi import WSGIRequest
from django.test import RequestFactory, TestCase
from apps.users.auth.bearer import require_admin, require_roles
from apps.users.models import User, UserRole
from config.errors import ForbiddenError
from ._helpers import _make_user
class RequireRolesTest(TestCase):
def setUp(self) -> None:
self.admin: User = _make_user(
username="rr_admin", email="rr_admin@x.com", role=UserRole.ADMIN
)
self.viewer: User = _make_user(
username="rr_viewer", email="rr_viewer@x.com", role=UserRole.VIEWER
)
def _make_request(self, user) -> WSGIRequest:
request: WSGIRequest = RequestFactory().get("/")
request.auth = user
return request
def test_require_admin_passes(self) -> None:
request: WSGIRequest = self._make_request(self.admin)
result = require_admin(request)
self.assertEqual(result, self.admin)
def test_require_admin_denies_viewer(self) -> None:
request: WSGIRequest = self._make_request(self.viewer)
with self.assertRaises(ForbiddenError):
require_admin(request)
def test_require_roles_multiple(self) -> None:
checker = require_roles("admin", "viewer")
request: WSGIRequest = self._make_request(self.viewer)
result = checker(request)
self.assertEqual(result, self.viewer)
def test_require_roles_no_auth_raises(self) -> None:
request: WSGIRequest = RequestFactory().get("/")
with self.assertRaises(ForbiddenError):
require_admin(request)
@@ -0,0 +1,123 @@
import uuid
from django.db.models import QuerySet
from django.test import TestCase
from apps.users.models import User, UserRole
from apps.users.selectors import (
user_exists_with_email,
user_exists_with_username,
user_get_by_email,
user_get_by_id,
user_get_by_username,
user_list,
user_list_admins,
user_list_approvers,
user_list_by_role,
user_list_experimenters,
user_list_viewers,
)
from ._helpers import _make_user
class UserSelectorsTest(TestCase):
def setUp(self) -> None:
self.admin: User = _make_user(
username="sel_admin",
email="sel_admin@x.com",
role=UserRole.ADMIN,
)
self.exp: User = _make_user(
username="sel_exp",
email="sel_exp@x.com",
role=UserRole.EXPERIMENTER,
)
self.appr: User = _make_user(
username="sel_appr",
email="sel_appr@x.com",
role=UserRole.APPROVER,
)
self.viewer: User = _make_user(
username="sel_viewer",
email="sel_viewer@x.com",
role=UserRole.VIEWER,
)
def test_get_by_id(self) -> None:
found: User | None = user_get_by_id(str(self.admin.pk))
self.assertEqual(found, self.admin)
def test_get_by_id_invalid_uuid(self) -> None:
self.assertIsNone(user_get_by_id("not-a-uuid"))
def test_get_by_id_nonexistent(self) -> None:
self.assertIsNone(user_get_by_id(str(uuid.uuid4())))
def test_get_by_username(self) -> None:
found: User | None = user_get_by_username("sel_exp")
self.assertEqual(found, self.exp)
def test_get_by_username_nonexistent(self) -> None:
self.assertIsNone(user_get_by_username("ghost"))
def test_get_by_email(self) -> None:
found: User | None = user_get_by_email("sel_appr@x.com")
self.assertEqual(found, self.appr)
def test_list_all(self) -> None:
qs: QuerySet[User] = user_list()
self.assertEqual(qs.count(), 4)
def test_list_filter_by_role(self) -> None:
qs: QuerySet[User] = user_list(role=UserRole.ADMIN)
self.assertEqual(qs.count(), 1)
self.assertEqual(qs.first(), self.admin)
def test_list_filter_by_is_active(self) -> None:
self.viewer.is_active = False
self.viewer.save()
qs: QuerySet[User] = user_list(is_active=True)
self.assertEqual(qs.count(), 3)
def test_list_filter_by_search(self) -> None:
qs: QuerySet[User] = user_list(search="sel_exp")
self.assertEqual(qs.count(), 1)
def test_list_by_role(self) -> None:
qs: QuerySet[User] = user_list_by_role(UserRole.APPROVER)
self.assertEqual(qs.count(), 1)
def test_list_admins(self) -> None:
self.assertEqual(user_list_admins().count(), 1)
def test_list_experimenters(self) -> None:
self.assertEqual(user_list_experimenters().count(), 1)
def test_list_approvers(self) -> None:
self.assertEqual(user_list_approvers().count(), 1)
def test_list_viewers(self) -> None:
self.assertEqual(user_list_viewers().count(), 1)
def test_exists_with_username(self) -> None:
self.assertTrue(user_exists_with_username("sel_admin"))
self.assertFalse(user_exists_with_username("ghost"))
def test_exists_with_username_exclude(self) -> None:
self.assertFalse(
user_exists_with_username(
"sel_admin", exclude_id=str(self.admin.pk)
)
)
def test_exists_with_email(self) -> None:
self.assertTrue(user_exists_with_email("sel_viewer@x.com"))
self.assertFalse(user_exists_with_email("ghost@x.com"))
def test_exists_with_email_exclude(self) -> None:
self.assertFalse(
user_exists_with_email(
"sel_viewer@x.com", exclude_id=str(self.viewer.pk)
)
)
@@ -0,0 +1,128 @@
from django.core.exceptions import ValidationError
from django.test import TestCase
from apps.users.models import User, UserRole
from apps.users.services import (
user_activate,
user_assign_role,
user_create,
user_deactivate,
user_delete,
user_update,
)
from ._helpers import _make_user
class UserCreateServiceTest(TestCase):
def test_create_with_defaults(self) -> None:
user: User = user_create(
username="svc_user",
email="svc@lotty.local",
password="pass1234",
)
self.assertEqual(user.role, UserRole.VIEWER)
self.assertTrue(user.check_password("pass1234"))
self.assertTrue(user.is_active)
def test_create_with_role(self) -> None:
user: User = user_create(
username="admin_svc",
email="admin_svc@lotty.local",
password="pass1234",
role=UserRole.ADMIN,
)
self.assertEqual(user.role, UserRole.ADMIN)
def test_create_without_password(self) -> None:
user: User = user_create(
username="nopw",
email="nopw@lotty.local",
)
self.assertFalse(user.has_usable_password())
def test_create_with_extra_fields(self) -> None:
user: User = user_create(
username="extra",
email="extra@lotty.local",
password="pass1234",
first_name="First",
last_name="Last",
)
self.assertEqual(user.first_name, "First")
self.assertEqual(user.last_name, "Last")
class UserUpdateServiceTest(TestCase):
def setUp(self) -> None:
self.user: User = _make_user()
def test_update_username(self) -> None:
updated: User = user_update(user=self.user, username="newname")
self.assertEqual(updated.username, "newname")
def test_update_email(self) -> None:
updated: User = user_update(user=self.user, email="new@lotty.local")
self.assertEqual(updated.email, "new@lotty.local")
def test_update_role(self) -> None:
updated: User = user_update(user=self.user, role=UserRole.ADMIN)
self.assertEqual(updated.role, UserRole.ADMIN)
def test_update_password(self) -> None:
updated: User = user_update(user=self.user, password="newpass99")
self.assertTrue(updated.check_password("newpass99"))
def test_update_is_active(self) -> None:
updated: User = user_update(user=self.user, is_active=False)
self.assertFalse(updated.is_active)
def test_partial_update_leaves_other_fields(self) -> None:
original_email = self.user.email
updated: User = user_update(user=self.user, username="changed")
self.assertEqual(updated.email, original_email)
def test_update_names(self) -> None:
updated: User = user_update(
user=self.user, first_name="Jane", last_name="Doe"
)
self.assertEqual(updated.first_name, "Jane")
self.assertEqual(updated.last_name, "Doe")
class UserAssignRoleServiceTest(TestCase):
def setUp(self) -> None:
self.user: User = _make_user()
def test_assign_valid_role(self) -> None:
updated: User = user_assign_role(
user=self.user, role=UserRole.EXPERIMENTER
)
self.assertEqual(updated.role, UserRole.EXPERIMENTER)
def test_assign_invalid_role_raises(self) -> None:
with self.assertRaises(ValidationError):
user_assign_role(user=self.user, role="superadmin")
class UserDeleteServiceTest(TestCase):
def test_hard_delete(self) -> None:
user: User = _make_user()
pk = user.pk
user_delete(user=user)
self.assertFalse(User.objects.filter(pk=pk).exists())
class UserActivateDeactivateServiceTest(TestCase):
def setUp(self) -> None:
self.user: User = _make_user()
def test_deactivate(self) -> None:
updated: User = user_deactivate(user=self.user)
self.assertFalse(updated.is_active)
def test_activate(self) -> None:
self.user.is_active = False
self.user.save()
updated: User = user_activate(user=self.user)
self.assertTrue(updated.is_active)