From 0573f58a9013524b89f5659b1ef4dc62c6dad15d Mon Sep 17 00:00:00 2001 From: vincentRamiere Date: Mon, 20 Oct 2025 22:28:40 +0200 Subject: [PATCH 1/2] =?UTF-8?q?Feat:=20crud=20pour=20les=20notes=20et=20le?= =?UTF-8?q?s=20pr=C3=A9f=C3=A9rences?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PreferenceController.java | 64 +++++++++++++++++ .../repository/PreferenceRepository.java | 17 +++++ .../service/PreferenceService.java | 71 +++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 src/main/java/fr/vincent/ramiere/mangerautourdesimplonback/controller/PreferenceController.java create mode 100644 src/main/java/fr/vincent/ramiere/mangerautourdesimplonback/repository/PreferenceRepository.java create mode 100644 src/main/java/fr/vincent/ramiere/mangerautourdesimplonback/service/PreferenceService.java diff --git a/src/main/java/fr/vincent/ramiere/mangerautourdesimplonback/controller/PreferenceController.java b/src/main/java/fr/vincent/ramiere/mangerautourdesimplonback/controller/PreferenceController.java new file mode 100644 index 0000000..b9ddc17 --- /dev/null +++ b/src/main/java/fr/vincent/ramiere/mangerautourdesimplonback/controller/PreferenceController.java @@ -0,0 +1,64 @@ +package fr.vincent.ramiere.mangerautourdesimplonback.controller; + +import fr.vincent.ramiere.mangerautourdesimplonback.models.Preference; +import fr.vincent.ramiere.mangerautourdesimplonback.service.PreferenceService; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * Controleur REST pour la gestion des préférences des utilisateurs. + */ +@RestController +@RequestMapping("/api/v1/preferences") +@AllArgsConstructor +public class PreferenceController { + + private final PreferenceService preferenceService; + + /** + * DTO pour recevoir les données de préférence. + */ + @Data + static class PreferenceRequest { + private Integer personneId; + private Integer restaurantId; + private Integer note; + private Boolean favori; + } + + /** + * Endpoint pour récupérer toutes les préférences d'un utilisateur. + * + * @param personneId L'ID de l'utilisateur. + * @return une ResponseEntity contenant la liste des préférences et le statut + * HTTP OK. + */ + @GetMapping("/user/{personneId}") + public ResponseEntity> getPreferencesByPersonneId(@PathVariable Integer personneId) { + List preferences = preferenceService.getPreferencesByPersonneId(personneId); + return ResponseEntity.ok(preferences); + } + + /** + * Endpoint pour créer ou mettre à jour une préférence (note et/ou favori). + * + * @param request Le corps de la requête contenant les détails de la préférence. + * @return une ResponseEntity contenant la préférence sauvegardée (OK) ou + * NOT_FOUND si l'utilisateur ou le restaurant n'existe pas. + */ + @PutMapping + public ResponseEntity saveOrUpdatePreference(@RequestBody PreferenceRequest request) { + return preferenceService.saveOrUpdatePreference( + request.getPersonneId(), + request.getRestaurantId(), + request.getNote(), + request.getFavori()) + .map(ResponseEntity::ok) + .orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND)); + } +} diff --git a/src/main/java/fr/vincent/ramiere/mangerautourdesimplonback/repository/PreferenceRepository.java b/src/main/java/fr/vincent/ramiere/mangerautourdesimplonback/repository/PreferenceRepository.java new file mode 100644 index 0000000..75d08fc --- /dev/null +++ b/src/main/java/fr/vincent/ramiere/mangerautourdesimplonback/repository/PreferenceRepository.java @@ -0,0 +1,17 @@ +package fr.vincent.ramiere.mangerautourdesimplonback.repository; + +import fr.vincent.ramiere.mangerautourdesimplonback.models.Preference; +import fr.vincent.ramiere.mangerautourdesimplonback.models.PreferencePK; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * Repository pour l'entité Preference. + * Fournit les méthodes pour les opérations CRUD sur les préférences. + */ +@Repository +public interface PreferenceRepository extends JpaRepository { + List findByPreferencePK_Personne_Id(Integer personneId); +} diff --git a/src/main/java/fr/vincent/ramiere/mangerautourdesimplonback/service/PreferenceService.java b/src/main/java/fr/vincent/ramiere/mangerautourdesimplonback/service/PreferenceService.java new file mode 100644 index 0000000..843b9de --- /dev/null +++ b/src/main/java/fr/vincent/ramiere/mangerautourdesimplonback/service/PreferenceService.java @@ -0,0 +1,71 @@ +package fr.vincent.ramiere.mangerautourdesimplonback.service; + +import fr.vincent.ramiere.mangerautourdesimplonback.models.Personne; +import fr.vincent.ramiere.mangerautourdesimplonback.models.Preference; +import fr.vincent.ramiere.mangerautourdesimplonback.models.PreferencePK; +import fr.vincent.ramiere.mangerautourdesimplonback.models.Restaurant; +import fr.vincent.ramiere.mangerautourdesimplonback.repository.PersonneRepository; +import fr.vincent.ramiere.mangerautourdesimplonback.repository.PreferenceRepository; +import fr.vincent.ramiere.mangerautourdesimplonback.repository.RestaurantRepository; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +/** + * Service pour la gestion des préférences des utilisateurs. + */ +@Service +@AllArgsConstructor +public class PreferenceService { + + private final PreferenceRepository preferenceRepository; + private final PersonneRepository personneRepository; + private final RestaurantRepository restaurantRepository; + + /** + * Récupère toutes les préférences pour un utilisateur donné. + * + * @param personneId L'ID de l'utilisateur. + * @return Une liste de préférences. + */ + public List getPreferencesByPersonneId(Integer personneId) { + return preferenceRepository.findByPreferencePK_Personne_Id(personneId); + } + + /** + * Crée ou met à jour une préférence pour un utilisateur et un restaurant. + * + * @param personneId L'ID de l'utilisateur. + * @param restaurantId L'ID du restaurant. + * @param note La note attribuée (peut être null). + * @param favori Le statut de favori (peut être null). + * @return Un Optional contenant la préférence sauvegardée, ou un Optional vide + * si l'utilisateur ou le restaurant n'existe pas. + */ + @Transactional + public Optional saveOrUpdatePreference(Integer personneId, Integer restaurantId, Integer note, + Boolean favori) { + Optional personneOpt = personneRepository.findById(personneId); + Optional restaurantOpt = restaurantRepository.findById(restaurantId); + + if (personneOpt.isEmpty() || restaurantOpt.isEmpty()) { + return Optional.empty(); + } + + Personne personne = personneOpt.get(); + Restaurant restaurant = restaurantOpt.get(); + + PreferencePK preferencePK = new PreferencePK(personne, restaurant); + + Preference preference = preferenceRepository.findById(preferencePK) + .orElse(new Preference(preferencePK)); + + preference.setNote(note); + preference.setFavori(favori); + + return Optional.of(preferenceRepository.save(preference)); + } +} From 8d625fa5778d77de4e56edb3fa909d78f4c04512 Mon Sep 17 00:00:00 2001 From: vincentRamiere Date: Mon, 20 Oct 2025 23:07:40 +0200 Subject: [PATCH 2/2] =?UTF-8?q?Feat(tu):=20tests=20pour=20la=20gestion=20d?= =?UTF-8?q?es=20pr=C3=A9f=C3=A9rences?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PreferenceControllerTest.java | 72 +++++++++++ .../models/PreferencePKTest.java | 48 ++++++++ .../models/PreferenceTest.java | 65 ++++++++++ .../services/PreferenceServiceTest.java | 116 ++++++++++++++++++ 4 files changed, 301 insertions(+) create mode 100644 src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/controller/PreferenceControllerTest.java create mode 100644 src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/models/PreferencePKTest.java create mode 100644 src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/models/PreferenceTest.java create mode 100644 src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/services/PreferenceServiceTest.java diff --git a/src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/controller/PreferenceControllerTest.java b/src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/controller/PreferenceControllerTest.java new file mode 100644 index 0000000..de56c65 --- /dev/null +++ b/src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/controller/PreferenceControllerTest.java @@ -0,0 +1,72 @@ +package fr.vincent.ramiere.mangerautourdesimplonback.controller; + +import fr.vincent.ramiere.mangerautourdesimplonback.models.Preference; +import fr.vincent.ramiere.mangerautourdesimplonback.service.PreferenceService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import java.util.Collections; +import java.util.Optional; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class PreferenceControllerTest { + + private PreferenceService preferenceService; + private MockMvc mockMvc; + + @BeforeEach + void setup() { + preferenceService = Mockito.mock(PreferenceService.class); + PreferenceController controller = new PreferenceController(preferenceService); + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + @Test + void getPreferencesByPersonneId_shouldReturnOkAndCallService() throws Exception { + when(preferenceService.getPreferencesByPersonneId(42)).thenReturn(Collections.emptyList()); + + mockMvc.perform(get("/api/v1/preferences/user/42")) + .andExpect(status().isOk()); + + verify(preferenceService, times(1)).getPreferencesByPersonneId(42); + } + + @Test + void saveOrUpdatePreference_shouldReturnNotFound_whenServiceReturnsEmpty() throws Exception { + when(preferenceService.saveOrUpdatePreference(1, 2, 5, true)) + .thenReturn(Optional.empty()); + + String json = "{\"personneId\":1,\"restaurantId\":2,\"note\":5,\"favori\":true}"; + + mockMvc.perform(put("/api/v1/preferences") + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isNotFound()); + + verify(preferenceService, times(1)).saveOrUpdatePreference(1, 2, 5, true); + } + + @Test + void saveOrUpdatePreference_shouldReturnOk_whenServiceReturnsPreference() throws Exception { + Preference prefMock = Mockito.mock(Preference.class); + when(preferenceService.saveOrUpdatePreference(7, 8, 3, false)) + .thenReturn(Optional.of(prefMock)); + + String json = "{\"personneId\":7,\"restaurantId\":8,\"note\":3,\"favori\":false}"; + + mockMvc.perform(put("/api/v1/preferences") + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isOk()); + + verify(preferenceService, times(1)).saveOrUpdatePreference(7, 8, 3, false); + } +} diff --git a/src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/models/PreferencePKTest.java b/src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/models/PreferencePKTest.java new file mode 100644 index 0000000..0c83749 --- /dev/null +++ b/src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/models/PreferencePKTest.java @@ -0,0 +1,48 @@ +package fr.vincent.ramiere.mangerautourdesimplonback.models; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +class PreferencePKTest { + + @Test + void noArgsConstructor_settersAndGetters_handleNulls() { + PreferencePK pk = new PreferencePK(); + // initially null + assertNull(pk.getPersonne()); + assertNull(pk.getRestau()); + + // set to null explicitly and verify + pk.setPersonne(null); + pk.setRestau(null); + assertNull(pk.getPersonne()); + assertNull(pk.getRestau()); + } + + @Test + void equals_and_hashCode_withBothFieldsNull_areEqual() { + PreferencePK a = new PreferencePK(); + PreferencePK b = new PreferencePK(); + + // Both have null personne and restau -> should be equal and have same hashCode + assertEquals(a, b); + assertEquals(b, a); + assertEquals(a.hashCode(), b.hashCode()); + + // reflexive + assertEquals(a, a); + } + + @Test + void equals_withSameReference_isTrue_and_hashCodeStable() { + PreferencePK original = new PreferencePK(); + PreferencePK sameRef = original; + + assertEquals(original, sameRef); + assertEquals(original.hashCode(), sameRef.hashCode()); + + int firstHash = original.hashCode(); + int secondHash = original.hashCode(); + assertEquals(firstHash, secondHash, "hashCode should be stable across invocations"); + } +} diff --git a/src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/models/PreferenceTest.java b/src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/models/PreferenceTest.java new file mode 100644 index 0000000..dc8d2e1 --- /dev/null +++ b/src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/models/PreferenceTest.java @@ -0,0 +1,65 @@ + +package fr.vincent.ramiere.mangerautourdesimplonback.models; + +import org.junit.jupiter.api.Test; +import java.io.*; +import static org.junit.jupiter.api.Assertions.*; + +class PreferenceTest { + + @Test + void defaultConstructor_and_setters_getters() { + Preference p = new Preference(); + assertNull(p.getPreferencePK()); + assertNull(p.getNote()); + assertNull(p.getFavori()); + + p.setNote(5); + p.setFavori(Boolean.TRUE); + + assertEquals(5, p.getNote()); + assertTrue(p.getFavori()); + } + + @Test + void allArgsConstructor_setsFieldsInOrder() { + // AllArgsConstructor order is (PreferencePK preferencePK, Integer note, Boolean + // favori) + Preference p = new Preference(null, 3, Boolean.FALSE); + + assertNull(p.getPreferencePK()); + assertEquals(3, p.getNote()); + assertFalse(p.getFavori()); + } + + @Test + void singleArgConstructor_setsOnlyPreferencePK() { + Preference p = new Preference((PreferencePK) null); + + assertNull(p.getPreferencePK()); + // other fields remain null when not provided + assertNull(p.getNote()); + assertNull(p.getFavori()); + } + + @Test + void implementsSerializable_and_canBeSerialized() throws Exception { + Preference original = new Preference(null, 4, Boolean.TRUE); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try (ObjectOutputStream oos = new ObjectOutputStream(bos)) { + oos.writeObject(original); + } + + byte[] bytes = bos.toByteArray(); + try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) { + Object obj = ois.readObject(); + assertTrue(obj instanceof Preference); + Preference deserialized = (Preference) obj; + + assertEquals(original.getPreferencePK(), deserialized.getPreferencePK()); + assertEquals(original.getNote(), deserialized.getNote()); + assertEquals(original.getFavori(), deserialized.getFavori()); + } + } +} diff --git a/src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/services/PreferenceServiceTest.java b/src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/services/PreferenceServiceTest.java new file mode 100644 index 0000000..dc05244 --- /dev/null +++ b/src/test/java/fr/vincent/ramiere/mangerautourdesimplonback/services/PreferenceServiceTest.java @@ -0,0 +1,116 @@ +package fr.vincent.ramiere.mangerautourdesimplonback.services; + +import fr.vincent.ramiere.mangerautourdesimplonback.models.Personne; +import fr.vincent.ramiere.mangerautourdesimplonback.models.Preference; +import fr.vincent.ramiere.mangerautourdesimplonback.models.PreferencePK; +import fr.vincent.ramiere.mangerautourdesimplonback.models.Restaurant; +import fr.vincent.ramiere.mangerautourdesimplonback.repository.PersonneRepository; +import fr.vincent.ramiere.mangerautourdesimplonback.repository.PreferenceRepository; +import fr.vincent.ramiere.mangerautourdesimplonback.repository.RestaurantRepository; +import fr.vincent.ramiere.mangerautourdesimplonback.service.PreferenceService; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import java.util.List; +import java.util.Optional; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests for PreferenceService. + */ +@ExtendWith(MockitoExtension.class) +class PreferenceServiceTest { + + @Mock + private PreferenceRepository preferenceRepository; + + @Mock + private PersonneRepository personneRepository; + + @Mock + private RestaurantRepository restaurantRepository; + + @InjectMocks + private PreferenceService preferenceService; + + @Test + void getPreferencesByPersonneId_returnsListFromRepository() { + Personne p = mock(Personne.class); + Restaurant r = mock(Restaurant.class); + Preference pref = new Preference(new PreferencePK(p, r)); + List expected = List.of(pref); + + when(preferenceRepository.findByPreferencePK_Personne_Id(1)).thenReturn(expected); + + List result = preferenceService.getPreferencesByPersonneId(1); + + assertSame(expected, result); + verify(preferenceRepository, times(1)).findByPreferencePK_Personne_Id(1); + } + + @Test + void saveOrUpdatePreference_whenPersonMissing_returnsEmpty() { + when(personneRepository.findById(1)).thenReturn(Optional.empty()); + // restaurant repo may or may not be called; service checks person first + Optional result = preferenceService.saveOrUpdatePreference(1, 2, 5, true); + + assertTrue(result.isEmpty()); + verify(personneRepository, times(1)).findById(1); + verifyNoInteractions(preferenceRepository); + } + + @Test + void saveOrUpdatePreference_createsNewPreference_andSavesIt() { + Personne personne = new Personne(); + Restaurant restaurant = new Restaurant(); + + when(personneRepository.findById(10)).thenReturn(Optional.of(personne)); + when(restaurantRepository.findById(20)).thenReturn(Optional.of(restaurant)); + when(preferenceRepository.findById(any(PreferencePK.class))).thenReturn(Optional.empty()); + when(preferenceRepository.save(any(Preference.class))).thenAnswer(invocation -> invocation.getArgument(0)); + + Optional result = preferenceService.saveOrUpdatePreference(10, 20, 4, Boolean.TRUE); + + assertTrue(result.isPresent()); + Preference saved = result.get(); + // assume typical getter names getNote/getFavori exist on Preference + assertEquals(4, saved.getNote()); + assertEquals(Boolean.TRUE, saved.getFavori()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Preference.class); + verify(preferenceRepository, times(1)).save(captor.capture()); + Preference captured = captor.getValue(); + assertEquals(4, captured.getNote()); + assertEquals(Boolean.TRUE, captured.getFavori()); + } + + @Test + void saveOrUpdatePreference_updatesExistingPreference_andSavesIt() { + Personne personne = new Personne(); + Restaurant restaurant = new Restaurant(); + PreferencePK pk = new PreferencePK(personne, restaurant); + Preference existing = new Preference(pk); + existing.setNote(1); + existing.setFavori(false); + + when(personneRepository.findById(7)).thenReturn(Optional.of(personne)); + when(restaurantRepository.findById(8)).thenReturn(Optional.of(restaurant)); + when(preferenceRepository.findById(any(PreferencePK.class))).thenReturn(Optional.of(existing)); + when(preferenceRepository.save(any(Preference.class))).thenAnswer(invocation -> invocation.getArgument(0)); + + Optional result = preferenceService.saveOrUpdatePreference(7, 8, 10, Boolean.TRUE); + + assertTrue(result.isPresent()); + Preference updated = result.get(); + assertSame(existing, updated); + assertEquals(10, updated.getNote()); + assertEquals(Boolean.TRUE, updated.getFavori()); + + verify(preferenceRepository, times(1)).save(existing); + } +}