This commit is contained in:
362
backend/modules/fleet/tests/test_photo_services.py
Normal file
362
backend/modules/fleet/tests/test_photo_services.py
Normal file
@@ -0,0 +1,362 @@
|
||||
import uuid
|
||||
from io import BytesIO
|
||||
from django.test import TestCase
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework.exceptions import ValidationError as DRFValidationError
|
||||
from PIL import Image
|
||||
|
||||
from modules.fleet.models import Vehicle, VehicleServiceRecord, VehicleServicePhoto
|
||||
from modules.fleet.services import VehicleServicePhotoService
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
def create_test_image(filename="test.jpg", size=(100, 100), color='red'):
|
||||
"""
|
||||
Stvori test sliku u memoriji koristeći Pillow.
|
||||
Vraća SimpleUploadedFile spreman za upload.
|
||||
"""
|
||||
file = BytesIO()
|
||||
image = Image.new('RGB', size=size, color=color)
|
||||
image.save(file, 'JPEG')
|
||||
file.seek(0)
|
||||
return SimpleUploadedFile(
|
||||
name=filename,
|
||||
content=file.getvalue(),
|
||||
content_type='image/jpeg'
|
||||
)
|
||||
|
||||
|
||||
class VehicleServicePhotoServiceTests(TestCase):
|
||||
def setUp(self):
|
||||
# Create users
|
||||
self.uploader = User.objects.create_user(
|
||||
username=f"uploader_{uuid.uuid4().hex[:6]}",
|
||||
email=f"uploader+{uuid.uuid4()}@example.test",
|
||||
password="pass"
|
||||
)
|
||||
self.other_user = User.objects.create_user(
|
||||
username=f"other_{uuid.uuid4().hex[:6]}",
|
||||
email=f"other+{uuid.uuid4()}@example.test",
|
||||
password="pass"
|
||||
)
|
||||
self.staff_user = User.objects.create_user(
|
||||
username=f"admin_{uuid.uuid4().hex[:6]}",
|
||||
email=f"admin+{uuid.uuid4()}@example.test",
|
||||
password="pass",
|
||||
is_staff=True
|
||||
)
|
||||
|
||||
# Create vehicle and service record
|
||||
self.vehicle = Vehicle.objects.create(
|
||||
registration_number=f"SRVPHOTO-{uuid.uuid4().hex[:6].upper()}",
|
||||
make="TestMake",
|
||||
model="TestModel",
|
||||
current_mileage=5000,
|
||||
service_interval_km=10000
|
||||
)
|
||||
|
||||
self.service_record = VehicleServiceRecord.objects.create(
|
||||
vehicle=self.vehicle,
|
||||
performed_by=self.uploader,
|
||||
description="Test service",
|
||||
mileage=5000,
|
||||
parts="part1;part2"
|
||||
)
|
||||
|
||||
def test_upload_photo_success(self):
|
||||
"""
|
||||
Test uspješnog uploada fotografije s valjanim podacima.
|
||||
"""
|
||||
image = create_test_image(filename="success.jpg")
|
||||
|
||||
data = {
|
||||
'service_record': self.service_record,
|
||||
'image': image,
|
||||
'description': 'Test brake replacement'
|
||||
}
|
||||
|
||||
photo = VehicleServicePhotoService.upload_photo(data=data, uploaded_by=self.uploader)
|
||||
|
||||
self.assertIsNotNone(photo.pk)
|
||||
self.assertEqual(photo.uploaded_by, self.uploader)
|
||||
self.assertEqual(photo.service_record, self.service_record)
|
||||
self.assertEqual(photo.description, 'Test brake replacement')
|
||||
self.assertTrue(photo.image)
|
||||
|
||||
def test_upload_photo_with_service_record_id(self):
|
||||
"""
|
||||
Test uploada koristeći service_record ID umjesto instancije.
|
||||
"""
|
||||
image = create_test_image(filename="by_id.jpg")
|
||||
|
||||
data = {
|
||||
'service_record': self.service_record.id, # ID, ne instanca
|
||||
'image': image,
|
||||
'description': 'Upload by ID'
|
||||
}
|
||||
|
||||
photo = VehicleServicePhotoService.upload_photo(data=data, uploaded_by=self.uploader)
|
||||
|
||||
self.assertIsNotNone(photo.pk)
|
||||
self.assertEqual(photo.service_record, self.service_record)
|
||||
|
||||
def test_upload_photo_without_user_raises(self):
|
||||
"""
|
||||
Test — upload bez korisnika (uploaded_by) baca ValidationError.
|
||||
"""
|
||||
image = create_test_image(filename="no_user.jpg")
|
||||
|
||||
data = {
|
||||
'service_record': self.service_record,
|
||||
'image': image,
|
||||
'description': 'No user'
|
||||
}
|
||||
|
||||
with self.assertRaises(DRFValidationError):
|
||||
VehicleServicePhotoService.upload_photo(data=data, uploaded_by=None)
|
||||
|
||||
def test_upload_photo_invalid_service_record_raises(self):
|
||||
"""
|
||||
Test — upload s nepostojeću service_record ID baca ValidationError.
|
||||
"""
|
||||
image = create_test_image(filename="invalid_sr.jpg")
|
||||
|
||||
data = {
|
||||
'service_record': 99999, # ne postoji
|
||||
'image': image,
|
||||
'description': 'Invalid SR'
|
||||
}
|
||||
|
||||
with self.assertRaises(DRFValidationError):
|
||||
VehicleServicePhotoService.upload_photo(data=data, uploaded_by=self.uploader)
|
||||
|
||||
def test_update_photo_by_uploader_success(self):
|
||||
"""
|
||||
Test ažuriranja fotografije od strane korisnika koji ju je uploadao.
|
||||
"""
|
||||
image = create_test_image(filename="original.jpg")
|
||||
photo = VehicleServicePhoto.objects.create(
|
||||
service_record=self.service_record,
|
||||
image=image,
|
||||
description="Original description",
|
||||
uploaded_by=self.uploader
|
||||
)
|
||||
|
||||
update_data = {'description': 'Updated description'}
|
||||
updated_photo = VehicleServicePhotoService.update_photo(
|
||||
instance=photo,
|
||||
data=update_data,
|
||||
user=self.uploader
|
||||
)
|
||||
|
||||
self.assertEqual(updated_photo.description, 'Updated description')
|
||||
photo.refresh_from_db()
|
||||
self.assertEqual(photo.description, 'Updated description')
|
||||
|
||||
def test_update_photo_by_staff_success(self):
|
||||
"""
|
||||
Test ažuriranja fotografije od strane staff korisnika (može ažurirati bilo koju).
|
||||
"""
|
||||
image = create_test_image(filename="staff_update.jpg")
|
||||
photo = VehicleServicePhoto.objects.create(
|
||||
service_record=self.service_record,
|
||||
image=image,
|
||||
description="Original",
|
||||
uploaded_by=self.uploader
|
||||
)
|
||||
|
||||
update_data = {'description': 'Staff updated'}
|
||||
updated_photo = VehicleServicePhotoService.update_photo(
|
||||
instance=photo,
|
||||
data=update_data,
|
||||
user=self.staff_user
|
||||
)
|
||||
|
||||
self.assertEqual(updated_photo.description, 'Staff updated')
|
||||
|
||||
def test_update_photo_by_non_uploader_denied(self):
|
||||
"""
|
||||
Test — korisnik koji nije uploader niti staff ne može ažurirati.
|
||||
"""
|
||||
image = create_test_image(filename="denied_update.jpg")
|
||||
photo = VehicleServicePhoto.objects.create(
|
||||
service_record=self.service_record,
|
||||
image=image,
|
||||
description="Original",
|
||||
uploaded_by=self.uploader
|
||||
)
|
||||
|
||||
update_data = {'description': 'Hacker attempt'}
|
||||
|
||||
with self.assertRaises(DRFValidationError):
|
||||
VehicleServicePhotoService.update_photo(
|
||||
instance=photo,
|
||||
data=update_data,
|
||||
user=self.other_user
|
||||
)
|
||||
|
||||
def test_update_photo_without_user_denied(self):
|
||||
"""
|
||||
Test — ažuriranje bez korisnika (user=None) baca ValidationError.
|
||||
"""
|
||||
image = create_test_image(filename="no_user_update.jpg")
|
||||
photo = VehicleServicePhoto.objects.create(
|
||||
service_record=self.service_record,
|
||||
image=image,
|
||||
description="Original",
|
||||
uploaded_by=self.uploader
|
||||
)
|
||||
|
||||
update_data = {'description': 'No auth'}
|
||||
|
||||
with self.assertRaises(DRFValidationError):
|
||||
VehicleServicePhotoService.update_photo(
|
||||
instance=photo,
|
||||
data=update_data,
|
||||
user=None
|
||||
)
|
||||
|
||||
def test_delete_photo_by_uploader_success(self):
|
||||
"""
|
||||
Test soft-delete fotografije od strane korisnika koji ju je uploadao.
|
||||
"""
|
||||
image = create_test_image(filename="delete.jpg")
|
||||
photo = VehicleServicePhoto.objects.create(
|
||||
service_record=self.service_record,
|
||||
image=image,
|
||||
description="To delete",
|
||||
uploaded_by=self.uploader
|
||||
)
|
||||
|
||||
self.assertTrue(photo.is_active)
|
||||
|
||||
deleted_photo = VehicleServicePhotoService.delete_photo(
|
||||
instance=photo,
|
||||
user=self.uploader
|
||||
)
|
||||
|
||||
self.assertFalse(deleted_photo.is_active)
|
||||
photo.refresh_from_db()
|
||||
self.assertFalse(photo.is_active)
|
||||
|
||||
def test_delete_photo_by_staff_success(self):
|
||||
"""
|
||||
Test soft-delete od strane staff korisnika.
|
||||
"""
|
||||
image = create_test_image(filename="staff_delete.jpg")
|
||||
photo = VehicleServicePhoto.objects.create(
|
||||
service_record=self.service_record,
|
||||
image=image,
|
||||
description="To delete",
|
||||
uploaded_by=self.uploader
|
||||
)
|
||||
|
||||
VehicleServicePhotoService.delete_photo(
|
||||
instance=photo,
|
||||
user=self.staff_user
|
||||
)
|
||||
|
||||
photo.refresh_from_db()
|
||||
self.assertFalse(photo.is_active)
|
||||
|
||||
def test_delete_photo_by_non_uploader_denied(self):
|
||||
"""
|
||||
Test — korisnik koji nije uploader niti staff ne može obrisati.
|
||||
"""
|
||||
image = create_test_image(filename="denied_delete.jpg")
|
||||
photo = VehicleServicePhoto.objects.create(
|
||||
service_record=self.service_record,
|
||||
image=image,
|
||||
description="Original",
|
||||
uploaded_by=self.uploader
|
||||
)
|
||||
|
||||
with self.assertRaises(DRFValidationError):
|
||||
VehicleServicePhotoService.delete_photo(
|
||||
instance=photo,
|
||||
user=self.other_user
|
||||
)
|
||||
|
||||
# Fotografija je i dalje aktivna
|
||||
photo.refresh_from_db()
|
||||
self.assertTrue(photo.is_active)
|
||||
|
||||
def test_delete_photo_without_user_denied(self):
|
||||
"""
|
||||
Test — brisanje bez korisnika (user=None) baca ValidationError.
|
||||
"""
|
||||
image = create_test_image(filename="no_user_delete.jpg")
|
||||
photo = VehicleServicePhoto.objects.create(
|
||||
service_record=self.service_record,
|
||||
image=image,
|
||||
description="Original",
|
||||
uploaded_by=self.uploader
|
||||
)
|
||||
|
||||
with self.assertRaises(DRFValidationError):
|
||||
VehicleServicePhotoService.delete_photo(
|
||||
instance=photo,
|
||||
user=None
|
||||
)
|
||||
|
||||
photo.refresh_from_db()
|
||||
self.assertTrue(photo.is_active)
|
||||
|
||||
def test_update_photo_does_not_allow_image_field_update(self):
|
||||
"""
|
||||
Test — update ne dozvoljava ponovno upload slike (ili je ignorirano).
|
||||
Sadržaj image polja se ne mijenja ako je proslijeđen u update_data.
|
||||
"""
|
||||
image1 = create_test_image(filename="original.jpg", color='red')
|
||||
photo = VehicleServicePhoto.objects.create(
|
||||
service_record=self.service_record,
|
||||
image=image1,
|
||||
description="Original",
|
||||
uploaded_by=self.uploader
|
||||
)
|
||||
|
||||
original_image_name = photo.image.name
|
||||
|
||||
# Pokušaj update s novom slikom
|
||||
image2 = create_test_image(filename="new.jpg", color='blue')
|
||||
update_data = {
|
||||
'description': 'Updated',
|
||||
'image': image2
|
||||
}
|
||||
|
||||
VehicleServicePhotoService.update_photo(
|
||||
instance=photo,
|
||||
data=update_data,
|
||||
user=self.uploader
|
||||
)
|
||||
|
||||
photo.refresh_from_db()
|
||||
# image bi trebao biti preskočen (ne ažurira se)
|
||||
self.assertEqual(photo.image.name, original_image_name)
|
||||
self.assertEqual(photo.description, 'Updated')
|
||||
|
||||
def test_multiple_photos_for_same_service_record(self):
|
||||
"""
|
||||
Test — isti servisni zapis može imati više fotografija.
|
||||
"""
|
||||
image1 = create_test_image(filename="photo1.jpg", color='red')
|
||||
image2 = create_test_image(filename="photo2.jpg", color='blue')
|
||||
image3 = create_test_image(filename="photo3.jpg", color='green')
|
||||
|
||||
data1 = {'service_record': self.service_record, 'image': image1, 'description': 'Photo 1'}
|
||||
data2 = {'service_record': self.service_record, 'image': image2, 'description': 'Photo 2'}
|
||||
data3 = {'service_record': self.service_record, 'image': image3, 'description': 'Photo 3'}
|
||||
|
||||
photo1 = VehicleServicePhotoService.upload_photo(data=data1, uploaded_by=self.uploader)
|
||||
photo2 = VehicleServicePhotoService.upload_photo(data=data2, uploaded_by=self.uploader)
|
||||
photo3 = VehicleServicePhotoService.upload_photo(data=data3, uploaded_by=self.other_user)
|
||||
|
||||
photos = VehicleServicePhoto.objects.filter(service_record=self.service_record, is_active=True)
|
||||
self.assertEqual(photos.count(), 3)
|
||||
|
||||
# Provjeri da su sve aktivne
|
||||
for photo in [photo1, photo2, photo3]:
|
||||
self.assertTrue(photo.is_active)
|
||||
Reference in New Issue
Block a user