package eu.ztsh.wymiana.service; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import eu.ztsh.wymiana.EntityCreator; import eu.ztsh.wymiana.WireMockExtension; import eu.ztsh.wymiana.exception.NoDataException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.mockito.Mockito; import org.springframework.web.client.RestClient; import java.time.Clock; import java.time.DayOfWeek; import java.time.LocalDate; import java.time.Month; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAdjusters; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @ExtendWith(WireMockExtension.class) class NbpServiceTest { private static final ZoneId zone = ZoneId.of("Europe/Warsaw"); private static final LocalDate today = LocalDate.of(2024, Month.MAY, 12); // Sunday private static Clock clock; private final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd"); private final RestClient restClient = RestClient.builder().baseUrl(WireMockExtension.baseUrl).build(); private NbpService nbpService; @BeforeAll static void prepare() { clock = Mockito.mock(Clock.class); Mockito.when(clock.getZone()).thenReturn(zone); } @BeforeEach void prepareTest() { nbpService = new NbpService(clock, restClient); } @DisplayName("Check if fetch date is calculated properly: weekdays") @ParameterizedTest @EnumSource(value = DayOfWeek.class, names = {"SATURDAY", "SUNDAY"}, mode = EnumSource.Mode.EXCLUDE) void getFetchDateOnWorkingDayTest(DayOfWeek dayOfWeek) { updateClock(dayOfWeek); assertThat(nbpService.getFetchDate()).isEqualTo( switch (dayOfWeek) { case MONDAY -> LocalDate.of(2024, Month.MAY, 6); case TUESDAY -> LocalDate.of(2024, Month.MAY, 7); case WEDNESDAY -> LocalDate.of(2024, Month.MAY, 8); case THURSDAY -> LocalDate.of(2024, Month.MAY, 9); case FRIDAY -> LocalDate.of(2024, Month.MAY, 10); default -> null; } ); } @DisplayName("Check if fetch date is calculated properly: weekends") @ParameterizedTest @EnumSource(value = DayOfWeek.class, names = {"SATURDAY", "SUNDAY"}) void getFetchDateOnWeekendTest(DayOfWeek dayOfWeek) { updateClock(dayOfWeek); assertThat(nbpService.getFetchDate()).isEqualTo(LocalDate.of(2024, Month.MAY, 10)); } @DisplayName("Fetch rates straight from server") @Test void getWithoutCacheTest() throws JsonProcessingException { var date = dtf.format(updateClock(DayOfWeek.FRIDAY)); var url = "/api/exchangerates/rates/c/usd/%s/".formatted(date); WireMockExtension.response(url, 200, new ObjectMapper().writeValueAsString(EntityCreator.rates(date))); assertThat(nbpService.getSellRate(EntityCreator.Constants.USD_SYMBOL)).isEqualTo(EntityCreator.Constants.SELL_RATE); WireMockExtension.verifyGet(1, url); } @DisplayName("Fetch rates from cache") @Test void getWithCacheTest() { var date = dtf.format(updateClock(DayOfWeek.FRIDAY)); var url = "/api/exchangerates/rates/c/usd/%s/".formatted(date); assertThat(nbpService.getSellRate(EntityCreator.Constants.USD_SYMBOL)).isEqualTo(EntityCreator.Constants.SELL_RATE); WireMockExtension.verifyGet(0, url); } @DisplayName("Support 404: invalid currency or no data") @Test void getInvalidCurrencyTest() { var date = dtf.format(updateClock(DayOfWeek.FRIDAY)); var url = "/api/exchangerates/rates/c/usb/%s/".formatted(date); WireMockExtension.response(url, 404, "404 NotFound - Not Found - Brak danych"); assertThatThrownBy(() -> nbpService.getSellRate("usb")).isInstanceOf(NoDataException.class); WireMockExtension.verifyGet(1, url); } private LocalDate updateClock(DayOfWeek dayOfWeek) { var date = today.with(TemporalAdjusters.previousOrSame(dayOfWeek)); Mockito.when(clock.instant()).thenReturn(date.atStartOfDay(zone).toInstant()); return LocalDate.from(date); } }