import io from datetime import timedelta from django.contrib.auth import get_user_model from django.core.files.uploadedfile import SimpleUploadedFile from django.utils import timezone from PIL import Image from rest_framework import status from rest_framework.authtoken.models import Token from rest_framework.test import APITestCase from .models import Campaign, Composition, CompositionImage, ContactUs def make_test_image(name='test.jpg', color='red', size=(100, 100)): buffer = io.BytesIO() Image.new('RGB', size, color=color).save(buffer, format='JPEG') buffer.seek(0) return SimpleUploadedFile(name, buffer.read(), content_type='image/jpeg') class ContactUsAPITests(APITestCase): def setUp(self): User = get_user_model() self.admin = User.objects.create_user( username='admin_test', password='test_admin_pass_123', is_staff=True, is_superuser=True, ) self.token = Token.objects.create(user=self.admin) self.contact = ContactUs.objects.create( name='Ali Reza', email_or_phone='ali@example.com', description='Need support', category='پشتیبانی', ) def test_list_contacts_requires_admin(self): response = self.client.get('/api/contact-us/') self.assertIn(response.status_code, ( status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN, )) def test_list_contacts_as_admin(self): self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}') response = self.client.get('/api/contact-us/') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 1) def test_create_contact_public(self): payload = { 'name': 'Sara', 'email_or_phone': '09121234567', 'description': 'Sales inquiry', 'category': 'فروش', } response = self.client.post('/api/contact-us/', payload, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(ContactUs.objects.count(), 2) self.assertEqual(response.data.get('admin_response'), '') def test_public_cannot_set_admin_response_on_create(self): payload = { 'name': 'Sara', 'email_or_phone': 'sara@example.com', 'description': 'Hello', 'category': 'سایر', 'admin_response': 'should be ignored', } response = self.client.post('/api/contact-us/', payload, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) contact = ContactUs.objects.get(pk=response.data['id']) self.assertEqual(contact.admin_response, '') def test_mine_contacts(self): ContactUs.objects.create( name='Other', email_or_phone='other@example.com', description='x', category='سایر', ) response = self.client.get( '/api/contact-us/mine/?email_or_phone=ali@example.com' ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) self.assertEqual(response.data[0]['email_or_phone'], 'ali@example.com') def test_mine_requires_email_or_phone(self): response = self.client.get('/api/contact-us/mine/') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_by_category_requires_admin(self): response = self.client.get('/api/contact-us/by_category/?category=پشتیبانی') self.assertIn(response.status_code, ( status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN, )) def test_admin_can_reply(self): self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}') response = self.client.patch( f'/api/contact-us/{self.contact.id}/', {'admin_response': 'We will help you.'}, format='json', ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['admin_response'], 'We will help you.') class CompositionAPITests(APITestCase): def setUp(self): User = get_user_model() self.admin = User.objects.create_user( username='admin_test', password='test_admin_pass_123', is_staff=True, ) self.token = Token.objects.create(user=self.admin) self.composition = Composition.objects.create( name='Test Composition', description='Test description', ) def test_list_compositions_public(self): response = self.client.get('/api/compositions/') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 1) def test_create_composition_requires_admin(self): payload = {'name': 'New Comp', 'description': 'Desc'} response = self.client.post('/api/compositions/', payload, format='json') self.assertIn(response.status_code, ( status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN, )) def test_create_composition_as_admin(self): self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}') payload = {'name': 'New Comp', 'description': 'Desc'} response = self.client.post('/api/compositions/', payload, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data['name'], 'New Comp') def test_retrieve_composition_public(self): response = self.client.get(f'/api/compositions/{self.composition.id}/') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn('images', response.data) self.assertIn('main_image', response.data) self.assertIn('created_at', response.data) def test_by_created_at_public(self): now = timezone.now() Composition.objects.create(name='Old Composition', description='Old') Composition.objects.filter(name='Old Composition').update( created_at=now - timedelta(days=10) ) from_dt = (now - timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%SZ') response = self.client.get( f'/api/compositions/by-created-at/?from={from_dt}' ) self.assertEqual(response.status_code, status.HTTP_200_OK) names = [item['name'] for item in response.data] self.assertIn('Test Composition', names) self.assertNotIn('Old Composition', names) def test_create_with_multiple_images_as_admin(self): self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}') img1 = make_test_image('img1.jpg', 'red') img2 = make_test_image('img2.jpg', 'blue') response = self.client.post( '/api/compositions/', { 'name': 'Multi Image', 'description': 'With images', 'uploaded_images': [img1, img2], 'main_image_index': '1', }, format='multipart', ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(len(response.data['images']), 2) self.assertTrue(response.data['main_image']['is_main']) def test_add_images_as_admin(self): self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}') img = make_test_image('added.jpg', 'green') response = self.client.post( f'/api/compositions/{self.composition.id}/add-images/', {'uploaded_images': [img]}, format='multipart', ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data['images']), 1) def test_set_main_image_as_admin(self): self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}') img1 = CompositionImage.objects.create( composition=self.composition, image=make_test_image('a.jpg'), is_main=True, ) img2 = CompositionImage.objects.create( composition=self.composition, image=make_test_image('b.jpg'), is_main=False, ) response = self.client.post( f'/api/compositions/{self.composition.id}/set-main-image/', {'image_id': img2.id}, format='json', ) self.assertEqual(response.status_code, status.HTTP_200_OK) img1.refresh_from_db() img2.refresh_from_db() self.assertFalse(img1.is_main) self.assertTrue(img2.is_main) def test_delete_image_as_admin(self): self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}') img = CompositionImage.objects.create( composition=self.composition, image=make_test_image('del.jpg'), is_main=True, ) response = self.client.delete( f'/api/compositions/{self.composition.id}/images/{img.id}/' ) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertFalse(CompositionImage.objects.filter(pk=img.id).exists()) class CampaignAPITests(APITestCase): def setUp(self): User = get_user_model() self.admin = User.objects.create_user( username='admin_test', password='test_admin_pass_123', is_staff=True, ) self.token = Token.objects.create(user=self.admin) now = timezone.now() self.active = Campaign.objects.create( name='Active Campaign', description='Running now', start_time=now - timedelta(days=1), end_time=now + timedelta(days=1), ) self.upcoming = Campaign.objects.create( name='Upcoming Campaign', description='Starts soon', start_time=now + timedelta(days=2), end_time=now + timedelta(days=5), ) self.ended = Campaign.objects.create( name='Ended Campaign', description='Already finished', start_time=now - timedelta(days=10), end_time=now - timedelta(days=5), ) def test_list_campaigns_public(self): response = self.client.get('/api/campaigns/') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 3) def test_create_campaign_requires_admin(self): now = timezone.now() payload = { 'name': 'New Campaign', 'description': 'Desc', 'start_time': (now + timedelta(days=1)).isoformat(), 'end_time': (now + timedelta(days=3)).isoformat(), } response = self.client.post('/api/campaigns/', payload, format='json') self.assertIn(response.status_code, ( status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN, )) def test_create_campaign_as_admin(self): self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}') now = timezone.now() payload = { 'name': 'New Campaign', 'description': 'Desc', 'start_time': (now + timedelta(days=1)).isoformat(), 'end_time': (now + timedelta(days=3)).isoformat(), } response = self.client.post('/api/campaigns/', payload, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_retrieve_campaign_public(self): response = self.client.get(f'/api/campaigns/{self.active.id}/') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertTrue(response.data['is_active']) def test_active_campaigns(self): response = self.client.get('/api/campaigns/active/') self.assertEqual(response.status_code, status.HTTP_200_OK) names = [item['name'] for item in response.data] self.assertIn('Active Campaign', names) def test_upcoming_campaigns(self): response = self.client.get('/api/campaigns/upcoming/') self.assertEqual(response.status_code, status.HTTP_200_OK) names = [item['name'] for item in response.data] self.assertIn('Upcoming Campaign', names) def test_ended_campaigns(self): response = self.client.get('/api/campaigns/ended/') self.assertEqual(response.status_code, status.HTTP_200_OK) names = [item['name'] for item in response.data] self.assertIn('Ended Campaign', names) class AdminLoginAPITests(APITestCase): def setUp(self): User = get_user_model() self.admin_username = 'admin_test' self.admin_password = 'test_admin_pass_123' self.admin = User.objects.create_user( username=self.admin_username, password=self.admin_password, is_staff=True, is_superuser=True, ) self.regular = User.objects.create_user( username='normaluser', password='normalpass123', is_staff=False, ) def test_admin_login_success(self): response = self.client.post( '/api/admin/login/', {'username': self.admin_username, 'password': self.admin_password}, format='json', ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn('token', response.data) self.assertEqual(response.data['user']['username'], self.admin_username) def test_admin_login_wrong_password(self): response = self.client.post( '/api/admin/login/', {'username': self.admin_username, 'password': 'wrong'}, format='json', ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_non_staff_cannot_login(self): response = self.client.post( '/api/admin/login/', {'username': 'normaluser', 'password': 'normalpass123'}, format='json', ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_admin_me_with_token(self): login = self.client.post( '/api/admin/login/', {'username': self.admin_username, 'password': self.admin_password}, format='json', ) token = login.data['token'] self.client.credentials(HTTP_AUTHORIZATION=f'Token {token}') response = self.client.get('/api/admin/me/') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['username'], self.admin_username) def test_admin_logout(self): login = self.client.post( '/api/admin/login/', {'username': self.admin_username, 'password': self.admin_password}, format='json', ) token = login.data['token'] self.client.credentials(HTTP_AUTHORIZATION=f'Token {token}') response = self.client.post('/api/admin/logout/') self.assertEqual(response.status_code, status.HTTP_200_OK) response = self.client.get('/api/admin/me/') self.assertIn(response.status_code, ( status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN, ))