Commit 11fdab00 authored by Christopher Bohn's avatar Christopher Bohn 🤔
Browse files

Modified existing methods to handle hourly and daily data from "onecall"

Still need some new methods:
- Hourly & Daily dew point
- Hourly & Daily UV index
- Daily morn, day, eve, night, min, max temps, ambient & feels_like
- Dail rain & snow totals
parent 22d0d1b2
...@@ -393,6 +393,13 @@ public class OpenWeatherConnector { ...@@ -393,6 +393,13 @@ public class OpenWeatherConnector {
return currentTimestamp; return currentTimestamp;
} }
/**
* Extracts epoch-based timestamps from the specified JSON list and places {@link Date} timestamps into the provided
* {@link List}.
* @param jsonListName the name of the JSON list containing data with timestamps
* @param timestamps the possibly-nonempty {@link List} to place the {@link Date} timestamps in
* @return the provided {@link List}, populated with the additional timestamps
*/
private List<Date> moveTimestampsFromJsonArrayToJavaList(String jsonListName, List<Date> timestamps) { private List<Date> moveTimestampsFromJsonArrayToJavaList(String jsonListName, List<Date> timestamps) {
if (data.containsKey(jsonListName)) { if (data.containsKey(jsonListName)) {
JSONArray entries = (JSONArray)data.get(jsonListName); JSONArray entries = (JSONArray)data.get(jsonListName);
...@@ -490,15 +497,20 @@ public class OpenWeatherConnector { ...@@ -490,15 +497,20 @@ public class OpenWeatherConnector {
* <p>Provides the broad categories for the forecasted weather. If there are more than one category provided, the * <p>Provides the broad categories for the forecasted weather. If there are more than one category provided, the
* "primary" category will at the head of the list. The weather categories are guaranteed to be in the same order * "primary" category will at the head of the list. The weather categories are guaranteed to be in the same order
* as the corresponding descriptions provided by {@link #getWeatherDescriptions()}.</p> * as the corresponding descriptions provided by {@link #getWeatherDescriptions()}.</p>
* <p>Available for the "forecast" dataset.</p> * <p>Available for the "forecast" and "onecall" datasets.</p>
* *
* @param timestamp the date/time corresponding to the desired data * @param timestamp the date/time corresponding to the desired data
* @return list of forecasted weather categories * @return list of forecasted weather categories
* @see #getWeatherDescriptions() * @see #getWeatherDescriptions()
*/ */
public List<WeatherCategory> getWeatherCategories(Date timestamp) { public List<WeatherCategory> getWeatherCategories(Date timestamp) {
checkForDataReadiness(Set.of("forecast")); checkForDataReadiness(Set.of("forecast", "onecall"));
return getWeatherCategories(getListEntry("list", timestamp)); String listName = "list";
if (dataSet.equals("onecall")) {
List<Date> hours = getTimestamps("hourly");
listName = hours.contains(timestamp) ? "hourly" : "daily";
}
return getWeatherCategories(getListEntry(listName, timestamp));
} }
/** /**
...@@ -544,15 +556,20 @@ public class OpenWeatherConnector { ...@@ -544,15 +556,20 @@ public class OpenWeatherConnector {
* <p>Provides descriptions of the forecasted weather. If there are more than one weather description provided, * <p>Provides descriptions of the forecasted weather. If there are more than one weather description provided,
* the "primary" description will be at the head of the list. The weather descriptions are guaranteed to be in * the "primary" description will be at the head of the list. The weather descriptions are guaranteed to be in
* the same order as the corresponding categories provided by {@link #getWeatherCategories()}.</p> * the same order as the corresponding categories provided by {@link #getWeatherCategories()}.</p>
* <p>Available for the "forecast" dataset.</p> * <p>Available for the "forecast" and "onecall" datasets.</p>
* *
* @param timestamp the date/time corresponding to the desired data * @param timestamp the date/time corresponding to the desired data
* @return list of forecasted weather descriptions * @return list of forecasted weather descriptions
* @see #getWeatherCategories() * @see #getWeatherCategories()
*/ */
public List<String> getWeatherDescriptions(Date timestamp) { public List<String> getWeatherDescriptions(Date timestamp) {
checkForDataReadiness(Set.of("forecast")); checkForDataReadiness(Set.of("forecast", "onecall"));
return getWeatherDescriptions(getListEntry("list", timestamp)); String listName = "list";
if (dataSet.equals("onecall")) {
List<Date> hours = getTimestamps("hourly");
listName = hours.contains(timestamp) ? "hourly" : "daily";
}
return getWeatherDescriptions(getListEntry(listName, timestamp));
} }
/** /**
...@@ -571,14 +588,19 @@ public class OpenWeatherConnector { ...@@ -571,14 +588,19 @@ public class OpenWeatherConnector {
/** /**
* <p>Provides the forecasted visibility in the default <a href="https://openweathermap.org/current#data">units</a> * <p>Provides the forecasted visibility in the default <a href="https://openweathermap.org/current#data">units</a>
* or those specified in the call to {@link #retrieveData(String)}.</p> * or those specified in the call to {@link #retrieveData(String)}.</p>
* <p>Available for the "forecast" dataset.</p> * <p>Available for the "forecast" and "onecall" (hourly only) datasets.</p>
* *
* @param timestamp the date/time corresponding to the desired data * @param timestamp the date/time corresponding to the desired data
* @return the forecasted visibility, or {@link Long#MIN_VALUE} if visibility is not in the forecast * @return the forecasted visibility, or {@link Long#MIN_VALUE} if visibility is not in the forecast
*/ */
public long getVisibility(Date timestamp) { public long getVisibility(Date timestamp) {
checkForDataReadiness(Set.of("forecast")); checkForDataReadiness(Set.of("forecast", "onecall"));
return extractLongFromJSON(getListEntry("list", timestamp), "visibility", Long.MIN_VALUE); if (dataSet.equals("onecall") && !getTimestamps("hourly").contains(timestamp)) {
return Long.MIN_VALUE;
} else {
String listName = dataSet.equals("onecall") ? "hourly" : "list";
return extractLongFromJSON(getListEntry(listName, timestamp), "visibility", Long.MIN_VALUE);
}
} }
/** /**
...@@ -597,14 +619,20 @@ public class OpenWeatherConnector { ...@@ -597,14 +619,20 @@ public class OpenWeatherConnector {
/** /**
* <p>Provides the forecasted temperature in the default <a href="https://openweathermap.org/current#data">units</a> * <p>Provides the forecasted temperature in the default <a href="https://openweathermap.org/current#data">units</a>
* or those specified in the call to {@link #retrieveData(String)}.</p> * or those specified in the call to {@link #retrieveData(String)}.</p>
* <p>Available for the "forecast" dataset.</p> * <p>Available for the "forecast" and "onecall" (hourly only) datasets.</p>
* *
* @param timestamp the date/time corresponding to the desired data * @param timestamp the date/time corresponding to the desired data
* @return the forecasted temperature, or {@link Double#NaN} if temperature is not in the forecast * @return the forecasted temperature, or {@link Double#NaN} if temperature is not in the forecast
*/ */
public double getTemperature(Date timestamp) { public double getTemperature(Date timestamp) {
checkForDataReadiness(Set.of("forecast")); checkForDataReadiness(Set.of("forecast", "onecall"));
return extractDoubleFromJSON(getListEntry("list", timestamp), "main", "temp", Double.NaN); if (dataSet.equals("forecast")) {
return extractDoubleFromJSON(getListEntry("list", timestamp), "main", "temp", Double.NaN);
} else if (dataSet.equals("onecall") && getTimestamps("hourly").contains(timestamp)) {
return extractDoubleFromJSON(getListEntry("hourly", timestamp), "temp", Double.NaN);
} else {
return Double.NaN;
}
} }
/** /**
...@@ -621,14 +649,21 @@ public class OpenWeatherConnector { ...@@ -621,14 +649,21 @@ public class OpenWeatherConnector {
/** /**
* <p>Provides the forecasted relative humidity (percent of saturation).</p> * <p>Provides the forecasted relative humidity (percent of saturation).</p>
* <p>Available for the "forecast" dataset.</p> * <p>Available for the "forecast" and "onecall" datasets.</p>
* *
* @param timestamp the date/time corresponding to the desired data * @param timestamp the date/time corresponding to the desired data
* @return the forecasted humidity, or {@link Long#MIN_VALUE} if visibility is not in the forecast * @return the forecasted humidity, or {@link Long#MIN_VALUE} if visibility is not in the forecast
*/ */
public long getHumidity(Date timestamp) { public long getHumidity(Date timestamp) {
checkForDataReadiness(Set.of("forecast")); checkForDataReadiness(Set.of("forecast", "onecall"));
return extractLongFromJSON(getListEntry("list", timestamp), "main", "humidity", Long.MIN_VALUE); if (dataSet.equals("forecast")) {
return extractLongFromJSON(getListEntry("list", timestamp), "main", "humidity", Long.MIN_VALUE);
} else if (dataSet.equals("onecall")) {
return extractLongFromJSON(getListEntry(getTimestamps("hourly").contains(timestamp) ? "hourly" : "daily",
timestamp), "humidity", Long.MIN_VALUE);
} else {
return Long.MIN_VALUE;
}
} }
/** /**
...@@ -662,15 +697,21 @@ public class OpenWeatherConnector { ...@@ -662,15 +697,21 @@ public class OpenWeatherConnector {
* <p>Provides the forecasted "feels like" temperature in the default * <p>Provides the forecasted "feels like" temperature in the default
* <a href="https://openweathermap.org/current#data">units</a> * <a href="https://openweathermap.org/current#data">units</a>
* or those specified in the call to {@link #retrieveData(String)}.</p> * or those specified in the call to {@link #retrieveData(String)}.</p>
* <p>Available for the "forecast" dataset.</p> * <p>Available for the "forecast" and "onecall" (hourly only) datasets.</p>
* *
* @param timestamp the date/time corresponding to the desired data * @param timestamp the date/time corresponding to the desired data
* @return the forecasted "feels like" temperature, or {@link Double#NaN} if "feels like" temperature is not in * @return the forecasted "feels like" temperature, or {@link Double#NaN} if "feels like" temperature is not in
* the forecast * the forecast
*/ */
public double getFeelsLike(Date timestamp) { public double getFeelsLike(Date timestamp) {
checkForDataReadiness(Set.of("forecast")); checkForDataReadiness(Set.of("forecast", "onecall"));
return extractDoubleFromJSON(getListEntry("list", timestamp), "main", "feels_like", Double.NaN); if (dataSet.equals("forecast")) {
return extractDoubleFromJSON(getListEntry("list", timestamp), "main", "feels_like", Double.NaN);
} else if (dataSet.equals("onecall") && getTimestamps("hourly").contains(timestamp)) {
return extractDoubleFromJSON(getListEntry("hourly", timestamp), "feels_like", Double.NaN);
} else {
return Double.NaN;
}
} }
/** /**
...@@ -689,14 +730,21 @@ public class OpenWeatherConnector { ...@@ -689,14 +730,21 @@ public class OpenWeatherConnector {
/** /**
* <p>Provides the forecasted pressure in the default <a href="https://openweathermap.org/current#data">units</a> * <p>Provides the forecasted pressure in the default <a href="https://openweathermap.org/current#data">units</a>
* or those specified in the call to {@link #retrieveData(String)}.</p> * or those specified in the call to {@link #retrieveData(String)}.</p>
* <p>Available for the "forecast" dataset.</p> * <p>Available for the "forecast" and "onecall" datasets.</p>
* *
* @param timestamp the date/time corresponding to the desired data * @param timestamp the date/time corresponding to the desired data
* @return the forecasted pressure, or {@link Long#MIN_VALUE} if pressure is not in the forecast * @return the forecasted pressure, or {@link Long#MIN_VALUE} if pressure is not in the forecast
*/ */
public long getPressure(Date timestamp) { public long getPressure(Date timestamp) {
checkForDataReadiness(Set.of("forecast")); checkForDataReadiness(Set.of("forecast", "onecall"));
return extractLongFromJSON(getListEntry("list", timestamp), "main", "pressure", Long.MIN_VALUE); if (dataSet.equals("forecast")) {
return extractLongFromJSON(getListEntry("list", timestamp), "main", "pressure", Long.MIN_VALUE);
} else if (dataSet.equals("onecall")) {
return extractLongFromJSON(getListEntry(getTimestamps("hourly").contains(timestamp) ? "hourly" : "daily",
timestamp), "pressure", Long.MIN_VALUE);
} else {
return Long.MIN_VALUE;
}
} }
/** /**
...@@ -716,14 +764,21 @@ public class OpenWeatherConnector { ...@@ -716,14 +764,21 @@ public class OpenWeatherConnector {
/** /**
* <p>Provides the forecasted wind direction, in degrees.</p> * <p>Provides the forecasted wind direction, in degrees.</p>
* <p>Available for the "forecast" dataset.</p> * <p>Available for the "forecast" and "onecall" datasets.</p>
* *
* @param timestamp the date/time corresponding to the desired data * @param timestamp the date/time corresponding to the desired data
* @return the forecasted wind direction, or {@link Long#MIN_VALUE} if winds are not in the forecast * @return the forecasted wind direction, or {@link Long#MIN_VALUE} if winds are not in the forecast
*/ */
public long getWindDirection(Date timestamp) { public long getWindDirection(Date timestamp) {
checkForDataReadiness(Set.of("forecast")); checkForDataReadiness(Set.of("forecast", "onecall"));
return extractLongFromJSON(getListEntry("list", timestamp), "wind", "deg", Long.MIN_VALUE); if (dataSet.equals("forecast")) {
return extractLongFromJSON(getListEntry("list", timestamp), "wind", "deg", Long.MIN_VALUE);
} else if (dataSet.equals("onecall")) {
return extractLongFromJSON(getListEntry(getTimestamps("hourly").contains(timestamp) ? "hourly" : "daily",
timestamp), "wind_deg", Long.MIN_VALUE);
} else {
return Long.MIN_VALUE;
}
} }
/** /**
...@@ -745,14 +800,21 @@ public class OpenWeatherConnector { ...@@ -745,14 +800,21 @@ public class OpenWeatherConnector {
/** /**
* <p>Provides the forecasted wind speed in the default <a href="https://openweathermap.org/current#data">units</a> * <p>Provides the forecasted wind speed in the default <a href="https://openweathermap.org/current#data">units</a>
* or those specified in the call to {@link #retrieveData(String)}.</p> * or those specified in the call to {@link #retrieveData(String)}.</p>
* <p>Available for the "forecast" dataset.</p> * <p>Available for the "forecast" and "onecall" datasets.</p>
* *
* @param timestamp the date/time corresponding to the desired data * @param timestamp the date/time corresponding to the desired data
* @return the forecasted wind speed, or 0.0 if winds are not in the forecast * @return the forecasted wind speed, or 0.0 if winds are not in the forecast
*/ */
public double getWindSpeed(Date timestamp) { public double getWindSpeed(Date timestamp) {
checkForDataReadiness(Set.of("forecast")); checkForDataReadiness(Set.of("forecast", "onecall"));
return extractDoubleFromJSON(getListEntry("list", timestamp), "wind", "speed", 0.0); if (dataSet.equals("forecast")) {
return extractDoubleFromJSON(getListEntry("list", timestamp), "wind", "speed", 0.0);
} else if (dataSet.equals("onecall")) {
return extractDoubleFromJSON(getListEntry(getTimestamps("hourly").contains(timestamp) ? "hourly" : "daily",
timestamp), "wind_speed", 0.0);
} else {
return 0.0;
}
} }
/** /**
...@@ -774,14 +836,21 @@ public class OpenWeatherConnector { ...@@ -774,14 +836,21 @@ public class OpenWeatherConnector {
/** /**
* <p>Provides the forecasted wind gust in the default <a href="https://openweathermap.org/current#data">units</a> * <p>Provides the forecasted wind gust in the default <a href="https://openweathermap.org/current#data">units</a>
* or those specified in the call to {@link #retrieveData(String)}.</p> * or those specified in the call to {@link #retrieveData(String)}.</p>
* <p>Available for the "forecast" dataset.</p> * <p>Available for the "forecast" and "onecall" datasets.</p>
* *
* @param timestamp the date/time corresponding to the desired data * @param timestamp the date/time corresponding to the desired data
* @return the forecasted wind gust, or current wind speed if wind gust is not in the forecast * @return the forecasted wind gust, or current wind speed if wind gust is not in the forecast
*/ */
public double getWindGust(Date timestamp) { public double getWindGust(Date timestamp) {
checkForDataReadiness(Set.of("forecast")); checkForDataReadiness(Set.of("forecast", "onecall"));
return extractDoubleFromJSON(getListEntry("list", timestamp), "wind", "gust", getWindSpeed(timestamp)); if (dataSet.equals("forecast")) {
return extractDoubleFromJSON(getListEntry("list", timestamp), "wind", "gust", getWindSpeed(timestamp));
} else if (dataSet.equals("onecall")) {
return extractDoubleFromJSON(getListEntry(getTimestamps("hourly").contains(timestamp) ? "hourly" : "daily",
timestamp), "wind_gust", getWindSpeed(timestamp));
} else {
return getWindSpeed(timestamp);
}
} }
/** /**
...@@ -812,26 +881,36 @@ public class OpenWeatherConnector { ...@@ -812,26 +881,36 @@ public class OpenWeatherConnector {
/** /**
* <p>Provides the forecasted cloud cover percentage (cloudiness).</p> * <p>Provides the forecasted cloud cover percentage (cloudiness).</p>
* <p>Available for the "forecast" dataset.</p> * <p>Available for the "forecast" and "onecall" datasets.</p>
* *
* @param timestamp the date/time corresponding to the desired data * @param timestamp the date/time corresponding to the desired data
* @return the forecasted cloudiness, or 0 if cloudiness is not in current observation * @return the forecasted cloudiness, or 0 if cloudiness is not in current observation
*/ */
public long getCloudCover(Date timestamp) { public long getCloudCover(Date timestamp) {
checkForDataReadiness(Set.of("forecast")); checkForDataReadiness(Set.of("forecast", "onecall"));
return extractLongFromJSON(getListEntry("list", timestamp), "clouds", "all", 0); if (dataSet.startsWith("onecall")) {
return extractLongFromJSON(getListEntry(getTimestamps("hourly").contains(timestamp) ? "hourly" : "daily",
timestamp), "clouds", 0);
} else {
return extractLongFromJSON(getListEntry("list", timestamp), "clouds", "all", 0);
}
} }
/** /**
* <p>Provides the forecasted probability of rain, on a 0.0-1.0 scale.</p> * <p>Provides the forecasted probability of rain, on a 0.0-1.0 scale.</p>
* <p>Available for the "forecast" dataset.</p> * <p>Available for the "forecast" and "onecall" datasets.</p>
* *
* @param timestamp the date/time corresponding to the desired data * @param timestamp the date/time corresponding to the desired data
* @return the forecasted probability of precipitation, or NaN if cloudiness is not in current observation * @return the forecasted probability of precipitation, or NaN if cloudiness is not in current observation
*/ */
public double getProbabilityOfPrecipitation(Date timestamp) { public double getProbabilityOfPrecipitation(Date timestamp) {
checkForDataReadiness(Set.of("forecast")); checkForDataReadiness(Set.of("forecast", "onecall"));
return extractDoubleFromJSON(getListEntry("list", timestamp), "pop", Double.NaN); String listName = "list";
if (dataSet.equals("onecall")) {
List<Date> hours = getTimestamps("hourly");
listName = hours.contains(timestamp) ? "hourly" : "daily";
}
return extractDoubleFromJSON(getListEntry(listName, timestamp), "pop", Double.NaN);
} }
/** /**
......
...@@ -83,6 +83,8 @@ public class ExceptionsTest { ...@@ -83,6 +83,8 @@ public class ExceptionsTest {
public void testAllowedOneCallMethods() { public void testAllowedOneCallMethods() {
OpenWeatherConnector connector = oneCall; OpenWeatherConnector connector = oneCall;
Date minutely = connector.getTimestamps("minutely").get(0); Date minutely = connector.getTimestamps("minutely").get(0);
Date hourly = connector.getTimestamps("hourly").get(0);
Date daily = connector.getTimestamps("daily").get(0);
connector.getLatitude(); connector.getLatitude();
connector.getLongitude(); connector.getLongitude();
connector.getTimestamp(); connector.getTimestamp();
...@@ -102,7 +104,20 @@ public class ExceptionsTest { ...@@ -102,7 +104,20 @@ public class ExceptionsTest {
connector.getOneHourSnowfall(); connector.getOneHourSnowfall();
connector.getDewPoint(); connector.getDewPoint();
connector.getUltravioletIndex(); connector.getUltravioletIndex();
connector.getMinutelyPrecipitation(minutely); connector.getMinutelyPrecipitation(minutely); // only
connector.getWeatherCategories(hourly);
connector.getWeatherDescriptions(daily);
connector.getVisibility(hourly); // only
connector.getTemperature(hourly); // only
connector.getHumidity(hourly);
connector.getFeelsLike(hourly); // only
connector.getPressure(daily);
connector.getWindDirection(hourly);
connector.getWindSpeed(daily);
connector.getWindGust(daily);
connector.getProbabilityOfPrecipitation(hourly);
connector.getCloudCover(daily);
// TODO: Introduce new methods
// pass // pass
} }
...@@ -768,61 +783,6 @@ public class ExceptionsTest { ...@@ -768,61 +783,6 @@ public class ExceptionsTest {
OpenWeatherConnector connector = oneCall; OpenWeatherConnector connector = oneCall;
Date timestamp = OpenWeatherConnector.IMPOSSIBLE_DATE; Date timestamp = OpenWeatherConnector.IMPOSSIBLE_DATE;
String failMessage = "Expected UnsupportedOperationException"; String failMessage = "Expected UnsupportedOperationException";
try {
connector.getWeatherCategories(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getWeatherDescriptions(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getVisibility(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getTemperature(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getHumidity(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getFeelsLike(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getPressure(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getWindDirection(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getWindGust(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getCloudCover(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getProbabilityOfPrecipitation(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try { try {
connector.getThreeHourRainfall(timestamp); connector.getThreeHourRainfall(timestamp);
fail(failMessage); fail(failMessage);
......
...@@ -13,11 +13,15 @@ import static org.junit.Assert.assertEquals; ...@@ -13,11 +13,15 @@ import static org.junit.Assert.assertEquals;
public class OneCall_WebsiteExampleTest { public class OneCall_WebsiteExampleTest {
private OpenWeatherConnector connector; private OpenWeatherConnector connector;
private Date minutely, hourly, daily;
@Before @Before
public void setup() throws IOException { public void setup() throws IOException {
connector = new OpenWeatherConnector("onecall"); connector = new OpenWeatherConnector("onecall");
connector.retrieveData("website-example.json"); connector.retrieveData("website-example.json");
minutely = connector.getTimestamps("minutely").get(0);
hourly = connector.getTimestamps("hourly").get(0);
daily = connector.getTimestamps("daily").get(0);
} }
@Test @Test
...@@ -130,10 +134,124 @@ public class OneCall_WebsiteExampleTest { ...@@ -130,10 +134,124 @@ public class OneCall_WebsiteExampleTest {
} }
@Test @Test
public void testMinutelyPrecipitation() { public void testWeatherForecast() {
Date timestamp = connector.getTimestamps("minutely").get(0); List<OpenWeatherConnector.WeatherCategory> expectedCategories = List.of(
OpenWeatherConnector.WeatherCategory.CLOUDS
);
List<String> expectedDescriptions = List.of("few clouds");
int expectedListSize = 1;
List<OpenWeatherConnector.WeatherCategory> actualCategories = connector.getWeatherCategories(hourly);
List<String> actualDescriptions = connector.getWeatherDescriptions(hourly);
assertEquals(expectedListSize, actualCategories.size());
assertEquals(expectedListSize, actualDescriptions.size());
assertEquals(expectedCategories, actualCategories);
assertEquals(expectedDescriptions, actualDescriptions);
expectedCategories = List.of(OpenWeatherConnector.WeatherCategory.RAIN);
expectedDescriptions = List.of("light rain");
actualCategories = connector.getWeatherCategories(daily);
actualDescriptions = connector.getWeatherDescriptions(daily);
assertEquals(expectedListSize, actualCategories.size());
assertEquals(expectedListSize, actualDescriptions.size());
assertEquals(expectedCategories, actualCategories);
assertEquals(expectedDescriptions, actualDescriptions);
}
@Test
public void testMainObservationForecast() {