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.test import APITestCase from .models import Campaign, Composition, CompositionImage, ContactUs def make_test_image(name='test.jpg', color='red', size=(100, 100)): """Create a minimal valid JPEG for upload tests.""" 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): self.contact = ContactUs.objects.create( name='Ali Reza', email_or_phone='ali@example.com', description='Need support', category='پشتیبانی', ) def test_list_contacts(self): 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(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) def test_retrieve_contact(self): response = self.client.get(f'/api/contact-us/{self.contact.id}/') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['name'], 'Ali Reza') def test_by_category(self): response = self.client.get('/api/contact-us/by_category/?category=پشتیبانی') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) def test_by_category_missing_param(self): response = self.client.get('/api/contact-us/by_category/') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_update_requires_auth(self): response = self.client.patch( f'/api/contact-us/{self.contact.id}/', {'name': 'Updated'}, format='json', ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) class CompositionAPITests(APITestCase): def setUp(self): User = get_user_model() self.admin = User.objects.create_user( username='testadmin', password='testpass123', is_staff=True ) self.composition = Composition.objects.create( name='Test Composition', description='Test description', ) def test_list_compositions(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_json(self): 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') self.assertEqual(response.data['images'], []) def test_retrieve_composition(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(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_by_created_at_missing_params(self): response = self.client.get('/api/compositions/by-created-at/') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_by_created_at_invalid_datetime(self): response = self.client.get( '/api/compositions/by-created-at/?from=not-a-date' ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_create_with_multiple_images(self): 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']) composition = Composition.objects.get(name='Multi Image') self.assertEqual(composition.images.count(), 2) self.assertEqual(composition.main_image.is_main, True) def test_add_images(self): self.client.force_authenticate(user=self.admin) 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) self.assertTrue(response.data['main_image']['is_main']) def test_set_main_image(self): self.client.force_authenticate(user=self.admin) 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(self): self.client.force_authenticate(user=self.admin) 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()) def test_update_requires_auth(self): response = self.client.patch( f'/api/compositions/{self.composition.id}/', {'name': 'Updated'}, format='json', ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) class CampaignAPITests(APITestCase): def setUp(self): 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(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(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.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_retrieve_campaign(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']) self.assertFalse(response.data['is_upcoming']) self.assertFalse(response.data['is_ended']) 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) self.assertNotIn('Upcoming 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) def test_create_campaign_invalid_dates(self): now = timezone.now() payload = { 'name': 'Bad Dates', 'description': 'Desc', 'start_time': (now + timedelta(days=3)).isoformat(), 'end_time': (now + timedelta(days=1)).isoformat(), } response = self.client.post('/api/campaigns/', payload, format='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)