diff --git a/srv/src/main/java/sap/capire/xtravels/handler/CreationHandler.java b/srv/src/main/java/sap/capire/xtravels/handler/CreationHandler.java index 01a50aa..646f613 100644 --- a/srv/src/main/java/sap/capire/xtravels/handler/CreationHandler.java +++ b/srv/src/main/java/sap/capire/xtravels/handler/CreationHandler.java @@ -41,9 +41,10 @@ void calculateTravelId(final Travels travel) { if (travel.getBookings() != null) { int nextPos = 1; + LocalDate now = LocalDate.now(); // $now uses timestamp unexpectedly for (Bookings booking : travel.getBookings()) { booking.setPos(nextPos++); - booking.setBookingDate(LocalDate.now()); // $now uses timestamp unexpectedly + booking.setBookingDate(now); } } } diff --git a/srv/src/test/java/sap/capire/xtravels/TestData.java b/srv/src/test/java/sap/capire/xtravels/TestData.java new file mode 100644 index 0000000..e1ff0e6 --- /dev/null +++ b/srv/src/test/java/sap/capire/xtravels/TestData.java @@ -0,0 +1,40 @@ +package sap.capire.xtravels; + +import cds.gen.travelservice.Bookings; +import cds.gen.travelservice.Travels; +import java.math.BigDecimal; +import java.time.LocalDate; + +public class TestData { + + public static Travels createTravelData() { + Travels travel = Travels.create(); + travel.setIsActiveEntity(true); + travel.setDescription("Test Travel"); + travel.setBeginDate(LocalDate.of(2024, 6, 1)); + travel.setEndDate(LocalDate.of(2024, 6, 14)); + travel.setBookingFee(BigDecimal.valueOf(100)); + travel.setCurrencyCode("EUR"); + travel.setAgencyId("070001"); + travel.setCustomerId("000001"); + + return travel; + } + + public static Bookings createBookingData() { + Bookings booking = Bookings.create(); + booking.setFlightId("GA0322"); + booking.setFlightDate(LocalDate.of(2024, 6, 2)); + booking.setFlightPrice(BigDecimal.valueOf(1103)); + booking.setCurrencyCode("EUR"); + return booking; + } + + public static Bookings.Supplements createSupplementData() { + Bookings.Supplements supplement = Bookings.Supplements.create(); + supplement.setBookedId("bv-0001"); + supplement.setPrice(new BigDecimal("2.30")); + supplement.setCurrencyCode("EUR"); + return supplement; + } +} diff --git a/srv/src/test/java/sap/capire/xtravels/handler/TravelServiceTest.java b/srv/src/test/java/sap/capire/xtravels/handler/TravelServiceTest.java new file mode 100644 index 0000000..9a0a45d --- /dev/null +++ b/srv/src/test/java/sap/capire/xtravels/handler/TravelServiceTest.java @@ -0,0 +1,170 @@ +package sap.capire.xtravels.handler; + +import static cds.gen.travelservice.TravelService_.TRAVELS; +import static java.time.temporal.ChronoUnit.DAYS; +import static org.assertj.core.api.Assertions.assertThat; +import static sap.capire.xtravels.TestData.createBookingData; +import static sap.capire.xtravels.TestData.createTravelData; +import static sap.capire.xtravels.util.ServiceExceptionAssert.assertThatServiceException; + +import cds.gen.travelservice.Bookings; +import cds.gen.travelservice.TravelService; +import cds.gen.travelservice.Travels; +import com.sap.cds.Result; +import com.sap.cds.ql.Insert; +import com.sap.cds.ql.cqn.CqnInsert; +import com.sap.cds.services.utils.CdsErrorStatuses; +import java.math.BigDecimal; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.test.context.support.WithMockUser; + +@SpringBootTest +public class TravelServiceTest { + + private Travels travel = createTravelData(); + + @Autowired private TravelService srv; + + // Travels + + @Test + @WithMockUser("admin") + public void testCreateTravel_OK() { + CqnInsert insert = Insert.into(TRAVELS).entry(travel); + + Result result = srv.run(insert); + + assertThat(result.rowCount()).isEqualTo(1l); + } + + @Test + @WithMockUser("admin") + public void testCreateTravel_DescriptionTooShort() { + travel.setDescription("12"); + CqnInsert insert = Insert.into(TRAVELS).entry(travel); + + assertThatServiceException() + .isThrownBy(() -> srv.run(insert)) + .isBadRequest() + .withMessageOrKey("Description too short") + .thatTargets("Description"); + } + + @Test + @WithMockUser("admin") + public void testCreateTravel_CustomerIsNull() { + travel.setCustomerId(null); + CqnInsert insert = Insert.into(TRAVELS).entry(travel); + + assertThatServiceException() + .isThrownBy(() -> srv.run(insert)) + .isBadRequest() + .withMessageOrKey(CdsErrorStatuses.VALUE_REQUIRED.getCodeString()) + .thatTargets("Customer"); + } + + @Test + @WithMockUser("admin") + public void testCreateTravel_CustomerDoesNotExist() { + travel.setCustomerId("bad-ID"); + CqnInsert insert = Insert.into(TRAVELS).entry(travel); + + assertThatServiceException() + .isThrownBy(() -> srv.run(insert)) + .isBadRequest() + .withMessageOrKey("Customer does not exist") + .thatTargets("Customer_ID"); + } + + @Test + @WithMockUser("admin") + public void testCreateTravel_AgencyDoesNotExist() { + travel.setAgencyId("bad-ID"); + CqnInsert insert = Insert.into(TRAVELS).entry(travel); + + assertThatServiceException() + .isThrownBy(() -> srv.run(insert)) + .isBadRequest() + .withMessageOrKey("Agency does not exist") + .thatTargets("Agency"); + } + + @Test + @WithMockUser("admin") + public void testCreateTravel_EndDateBeforeBeginDate() { + travel.setEndDate(travel.getBeginDate().minus(1, DAYS)); + CqnInsert insert = Insert.into(TRAVELS).entry(travel); + + assertThatServiceException() + .isThrownBy(() -> srv.run(insert)) + .isBadRequest() + .withMessageOrKey("ASSERT_ENDDATE_AFTER_BEGINDATE") + .thatTargets("BeginDate", "EndDate"); + } + + @Test + @WithMockUser("admin") + public void testCreateTravel_NegativeBookingFee() { + travel.setBookingFee(BigDecimal.valueOf(-1.0)); + CqnInsert insert = Insert.into(TRAVELS).entry(travel); + + assertThatServiceException() + .isThrownBy(() -> srv.run(insert)) + .isBadRequest() + .withMessageOrKey("ASSERT_BOOKING_FEE_NON_NEGATIVE") + .thatTargets("BookingFee"); + } + + // Bookings + + @Test + @WithMockUser("admin") + public void testCreateTravel_withBooking_BookingDateNotWithinTravelDate() { + Bookings booking = createBookingData(); + booking.setFlightDate(travel.getEndDate().plus(2, DAYS)); + travel.setBookings(List.of(booking)); + + CqnInsert insert = Insert.into(TRAVELS).entry(travel); + + assertThatServiceException() + .isThrownBy(() -> srv.run(insert)) + .isBadRequest() + .withMessageOrKey("ASSERT_BOOKINGS_IN_TRAVEL_PERIOD") + .thatTargets("Bookings.Flight_date"); + } + + @Test + @WithMockUser("admin") + public void testCreateTravel_withBooking_NegativeFlightPrice() { + Bookings booking = createBookingData(); + booking.setFlightPrice(BigDecimal.valueOf(-1.0)); + travel.setBookings(List.of(booking)); + + CqnInsert insert = Insert.into(TRAVELS).entry(travel); + + assertThatServiceException() + .isThrownBy(() -> srv.run(insert)) + .isBadRequest() + .withMessageOrKey("ASSERT_FLIGHT_PRICE_POSITIVE") + .thatTargets("Bookings.FlightPrice"); + } + + @Test + @WithMockUser("admin") + public void testCreateTravel_withBooking_CurrencyMismatch() { + Bookings booking = createBookingData(); + booking.setCurrencyCode("DKK"); + travel.setBookings(List.of(booking)); + + CqnInsert insert = Insert.into(TRAVELS).entry(travel); + + assertThatServiceException() + .isThrownBy(() -> srv.run(insert)) + .isBadRequest() + .withMessageOrKey("ASSERT_BOOKING_CURRENCY_MATCHES_TRAVEL") + .thatTargets("Bookings.Currency_code"); + } +} diff --git a/srv/src/test/java/sap/capire/xtravels/TravelServiceIntegrationTest.java b/srv/src/test/java/sap/capire/xtravels/it/TravelServiceIntegrationTest.java similarity index 88% rename from srv/src/test/java/sap/capire/xtravels/TravelServiceIntegrationTest.java rename to srv/src/test/java/sap/capire/xtravels/it/TravelServiceIntegrationTest.java index 6d9181f..853cc77 100644 --- a/srv/src/test/java/sap/capire/xtravels/TravelServiceIntegrationTest.java +++ b/srv/src/test/java/sap/capire/xtravels/it/TravelServiceIntegrationTest.java @@ -1,4 +1,4 @@ -package sap.capire.xtravels; +package sap.capire.xtravels.it; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.notNullValue; @@ -8,6 +8,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static sap.capire.xtravels.TestData.createBookingData; +import static sap.capire.xtravels.TestData.createSupplementData; +import static sap.capire.xtravels.TestData.createTravelData; import cds.gen.travelservice.Bookings; import cds.gen.travelservice.Travels; @@ -16,7 +19,6 @@ import com.sap.cds.CdsJsonConverter.UnknownPropertyHandling; import com.sap.cds.reflect.CdsModel; import java.math.BigDecimal; -import java.time.LocalDate; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -69,7 +71,7 @@ void shouldGetAllTravels() throws Exception { @Test @WithMockUser("admin") void shouldCreateTravel() throws Exception { - Travels travel = createTravelData("shouldCreateTravel"); + Travels travel = createTravelData(); mockMvc .perform(post(TRAVELS_ENDPOINT).contentType("application/json").content(travel.toJson())) @@ -86,7 +88,7 @@ void shouldCreateTravel() throws Exception { @Test @WithMockUser("admin") void shouldCreateAndRetrieveTravelSuccessfully() throws Exception { - Travels travel = createTravelData("shouldCreateAndRetrieveTravelSuccessfully"); + Travels travel = createTravelData(); travel.setBookingFee(BigDecimal.valueOf(200.0)); travel.setCurrencyCode("USD"); @@ -115,16 +117,9 @@ void shouldCreateAndRetrieveTravelSuccessfully() throws Exception { @Test @WithMockUser("admin") void shouldCreateAndRetrieveTravelWithBookingsSuccessfully() throws Exception { - Travels travel = createTravelData("shouldCreateAndRetrieveTravelWithBookingsSuccessfully"); - Bookings booking = Bookings.create(); - booking.setFlightId("GA0322"); - booking.setFlightDate(LocalDate.of(2024, 6, 2)); - booking.setFlightPrice(BigDecimal.valueOf(1103)); - booking.setCurrencyCode("EUR"); - Bookings.Supplements supplement = Bookings.Supplements.create(); - supplement.setBookedId("bv-0001"); - supplement.setPrice(new BigDecimal("2.30")); - supplement.setCurrencyCode("EUR"); + Travels travel = createTravelData(); + Bookings booking = createBookingData(); + Bookings.Supplements supplement = createSupplementData(); booking.setSupplements(List.of(supplement)); travel.setBookings(List.of(booking)); @@ -182,7 +177,7 @@ void shouldGetReadOnlyEntitiesSuccessfully() throws Exception { @WithMockUser("admin") void shouldReturn400ForInvalidDiscountPercentage() throws Exception { // First create a travel - Travels travelData = createTravelData("shouldReturn400ForInvalidDiscountPercentage"); + Travels travelData = createTravelData(); String response = mockMvc .perform( @@ -213,7 +208,7 @@ void shouldReturn400ForInvalidDiscountPercentage() throws Exception { @WithMockUser("admin") void shouldExecuteAcceptTravelAction() throws Exception { // First create a travel - Travels travelData = createTravelData("shouldExecuteAcceptTravelAction"); + Travels travelData = createTravelData(); String response = mockMvc .perform( @@ -248,7 +243,7 @@ void shouldExecuteAcceptTravelAction() throws Exception { @WithMockUser("admin") void shouldExecuteRejectTravelAction() throws Exception { // First create a travel - Travels travelData = createTravelData("shouldExecuteRejectTravelAction"); + Travels travelData = createTravelData(); String response = mockMvc .perform( @@ -283,7 +278,7 @@ void shouldExecuteRejectTravelAction() throws Exception { @WithMockUser("admin") void shouldExecuteDeductDiscountAction() throws Exception { // First create a travel - Travels travelData = createTravelData("shouldExecuteDeductDiscountAction"); + Travels travelData = createTravelData(); String response = mockMvc @@ -316,17 +311,4 @@ void shouldExecuteDeductDiscountAction() throws Exception { .andExpect(content().contentTypeCompatibleWith("application/json")) .andExpect(jsonPath("$.BookingFee").value(90)); } - - private Travels createTravelData(String testName) { - Travels travel = Travels.create(); - travel.setIsActiveEntity(true); - travel.setDescription(testName + " - Test Travel"); - travel.setBeginDate(LocalDate.of(2024, 6, 1)); - travel.setEndDate(LocalDate.of(2024, 6, 14)); - travel.setBookingFee(BigDecimal.valueOf(100)); - travel.setCurrencyCode("EUR"); - travel.setAgencyId("070001"); - travel.setCustomerId("000001"); - return travel; - } } diff --git a/srv/src/test/java/sap/capire/xtravels/util/ServiceExceptionAssert.java b/srv/src/test/java/sap/capire/xtravels/util/ServiceExceptionAssert.java new file mode 100644 index 0000000..2c3f577 --- /dev/null +++ b/srv/src/test/java/sap/capire/xtravels/util/ServiceExceptionAssert.java @@ -0,0 +1,62 @@ +package sap.capire.xtravels.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.sap.cds.services.ErrorStatuses; +import com.sap.cds.services.ServiceException; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.assertj.core.api.ThrowableAssertAlternative; +import org.assertj.core.api.ThrowableTypeAssert; + +public class ServiceExceptionAssert extends ThrowableTypeAssert { + + private ServiceExceptionAssert() { + super(ServiceException.class); + } + + public static ServiceExceptionAssert assertThatServiceException() { + return new ServiceExceptionAssert(); + } + + @Override + public ServiceExceptionAssertAlternative isThrownBy(ThrowingCallable throwingCallable) { + return (ServiceExceptionAssertAlternative) super.isThrownBy(throwingCallable); + } + + @Override + protected ThrowableAssertAlternative buildThrowableTypeAssert( + ServiceException throwable) { + return new ServiceExceptionAssertAlternative(throwable); + } + + public static class ServiceExceptionAssertAlternative + extends ThrowableAssertAlternative { + + public ServiceExceptionAssertAlternative(ServiceException actual) { + super(actual); + } + + public ServiceExceptionAssertAlternative isBadRequest() { + assertThat(actual.getErrorStatus().getHttpStatus()) + .isEqualTo(ErrorStatuses.BAD_REQUEST.getHttpStatus()); + + return this; + } + + public ServiceExceptionAssertAlternative withMessageOrKey(String messageOrKey) { + assertThat(actual.getMessageLookup().getMessageOrKey()).isEqualTo(messageOrKey); + + return this; + } + + public ServiceExceptionAssertAlternative thatTargets(String target, String... additional) { + assertThat(actual.getMessageTarget().getRef().path()).isEqualTo(target); + assertThat(actual.getAdditionalTargets().size()).isEqualTo(additional.length); + for (int i = 0; i < additional.length; i++) { + assertThat(actual.getAdditionalTargets().get(i).getRef().path()).isEqualTo(additional[i]); + } + + return this; + } + } +}