Added more tests, removed groups and tweaked admin

This commit is contained in:
Viswamedha Nalabotu 2025-12-07 15:55:48 +00:00
parent 38adc358c8
commit ef839aea5c
6 changed files with 899 additions and 46 deletions

View file

@ -1,13 +1,16 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
from django.contrib.auth.models import Group
from apps.users.models import User from apps.users.models import User
admin.site.unregister(Group)
@admin.register(User) @admin.register(User)
class UserAdmin(DjangoUserAdmin): class UserAdmin(DjangoUserAdmin):
fieldsets = ( fieldsets = (
(None, {'fields': ('email_address', 'password')}), (None, {'fields': ('email_address', 'password')}),
('Personal info', {'fields': ('first_name', 'last_name')}), ('Personal info', {'fields': ('first_name', 'last_name')}),
('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}), ('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'role')}),
('Dates', {'fields': ('last_login',)}), ('Dates', {'fields': ('last_login',)}),
) )

View file

View file

@ -0,0 +1,57 @@
from django.test import TestCase
from django.contrib.auth import get_user_model
from rest_framework.test import APIClient
from rest_framework import status
User = get_user_model()
class UserListAPITests(TestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(
password='pass1234',
email_address='apiuser@example.com',
first_name='API',
last_name='User',
date_of_birth='1995-05-05',
)
def test_list_users(self):
url = '/api/user/'
resp = self.client.get(url)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
data = resp.json()
self.assertIsInstance(data, (list, dict))
def test_api_response_contains_expected_fields(self):
url = '/api/user/'
resp = self.client.get(url)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
data = resp.json()
if isinstance(data, dict) and 'results' in data:
users = data['results']
else:
users = data
self.assertTrue(len(users) >= 1)
sample = users[0]
expected_keys = {'id', 'uuid', 'email_address', 'first_name', 'last_name', 'bio', 'timezone', 'avatar_url'}
self.assertTrue(expected_keys.issubset(set(sample.keys())))
def test_retrieve_user_by_uuid(self):
url = f'/api/user/{self.user.uuid}/'
resp = self.client.get(url)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
data = resp.json()
self.assertEqual(data['email_address'], 'apiuser@example.com')
def test_retrieve_user_not_found(self):
import uuid
fake_uuid = uuid.uuid4()
url = f'/api/user/{fake_uuid}/'
resp = self.client.get(url)
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)

View file

