diff --git a/src/test/java/com/capgemini/training/appointmentbooking/service/impl/AppointmentsApiControllerTest.java b/src/test/java/com/capgemini/training/appointmentbooking/service/impl/AppointmentsApiControllerTest.java new file mode 100644 index 0000000..3716eb7 --- /dev/null +++ b/src/test/java/com/capgemini/training/appointmentbooking/service/impl/AppointmentsApiControllerTest.java @@ -0,0 +1,207 @@ +package com.capgemini.training.appointmentbooking.service.impl; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.ArgumentMatchers.any; +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.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.capgemini.training.appointmentbooking.common.BaseTest; +import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus; +import com.capgemini.training.appointmentbooking.common.to.AppointmentCto; +import com.capgemini.training.appointmentbooking.common.to.AppointmentEto; +import com.capgemini.training.appointmentbooking.common.to.ClientEto; +import com.capgemini.training.appointmentbooking.common.to.TreatmentCto; +import com.capgemini.training.appointmentbooking.common.to.TreatmentEto; +import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria; +import com.capgemini.training.appointmentbooking.logic.FindAppointmentUc; +import com.capgemini.training.appointmentbooking.logic.ManageAppointmentUc; +import com.capgemini.training.appointmentbooking.service.config.ServiceMappingConfiguration; +import com.capgemini.training.appointmentbooking.service.model.AppointmentRequest; +import com.capgemini.training.appointmentbooking.service.model.AppointmentStatusUpdate; +import com.capgemini.training.appointmentbooking.service.model.AppointmentStatusUpdate.StatusEnum; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.Instant; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(controllers = AppointmentsApiController.class) +@Import(ServiceMappingConfiguration.class) +public class AppointmentsApiControllerTest extends BaseTest { + + @Autowired private MockMvc mockMvc; + + @MockitoBean private FindAppointmentUc findAppointmentUc; + + @MockitoBean private ManageAppointmentUc manageAppointmentUc; + + @Autowired private ObjectMapper objectMapper; + + @Test + void shouldCreateAppointmentAndReturn201() throws Exception { + Instant now = Instant.now(); + AppointmentRequest request = createAppointmentRequest(1L, 2L, now); + AppointmentCto appointmentCto = + createAppointmentCto(1L, now, AppointmentStatus.SCHEDULED, 1L, 2L, "Cleaning"); + + when(manageAppointmentUc.bookAppointment(any())).thenReturn(appointmentCto); + + mockMvc + .perform( + post("/api/v1/appointments") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.status").value(AppointmentStatus.SCHEDULED.name())) + .andExpect(jsonPath("$.dateTime", notNullValue())) + .andExpect(jsonPath("$.clientId").value(1)) + .andExpect(jsonPath("$.treatmentId").value(2)); + } + + @Test + void shouldReturn400WhenCreatingAppointmentWithInvalidPayload() throws Exception { + mockMvc + .perform( + post("/api/v1/appointments") + .contentType(MediaType.APPLICATION_JSON) + .content("{ \"specialistId\" : \"abc\" }")) + .andExpect(status().isBadRequest()); + } + + @Test + void shouldReturnAppointmentsListWith200() throws Exception { + Instant now = Instant.now(); + AppointmentCto appointmentCto = + createAppointmentCto(1L, now, AppointmentStatus.SCHEDULED, 1L, 2L, "Filling"); + + when(findAppointmentUc.findByCriteria(any())).thenReturn(List.of(appointmentCto)); + + mockMvc + .perform(get("/api/v1/appointments")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].status").value(AppointmentStatus.SCHEDULED.name())) + .andExpect(jsonPath("$[0].clientId").value(1)) + .andExpect(jsonPath("$[0].treatmentId").value(2)); + } + + @Test + void shouldReturnAppointmentsListWith200Filtered() throws Exception { + Instant now = Instant.now(); + AppointmentCto appointmentCto = + createAppointmentCto(1L, now, AppointmentStatus.SCHEDULED, 1L, 2L, "Filling"); + + when(findAppointmentUc.findByCriteria( + AppointmentCriteria.builder().status(AppointmentStatus.SCHEDULED).build())) + .thenReturn(List.of(appointmentCto)); + when(findAppointmentUc.findByCriteria( + AppointmentCriteria.builder().status(AppointmentStatus.CANCELLED).build())) + .thenReturn(List.of()); + + mockMvc + .perform(get("/api/v1/appointments").param("status", AppointmentStatus.SCHEDULED.name())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].status").value(AppointmentStatus.SCHEDULED.name())) + .andExpect(jsonPath("$[0].clientId").value(1)) + .andExpect(jsonPath("$[0].treatmentId").value(2)); + + mockMvc + .perform(get("/api/v1/appointments").param("status", AppointmentStatus.CANCELLED.name())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()", is(0))); + } + + @Test + void shouldReturnEmptyListWhenNoAppointmentsExist() throws Exception { + when(findAppointmentUc.findAll()).thenReturn(Collections.emptyList()); + + mockMvc + .perform(get("/api/v1/appointments")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()", is(0))); + } + + @Test + void shouldUpdateAppointmentStatusAndReturn200() throws Exception { + AppointmentStatusUpdate update = new AppointmentStatusUpdate(); + update.setStatus(StatusEnum.CANCELLED); + + mockMvc + .perform( + patch("/api/v1/appointments/1") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(update))) + .andExpect(status().isOk()); + } + + @Test + void shouldReturn400WhenUpdatingAppointmentWithInvalidStatus() throws Exception { + String invalidJson = "{ \"status\": \"INVALID\" }"; + + mockMvc + .perform( + patch("/api/v1/appointments/1") + .contentType(MediaType.APPLICATION_JSON) + .content(invalidJson)) + .andExpect(status().isBadRequest()); + } + + @Test + void shouldReturnAvailabilityStatusWith200() throws Exception { + String date = "2024-12-12T10:15:30Z"; + when(findAppointmentUc.hasConflictingAppointment(1L, Instant.parse(date))).thenReturn(false); + + mockMvc + .perform(get("/api/v1/availability").param("specialistId", "1").param("dateTime", date)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.available", is(true))); + } + + @Test + void shouldReturn400WhenAvailabilityQueryIsMissingParams() throws Exception { + mockMvc.perform(get("/api/v1/availability")).andExpect(status().isBadRequest()); + } + + // ======= HELPER METHODS ======= + + private AppointmentRequest createAppointmentRequest( + Long clientId, Long treatmentId, Instant dateTime) { + AppointmentRequest request = new AppointmentRequest(); + request.setClientId(clientId); + request.setTreatmentId(treatmentId); + request.setDateTime(Date.from(dateTime)); + return request; + } + + private AppointmentCto createAppointmentCto( + Long appointmentId, + Instant dateTime, + AppointmentStatus status, + Long clientId, + Long treatmentId, + String treatmentName) { + return AppointmentCto.builder() + .appointmentEto( + AppointmentEto.builder().id(appointmentId).dateTime(dateTime).status(status).build()) + .clientEto(ClientEto.builder().id(clientId).build()) + .treatmentCto( + TreatmentCto.builder() + .treatmentEto(TreatmentEto.builder().id(treatmentId).name(treatmentName).build()) + .build()) + .build(); + } +} diff --git a/src/test/java/com/capgemini/training/appointmentbooking/service/impl/TreatmentsApiControllerTest.java b/src/test/java/com/capgemini/training/appointmentbooking/service/impl/TreatmentsApiControllerTest.java new file mode 100644 index 0000000..f1aa0ed --- /dev/null +++ b/src/test/java/com/capgemini/training/appointmentbooking/service/impl/TreatmentsApiControllerTest.java @@ -0,0 +1,148 @@ +package com.capgemini.training.appointmentbooking.service.impl; + +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +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.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.capgemini.training.appointmentbooking.common.BaseTest; +import com.capgemini.training.appointmentbooking.common.datatype.Specialization; +import com.capgemini.training.appointmentbooking.common.to.SpecialistEto; +import com.capgemini.training.appointmentbooking.common.to.TreatmentCto; +import com.capgemini.training.appointmentbooking.common.to.TreatmentEto; +import com.capgemini.training.appointmentbooking.logic.FindTreatmentUc; +import com.capgemini.training.appointmentbooking.logic.ManageTreatmentUc; +import com.capgemini.training.appointmentbooking.service.config.ServiceMappingConfiguration; +import com.capgemini.training.appointmentbooking.service.model.TreatmentRequest; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(controllers = TreatmentsApiController.class) +@Import(ServiceMappingConfiguration.class) +public class TreatmentsApiControllerTest extends BaseTest { + @Autowired private MockMvc mockMvc; + + @MockitoBean private FindTreatmentUc findTreatmentUc; + + @MockitoBean private ManageTreatmentUc manageTreatmentUc; + + @Autowired private ObjectMapper objectMapper; + + @Test + void shouldCreateTreatmentAndReturn201() throws Exception { + // given + String name = "Test name"; + int duration = 30; + Long specialistId = 101L; + + TreatmentRequest request = createTreatmentRequest(name, duration, specialistId); + TreatmentCto treatmentCto = + createTreatmentCto(1L, name, duration, specialistId, Specialization.DENTIST); + + when(manageTreatmentUc.createTreatment(any())).thenReturn(treatmentCto); + + // when / then + mockMvc + .perform( + post("/api/v1/treatments") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.name", is(name))) + .andExpect(jsonPath("$.duration", is(duration))) + .andExpect(jsonPath("$.specialistId").value(specialistId)); + } + + @Test + void shouldReturn400OnInvalidTreatmentRequest() throws Exception { + // given – invalid JSON (wrong type) + String invalidJson = "{ \"specialistId\" : \"abc\" }"; + + // when / then + mockMvc + .perform( + post("/api/v1/treatments").contentType(MediaType.APPLICATION_JSON).content(invalidJson)) + .andExpect(status().isBadRequest()); + } + + @Test + void shouldReturnTreatmentsListWith200() throws Exception { + // given + String name = "Treatment A"; + int duration = 45; + Long specialistId = 123L; + + TreatmentCto treatmentCto = + createTreatmentCto(1L, name, duration, specialistId, Specialization.DENTIST); + when(findTreatmentUc.findAll()).thenReturn(List.of(treatmentCto)); + + // when / then + mockMvc + .perform(get("/api/v1/treatments")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].name", is(name))) + .andExpect(jsonPath("$[0].duration", is(duration))) + .andExpect(jsonPath("$[0].specialistId").value(specialistId)); + } + + @Test + void shouldReturnTreatmentDetailsWith200WhenFound() throws Exception { + // given + Long treatmentId = 101L; + String name = "Test treatment"; + int duration = 30; + Long specialistId = 101L; + + TreatmentCto treatmentCto = + createTreatmentCto(treatmentId, name, duration, specialistId, Specialization.DENTIST); + when(findTreatmentUc.findById(treatmentId)).thenReturn(Optional.of(treatmentCto)); + + // when / then + mockMvc + .perform(get("/api/v1/treatments/" + treatmentId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name", is(name))) + .andExpect(jsonPath("$.duration", is(duration))) + .andExpect(jsonPath("$.specialistId").value(specialistId)) + .andExpect(jsonPath("$.specialist.name", is(Specialization.DENTIST.name()))); + } + + @Test + void shouldReturn404WhenTreatmentNotFound() throws Exception { + // given + Long treatmentId = 101L; + + when(findTreatmentUc.findById(treatmentId)).thenReturn(Optional.empty()); + + // when / then + mockMvc.perform(get("/api/v1/treatments/" + treatmentId)).andExpect(status().isNotFound()); + } + + private TreatmentRequest createTreatmentRequest(String name, int duration, Long specialistId) { + TreatmentRequest request = new TreatmentRequest(); + request.setName(Optional.of(name)); + request.setDuration(Optional.of(duration)); + request.setSpecialistId(Optional.of(specialistId)); + return request; + } + + private TreatmentCto createTreatmentCto( + Long id, String name, int duration, Long specialistId, Specialization specialization) { + return TreatmentCto.builder() + .specialistEto( + SpecialistEto.builder().id(specialistId).specialization(specialization).build()) + .treatmentEto(TreatmentEto.builder().id(id).name(name).durationMinutes(duration).build()) + .build(); + } +}