Finish update, working

This commit is contained in:
Minecon724 2025-05-23 08:14:19 +02:00
commit 1895673c50
Signed by: Minecon724
GPG key ID: A02E6E67AB961189
13 changed files with 289 additions and 592 deletions

View file

@ -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<Weather[]> getWeather(Coordinates... coordinates);
public abstract CompletableFuture<WeatherQueryResult> getWeather(Coordinates... coordinates);
public abstract Quota getQuota();
}

View file

@ -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
) {
}

View file

@ -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<Weather[]> getWeather(Coordinates... coordinates) {
public CompletableFuture<WeatherQueryResult> 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&current=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<HttpResponse<String>> 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<String> 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);
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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<Weather> weatherFuture =
getWeather(new Coordinates(0, 0));
weatherFuture.join();
}
private CompletableFuture<Weather> 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<HttpResponse<String>> responseFuture =
HttpClient.newBuilder()
.proxy(ProxySelector.getDefault()).build()
.sendAsync(request, BodyHandlers.ofString());
CompletableFuture<Weather> 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<Weather[]> getWeather(Coordinates... coordinates) {
ArrayList<CompletableFuture<Weather>> weatherFutures = new ArrayList<>();
for (Coordinates coordinates : coordinatesArray) {
weatherFutures.add(getWeather(coordinates));
}
CompletableFuture<Weather[]> 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;
}
}

View file

@ -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");
}
}

View file

@ -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);

View file

@ -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<Weather> 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<Weather> completableFuture =
new CompletableFuture<>();
if (faulty)
completableFuture.completeExceptionally(new ProviderRequestException("Imagined server is down"));
else
completableFuture.complete(new Weather());
return completableFuture;
}
@Override
public CompletableFuture<Weather[]> 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<Weather[]> completableFuture =
new CompletableFuture<>();
Weather[] weathers = new Weather[len];
for (int i=0; i<len; i++) {
if (coordinatesArray[i] == null)
throw new IllegalArgumentException("A coordinate is null");
weathers[i] = new Weather();
public CompletableFuture<WeatherQueryResult> 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<Weather[]> 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;
}
}

View file

@ -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<WeatherQueryResult> 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 }));
}
}

View file

@ -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<Weather[]> 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);
}
}
}

View file

@ -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<Weather> weatherFuture =
provider.getWeather(new Coordinates(0, 0));
Weather weather = weatherFuture.get();
assertNotNull(weather);
CompletableFuture<Weather[]> 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<Weather> 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<Weather[]> 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 }));
}
}

View file

@ -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<WeatherQueryResult> 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<WeatherQueryResult> 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<WeatherQueryResult> 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 }));
}
}