@ -0,0 +1,641 @@
from django.test import TestCase
from django.contrib.auth import get_user_model
from rest_framework.test import APIClient
from rest_framework import status
User = get_user_model()
class UserLoginActionTests(TestCase):
def setUp(self):
self.client = APIClient()
self.user_data = {
'email_address': 'testuser@example.com',
'password': 'testpass123',
'first_name': 'Test',
'last_name': 'User',
'date_of_birth': '1990-01-01'
}
self.user = User.objects.create_user(**self.user_data)
def test_login_successful(self):
response = self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.json()
self.assertTrue(data['success'])
self.assertEqual(data['message'], 'Login successful')
self.assertIn('user', data)
self.assertEqual(data['user']['email_address'], 'testuser@example.com')
def test_login_missing_email(self):
response = self.client.post('/api/user/login/', {
'password': 'testpass123'
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
data = response.json()
self.assertIn('error', data)
def test_login_missing_password(self):
response = self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com'
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
data = response.json()
self.assertIn('error', data)
def test_login_invalid_credentials(self):
response = self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'wrongpassword'
})
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_login_nonexistent_user(self):
response = self.client.post('/api/user/login/', {
'email_address': 'nonexistent@example.com',
'password': 'testpass123'
})
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_login_session_created(self):
response = self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('sessionid', self.client.cookies)
def test_login_inactive_user(self):
self.user.is_active = False
self.user.save()
response = self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_login_case_insensitive_email(self):
response = self.client.post('/api/user/login/', {
'email_address': 'testuser@EXAMPLE.COM',
'password': 'testpass123'
})
self.assertEqual(response.status_code, status.HTTP_200_OK)
class UserLogoutActionTests(TestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(
email_address='testuser@example.com',
password='testpass123',
first_name='Test',
last_name='User'
)
def test_logout_successful(self):
self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
response = self.client.post('/api/user/logout/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.json()
self.assertTrue(data['success'])
def test_logout_without_login(self):
response = self.client.post('/api/user/logout/')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_session_destroyed_after_logout(self):
self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
self.client.post('/api/user/logout/')
response = self.client.get('/api/user/me/')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
class UserMeActionTests(TestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(
email_address='testuser@example.com',
password='testpass123',
first_name='Test',
last_name='User'
)
def test_me_authenticated(self):
self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
response = self.client.get('/api/user/me/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.json()
self.assertTrue(data['success'])
self.assertEqual(data['email_address'], 'testuser@example.com')
def test_me_unauthenticated(self):
response = self.client.get('/api/user/me/')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_me_returns_correct_user_data(self):
self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
response = self.client.get('/api/user/me/')
data = response.json()
expected_fields = {'id', 'uuid', 'email_address', 'first_name', 'last_name'}
self.assertTrue(expected_fields.issubset(set(data.keys())))
class UserSessionActionTests(TestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(
email_address='testuser@example.com',
password='testpass123',
first_name='Test',
last_name='User'
)
def test_session_authenticated(self):
self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
response = self.client.get('/api/user/session/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.json()
self.assertTrue(data['isAuthenticated'])
def test_session_unauthenticated(self):
response = self.client.get('/api/user/session/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.json()
self.assertFalse(data['isAuthenticated'])
def test_session_staff_status(self):
self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
response = self.client.get('/api/user/session/')
data = response.json()
self.assertIn('isStaff', data)
self.assertFalse(data['isStaff'])
def test_session_unauthenticated_no_staff(self):
response = self.client.get('/api/user/session/')
data = response.json()
self.assertFalse(data['isAuthenticated'])
class UserSignupActionTests(TestCase):
def setUp(self):
self.client = APIClient()
def test_signup_successful(self):
response = self.client.post('/api/user/signup/', {
'email_address': 'newuser@example.com',
'password': 'newpass123',
'confirm_password': 'newpass123',
'first_name': 'New',
'last_name': 'User',
'date_of_birth': '1995-05-05'
})
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
data = response.json()
self.assertTrue(data['success'])
self.assertIn('User account created successfully', data['detail'])
self.assertTrue(User.objects.filter(email_address='newuser@example.com').exists())
def test_signup_email_exists(self):
User.objects.create_user(
email_address='existing@example.com',
password='pass',
first_name='Existing',
last_name='User'
)
response = self.client.post('/api/user/signup/', {
'email_address': 'existing@example.com',
'password': 'newpass123',
'confirm_password': 'newpass123',
'first_name': 'New',
'last_name': 'User'
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
data = response.json()
self.assertFalse(data['success'])
self.assertIn('Email address already exists', data['detail'])
def test_signup_missing_first_name(self):
response = self.client.post('/api/user/signup/', {
'email_address': 'newuser2@example.com',
'password': 'newpass123',
'confirm_password': 'newpass123',
'last_name': 'User'
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
data = response.json()
self.assertFalse(data['success'])
def test_signup_missing_last_name(self):
response = self.client.post('/api/user/signup/', {
'email_address': 'newuser3@example.com',
'password': 'newpass123',
'confirm_password': 'newpass123',
'first_name': 'New'
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
data = response.json()
self.assertFalse(data['success'])
def test_signup_passwords_mismatch(self):
response = self.client.post('/api/user/signup/', {
'email_address': 'newuser4@example.com',
'password': 'newpass123',
'confirm_password': 'differentpass',
'first_name': 'New',
'last_name': 'User'
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
data = response.json()
self.assertIn('Passwords do not match', data['detail'])
def test_signup_missing_email(self):
response = self.client.post('/api/user/signup/', {
'password': 'newpass123',
'confirm_password': 'newpass123',
'first_name': 'New',
'last_name': 'User'
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_signup_missing_password(self):
response = self.client.post('/api/user/signup/', {
'email_address': 'newuser@example.com',
'confirm_password': 'newpass123',
'first_name': 'New',
'last_name': 'User'
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_signup_empty_data(self):
response = self.client.post('/api/user/signup/', {})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_signup_case_insensitive_email(self):
response = self.client.post('/api/user/signup/', {
'email_address': 'NewUser@EXAMPLE.COM',
'password': 'newpass123',
'confirm_password': 'newpass123',
'first_name': 'New',
'last_name': 'User'
})
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
user = User.objects.get(email_address='NewUser@example.com')
self.assertEqual(user.email_address, 'NewUser@example.com')
def test_signup_duplicate_case_insensitive(self):
User.objects.create_user(
email_address='test@example.com',
password='pass',
first_name='Test',
last_name='User'
)
response = self.client.post('/api/user/signup/', {
'password': 'newpass123',
'confirm_password': 'newpass123',
'first_name': 'New',
'last_name': 'User'
})
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
class UserChangePasswordActionTests(TestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(
email_address='testuser@example.com',
password='testpass123',
first_name='Test',
last_name='User'
)
def test_change_password_successful(self):
self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
response = self.client.post('/api/user/change_password/', {
'old_password': 'testpass123',
'password': 'newpass456',
'confirm_password': 'newpass456'
})
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.json()
self.assertTrue(data['success'])
self.user.refresh_from_db()
self.assertTrue(self.user.check_password('newpass456'))
def test_change_password_wrong_old_password(self):
self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
response = self.client.post('/api/user/change_password/', {
'old_password': 'wrongoldpass',
'password': 'newpass456',
'confirm_password': 'newpass456'
})
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
data = response.json()
self.assertFalse(data['success'])
def test_change_password_mismatch(self):
self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
response = self.client.post('/api/user/change_password/', {
'old_password': 'testpass123',
'password': 'newpass456',
'confirm_password': 'differentpass'
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
data = response.json()
self.assertIn('Passwords do not match', data['detail'])
def test_change_password_missing_old_password(self):
self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
response = self.client.post('/api/user/change_password/', {
'password': 'newpass456',
'confirm_password': 'newpass456'
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
data = response.json()
self.assertIn('old_password', data['detail'])
def test_change_password_missing_new_password(self):
self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
response = self.client.post('/api/user/change_password/', {
'old_password': 'testpass123',
'confirm_password': 'newpass456'
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_change_password_unauthenticated(self):
response = self.client.post('/api/user/change_password/', {
'old_password': 'testpass123',
'password': 'newpass456',
'confirm_password': 'newpass456'
})
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_change_password_empty_old_password(self):
self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
response = self.client.post('/api/user/change_password/', {
'old_password': '',
'password': 'newpass456',
'confirm_password': 'newpass456'
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_can_login_with_new_password_after_change(self):
self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'testpass123'
})
self.client.post('/api/user/change_password/', {
'old_password': 'testpass123',
'password': 'brandnewpass789',
'confirm_password': 'brandnewpass789'
})
self.client.logout()
response = self.client.post('/api/user/login/', {
'email_address': 'testuser@example.com',
'password': 'brandnewpass789'
})
self.assertEqual(response.status_code, status.HTTP_200_OK)
class UserEdgeCaseTests(TestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(
email_address='edgecase@example.com',
password='testpass123',
first_name='Edge',
last_name='Case'
)
def test_login_with_whitespace_email(self):
response = self.client.post('/api/user/login/', {
'email_address': ' testuser@example.com ',
'password': 'testpass123'
})
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_signup_with_very_long_name(self):
long_name = 'A' * 255
response = self.client.post('/api/user/signup/', {
'email_address': 'longname@example.com',
'password': 'newpass123',
'confirm_password': 'newpass123',
'first_name': long_name,
'last_name': long_name
})
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_signup_with_too_long_name(self):
too_long_name = 'A' * 256
response = self.client.post('/api/user/signup/', {
'email_address': 'verylongname@example.com',
'password': 'newpass123',
'confirm_password': 'newpass123',
'first_name': too_long_name,
'last_name': 'User'
})
self.assertIn(response.status_code, [status.HTTP_400_BAD_REQUEST, status.HTTP_201_CREATED])
def test_signup_with_special_characters_in_name(self):
response = self.client.post('/api/user/signup/', {
'email_address': 'special@example.com',
'password': 'newpass123',
'confirm_password': 'newpass123',
'first_name': 'José',
'last_name': "O'Brien-Smith"
})
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_change_password_same_as_old(self):
self.client.post('/api/user/login/', {
'email_address': 'edgecase@example.com',
'password': 'testpass123'
})
response = self.client.post('/api/user/change_password/', {
'old_password': 'testpass123',
'password': 'testpass123',
'confirm_password': 'testpass123'
})
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_signup_missing_confirm_password_field(self):
response = self.client.post('/api/user/signup/', {
'email_address': 'missingconfirm@example.com',
'password': 'newpass123',
'first_name': 'Missing',
'last_name': 'Confirm'
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_login_multiple_times_same_session(self):
response1 = self.client.post('/api/user/login/', {
'email_address': 'edgecase@example.com',
'password': 'testpass123'
})
session_id_1 = self.client.cookies.get('sessionid')
me1 = self.client.get('/api/user/me/')
self.assertEqual(me1.status_code, status.HTTP_200_OK)
response2 = self.client.post('/api/user/login/', {
'email_address': 'edgecase@example.com',
'password': 'testpass123'
})
session_id_2 = self.client.cookies.get('sessionid')
self.assertEqual(response1.status_code, status.HTTP_200_OK)
self.assertEqual(response2.status_code, status.HTTP_200_OK)
def test_staff_user_login_shows_staff_status(self):
staff_user = User.objects.create_user(
email_address='staff@example.com',
password='staffpass',
first_name='Staff',
last_name='User',
is_staff=True
)
response = self.client.post('/api/user/login/', {
'email_address': 'staff@example.com',
'password': 'staffpass'
})
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.json()
self.assertIn('user', data)
def test_session_status_after_explicit_logout(self):
self.client.post('/api/user/login/', {
'email_address': 'edgecase@example.com',
'password': 'testpass123'
})
self.client.post('/api/user/logout/')
response = self.client.get('/api/user/session/')
data = response.json()
self.assertFalse(data['isAuthenticated'])
def test_signup_with_null_optional_fields(self):
response = self.client.post('/api/user/signup/', {
'email_address': 'optional@example.com',
'password': 'newpass123',
'confirm_password': 'newpass123',
'first_name': 'Optional',
'last_name': 'Fields'
})
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_change_password_with_missing_confirm_password(self):
self.client.post('/api/user/login/', {
'email_address': 'edgecase@example.com',
'password': 'testpass123'
})
response = self.client.post('/api/user/change_password/', {
'old_password': 'testpass123',
'password': 'newpass456'
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_login_and_logout_sequence(self):
resp1 = self.client.post('/api/user/login/', {
'email_address': 'edgecase@example.com',
'password': 'testpass123'
})
self.assertEqual(resp1.status_code, status.HTTP_200_OK)
me1 = self.client.get('/api/user/me/')
self.assertEqual(me1.status_code, status.HTTP_200_OK)
logout_resp = self.client.post('/api/user/logout/')
self.assertEqual(logout_resp.status_code, status.HTTP_200_OK)
me2 = self.client.get('/api/user/me/')
self.assertEqual(me2.status_code, status.HTTP_403_FORBIDDEN)
resp2 = self.client.post('/api/user/login/', {
'email_address': 'edgecase@example.com',
'password': 'testpass123'
})
self.assertEqual(resp2.status_code, status.HTTP_200_OK)
me3 = self.client.get('/api/user/me/')
self.assertEqual(me3.status_code, status.HTTP_200_OK)
def test_invalid_email_format(self):
response = self.client.post('/api/user/signup/', {
'email_address': 'not-an-email',
'password': 'newpass123',
'confirm_password': 'newpass123',
'first_name': 'Invalid',
'last_name': 'Email'
})
self.assertIn(response.status_code, [status.HTTP_400_BAD_REQUEST, status.HTTP_201_CREATED])
def test_empty_password_signup(self):
response = self.client.post('/api/user/signup/', {
'email_address': 'emptypass@example.com',
'password': '',
'confirm_password': '',
'first_name': 'Empty',
'last_name': 'Pass'
})
self.assertIn(response.status_code, [status.HTTP_400_BAD_REQUEST, status.HTTP_201_CREATED])
def test_role_preserved_after_login(self):
_ = User.objects.create_user(
email_address='manager@example.com',
password='managerpass',
first_name='Manager',
last_name='User',
role=User.Roles.MANAGER
)
response = self.client.post('/api/user/login/', {
'email_address': 'manager@example.com',
'password': 'managerpass'
})
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.json()
self.assertEqual(data['user']['role'], User.Roles.MANAGER)

View file

@ -1,9 +1,7 @@
from django.test import TestCase from django.test import TestCase
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from rest_framework.test import APIClient
from rest_framework import status
from django.conf import settings
from django.db import IntegrityError from django.db import IntegrityError
from django.conf import settings
import uuid import uuid
@ -11,6 +9,7 @@ User = get_user_model()
class UserModelTests(TestCase): class UserModelTests(TestCase):
def setUp(self): def setUp(self):
self.user_data = { self.user_data = {
'email_address': 'Test@Example.com', 'email_address': 'Test@Example.com',
@ -22,47 +21,15 @@ class UserModelTests(TestCase):
def test_create_user_and_properties(self): def test_create_user_and_properties(self):
user = User.objects.create_user(password='pass1234', **self.user_data) user = User.objects.create_user(password='pass1234', **self.user_data)
self.assertIsNotNone(user.pk) self.assertIsNotNone(user.pk)
# email should be normalized by the manager (domain lowercased) self.assertEqual(user.email_address, 'Test@example.com')
self.assertEqual(user.email_address.lower(), 'test@example.com')
# full_name property
self.assertEqual(user.full_name, 'Test User') self.assertEqual(user.full_name, 'Test User')
def test_create_superuser(self): def test_create_superuser(self):
su = User.objects.create_superuser(password='adminpass', **self.user_data) su = User.objects.create_superuser(password='adminpass', **self.user_data)
self.assertTrue(su.is_staff) self.assertTrue(su.is_staff)
self.assertTrue(su.pk) self.assertIsNotNone(su.pk)
self.assertTrue(su.is_active)
class UserAPITests(TestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(
password='pass1234',
email_address='apiuser@example.com',
first_name='API',
last_name='User',
date_of_birth='1995-05-05',
)
def test_list_users(self):
url = '/api/users/'
resp = self.client.get(url)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
emails = [u.get('email_address') for u in resp.json()]
self.assertIn(self.user.email_address, emails)
def test_api_response_contains_expected_fields(self):
url = '/api/users/'
resp = self.client.get(url)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
data = resp.json()
self.assertTrue(len(data) >= 1)
sample = data[0]
expected_keys = {'id', 'uuid', 'email_address', 'first_name', 'last_name', 'bio', 'timezone', 'avatar_url'}
self.assertTrue(expected_keys.issubset(set(sample.keys())))
class UserModelExtraTests(TestCase):
def test_password_hashed_and_check(self): def test_password_hashed_and_check(self):
user = User.objects.create_user(email_address='hashme@example.com', password='secret123') user = User.objects.create_user(email_address='hashme@example.com', password='secret123')
self.assertNotEqual(user.password, 'secret123') self.assertNotEqual(user.password, 'secret123')
@ -82,6 +49,8 @@ class UserModelExtraTests(TestCase):
self.assertEqual(u.bio, "") self.assertEqual(u.bio, "")
self.assertEqual(u.timezone, settings.TIME_ZONE) self.assertEqual(u.timezone, settings.TIME_ZONE)
self.assertEqual(u.avatar_url, "") self.assertEqual(u.avatar_url, "")
self.assertTrue(u.is_active)
self.assertFalse(u.is_staff)
def test_unique_email_constraint(self): def test_unique_email_constraint(self):
User.objects.create_user(email_address='dup@example.com', password='p') User.objects.create_user(email_address='dup@example.com', password='p')
@ -97,12 +66,56 @@ class UserModelExtraTests(TestCase):
self.assertIsNone(u.date_of_birth) self.assertIsNone(u.date_of_birth)
def test_str_and_full_name(self): def test_str_and_full_name(self):
u = User.objects.create_user(email_address='name@example.com', password='p', first_name='A', last_name='B') u = User.objects.create_user(
email_address='name@example.com',
password='p',
first_name='A',
last_name='B'
)
self.assertEqual(u.full_name, 'A B') self.assertEqual(u.full_name, 'A B')
self.assertEqual(str(u), 'A B') self.assertEqual(str(u), 'A B')
def test_create_superuser_defaults(self): def test_email_normalization_domain_lowercase(self):
su = User.objects.create_superuser(email_address='admin@example.com', password='admin') user1 = User.objects.create_user(email_address='Test@EXAMPLE.COM', password='p')
self.assertTrue(su.is_staff) self.assertEqual(user1.email_address, 'Test@example.com')
self.assertTrue(su.is_active) user2 = User.objects.create_user(email_address='test@EXAMPLE.COM', password='p2')
self.assertEqual(user2.email_address, 'test@example.com')
self.assertNotEqual(user1.email_address, user2.email_address)
def test_superuser_must_have_is_staff(self):
with self.assertRaises(ValueError):
User.objects.create_superuser(
email_address='fail@example.com',
password='p',
is_staff=False
)
def test_role_default_is_employee(self):
u = User.objects.create_user(email_address='role@example.com', password='p')
self.assertEqual(u.role, User.Roles.EMPLOYEE)
def test_role_choices(self):
u = User.objects.create_user(
email_address='manager@example.com',
password='p',
role=User.Roles.MANAGER
)
self.assertEqual(u.role, User.Roles.MANAGER)
def test_timestamps_auto_set(self):
from datetime import timedelta
u = User.objects.create_user(email_address='timestamps@example.com', password='p')
self.assertIsNotNone(u.created_at)
self.assertIsNotNone(u.updated_at)
time_diff = abs((u.updated_at - u.created_at).total_seconds())
self.assertLess(time_diff, 1.0)
def test_has_perm_returns_true(self):
u = User.objects.create_user(email_address='perm@example.com', password='p')
self.assertTrue(u.has_perm('any.permission'))
self.assertTrue(u.has_perm('another.permission', obj=None))
def test_has_module_perms_returns_true(self):
u = User.objects.create_user(email_address='modperm@example.com', password='p')
self.assertTrue(u.has_module_perms('auth'))
self.assertTrue(u.has_module_perms('users'))

View file

@ -1,5 +1,8 @@
from rest_framework import viewsets from rest_framework import viewsets, status
from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticatedOrReadOnly, AllowAny, IsAuthenticated
from django.contrib.auth import authenticate, login, logout
from apps.users.models import User from apps.users.models import User
from apps.users.serializers import UserSerializer from apps.users.serializers import UserSerializer
@ -8,3 +11,139 @@ class UserViewSet(viewsets.ReadOnlyModelViewSet):
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = UserSerializer serializer_class = UserSerializer
permission_classes = [IsAuthenticatedOrReadOnly] permission_classes = [IsAuthenticatedOrReadOnly]
lookup_field = 'uuid'
@action(detail=False, methods=['post'], permission_classes=[AllowAny])
def login(self, request):
email_address = request.data.get('email_address')
password = request.data.get('password')
if not email_address or not password:
return Response(
{'error': 'Email and password are required'},
status=status.HTTP_400_BAD_REQUEST
)
email_address = User.objects.normalize_email(email_address)
user = authenticate(request, username=email_address, password=password)
if user is None:
return Response(
{'error': 'Invalid credentials'},
status=status.HTTP_401_UNAUTHORIZED
)
login(request, user)
return Response({
'user': UserSerializer(user).data,
'message': 'Login successful',
'success': True
}, status=status.HTTP_200_OK)
@action(detail=False, methods=['post'], permission_classes=[IsAuthenticated])
def logout(self, request):
logout(request)
return Response(
{'message': 'Logout successful', 'success': True},
status=status.HTTP_200_OK
)
@action(detail=False, methods=['get'], permission_classes=[IsAuthenticated])
def me(self, request):
user_data = UserSerializer(request.user).data
user_data['success'] = True
return Response(user_data)
@action(detail=False, methods=['get'], permission_classes=[AllowAny])
def session(self, request):
return Response({
'isAuthenticated': request.user.is_authenticated,
'isStaff': request.user.is_staff if request.user.is_authenticated else False
})
@action(detail=False, methods=['post'], permission_classes=[AllowAny])
def signup(self, request):
try:
data = request.data
except:
return Response(
{'detail': 'Invalid data provided.', 'success': False},
status=status.HTTP_400_BAD_REQUEST
)
email_address = data.get('email_address')
if not email_address:
return Response(
{'detail': 'Email address is required.', 'success': False},
status=status.HTTP_400_BAD_REQUEST
)
# Normalize email
email_address = User.objects.normalize_email(email_address)
if User.objects.filter(email_address=email_address).exists():
return Response(
{'detail': 'Email address already exists.', 'success': False},
status=status.HTTP_400_BAD_REQUEST
)
if not data.get('first_name') or not data.get('last_name'):
return Response(
{'detail': 'First and last name(s) must be provided.', 'success': False},
status=status.HTTP_400_BAD_REQUEST
)
if data.get('password') != data.get('confirm_password'):
return Response(
{'detail': 'Passwords do not match.', 'success': False},
status=status.HTTP_400_BAD_REQUEST
)
try:
user = User.objects.create_user(
email_address=email_address,
password=data.get('password'),
first_name=data.get('first_name'),
last_name=data.get('last_name'),
date_of_birth=data.get('date_of_birth')
)
return Response(
{'detail': 'User account created successfully.', 'success': True},
status=status.HTTP_201_CREATED
)
except Exception as e:
return Response(
{'detail': str(e), 'success': False},
status=status.HTTP_400_BAD_REQUEST
)
@action(detail=False, methods=['post'], permission_classes=[IsAuthenticated])
def change_password(self, request):
data = request.data
required_fields = ['old_password', 'password', 'confirm_password']
for field in required_fields:
if not data.get(field):
return Response(
{'detail': f'"{field}" not provided', 'success': False},
status=status.HTTP_400_BAD_REQUEST
)
if data.get('password') != data.get('confirm_password'):
return Response(
{'detail': 'Passwords do not match', 'success': False},
status=status.HTTP_400_BAD_REQUEST
)
user = request.user
if not user.check_password(data.get('old_password')):
return Response(
{'detail': 'Old password is incorrect', 'success': False},
status=status.HTTP_401_UNAUTHORIZED
)
user.set_password(data.get('password'))
user.save()
return Response(
{'detail': 'Password changed successfully', 'success': True},
status=status.HTTP_200_OK
)