Files
site_backend/api/tests.py
Shayan Azadi 149fb614b0 Fix permissions and prepare admin login for deploy
- Admin-only: list contacts, create/edit campaigns and compositions
- Public: submit contact, mine contacts by email/phone, read campaigns/compositions
- Add admin_response field on ContactUs
- Update Postman base_url to https://zoneco.org/api with token save

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 22:52:44 +03:30

389 lines
15 KiB
Python

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,
))