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.auth.admin import UserAdmin as DjangoUserAdmin
from django.contrib.auth.models import Group
from apps.users.models import User
admin.site.unregister(Group)
@admin.register(User)
class UserAdmin(DjangoUserAdmin):
fieldsets = (
(None, {'fields': ('email_address', 'password')}),
('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',)}),
)

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.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.conf import settings
import uuid
@ -11,6 +9,7 @@ User = get_user_model()
class UserModelTests(TestCase):
def setUp(self):
self.user_data = {
'email_address': 'Test@Example.com',
@ -22,47 +21,15 @@ class UserModelTests(TestCase):
def test_create_user_and_properties(self):
user = User.objects.create_user(password='pass1234', **self.user_data)
self.assertIsNotNone(user.pk)
# email should be normalized by the manager (domain lowercased)
self.assertEqual(user.email_address.lower(), 'test@example.com')
# full_name property
self.assertEqual(user.email_address, 'Test@example.com')
self.assertEqual(user.full_name, 'Test User')
def test_create_superuser(self):
su = User.objects.create_superuser(password='adminpass', **self.user_data)
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):
user = User.objects.create_user(email_address='hashme@example.com', password='secret123')
self.assertNotEqual(user.password, 'secret123')
@ -82,6 +49,8 @@ class UserModelExtraTests(TestCase):
self.assertEqual(u.bio, "")
self.assertEqual(u.timezone, settings.TIME_ZONE)
self.assertEqual(u.avatar_url, "")
self.assertTrue(u.is_active)
self.assertFalse(u.is_staff)
def test_unique_email_constraint(self):
User.objects.create_user(email_address='dup@example.com', password='p')
@ -97,12 +66,56 @@ class UserModelExtraTests(TestCase):
self.assertIsNone(u.date_of_birth)
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(str(u), 'A B')
def test_create_superuser_defaults(self):
su = User.objects.create_superuser(email_address='admin@example.com', password='admin')
self.assertTrue(su.is_staff)
self.assertTrue(su.is_active)
def test_email_normalization_domain_lowercase(self):
user1 = User.objects.create_user(email_address='Test@EXAMPLE.COM', password='p')
self.assertEqual(user1.email_address, 'Test@example.com')
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.permissions import IsAuthenticatedOrReadOnly
from rest_framework import viewsets, status
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.serializers import UserSerializer
@ -8,3 +11,139 @@ class UserViewSet(viewsets.ReadOnlyModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
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
)