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)