diff --git a/src/test/java/local/epul4a/fotosharing/integration/CascadeDeleteIntegrationTest.java b/src/test/java/local/epul4a/fotosharing/integration/CascadeDeleteIntegrationTest.java new file mode 100644 index 0000000..eb32461 --- /dev/null +++ b/src/test/java/local/epul4a/fotosharing/integration/CascadeDeleteIntegrationTest.java @@ -0,0 +1,297 @@ +package local.epul4a.fotosharing.integration; + +import local.epul4a.fotosharing.model.Commentaire; +import local.epul4a.fotosharing.model.Photo; +import local.epul4a.fotosharing.model.Utilisateur; +import local.epul4a.fotosharing.repository.CommentaireRepository; +import local.epul4a.fotosharing.repository.PhotoRepository; +import local.epul4a.fotosharing.repository.UtilisateurRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests d'intégration pour la suppression en cascade + * Teste : Supprimer un user doit supprimer ses photos et commentaires + */ +@SpringBootTest +@ActiveProfiles("test") +@Transactional +class CascadeDeleteIntegrationTest { + + @Autowired + private UtilisateurRepository utilisateurRepository; + + @Autowired + private PhotoRepository photoRepository; + + @Autowired + private CommentaireRepository commentaireRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + private Utilisateur userA; + private Utilisateur userB; + + @BeforeEach + void setUp() { + // Nettoyer la BDD + commentaireRepository.deleteAll(); + photoRepository.deleteAll(); + utilisateurRepository.deleteAll(); + + // Créer User A + userA = new Utilisateur(); + userA.setEmail("userA@example.com"); + userA.setNom("A"); + userA.setPrenom("User"); + userA.setPassword(passwordEncoder.encode("password")); + userA = utilisateurRepository.save(userA); + + // Créer User B + userB = new Utilisateur(); + userB.setEmail("userB@example.com"); + userB.setNom("B"); + userB.setPrenom("User"); + userB.setPassword(passwordEncoder.encode("password")); + userB = utilisateurRepository.save(userB); + } + + @Test + void testDeleteUser_ShouldDeleteUserOwnPhotos() { + // Given - User A possède 3 photos + Photo photo1 = createPhoto("photo1.jpg", userA); + Photo photo2 = createPhoto("photo2.jpg", userA); + Photo photo3 = createPhoto("photo3.jpg", userA); + + photoRepository.save(photo1); + photoRepository.save(photo2); + photoRepository.save(photo3); + + // Given - User B possède 2 photos (ne doivent PAS être supprimées) + Photo photoB1 = createPhoto("photoB1.jpg", userB); + Photo photoB2 = createPhoto("photoB2.jpg", userB); + photoRepository.save(photoB1); + photoRepository.save(photoB2); + + // Vérifier la situation initiale + assertEquals(5, photoRepository.count(), "5 photos au total"); + assertEquals(3, photoRepository.findByProprietaire_Email(userA.getEmail()).size()); + assertEquals(2, photoRepository.findByProprietaire_Email(userB.getEmail()).size()); + + // When - Supprimer User A + utilisateurRepository.delete(userA); + utilisateurRepository.flush(); + + // Then - Les photos de User A doivent être supprimées + assertEquals(2, photoRepository.count(), + "Seules les 2 photos de User B doivent rester"); + + List remainingPhotos = photoRepository.findAll(); + assertEquals(2, remainingPhotos.size()); + assertTrue(remainingPhotos.stream().allMatch(p -> p.getProprietaire().getId().equals(userB.getId())), + "Toutes les photos restantes doivent appartenir à User B"); + } + + @Test + void testDeleteUser_ShouldDeleteUserOwnComments() { + // Given - Photo de User B + Photo photoB = createPhoto("photoB.jpg", userB); + photoB = photoRepository.save(photoB); + + // Given - User A commente la photo de User B + Commentaire comment1 = createCommentaire("Super photo !", userA, photoB); + Commentaire comment2 = createCommentaire("J'adore !", userA, photoB); + Commentaire comment3 = createCommentaire("Merci !", userB, photoB); // Commentaire de User B + + commentaireRepository.save(comment1); + commentaireRepository.save(comment2); + commentaireRepository.save(comment3); + + // Vérifier la situation initiale + assertEquals(3, commentaireRepository.count()); + + // When - Supprimer User A + utilisateurRepository.delete(userA); + utilisateurRepository.flush(); + + // Then - Les commentaires de User A doivent être supprimés + assertEquals(1, commentaireRepository.count(), + "Seul le commentaire de User B doit rester"); + + List remainingComments = commentaireRepository.findAll(); + assertEquals(1, remainingComments.size()); + assertEquals(userB.getId(), remainingComments.get(0).getAuteur().getId(), + "Le commentaire restant doit être celui de User B"); + } + + @Test + void testDeleteUser_ShouldDeletePhotosAndAllCommentsOnThosePhotos() { + // Given - User A possède une photo + Photo photoA = createPhoto("photoA.jpg", userA); + photoA = photoRepository.save(photoA); + + // Given - Plusieurs utilisateurs commentent la photo de User A + Commentaire commentByA = createCommentaire("Ma photo", userA, photoA); + Commentaire commentByB = createCommentaire("Belle photo", userB, photoA); + + commentaireRepository.save(commentByA); + commentaireRepository.save(commentByB); + + // Vérifier la situation initiale + assertEquals(1, photoRepository.count()); + assertEquals(2, commentaireRepository.count()); + + // When - Supprimer User A (et donc sa photo) + utilisateurRepository.delete(userA); + utilisateurRepository.flush(); + + // Then - La photo ET tous ses commentaires doivent être supprimés + assertEquals(0, photoRepository.count(), + "La photo de User A doit être supprimée"); + + assertEquals(0, commentaireRepository.count(), + "TOUS les commentaires sur la photo supprimée doivent disparaître (cascade)"); + } + + @Test + void testDeleteUser_ComplexScenario() { + // Scénario complexe : + // - User A a 2 photos + // - User B a 1 photo + // - User A commente sa propre photo ET la photo de User B + // - User B commente sa propre photo ET une photo de User A + + // Given - Photos + Photo photoA1 = photoRepository.save(createPhoto("photoA1.jpg", userA)); + Photo photoA2 = photoRepository.save(createPhoto("photoA2.jpg", userA)); + Photo photoB = photoRepository.save(createPhoto("photoB.jpg", userB)); + + // Given - Commentaires + commentaireRepository.save(createCommentaire("A sur A1", userA, photoA1)); + commentaireRepository.save(createCommentaire("A sur A2", userA, photoA2)); + commentaireRepository.save(createCommentaire("A sur B", userA, photoB)); + commentaireRepository.save(createCommentaire("B sur A1", userB, photoA1)); + commentaireRepository.save(createCommentaire("B sur B", userB, photoB)); + + // Vérifier la situation initiale + assertEquals(3, photoRepository.count()); + assertEquals(5, commentaireRepository.count()); + + // When - Supprimer User A + utilisateurRepository.delete(userA); + utilisateurRepository.flush(); + + // Then - Vérifier les suppressions + assertEquals(1, photoRepository.count(), + "Seule la photo de User B doit rester"); + + List remainingPhotos = photoRepository.findAll(); + assertEquals(photoB.getId(), remainingPhotos.get(0).getId()); + + // Then - Vérifier les commentaires restants + List remainingComments = commentaireRepository.findAll(); + assertEquals(1, remainingComments.size(), + "Seul le commentaire 'B sur B' doit rester"); + + Commentaire lastComment = remainingComments.get(0); + assertEquals("B sur B", lastComment.getTexte()); + assertEquals(userB.getId(), lastComment.getAuteur().getId()); + assertEquals(photoB.getId(), lastComment.getPhoto().getId()); + } + + @Test + void testDeleteUser_ShouldNotAffectOtherUsers() { + // Given - Créer User C + Utilisateur userC = new Utilisateur(); + userC.setEmail("userC@example.com"); + userC.setNom("C"); + userC.setPrenom("User"); + userC.setPassword(passwordEncoder.encode("password")); + userC = utilisateurRepository.save(userC); + + // Given - Chaque utilisateur a des photos et commentaires + Photo photoA = photoRepository.save(createPhoto("photoA.jpg", userA)); + Photo photoB = photoRepository.save(createPhoto("photoB.jpg", userB)); + Photo photoC = photoRepository.save(createPhoto("photoC.jpg", userC)); + + commentaireRepository.save(createCommentaire("Comment A", userA, photoA)); + commentaireRepository.save(createCommentaire("Comment B", userB, photoB)); + commentaireRepository.save(createCommentaire("Comment C", userC, photoC)); + + // Vérifier la situation initiale + assertEquals(3, utilisateurRepository.count()); + assertEquals(3, photoRepository.count()); + assertEquals(3, commentaireRepository.count()); + + // When - Supprimer User A + utilisateurRepository.delete(userA); + utilisateurRepository.flush(); + + // Then - User B et C ne doivent pas être affectés + assertEquals(2, utilisateurRepository.count(), + "Users B et C doivent rester"); + assertEquals(2, photoRepository.count(), + "Photos de B et C doivent rester"); + assertEquals(2, commentaireRepository.count(), + "Commentaires de B et C doivent rester"); + + // Then - Vérifier que les bonnes entités restent + assertTrue(utilisateurRepository.findById(userB.getId()).isPresent()); + assertTrue(utilisateurRepository.findById(userC.getId()).isPresent()); + assertFalse(utilisateurRepository.findById(userA.getId()).isPresent()); + } + + @Test + void testDeleteMultipleUsers_ShouldDeleteRespectiveData() { + // Given - Chaque user a des photos + photoRepository.save(createPhoto("photoA.jpg", userA)); + photoRepository.save(createPhoto("photoB.jpg", userB)); + + assertEquals(2, photoRepository.count()); + + // When - Supprimer les deux utilisateurs + utilisateurRepository.delete(userA); + utilisateurRepository.delete(userB); + utilisateurRepository.flush(); + + // Then - Tout doit être supprimé + assertEquals(0, utilisateurRepository.count()); + assertEquals(0, photoRepository.count()); + } + + // ===================== Méthodes Utilitaires ===================== + + private Photo createPhoto(String filename, Utilisateur owner) { + Photo photo = new Photo(); + photo.setNomFichierOriginal(filename); + photo.setUuidFichier("uuid-" + filename); + photo.setUuidThumbnail("thumb-" + filename); + photo.setVisibilite(Photo.Visibilite.PUBLIC); + photo.setDateUpload(LocalDateTime.now()); + photo.setProprietaire(owner); + photo.setMimeType("image/jpeg"); + photo.setTailleFichier(1024L); + return photo; + } + + private Commentaire createCommentaire(String texte, Utilisateur auteur, Photo photo) { + Commentaire commentaire = new Commentaire(); + commentaire.setTexte(texte); + commentaire.setAuteur(auteur); + commentaire.setPhoto(photo); + commentaire.setDateCommentaire(LocalDateTime.now()); + return commentaire; + } +} + diff --git a/src/test/java/local/epul4a/fotosharing/integration/PhotoUploadIntegrationTest.java b/src/test/java/local/epul4a/fotosharing/integration/PhotoUploadIntegrationTest.java new file mode 100644 index 0000000..e1fa27b --- /dev/null +++ b/src/test/java/local/epul4a/fotosharing/integration/PhotoUploadIntegrationTest.java @@ -0,0 +1,288 @@ +package local.epul4a.fotosharing.integration; + +import local.epul4a.fotosharing.dto.PhotoDTO; +import local.epul4a.fotosharing.model.Photo; +import local.epul4a.fotosharing.model.Utilisateur; +import local.epul4a.fotosharing.repository.PhotoRepository; +import local.epul4a.fotosharing.repository.UtilisateurRepository; +import local.epul4a.fotosharing.service.PhotoService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.ActiveProfiles; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests d'intégration pour le flux complet d'upload de photos + * Teste : Envoi fichier -> Vérification présence sur disque -> Vérification entrée en BDD + */ +@SpringBootTest +@ActiveProfiles("test") +class PhotoUploadIntegrationTest { + + @Autowired + private PhotoService photoService; + + @Autowired + private PhotoRepository photoRepository; + + @Autowired + private UtilisateurRepository utilisateurRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Value("${file.upload-dir}") + private String uploadDir; + + private Utilisateur testUser; + private Path uploadPath; + + @BeforeEach + void setUp() throws IOException { + // Créer le dossier d'upload pour les tests + uploadPath = Paths.get(uploadDir); + if (!Files.exists(uploadPath)) { + Files.createDirectories(uploadPath); + } + + // Créer un utilisateur de test + testUser = new Utilisateur(); + testUser.setEmail("testupload@example.com"); + testUser.setNom("Upload"); + testUser.setPrenom("Test"); + testUser.setPassword(passwordEncoder.encode("password")); + testUser = utilisateurRepository.save(testUser); + } + + @AfterEach + void tearDown() throws IOException { + // Nettoyer les photos de test + photoRepository.findAll().forEach(photo -> { + try { + // Supprimer les fichiers sur disque + Path originalFile = uploadPath.resolve(photo.getUuidFichier()); + Path thumbFile = uploadPath.resolve(photo.getUuidThumbnail()); + Files.deleteIfExists(originalFile); + Files.deleteIfExists(thumbFile); + } catch (IOException e) { + // Ignorer les erreurs de suppression + } + }); + + // Supprimer de la BDD + photoRepository.deleteAll(); + utilisateurRepository.deleteAll(); + } + + @Test + void testCompleteUploadFlow_ShouldCreateFileOnDiskAndDatabaseEntry() throws IOException { + // Given - Créer un fichier JPEG valide + byte[] jpegContent = createValidJpegBytes(); + MockMultipartFile file = new MockMultipartFile( + "file", + "test-photo.jpg", + "image/jpeg", + jpegContent + ); + + // When - Upload de la photo + PhotoDTO uploadedPhoto = photoService.store(file, "PUBLIC", testUser.getEmail()); + + // Then - Vérifier que le DTO est retourné + assertNotNull(uploadedPhoto, "Le DTO de la photo uploadée ne doit pas être null"); + assertNotNull(uploadedPhoto.getId(), "L'ID de la photo doit être généré"); + assertEquals("test-photo.jpg", uploadedPhoto.getNomFichierOriginal()); + assertEquals("PUBLIC", uploadedPhoto.getVisibilite()); + assertEquals("image/jpeg", uploadedPhoto.getMimeType()); + assertEquals(jpegContent.length, uploadedPhoto.getTailleFichier()); + + // Then - Vérifier la présence du fichier ORIGINAL sur le disque + Path originalFile = uploadPath.resolve(uploadedPhoto.getUuidFichier()); + assertTrue(Files.exists(originalFile), + "Le fichier original doit exister sur le disque : " + originalFile); + assertEquals(jpegContent.length, Files.size(originalFile), + "La taille du fichier sur disque doit correspondre"); + + // Then - Vérifier la présence de la MINIATURE sur le disque + Path thumbnailFile = uploadPath.resolve(uploadedPhoto.getUuidThumbnail()); + assertTrue(Files.exists(thumbnailFile), + "La miniature doit exister sur le disque : " + thumbnailFile); + assertTrue(Files.size(thumbnailFile) > 0, + "La miniature ne doit pas être vide"); + + // Then - Vérifier l'entrée en BDD + Photo photoInDb = photoRepository.findById(uploadedPhoto.getId()).orElse(null); + assertNotNull(photoInDb, "La photo doit être enregistrée en BDD"); + assertEquals("test-photo.jpg", photoInDb.getNomFichierOriginal()); + assertEquals(Photo.Visibilite.PUBLIC, photoInDb.getVisibilite()); + assertEquals(testUser.getId(), photoInDb.getProprietaire().getId()); + assertEquals("image/jpeg", photoInDb.getMimeType()); + assertEquals((long) jpegContent.length, photoInDb.getTailleFichier()); + + // Then - Vérifier les métadonnées + assertNotNull(photoInDb.getLargeur(), "La largeur doit être extraite"); + assertNotNull(photoInDb.getHauteur(), "La hauteur doit être extraite"); + assertTrue(photoInDb.getLargeur() > 0, "La largeur doit être positive"); + assertTrue(photoInDb.getHauteur() > 0, "La hauteur doit être positive"); + } + + @Test + void testUploadPngFile_ShouldDetectCorrectMimeType() { + // Given - Créer un fichier PNG valide + byte[] pngContent = createValidPngBytes(); + MockMultipartFile file = new MockMultipartFile( + "file", + "test-image.png", + "image/png", + pngContent + ); + + // When + PhotoDTO uploadedPhoto = photoService.store(file, "PRIVATE", testUser.getEmail()); + + // Then - Vérifier le type MIME détecté + assertNotNull(uploadedPhoto); + assertEquals("image/png", uploadedPhoto.getMimeType(), + "Le type MIME doit être détecté comme PNG"); + + // Then - Vérifier en BDD + Photo photoInDb = photoRepository.findById(uploadedPhoto.getId()).orElse(null); + assertNotNull(photoInDb); + assertEquals("image/png", photoInDb.getMimeType()); + } + + @Test + void testUploadMultiplePhotos_ShouldCreateSeparateFiles() throws IOException { + // Given - Uploader plusieurs photos + for (int i = 1; i <= 3; i++) { + byte[] content = createValidJpegBytes(); + MockMultipartFile file = new MockMultipartFile( + "file", + "photo-" + i + ".jpg", + "image/jpeg", + content + ); + + // When + photoService.store(file, "PUBLIC", testUser.getEmail()); + } + + // Then - Vérifier que 3 photos sont en BDD + long photoCount = photoRepository.count(); + assertEquals(3, photoCount, "3 photos doivent être enregistrées"); + + // Then - Vérifier que tous les fichiers existent + photoRepository.findAll().forEach(photo -> { + Path originalFile = uploadPath.resolve(photo.getUuidFichier()); + Path thumbFile = uploadPath.resolve(photo.getUuidThumbnail()); + + assertTrue(Files.exists(originalFile), + "Le fichier original doit exister : " + photo.getUuidFichier()); + assertTrue(Files.exists(thumbFile), + "La miniature doit exister : " + photo.getUuidThumbnail()); + }); + } + + @Test + void testUploadWithDifferentVisibilities() { + // Test chaque type de visibilité + Photo.Visibilite[] visibilities = {Photo.Visibilite.PUBLIC, Photo.Visibilite.PRIVATE, Photo.Visibilite.SHARED}; + + for (Photo.Visibilite visibility : visibilities) { + // Given + byte[] content = createValidJpegBytes(); + MockMultipartFile file = new MockMultipartFile( + "file", + "photo-" + visibility.name().toLowerCase() + ".jpg", + "image/jpeg", + content + ); + + // When + PhotoDTO uploadedPhoto = photoService.store(file, visibility.name(), testUser.getEmail()); + + // Then + assertEquals(visibility.name(), uploadedPhoto.getVisibilite(), + "La visibilité doit être " + visibility); + + Photo photoInDb = photoRepository.findById(uploadedPhoto.getId()).orElse(null); + assertNotNull(photoInDb); + assertEquals(visibility, photoInDb.getVisibilite()); + } + } + + @Test + void testUpload_FileNameWithUUID_ShouldBeUnique() { + // Given - Uploader deux fichiers avec le même nom + byte[] content = createValidJpegBytes(); + + MockMultipartFile file1 = new MockMultipartFile("file", "same-name.jpg", "image/jpeg", content); + MockMultipartFile file2 = new MockMultipartFile("file", "same-name.jpg", "image/jpeg", content); + + // When + PhotoDTO photo1 = photoService.store(file1, "PUBLIC", testUser.getEmail()); + PhotoDTO photo2 = photoService.store(file2, "PUBLIC", testUser.getEmail()); + + // Then - Les UUID doivent être différents + assertNotEquals(photo1.getUuidFichier(), photo2.getUuidFichier(), + "Les UUID des fichiers doivent être différents même avec le même nom original"); + + // Then - Les deux fichiers doivent exister sur le disque + assertTrue(Files.exists(uploadPath.resolve(photo1.getUuidFichier()))); + assertTrue(Files.exists(uploadPath.resolve(photo2.getUuidFichier()))); + } + + // ===================== Méthodes Utilitaires ===================== + + /** + * Crée un tableau de bytes représentant un JPEG valide minimal + */ + private byte[] createValidJpegBytes() { + // Magic number JPEG + données minimales pour créer une image valide + return new byte[]{ + (byte) 0xFF, (byte) 0xD8, (byte) 0xFF, (byte) 0xE0, // JPEG header + 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, // JFIF marker + 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, + (byte) 0xFF, (byte) 0xDB, 0x00, 0x43, 0x00, // Quantization table + 0x08, 0x06, 0x06, 0x07, 0x06, 0x05, 0x08, 0x07, + 0x07, 0x07, 0x09, 0x09, 0x08, 0x0A, 0x0C, 0x14, + 0x0D, 0x0C, 0x0B, 0x0B, 0x0C, 0x19, 0x12, 0x13, + 0x0F, 0x14, 0x1D, 0x1A, 0x1F, 0x1E, 0x1D, 0x1A, + 0x1C, 0x1C, 0x20, 0x24, 0x2E, 0x27, 0x20, 0x22, + 0x2C, 0x23, 0x1C, 0x1C, 0x28, 0x37, 0x29, 0x2C, + 0x30, 0x31, 0x34, 0x34, 0x34, 0x1F, 0x27, 0x39, + 0x3D, 0x38, 0x32, 0x3C, 0x2E, 0x33, 0x34, 0x32, + (byte) 0xFF, (byte) 0xD9 // End of image + }; + } + + /** + * Crée un tableau de bytes représentant un PNG valide minimal + */ + private byte[] createValidPngBytes() { + // Magic number PNG + données minimales + return new byte[]{ + (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature + 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, // IHDR chunk + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, // 1x1 pixel + 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, (byte) 0xC4, (byte) 0x89, + 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41, 0x54, // IDAT chunk + 0x78, (byte) 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, // IEND chunk + (byte) 0xAE, 0x42, 0x60, (byte) 0x82 + }; + } +} + diff --git a/src/test/java/local/epul4a/fotosharing/mapper/PhotoMapperTest.java b/src/test/java/local/epul4a/fotosharing/mapper/PhotoMapperTest.java new file mode 100644 index 0000000..b67cc20 --- /dev/null +++ b/src/test/java/local/epul4a/fotosharing/mapper/PhotoMapperTest.java @@ -0,0 +1,153 @@ +package local.epul4a.fotosharing.mapper; + +import local.epul4a.fotosharing.dto.PhotoDTO; +import local.epul4a.fotosharing.dto.UtilisateurDTO; +import local.epul4a.fotosharing.model.Photo; +import local.epul4a.fotosharing.model.Utilisateur; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests unitaires pour PhotoMapper + * Teste la conversion bidirectionnelle entre Photo et PhotoDTO + */ +class PhotoMapperTest { + + @Test + void testToDTO_WithCompletePhoto_ShouldMapAllFields() { + // Given - Créer un utilisateur + Utilisateur utilisateur = new Utilisateur(); + utilisateur.setId(1L); + utilisateur.setEmail("test@example.com"); + utilisateur.setNom("Dupont"); + utilisateur.setPrenom("Jean"); + + // Given - Créer une photo complète + Photo photo = new Photo(); + photo.setId(100L); + photo.setNomFichierOriginal("vacances.jpg"); + photo.setUuidFichier("uuid-123-vacances.jpg"); + photo.setUuidThumbnail("thumb-uuid-123-vacances.jpg"); + photo.setDateUpload(LocalDateTime.of(2025, 12, 3, 10, 30)); + photo.setVisibilite(Photo.Visibilite.PUBLIC); + photo.setProprietaire(utilisateur); + photo.setMimeType("image/jpeg"); + photo.setTailleFichier(2457600L); + photo.setLargeur(1920); + photo.setHauteur(1080); + + // When - Convertir en DTO + PhotoDTO dto = PhotoMapper.toDTO(photo); + + // Then - Vérifier tous les champs + assertNotNull(dto, "Le DTO ne doit pas être null"); + assertEquals(100L, dto.getId()); + assertEquals("vacances.jpg", dto.getNomFichierOriginal()); + assertEquals("uuid-123-vacances.jpg", dto.getUuidFichier()); + assertEquals("thumb-uuid-123-vacances.jpg", dto.getUuidThumbnail()); + assertEquals(LocalDateTime.of(2025, 12, 3, 10, 30), dto.getDateUpload()); + assertEquals("PUBLIC", dto.getVisibilite()); + assertEquals("image/jpeg", dto.getMimeType()); + assertEquals(2457600L, dto.getTailleFichier()); + assertEquals(1920, dto.getLargeur()); + assertEquals(1080, dto.getHauteur()); + + // Then - Vérifier le propriétaire + assertNotNull(dto.getProprietaire(), "Le propriétaire ne doit pas être null"); + assertEquals(1L, dto.getProprietaire().getId()); + assertEquals("test@example.com", dto.getProprietaire().getEmail()); + assertEquals("Dupont", dto.getProprietaire().getNom()); + assertEquals("Jean", dto.getProprietaire().getPrenom()); + } + + @Test + void testToDTO_WithNullPhoto_ShouldReturnNull() { + // When - Convertir null + PhotoDTO dto = PhotoMapper.toDTO(null); + + // Then + assertNull(dto, "Le DTO doit être null quand la photo est null"); + } + + @Test + void testToDTO_WithPhotoWithoutOwner_ShouldNotFail() { + // Given - Photo sans propriétaire + Photo photo = new Photo(); + photo.setId(50L); + photo.setNomFichierOriginal("test.png"); + photo.setVisibilite(Photo.Visibilite.PRIVATE); + + // When + PhotoDTO dto = PhotoMapper.toDTO(photo); + + // Then + assertNotNull(dto); + assertEquals(50L, dto.getId()); + assertNull(dto.getProprietaire(), "Le propriétaire doit être null"); + } + + @Test + void testToDTO_WithMinimalPhoto_ShouldMapBasicFields() { + // Given - Photo avec champs minimaux + Photo photo = new Photo(); + photo.setId(25L); + photo.setNomFichierOriginal("minimal.jpg"); + photo.setVisibilite(Photo.Visibilite.SHARED); + + // When + PhotoDTO dto = PhotoMapper.toDTO(photo); + + // Then + assertNotNull(dto); + assertEquals(25L, dto.getId()); + assertEquals("minimal.jpg", dto.getNomFichierOriginal()); + assertEquals("SHARED", dto.getVisibilite()); + assertNull(dto.getMimeType()); + assertNull(dto.getTailleFichier()); + } + + @Test + void testToDTO_MetadataMapping() { + // Given - Photo avec métadonnées + Photo photo = new Photo(); + photo.setId(1L); + photo.setNomFichierOriginal("hd-photo.jpg"); + photo.setVisibilite(Photo.Visibilite.PUBLIC); + photo.setMimeType("image/png"); + photo.setTailleFichier(5242880L); // 5 MB + photo.setLargeur(3840); + photo.setHauteur(2160); + + // When + PhotoDTO dto = PhotoMapper.toDTO(photo); + + // Then - Vérifier les métadonnées + assertEquals("image/png", dto.getMimeType()); + assertEquals(5242880L, dto.getTailleFichier()); + assertEquals(3840, dto.getLargeur()); + assertEquals(2160, dto.getHauteur()); + } + + @Test + void testToDTO_AllVisibilityTypes() { + // Test pour chaque type de visibilité + for (Photo.Visibilite visibilite : Photo.Visibilite.values()) { + // Given + Photo photo = new Photo(); + photo.setId(1L); + photo.setNomFichierOriginal("test.jpg"); + photo.setVisibilite(visibilite); + + // When + PhotoDTO dto = PhotoMapper.toDTO(photo); + + // Then + assertEquals(visibilite.name(), dto.getVisibilite(), + "La visibilité " + visibilite + " doit être correctement mappée"); + } + } +} + diff --git a/src/test/java/local/epul4a/fotosharing/security/SecurityServiceTest.java b/src/test/java/local/epul4a/fotosharing/security/SecurityServiceTest.java new file mode 100644 index 0000000..5c1a82e --- /dev/null +++ b/src/test/java/local/epul4a/fotosharing/security/SecurityServiceTest.java @@ -0,0 +1,319 @@ +package local.epul4a.fotosharing.security; + +import local.epul4a.fotosharing.service.PartageService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.Authentication; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Tests unitaires pour SecurityService + * Teste les scénarios d'accès aux photos (READ / COMMENT / ADMIN) + */ +@ExtendWith(MockitoExtension.class) +class SecurityServiceTest { + + @Mock + private PartageService partageService; + + @Mock + private Authentication authentication; + + private SecurityService securityService; + + @BeforeEach + void setUp() { + securityService = new SecurityService(partageService); + } + + // ===================== TESTS canAccessPhoto ===================== + + @Test + void canAccessPhoto_UserAAccessesPrivatePhotoB_ShouldReturnFalse() { + // Given - User A essaie d'accéder à Photo B privée (pas de partage) + Long photoId = 100L; + String userAEmail = "userA@example.com"; + + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getName()).thenReturn(userAEmail); + when(partageService.canView(photoId, userAEmail)).thenReturn(false); + + // When + boolean canAccess = securityService.canAccessPhoto(authentication, photoId); + + // Then + assertFalse(canAccess, "User A ne devrait PAS pouvoir accéder à Photo B privée"); + verify(partageService).canView(photoId, userAEmail); + } + + @Test + void canAccessPhoto_UserAAccessesSharedPhotoB_ShouldReturnTrue() { + // Given - User A accède à Photo B partagée avec lui (READ) + Long photoId = 200L; + String userAEmail = "userA@example.com"; + + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getName()).thenReturn(userAEmail); + when(partageService.canView(photoId, userAEmail)).thenReturn(true); + + // When + boolean canAccess = securityService.canAccessPhoto(authentication, photoId); + + // Then + assertTrue(canAccess, "User A DEVRAIT pouvoir accéder à Photo B partagée"); + verify(partageService).canView(photoId, userAEmail); + } + + @Test + void canAccessPhoto_UserAAccessesOwnPhoto_ShouldReturnTrue() { + // Given - User A accède à sa propre photo + Long photoId = 300L; + String userAEmail = "userA@example.com"; + + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getName()).thenReturn(userAEmail); + when(partageService.canView(photoId, userAEmail)).thenReturn(true); // Propriétaire = accès + + // When + boolean canAccess = securityService.canAccessPhoto(authentication, photoId); + + // Then + assertTrue(canAccess, "User A DEVRAIT pouvoir accéder à sa propre photo"); + } + + @Test + void canAccessPhoto_UnauthenticatedUser_ShouldReturnFalse() { + // Given - Utilisateur non authentifié + when(authentication.isAuthenticated()).thenReturn(false); + + // When + boolean canAccess = securityService.canAccessPhoto(authentication, 100L); + + // Then + assertFalse(canAccess, "Un utilisateur non authentifié ne devrait PAS avoir accès"); + verify(partageService, never()).canView(anyLong(), anyString()); + } + + @Test + void canAccessPhoto_NullAuthentication_ShouldReturnFalse() { + // When + boolean canAccess = securityService.canAccessPhoto(null, 100L); + + // Then + assertFalse(canAccess, "Avec authentication null, l'accès devrait être refusé"); + verify(partageService, never()).canView(anyLong(), anyString()); + } + + @Test + void canAccessPhoto_PublicPhoto_ShouldReturnTrue() { + // Given - Photo publique (accessible par tous les utilisateurs authentifiés) + Long photoId = 400L; + String userEmail = "anyone@example.com"; + + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getName()).thenReturn(userEmail); + when(partageService.canView(photoId, userEmail)).thenReturn(true); + + // When + boolean canAccess = securityService.canAccessPhoto(authentication, photoId); + + // Then + assertTrue(canAccess, "Une photo publique devrait être accessible"); + } + + // ===================== TESTS canCommentPhoto ===================== + + @Test + void canCommentPhoto_UserWithCommentRights_ShouldReturnTrue() { + // Given - User A a le droit COMMENT sur Photo B + Long photoId = 500L; + String userEmail = "userA@example.com"; + + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getName()).thenReturn(userEmail); + when(partageService.canComment(photoId, userEmail)).thenReturn(true); + + // When + boolean canComment = securityService.canCommentPhoto(authentication, photoId); + + // Then + assertTrue(canComment, "User A DEVRAIT pouvoir commenter Photo B"); + verify(partageService).canComment(photoId, userEmail); + } + + @Test + void canCommentPhoto_UserWithoutCommentRights_ShouldReturnFalse() { + // Given - User A n'a que READ (pas COMMENT) + Long photoId = 600L; + String userEmail = "userA@example.com"; + + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getName()).thenReturn(userEmail); + when(partageService.canComment(photoId, userEmail)).thenReturn(false); + + // When + boolean canComment = securityService.canCommentPhoto(authentication, photoId); + + // Then + assertFalse(canComment, "User A ne devrait PAS pouvoir commenter (pas de droit COMMENT)"); + } + + @Test + void canCommentPhoto_UnauthenticatedUser_ShouldReturnFalse() { + // Given + when(authentication.isAuthenticated()).thenReturn(false); + + // When + boolean canComment = securityService.canCommentPhoto(authentication, 100L); + + // Then + assertFalse(canComment); + verify(partageService, never()).canComment(anyLong(), anyString()); + } + + // ===================== TESTS canAdminPhoto ===================== + + @Test + void canAdminPhoto_OwnerOfPhoto_ShouldReturnTrue() { + // Given - Propriétaire de la photo + Long photoId = 700L; + String ownerEmail = "owner@example.com"; + + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getName()).thenReturn(ownerEmail); + when(partageService.canAdmin(photoId, ownerEmail)).thenReturn(true); + + // When + boolean canAdmin = securityService.canAdminPhoto(authentication, photoId); + + // Then + assertTrue(canAdmin, "Le propriétaire DEVRAIT avoir les droits ADMIN"); + } + + @Test + void canAdminPhoto_UserWithAdminRights_ShouldReturnTrue() { + // Given - User A a le droit ADMIN sur Photo B (partagé par le propriétaire) + Long photoId = 800L; + String userEmail = "admin@example.com"; + + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getName()).thenReturn(userEmail); + when(partageService.canAdmin(photoId, userEmail)).thenReturn(true); + + // When + boolean canAdmin = securityService.canAdminPhoto(authentication, photoId); + + // Then + assertTrue(canAdmin); + } + + @Test + void canAdminPhoto_UserWithoutAdminRights_ShouldReturnFalse() { + // Given - User A n'a que READ ou COMMENT (pas ADMIN) + Long photoId = 900L; + String userEmail = "user@example.com"; + + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getName()).thenReturn(userEmail); + when(partageService.canAdmin(photoId, userEmail)).thenReturn(false); + + // When + boolean canAdmin = securityService.canAdminPhoto(authentication, photoId); + + // Then + assertFalse(canAdmin, "User sans droit ADMIN ne devrait PAS pouvoir administrer"); + } + + @Test + void canAdminPhoto_NullAuthentication_ShouldReturnFalse() { + // When + boolean canAdmin = securityService.canAdminPhoto(null, 100L); + + // Then + assertFalse(canAdmin); + verify(partageService, never()).canAdmin(anyLong(), anyString()); + } + + // ===================== TESTS Albums ===================== + + @Test + void canViewAlbum_UserWithAccess_ShouldReturnTrue() { + // Given + Long albumId = 1L; + String userEmail = "user@example.com"; + + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getName()).thenReturn(userEmail); + when(partageService.canViewAlbum(albumId, userEmail)).thenReturn(true); + + // When + boolean canView = securityService.canViewAlbum(authentication, albumId); + + // Then + assertTrue(canView); + verify(partageService).canViewAlbum(albumId, userEmail); + } + + @Test + void canCommentAlbum_UserWithoutAccess_ShouldReturnFalse() { + // Given + Long albumId = 2L; + String userEmail = "user@example.com"; + + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getName()).thenReturn(userEmail); + when(partageService.canCommentAlbum(albumId, userEmail)).thenReturn(false); + + // When + boolean canComment = securityService.canCommentAlbum(authentication, albumId); + + // Then + assertFalse(canComment); + } + + @Test + void canAdminAlbum_UnauthenticatedUser_ShouldReturnFalse() { + // Given + when(authentication.isAuthenticated()).thenReturn(false); + + // When + boolean canAdmin = securityService.canAdminAlbum(authentication, 1L); + + // Then + assertFalse(canAdmin); + verify(partageService, never()).canAdminAlbum(anyLong(), anyString()); + } + + // ===================== TESTS Scénarios Complexes ===================== + + @Test + void scenarioComplete_UserATriesDifferentActionsOnPhotoB() { + // Scénario : User A a seulement READ sur Photo B + Long photoId = 1000L; + String userAEmail = "userA@example.com"; + + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getName()).thenReturn(userAEmail); + when(partageService.canView(photoId, userAEmail)).thenReturn(true); + when(partageService.canComment(photoId, userAEmail)).thenReturn(false); + when(partageService.canAdmin(photoId, userAEmail)).thenReturn(false); + + // When/Then - User A peut voir + assertTrue(securityService.canAccessPhoto(authentication, photoId), + "User A devrait pouvoir VOIR la photo"); + + // When/Then - User A ne peut pas commenter + assertFalse(securityService.canCommentPhoto(authentication, photoId), + "User A ne devrait PAS pouvoir COMMENTER"); + + // When/Then - User A ne peut pas administrer + assertFalse(securityService.canAdminPhoto(authentication, photoId), + "User A ne devrait PAS pouvoir ADMINISTRER"); + } +} + diff --git a/target/FotoSharing-0.0.1-SNAPSHOT.war b/target/FotoSharing-0.0.1-SNAPSHOT.war index b5deac6..e8e548e 100644 Binary files a/target/FotoSharing-0.0.1-SNAPSHOT.war and b/target/FotoSharing-0.0.1-SNAPSHOT.war differ diff --git a/target/FotoSharing-0.0.1-SNAPSHOT.war.original b/target/FotoSharing-0.0.1-SNAPSHOT.war.original index ae3b6ab..9deb4b6 100644 Binary files a/target/FotoSharing-0.0.1-SNAPSHOT.war.original and b/target/FotoSharing-0.0.1-SNAPSHOT.war.original differ