diff --git a/src/main/java/eu/m724/wtapi/provider/weather/WeatherProvider.java b/src/main/java/eu/m724/wtapi/provider/weather/WeatherProvider.java index 4b2e56b..a7c3785 100644 --- a/src/main/java/eu/m724/wtapi/provider/weather/WeatherProvider.java +++ b/src/main/java/eu/m724/wtapi/provider/weather/WeatherProvider.java @@ -1,11 +1,9 @@ package eu.m724.wtapi.provider.weather; -import java.time.Duration; import java.util.concurrent.CompletableFuture; import eu.m724.wtapi.object.Coordinates; import eu.m724.wtapi.object.Quota; -import eu.m724.wtapi.object.Weather; import eu.m724.wtapi.provider.exception.ProviderException; public abstract class WeatherProvider { @@ -20,9 +18,9 @@ public abstract class WeatherProvider { * Get the current weather of a single or multiple points * * @param coordinates The coordinates - * @return A future that CAN throw {@link ProviderException} + * @return A {@link WeatherQueryResult} */ - public abstract CompletableFuture getWeather(Coordinates... coordinates); + public abstract CompletableFuture getWeather(Coordinates... coordinates); public abstract Quota getQuota(); } diff --git a/src/main/java/eu/m724/wtapi/provider/weather/WeatherQueryResult.java b/src/main/java/eu/m724/wtapi/provider/weather/WeatherQueryResult.java new file mode 100644 index 0000000..f9a3b83 --- /dev/null +++ b/src/main/java/eu/m724/wtapi/provider/weather/WeatherQueryResult.java @@ -0,0 +1,15 @@ +package eu.m724.wtapi.provider.weather; + +import eu.m724.wtapi.object.Weather; + +/** + * A result from a weather query. + * + * @param weathers The weathers, null if exception + * @param exception The exception, null if no exception + */ +public record WeatherQueryResult( + Weather[] weathers, + Throwable exception +) { +} diff --git a/src/main/java/eu/m724/wtapi/provider/weather/impl/openmeteo/OpenMeteoProvider.java b/src/main/java/eu/m724/wtapi/provider/weather/impl/openmeteo/OpenMeteoProvider.java index b27d667..71174a4 100644 --- a/src/main/java/eu/m724/wtapi/provider/weather/impl/openmeteo/OpenMeteoProvider.java +++ b/src/main/java/eu/m724/wtapi/provider/weather/impl/openmeteo/OpenMeteoProvider.java @@ -1,9 +1,6 @@ package eu.m724.wtapi.provider.weather.impl.openmeteo; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; +import com.google.gson.*; import eu.m724.wtapi.object.Coordinates; import eu.m724.wtapi.object.Quota; import eu.m724.wtapi.object.Weather; @@ -11,24 +8,39 @@ import eu.m724.wtapi.provider.exception.ProviderException; import eu.m724.wtapi.provider.exception.QuotaExceededException; import eu.m724.wtapi.provider.exception.ProviderRequestException; import eu.m724.wtapi.provider.weather.WeatherProvider; +import eu.m724.wtapi.provider.weather.WeatherQueryResult; -import java.net.ProxySelector; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.Arrays; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; public class OpenMeteoProvider extends WeatherProvider { + private final HttpClient httpClient = HttpClient.newBuilder().build(); + @Override public void init() throws ProviderException { - + // TODO connectivity check } @Override - public CompletableFuture getWeather(Coordinates... coordinates) { + public CompletableFuture getWeather(Coordinates... coordinates) { + if (coordinates == null) { + throw new NullPointerException("Coordinates are null"); + } + + if (coordinates.length == 0) { + throw new IllegalArgumentException("coordinates is empty"); + } + + if (Arrays.stream(coordinates).anyMatch(Objects::isNull)) { + throw new IllegalArgumentException("One or more coordinates are null"); + } + String latitudes = Arrays.stream(coordinates) .map(Coordinates::latitude) .map(Number::toString) @@ -42,48 +54,59 @@ public class OpenMeteoProvider extends WeatherProvider { String url = "https://api.open-meteo.com/v1/forecast?latitude=%s&longitude=%s¤t=temperature_2m,relative_humidity_2m,rain,snowfall,apparent_temperature,cloud_cover,weather_code&timeformat=unixtime&wind_speed_unit=ms" .formatted(latitudes, longitudes); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(url)) - .build(); + HttpRequest request = + HttpRequest.newBuilder(URI.create(url)).build(); CompletableFuture> responseFuture = - HttpClient.newBuilder().build() - .sendAsync(request, HttpResponse.BodyHandlers.ofString()); + httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()); - return responseFuture.thenApply((response) -> { - int code = response.statusCode(); - - JsonObject jsonResponse; - try { - jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); - } catch (JsonParseException e) { - throw new ProviderRequestException(e); - } - - if (code == 429) { - long now = System.currentTimeMillis(); - int retryIn = -1; - - String reason = jsonResponse.get("reason").getAsString(); - - if (reason.contains("Minutely")) { - retryIn = (int) (60 - (now % 60)); - } else if (reason.contains("Daily")) { - retryIn = (int) (3600 - (now % 3600)); - } - - throw new QuotaExceededException(1000); - } - - if (code != 200) - throw new ProviderRequestException("Status code: " + code); - - return OpenMeteoResponseConverter.convert(jsonResponse); - }); + return responseFuture + .thenApply(this::processResponse) + .handle(WeatherQueryResult::new); } @Override public Quota getQuota() { return Quota.daily(10000); } + + private Weather[] processResponse(HttpResponse response) throws ProviderException { + int code = response.statusCode(); + + JsonElement jsonResponse; + try { + jsonResponse = JsonParser.parseString(response.body()); + } catch (JsonParseException e) { + throw new ProviderRequestException(e); + } + + if (code == 429) { + long now = System.currentTimeMillis(); + int retryIn = -1; + + if (jsonResponse.isJsonObject() && jsonResponse.getAsJsonObject().has("reason")) { + String reason = jsonResponse.getAsJsonObject().get("reason").getAsString(); + + if (reason.contains("Minutely")) { + retryIn = (int) (60 - (now % 60)); + } else if (reason.contains("Daily")) { + retryIn = (int) (3600 - (now % 3600)); + } + } + + throw new QuotaExceededException(retryIn); + } + + if (code != 200) { + if (jsonResponse.isJsonObject() && jsonResponse.getAsJsonObject().has("reason")) { + String reason = jsonResponse.getAsJsonObject().get("reason").getAsString(); + + throw new ProviderRequestException("Error reason: " + reason); + } + + throw new ProviderRequestException("Status code: " + code); + } + + return OpenMeteoResponseConverter.convert(jsonResponse); + } } diff --git a/src/main/java/eu/m724/wtapi/provider/weather/impl/openmeteo/OpenMeteoResponseConverter.java b/src/main/java/eu/m724/wtapi/provider/weather/impl/openmeteo/OpenMeteoResponseConverter.java index d32f7a7..c1dbdf8 100644 --- a/src/main/java/eu/m724/wtapi/provider/weather/impl/openmeteo/OpenMeteoResponseConverter.java +++ b/src/main/java/eu/m724/wtapi/provider/weather/impl/openmeteo/OpenMeteoResponseConverter.java @@ -42,7 +42,7 @@ public class OpenMeteoResponseConverter { boolean thundering = weatherCode >= 90 && weatherCode < 100; boolean snowing = current.get("snowfall").getAsDouble() > 0; - float temperatureCelsius = current.get("temperature").getAsFloat(); + float temperatureCelsius = current.get("temperature_2m").getAsFloat(); float temperatureApparentCelsius = current.get("apparent_temperature").getAsFloat(); float relativeHumidityPercentage = current.get("relative_humidity_2m").getAsInt() / 100f; diff --git a/src/main/java/eu/m724/wtapi/provider/weather/impl/openweathermap/OWMResponseConverter.java b/src/main/java/eu/m724/wtapi/provider/weather/impl/openweathermap/OWMResponseConverter.java deleted file mode 100644 index c464e62..0000000 --- a/src/main/java/eu/m724/wtapi/provider/weather/impl/openweathermap/OWMResponseConverter.java +++ /dev/null @@ -1,235 +0,0 @@ -package eu.m724.wtapi.provider.weather.impl.openweathermap; - -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; - -import eu.m724.wtapi.object.Coordinates; -import eu.m724.wtapi.object.Severity; -import eu.m724.wtapi.object.Weather; - -public class OWMResponseConverter { - static Weather convert(JsonObject json) { - Coordinates coordinates = new Coordinates( - json.getAsJsonObject("coord").getAsJsonPrimitive("lon").getAsDouble(), - json.getAsJsonObject("coord").getAsJsonPrimitive("lat").getAsDouble() - ); - Weather weather = new Weather(); - - int conditionCode = json.getAsJsonArray("weather") - .get(0).getAsJsonObject() - .getAsJsonPrimitive("id").getAsInt(); - - switch (conditionCode) { - // Group 2xx: Thunderstorm - case 200: // thunderstorm with light rain - weather.thunderstormSeverity = Severity.LIGHT; - weather.rainSeverity = Severity.LIGHT; - break; - case 201: // thunderstorm with rain - weather.thunderstormSeverity = Severity.MODERATE; - weather.rainSeverity = Severity.MODERATE; - break; - case 202: // thunderstorm with heavy rain - weather.thunderstormSeverity = Severity.HEAVY; - weather.rainSeverity = Severity.HEAVY; - break; - case 210: // light thunderstorm - weather.thunderstormSeverity = Severity.LIGHT; - break; - case 211: // thunderstorm - weather.thunderstormSeverity = Severity.MODERATE; - break; - case 212: // heavy thunderstorm - weather.thunderstormSeverity = Severity.HEAVY; - break; - case 221: // ragged thunderstorm - weather.thunderstormSeverity = Severity.MODERATE; - break; - case 230: // thunderstorm with light drizzle - weather.thunderstormSeverity = Severity.LIGHT; - weather.drizzleSeverity = Severity.LIGHT; - break; - case 231: // thunderstorm with drizzle - weather.thunderstormSeverity = Severity.MODERATE; - weather.drizzleSeverity = Severity.MODERATE; - break; - case 232: // thunderstorm with heavy drizzle - weather.thunderstormSeverity = Severity.HEAVY; - weather.drizzleSeverity = Severity.HEAVY; - break; - - // Group 3xx: Drizzle - case 300: // light intensity drizzle - weather.drizzleSeverity = Severity.LIGHT; - break; - case 301: // drizzle - weather.drizzleSeverity = Severity.MODERATE; - break; - case 302: // heavy intensity drizzle - weather.drizzleSeverity = Severity.HEAVY; - break; - case 310: // light intensity drizzle rain - weather.drizzleSeverity = Severity.LIGHT; - break; - case 311: // drizzle rain - weather.drizzleSeverity = Severity.MODERATE; - break; - case 312: // heavy intensity drizzle rain - weather.drizzleSeverity = Severity.HEAVY; - break; - case 313: // shower rain and drizzle - weather.rainSeverity = Severity.MODERATE; - weather.drizzleSeverity = Severity.MODERATE; - weather.shower = true; - break; - case 314: // heavy shower rain and drizzle - weather.rainSeverity = Severity.HEAVY; - weather.drizzleSeverity = Severity.HEAVY; - weather.shower = true; - break; - case 321: // shower drizzle - weather.drizzleSeverity = Severity.MODERATE; - weather.shower = true; - break; - - // Group 5xx: Rain - case 500: // light rain - weather.rainSeverity = Severity.LIGHT; - break; - case 501: // moderate rain - weather.rainSeverity = Severity.MODERATE; - break; - case 502: // heavy intensity rain - weather.rainSeverity = Severity.HEAVY; - break; - case 503: // very heavy rain - weather.rainSeverity = Severity.HEAVY; - break; - case 504: // extreme rain - weather.rainSeverity = Severity.HEAVY; - break; - case 511: // freezing rain - weather.rainSeverity = Severity.MODERATE; - break; - case 520: // light intensity shower rain - weather.rainSeverity = Severity.LIGHT; - weather.shower = true; - break; - case 521: // shower rain - weather.rainSeverity = Severity.MODERATE; - weather.shower = true; - break; - case 522: // heavy intensity shower rain - weather.rainSeverity = Severity.HEAVY; - weather.shower = true; - break; - case 531: // ragged shower rain - weather.rainSeverity = Severity.MODERATE; - weather.shower = true; - break; - - // Group 6xx: Snow - case 600: // light snow - weather.snowSeverity = Severity.LIGHT; - break; - case 601: // snow - weather.snowSeverity = Severity.MODERATE; - break; - case 602: // heavy snow - weather.snowSeverity = Severity.HEAVY; - break; - case 611: // sleet - weather.sleetSeverity = Severity.MODERATE; - break; - case 612: // light shower sleet - weather.sleetSeverity = Severity.LIGHT; - weather.shower = true; - break; - case 613: // shower sleet - weather.sleetSeverity = Severity.MODERATE; - weather.shower = true; - break; - case 615: // light rain and snow - weather.rainSeverity = Severity.LIGHT; - weather.snowSeverity = Severity.LIGHT; - break; - case 616: // rain and snow - weather.rainSeverity = Severity.MODERATE; - weather.snowSeverity = Severity.MODERATE; - break; - case 620: // light shower snow - weather.snowSeverity = Severity.LIGHT; - weather.shower = true; - break; - case 621: // shower snow - weather.snowSeverity = Severity.MODERATE; - weather.shower = true; - break; - case 622: // heavy shower snow - weather.snowSeverity = Severity.HEAVY; - weather.shower = true; - break; - } - - String city = json.getAsJsonPrimitive("name").getAsString(); - if (city.equals("Globe") || city.equals("")) city = null; - JsonPrimitive countryJson = json.getAsJsonObject("sys").getAsJsonPrimitive("country"); - String country = countryJson != null ? countryJson.getAsString() : null; - - weather.coordinates = - new Coordinates( - json.getAsJsonObject("coord").getAsJsonPrimitive("lon").getAsDouble(), - json.getAsJsonObject("coord").getAsJsonPrimitive("lat").getAsDouble() - ).setAddress(country, city); - - weather.temperatureCelsius = json - .getAsJsonObject("main") - .getAsJsonPrimitive("temp") - .getAsFloat(); - - weather.temperatureApparentCelsius = json - .getAsJsonObject("main") - .getAsJsonPrimitive("feels_like") - .getAsFloat(); - - weather.windSpeed = json - .getAsJsonObject("wind") - .getAsJsonPrimitive("speed") - .getAsFloat(); - - JsonPrimitive pri = json - .getAsJsonObject("wind") - .getAsJsonPrimitive("gust"); - - if (pri != null) - weather.windSpeed = pri.getAsFloat(); - - weather.relativeHumidity = json - .getAsJsonObject("main") - .getAsJsonPrimitive("relativeHumidityPercentage") - .getAsInt() / 100f; - - weather.cloudCover = json - .getAsJsonObject("clouds") - .getAsJsonPrimitive("all") - .getAsInt() / 100f; - - weather.sunrise = json - .getAsJsonObject("sys") - .getAsJsonPrimitive("sunrise") - .getAsLong(); - - weather.sunset = json - .getAsJsonObject("sys") - .getAsJsonPrimitive("sunrise") - .getAsLong(); - - weather.description = json - .getAsJsonArray("weather") - .get(0).getAsJsonObject() - .getAsJsonPrimitive("description") - .getAsString(); - - return weather; - } -} diff --git a/src/main/java/eu/m724/wtapi/provider/weather/impl/openweathermap/OpenWeatherMapProvider.java b/src/main/java/eu/m724/wtapi/provider/weather/impl/openweathermap/OpenWeatherMapProvider.java deleted file mode 100644 index eff20ed..0000000 --- a/src/main/java/eu/m724/wtapi/provider/weather/impl/openweathermap/OpenWeatherMapProvider.java +++ /dev/null @@ -1,108 +0,0 @@ -package eu.m724.wtapi.provider.weather.impl.openweathermap; - -import java.net.ProxySelector; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.net.http.HttpResponse.BodyHandlers; -import java.time.Duration; -import java.util.ArrayList; -import java.util.concurrent.CompletableFuture; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import eu.m724.wtapi.object.Coordinates; -import eu.m724.wtapi.object.Weather; -import eu.m724.wtapi.provider.exception.ProviderAuthorizationException; -import eu.m724.wtapi.provider.exception.ProviderException; -import eu.m724.wtapi.provider.exception.QuotaExceededException; -import eu.m724.wtapi.provider.exception.ProviderRequestException; -import eu.m724.wtapi.provider.weather.WeatherProvider; -import eu.m724.wtapi.provider.weather.impl.openmeteo.OpenMeteoProvider; - -/** - * @deprecated - * Development will focus on {@link OpenMeteoProvider} instead. - */ -@Deprecated -public class OpenWeatherMapProvider extends WeatherProvider { - private String apiKey; - - public OpenWeatherMapProvider(String apiKey) { - this.apiKey = apiKey; - } - - @Override - public void init() throws ProviderException { - CompletableFuture weatherFuture = - getWeather(new Coordinates(0, 0)); - - weatherFuture.join(); - } - - private CompletableFuture getWeather(Coordinates coordinates) { - String url = "https://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=%s" - .formatted(coordinates.latitude(), coordinates.longitude(), apiKey); - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(url)) - .build(); - - CompletableFuture> responseFuture = - HttpClient.newBuilder() - .proxy(ProxySelector.getDefault()).build() - .sendAsync(request, BodyHandlers.ofString()); - - CompletableFuture weatherFuture = - responseFuture.thenApply((response) -> { - int code = response.statusCode(); - - if (code == 401) - throw new ProviderAuthorizationException("Invalid api key"); - if (code == 429) - throw new QuotaExceededException("Ratelimited", 1000); - if (code != 200) - throw new ProviderRequestException("Status code: " + code); - - JsonObject jsonResponse = - JsonParser.parseString(response.body()) - .getAsJsonObject(); - - return OWMResponseConverter.convert(jsonResponse); - }); - - return weatherFuture; - } - - @Override - public CompletableFuture getWeather(Coordinates... coordinates) { - ArrayList> weatherFutures = new ArrayList<>(); - - for (Coordinates coordinates : coordinatesArray) { - weatherFutures.add(getWeather(coordinates)); - } - - CompletableFuture weathersFuture = - CompletableFuture.allOf( - weatherFutures.toArray(new CompletableFuture[0])) - .thenApply(v -> - weatherFutures.stream() - .map(CompletableFuture::join) - .toArray(Weather[]::new)); - - return weathersFuture; - } - - @Override - public int getQuota() { - return Duration.ofHours(1370); - } - - @Override - public int getBulkLimit() { - return 1; - } - -} diff --git a/src/test/java/eu/m724/wtapi/object/CoordinateTest.java b/src/test/java/eu/m724/wtapi/object/CoordinateTest.java index 4c87bfe..f3acee6 100644 --- a/src/test/java/eu/m724/wtapi/object/CoordinateTest.java +++ b/src/test/java/eu/m724/wtapi/object/CoordinateTest.java @@ -1,7 +1,5 @@ package eu.m724.wtapi.object; -import static org.junit.Assert.assertNull; - import org.junit.Test; public class CoordinateTest { @@ -15,15 +13,7 @@ public class CoordinateTest { assert coordinates.longitude() == -179.99; coordinates = new Coordinates(-91.1, 180.1); - System.out.printf("Precision loss expected: %f\n", coordinates.longitude()); + System.out.printf("Precision loss expected: %f%n", coordinates.longitude()); assert coordinates.longitude() != -179.9; // TODO fix precision loss - - assertNull(coordinates.city); - assertNull(coordinates.country); - - coordinates.setAddress("SQ", "San Escobar"); - - assert coordinates.city.equals("San Escobar"); - assert coordinates.country.equals("SQ"); } } diff --git a/src/test/java/eu/m724/wtapi/twilight/ApproximateTwilightTimeTest.java b/src/test/java/eu/m724/wtapi/twilight/ApproximateTwilightTimeTest.java index e964c50..8d7e1a6 100644 --- a/src/test/java/eu/m724/wtapi/twilight/ApproximateTwilightTimeTest.java +++ b/src/test/java/eu/m724/wtapi/twilight/ApproximateTwilightTimeTest.java @@ -24,7 +24,6 @@ public class ApproximateTwilightTimeTest { double solarAltitude = provider.calculateSolarAltitude(LocalDateTime.now(), new Coordinates(51, 0)); System.out.println(Math.toDegrees(solarAltitude)); - assert false; // used https://gml.noaa.gov/grad/solcalc/index.html for reference values testLocation(provider, 26, 6, 2023, 53.123394, 23.0864867, LocalDateTime.of(2023, 6, 26, 2, 2), LocalDateTime.of(2023, 6, 26, 18, 59), 0); diff --git a/src/test/java/eu/m724/wtapi/weather/MockWeatherProvider.java b/src/test/java/eu/m724/wtapi/weather/MockWeatherProvider.java index ef8fe3c..2c9188f 100644 --- a/src/test/java/eu/m724/wtapi/weather/MockWeatherProvider.java +++ b/src/test/java/eu/m724/wtapi/weather/MockWeatherProvider.java @@ -1,15 +1,20 @@ package eu.m724.wtapi.weather; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Objects; import java.util.concurrent.CompletableFuture; + import eu.m724.wtapi.object.Coordinates; +import eu.m724.wtapi.object.Quota; import eu.m724.wtapi.object.Weather; import eu.m724.wtapi.provider.exception.ProviderException; -import eu.m724.wtapi.provider.exception.QuotaExceededException; import eu.m724.wtapi.provider.exception.ProviderRequestException; import eu.m724.wtapi.provider.weather.WeatherProvider; +import eu.m724.wtapi.provider.weather.WeatherQueryResult; public class MockWeatherProvider extends WeatherProvider { - private boolean faulty; + private final boolean faulty; public MockWeatherProvider(boolean faulty) { this.faulty = faulty; @@ -17,66 +22,51 @@ public class MockWeatherProvider extends WeatherProvider { @Override public void init() throws ProviderException { - System.out.println("hello from mock provider"); + System.out.println("Mock provider 'initialized'"); } @Override - public CompletableFuture getWeather(Coordinates coordinates) { - if (coordinates == null) - throw new NullPointerException("no coordinates passed"); - - System.out.printf("mock getting weather for %f, %f\n", - coordinates.latitude(), coordinates.longitude()); - - CompletableFuture completableFuture = - new CompletableFuture<>(); - - if (faulty) - completableFuture.completeExceptionally(new ProviderRequestException("Imagined server is down")); - else - completableFuture.complete(new Weather()); - - return completableFuture; - } - - @Override - public CompletableFuture getWeatherBulk(Coordinates[] coordinatesArray) { - int len = coordinatesArray.length; - - if (len == 0) - throw new IllegalArgumentException("no coordinates passed"); - - System.out.println("mock getting weather for multiple coords"); - - CompletableFuture completableFuture = - new CompletableFuture<>(); - - - Weather[] weathers = new Weather[len]; - - for (int i=0; i getWeather(Coordinates... coordinates) { + if (coordinates == null) { + throw new NullPointerException("Coordinates are null"); } - - if (faulty) - completableFuture.completeExceptionally(new QuotaExceededException("Quota exceeded", 60)); - else - completableFuture.complete(weathers); - - return completableFuture; + + if (coordinates.length == 0) { + throw new IllegalArgumentException("coordinates is empty"); + } + + if (Arrays.stream(coordinates).anyMatch(Objects::isNull)) { + throw new IllegalArgumentException("One or more coordinates are null"); + } + + System.out.printf("Mock getting weather%n"); + + CompletableFuture completableFuture = + CompletableFuture.supplyAsync(() -> { + if (faulty) { + throw new ProviderRequestException("Imagined server is down"); + } + + return Arrays.stream(coordinates).map( + c -> new Weather( + c, + LocalDateTime.now(), + false, + false, + false, + 20f, + 100f, + 0f, + 0f + ) + ).toArray(Weather[]::new); + }); + + return completableFuture.handle(WeatherQueryResult::new); } @Override - public int getQuota() { - return 5; + public Quota getQuota() { + return Quota.daily(120); } - - @Override - public int getBulkLimit() { - return 1; - } - } diff --git a/src/test/java/eu/m724/wtapi/weather/OpenMeteoProviderTest.java b/src/test/java/eu/m724/wtapi/weather/OpenMeteoProviderTest.java new file mode 100644 index 0000000..1a879cd --- /dev/null +++ b/src/test/java/eu/m724/wtapi/weather/OpenMeteoProviderTest.java @@ -0,0 +1,63 @@ +package eu.m724.wtapi.weather; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import eu.m724.wtapi.provider.weather.WeatherQueryResult; +import eu.m724.wtapi.provider.weather.impl.openmeteo.OpenMeteoProvider; +import org.junit.Test; + +import eu.m724.wtapi.object.Coordinates; +import eu.m724.wtapi.object.Weather; +import eu.m724.wtapi.provider.weather.WeatherProvider; + +import static org.junit.Assert.assertThrows; + +public class OpenMeteoProviderTest { + @Test + public void openWeatherMapTest() throws InterruptedException, ExecutionException { + WeatherProvider provider = new OpenMeteoProvider(); + + provider.init(); + + testInputErrors(provider); + + CompletableFuture weathersFuture = + provider.getWeather( + new Coordinates(54.6606714, -3.3827237), + new Coordinates(47.5705952, -53.5556464), + new Coordinates(34.2073721, -84.1402857) + ); + + WeatherQueryResult result = weathersFuture.get(); + + if (result.exception() != null) { + throw new ExecutionException("Query errored", result.exception()); + } + + Weather[] weathers = result.weathers(); + assert weathers.length == 3; + + for (Weather weather : weathers) { + printWeather(weather); + } + } + + private void printWeather(Weather weather) { + System.out.printf("Weather in %f,%f as of %s:%n", weather.coordinates().latitude(), weather.coordinates().longitude(), weather.timestamp().toString()); + System.out.printf(" Rain: %b | Snow: %b | Thunder: %b%n", weather.raining(), weather.snowing(), weather.thundering()); + System.out.printf(" Temperature: %.1f°C (feels like %.1f°C)%n", weather.temperatureCelsius(), weather.temperatureApparentCelsius()); + System.out.printf(" Relative humidity: %.0f%%%n", weather.relativeHumidityPercentage() * 100); + System.out.printf(" Cloudiness: %.0f%%%n", weather.cloudCoverPercentage() * 100); + System.out.println(); + } + + public void testInputErrors(WeatherProvider provider) { + assertThrows(NullPointerException.class, () -> provider.getWeather(null)); + + assertThrows(IllegalArgumentException.class, () -> provider.getWeather(new Coordinates[0])); + + assertThrows(IllegalArgumentException.class, () -> + provider.getWeather(new Coordinates[] { new Coordinates(0, 0), null })); + } +} diff --git a/src/test/java/eu/m724/wtapi/weather/OpenWeatherMapTest.java b/src/test/java/eu/m724/wtapi/weather/OpenWeatherMapTest.java deleted file mode 100644 index af8b9cc..0000000 --- a/src/test/java/eu/m724/wtapi/weather/OpenWeatherMapTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package eu.m724.wtapi.weather; - -import static org.junit.Assert.assertNotNull; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import org.junit.Test; - -import eu.m724.wtapi.object.Coordinates; -import eu.m724.wtapi.object.Weather; -import eu.m724.wtapi.provider.weather.WeatherProvider; -import eu.m724.wtapi.provider.weather.impl.openweathermap.OpenWeatherMapProvider; - -public class OpenWeatherMapTest { - @Test - public void openWeatherMapTest() throws InterruptedException, ExecutionException { - String apiKey = "48ac630acaa5c003316aea1dd7327d05"; - - WeatherProvider provider = new OpenWeatherMapProvider(apiKey); - - provider.init(); - - CompletableFuture weathersFuture = - provider.getWeather( - new Coordinates(54.6606714, -3.3827237), - new Coordinates(47.5705952, -53.5556464), - new Coordinates(34.2073721, -84.1402857)); - - Weather[] weathers = weathersFuture.get(); - assert weathers.length == 3; - - for (Weather weather : weathers) { - System.out.printf("Current weather in %s, %s: %s\n", weather.coordinates().city, weather.coordinates().country, weather.description); - } - } -} diff --git a/src/test/java/eu/m724/wtapi/weather/ProviderTest.java b/src/test/java/eu/m724/wtapi/weather/ProviderTest.java deleted file mode 100644 index 76b5d66..0000000 --- a/src/test/java/eu/m724/wtapi/weather/ProviderTest.java +++ /dev/null @@ -1,97 +0,0 @@ -package eu.m724.wtapi.weather; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.fail; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import org.junit.Test; - -import eu.m724.wtapi.object.Coordinates; -import eu.m724.wtapi.object.Weather; -import eu.m724.wtapi.provider.exception.ProviderException; -import eu.m724.wtapi.provider.exception.QuotaExceededException; -import eu.m724.wtapi.provider.weather.WeatherProvider; - -public class ProviderTest { - @Test - public void testProvider() throws InterruptedException, ExecutionException { - WeatherProvider provider = new MockWeatherProvider(false); - provider.init(); - - assert provider.getQuota() == 5; - assert provider.getBulkLimit() == 1; - - CompletableFuture weatherFuture = - provider.getWeather(new Coordinates(0, 0)); - - Weather weather = weatherFuture.get(); - assertNotNull(weather); - - CompletableFuture weathersFuture = - provider.getWeatherBulk(new Coordinates[] { - new Coordinates(0, 0), - new Coordinates(0, 100) - }); - - Weather[] weathers = weathersFuture.get(); - assertNotNull(weathers); - assert weathers.length == 2; - assertNotNull(weathers[0]); - assertNotNull(weathers[1]); - } - - @Test - public void testFaultyProvider() throws InterruptedException { - WeatherProvider provider = new MockWeatherProvider(true); - - CompletableFuture weatherFuture = - provider.getWeather(new Coordinates(0, 0)); - - Weather weather = null; - - try { - weather = weatherFuture.get(); - fail("Shouldn't pass"); - } catch (ExecutionException e) { - assert e.getCause() instanceof ProviderException; - } - - assertNull(weather); - - Weather[] weathers = null; - - CompletableFuture weathersFuture = - provider.getWeatherBulk(new Coordinates[] { - new Coordinates(0, 0), - new Coordinates(0, 100) - }); - - try { - weathers = weathersFuture.get(); - fail("Shouldn't pass"); - } catch (ExecutionException e) { - QuotaExceededException qee = (QuotaExceededException) e.getCause(); - assert qee.getRetryIn() == 60; - } - - assertNull(weathers); - - } - - @Test - public void testInputErrors() { - WeatherProvider provider = new MockWeatherProvider(false); - - assertThrows(NullPointerException.class, () -> provider.getWeather(null)); - assertThrows(NullPointerException.class, () -> provider.getWeatherBulk(null)); - - assertThrows(IllegalArgumentException.class, () -> provider.getWeatherBulk(new Coordinates[0])); - - assertThrows(IllegalArgumentException.class, () -> - provider.getWeatherBulk(new Coordinates[] { new Coordinates(0, 0), null })); - } -} diff --git a/src/test/java/eu/m724/wtapi/weather/WeatherProviderTest.java b/src/test/java/eu/m724/wtapi/weather/WeatherProviderTest.java new file mode 100644 index 0000000..ea32fda --- /dev/null +++ b/src/test/java/eu/m724/wtapi/weather/WeatherProviderTest.java @@ -0,0 +1,96 @@ +package eu.m724.wtapi.weather; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import eu.m724.wtapi.provider.weather.WeatherQueryResult; +import org.junit.Before; +import org.junit.Test; + +import eu.m724.wtapi.object.Coordinates; +import eu.m724.wtapi.object.Weather; +import eu.m724.wtapi.provider.exception.ProviderException; +import eu.m724.wtapi.provider.weather.WeatherProvider; + +public class WeatherProviderTest { + WeatherProvider provider; + + @Before + public void initProvider() { + this.provider = new MockWeatherProvider(false); + provider.init(); + } + + @Test + public void testProvider() throws InterruptedException, ExecutionException { + assert provider.getQuota().getHourlyQuota() == 5; + + CompletableFuture weatherFuture = + provider.getWeather(new Coordinates(0, 0)); + + WeatherQueryResult result = weatherFuture.get(); + + if (result.exception() != null) { + throw new ExecutionException("Query errored", result.exception()); + } + + Weather[] weathers = result.weathers(); + assert weathers.length == 1; + } + + @Test + public void testMultiple() throws ExecutionException, InterruptedException { + CompletableFuture weatherFuture = + provider.getWeather( + new Coordinates(0, 0), + new Coordinates(0, 100) + ); + + WeatherQueryResult result = weatherFuture.get(); + + if (result.exception() != null) { + throw new ExecutionException("Query errored", result.exception()); + } + + Weather[] weathers = result.weathers(); + assert weathers.length == 2; + + assertNotNull(weathers[0]); + assertNotNull(weathers[1]); + } + + @Test + public void testFaultyProvider() throws InterruptedException, ExecutionException { + WeatherProvider provider = new MockWeatherProvider(true); + + CompletableFuture weatherFuture = + provider.getWeather(new Coordinates(0, 0)); + + WeatherQueryResult result = weatherFuture.get(); + + assertNull(result.weathers()); + + if (result.exception() == null) { + fail("Faulty provider didn't throw"); + } + + assert result.exception() instanceof ProviderException; + } + + @Test + public void testInputErrors() { + WeatherProvider provider = new MockWeatherProvider(false); + + assertThrows(NullPointerException.class, () -> provider.getWeather(null)); + + assertThrows(IllegalArgumentException.class, () -> provider.getWeather(new Coordinates[0])); + + assertThrows(IllegalArgumentException.class, () -> + provider.getWeather(new Coordinates[] { new Coordinates(0, 0), null })); + } +}