362 lines
12 KiB
Python
362 lines
12 KiB
Python
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) |