From 80d4104e2133826fc964f4960f86ef89acaab739 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier <155652256+pierrepetersmeier@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:04:00 +0100 Subject: [PATCH 1/8] Implement validation for required fields in Icon and Cosmo time-based weather value factories --- CHANGELOG.md | 1 + .../CosmoTimeBasedWeatherValueFactory.java | 27 ++ .../IconTimeBasedWeatherValueFactory.java | 27 ++ ...conTimeBasedWeatherValueFactoryTest.groovy | 375 ++++++++++-------- 4 files changed, 255 insertions(+), 175 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20bf33dc1..d00fdc113 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added converters documentation [#1139](https://github.com/ie3-institute/PowerSystemDataModel/issues/1139) - Added abstraction for power value sources [#1438](https://github.com/ie3-institute/PowerSystemDataModel/issues/1438) - Add ground temperatures level 1 and 2 as option to weather data. [#1343](https://github.com/ie3-institute/PowerSystemDataModel/issues/1343) +- Add validation for required fields in Icon and Cosmo time-based weather value factories [#1537](https://github.com/ie3-institute/PowerSystemDataModel/issues/1537) ### Fixed - Fixed small issues in tests [#1400](https://github.com/ie3-institute/PowerSystemDataModel/issues/1400) diff --git a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactory.java index 33d705b30..cc026afda 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactory.java @@ -5,6 +5,7 @@ */ package edu.ie3.datamodel.io.factory.timeseries; +import edu.ie3.datamodel.exceptions.FactoryException; import edu.ie3.datamodel.models.StandardUnits; import edu.ie3.datamodel.models.timeseries.individual.TimeBasedValue; import edu.ie3.datamodel.models.value.WeatherValue; @@ -67,6 +68,15 @@ protected List> getFields(Class entityClass) { @Override protected TimeBasedValue buildModel(TimeBasedWeatherValueData data) { + Set requiredFields = + newSet( + TIME, + DIFFUSE_IRRADIANCE, + DIRECT_IRRADIANCE, + TEMPERATURE, + WIND_DIRECTION, + WIND_VELOCITY); + validatedRequiredFields(data, requiredFields); Point coordinate = data.getCoordinate(); ZonedDateTime time = timeUtil.toZonedDateTime(data.getField(TIME)); ComparableQuantity directIrradiance = @@ -96,4 +106,21 @@ protected TimeBasedValue buildModel(TimeBasedWeatherValueData data return new TimeBasedValue<>(time, weatherValue); } + + /** + * * Validates that all required fields are present and not empty in the provided data + * + * @param data the data to validate + * @param requiredFields the fields that must be present and non-empty + * @throws FactoryException if any required field is missing or empty + */ + protected void validatedRequiredFields( + TimeBasedWeatherValueData data, Set requiredFields) { + for (String field : requiredFields) { + String value = data.getField(field); + if (value == null || value.isEmpty()) { + throw new FactoryException("The field \"" + field + "\" is missing or empty."); + } + } + } } diff --git a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactory.java index 8dbea5357..c9dbb4757 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactory.java @@ -5,6 +5,7 @@ */ package edu.ie3.datamodel.io.factory.timeseries; +import edu.ie3.datamodel.exceptions.FactoryException; import edu.ie3.datamodel.models.StandardUnits; import edu.ie3.datamodel.models.timeseries.individual.TimeBasedValue; import edu.ie3.datamodel.models.value.WeatherValue; @@ -81,6 +82,15 @@ protected List> getFields(Class entityClass) { @Override protected TimeBasedValue buildModel(TimeBasedWeatherValueData data) { + Set requiredFields = + newSet( + TIME, + DIFFUSE_IRRADIANCE, + DIRECT_IRRADIANCE, + TEMPERATURE, + WIND_VELOCITY_U, + WIND_VELOCITY_V); + validatedRequiredFields(data, requiredFields); Point coordinate = data.getCoordinate(); ZonedDateTime time = timeUtil.toZonedDateTime(data.getField(TIME)); ComparableQuantity directIrradiance = @@ -155,4 +165,21 @@ private static ComparableQuantity getWindVelocity(TimeBasedWeatherValueDa double velocity = Math.sqrt(Math.pow(u, 2) + Math.pow(v, 2)); return Quantities.getQuantity(velocity, Units.METRE_PER_SECOND).to(StandardUnits.WIND_VELOCITY); } + + /** + * * Validates that all required fields are present and not empty in the provided data + * + * @param data the data to validate + * @param requiredFields the fields that must be present and non-empty + * @throws FactoryException if any required field is missing or empty + */ + protected void validatedRequiredFields( + TimeBasedWeatherValueData data, Set requiredFields) { + for (String field : requiredFields) { + String value = data.getField(field); + if (value == null || value.isEmpty()) { + throw new FactoryException("The field \"" + field + "\" is missing or empty."); + } + } + } } diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy index 5749fe9fc..4f4a7dbdd 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy @@ -19,180 +19,205 @@ import tech.units.indriya.quantity.Quantities import tech.units.indriya.unit.Units class IconTimeBasedWeatherValueFactoryTest extends Specification { - def "A time based weather value factory for ICON column scheme determines wind velocity angle correctly"() { - given: - def data = new TimeBasedWeatherValueData([ - "u131m": u.toString(), - "v131m": v.toString(), - ], Mock(Point)) - def expected = Quantities.getQuantity(expectedValue, PowerSystemUnits.DEGREE_GEOM) - - when: - def actual = IconTimeBasedWeatherValueFactory.getWindDirection(data) - - then: - actual.getUnit() == StandardUnits.WIND_DIRECTION - QuantityUtil.isEquivalentAbs(actual, expected, 1E-10.doubleValue()) - - where: - u | v || expectedValue - 0.0 | -5.0 || 0.0 - -5.0 | -5.0 || 45.0 - -5.0 | 0.0 || 90.0 - -5.0 | 5.0 || 135.0 - 0.0 | 5.0 || 180.0 - 5.0 | 5.0 || 225.0 - 5.0 | 0.0 || 270.0 - 5.0 | -5.0 || 315.0 - } - - def "A time based weather value factory for ICON column scheme determines wind velocity correctly"() { - given: - def data = new TimeBasedWeatherValueData([ - "u131m": u.toString(), - "v131m": v.toString(), - ], Mock(Point)) - def expected = Quantities.getQuantity(expectedValue, PowerSystemUnits.METRE_PER_SECOND) - - when: - def actual = IconTimeBasedWeatherValueFactory.getWindVelocity(data) - - then: - actual.getUnit() == StandardUnits.WIND_VELOCITY - QuantityUtil.isEquivalentAbs(actual, expected, 1E-10.doubleValue()) - - where: - u | v | w || expectedValue - 0.0 | -5.0 | 0.0 || 5.0 - -5.0 | -5.0 | 10.0 || 7.071067811865 - -5.0 | 0.0 | 20.0 || 5.0 - -5.0 | 5.0 | 30.0 || 7.071067811865 - 0.0 | 5.0 | 40.0 || 5.0 - 5.0 | 5.0 | 50.0 || 7.071067811865 - 5.0 | 0.0 | 60.0 || 5.0 - 5.0 | -5.0 | 70.0 || 7.071067811865 - } - - def "A time based weather value factory for ICON column scheme builds a single time based value correctly"() { - given: - def factory = new IconTimeBasedWeatherValueFactory() - def coordinate = CosmoWeatherTestData.COORDINATE_67775 - - def parameter = [ - "time" : "2019-08-01T01:00:00Z", - "albRad" : "13.015240669", - "asobS" : "3.555093673828124", - "aswdifdS" : "1.8088226191406245", - "aswdifuS" : "0.5713421484374998", - "aswdirS" : "2.317613203124999", - "t2m" : "289.1179319051744", - "tg1" : "288.4101691197649", - "tg2" : "288.4101691197649", - "u10m" : "0.3021732864307963", - "u131m" : "2.6058700426057797", - "u20m" : "0.32384365019387784", - "u216m" : "3.9015497418041756", - "u65m" : "1.2823686334340363", - "v10m" : "1.3852550649486943", - "v131m" : "3.8391590569599927", - "v20m" : "1.3726831152710628", - "v216m" : "4.339362039492466", - "v65m" : "2.809877942347672", - "w131m" : "-0.02633474740256081", - "w20m" : "-0.0100060345167524", - "w216m" : "-0.030348050471342078", - "w65m" : "-0.01817112027569893", - "z0" : "0.955323922526438", - "coordinateId": "67775", - "p131m" : "", - "p20m" : "", - "p65m" : "", - "sobsRad" : "", - "t131m" : "" - ] - def data = new TimeBasedWeatherValueData(parameter, coordinate) - - when: - def actual = factory.buildModel(data) - - then: - actual.with { - assert it.time == TimeUtil.withDefaults.toZonedDateTime("2019-08-01T01:00:00Z") - assert it.value.coordinate == coordinate - assert it.value.solarIrradiance.directIrradiance.present - assert it.value.solarIrradiance.directIrradiance.get() == Quantities.getQuantity(0.002317613203124999, PowerSystemUnits.KILOWATT_PER_SQUAREMETRE) - assert it.value.solarIrradiance.diffuseIrradiance.present - assert it.value.solarIrradiance.diffuseIrradiance.get() == Quantities.getQuantity(0.0018088226191406245, PowerSystemUnits.KILOWATT_PER_SQUAREMETRE) - assert it.value.temperature.temperature.present - assert QuantityUtil.isEquivalentAbs(it.value.temperature.temperature.get(), Quantities.getQuantity(15.9679319051744, Units.CELSIUS)) - assert it.value.wind.direction.present - assert QuantityUtil.isEquivalentAbs(it.value.wind.direction.get(), Quantities.getQuantity(214.16711674907722, PowerSystemUnits.DEGREE_GEOM)) - assert it.value.wind.velocity.present - assert QuantityUtil.isEquivalentAbs(it.value.wind.velocity.get(), Quantities.getQuantity(4.640010877529081, PowerSystemUnits.METRE_PER_SECOND)) - assert it.value.groundTemperatureLevel1.present - assert QuantityUtil.isEquivalentAbs(it.value.groundTemperatureLevel1.get().temperature.get(), Quantities.getQuantity(15.2601691197649, Units.CELSIUS)) - assert it.value.groundTemperatureLevel2.present - assert QuantityUtil.isEquivalentAbs(it.value.groundTemperatureLevel2.get().temperature.get(), Quantities.getQuantity(15.2601691197649, Units.CELSIUS)) + def "A time based weather value factory for ICON column scheme determines wind velocity angle correctly"() { + given: + def data = new TimeBasedWeatherValueData([ + "u131m": u.toString(), + "v131m": v.toString(), + ], Mock(Point)) + def expected = Quantities.getQuantity(expectedValue, PowerSystemUnits.DEGREE_GEOM) + + when: + def actual = IconTimeBasedWeatherValueFactory.getWindDirection(data) + + then: + actual.getUnit() == StandardUnits.WIND_DIRECTION + QuantityUtil.isEquivalentAbs(actual, expected, 1E-10.doubleValue()) + + where: + u | v || expectedValue + 0.0 | -5.0 || 0.0 + -5.0 | -5.0 || 45.0 + -5.0 | 0.0 || 90.0 + -5.0 | 5.0 || 135.0 + 0.0 | 5.0 || 180.0 + 5.0 | 5.0 || 225.0 + 5.0 | 0.0 || 270.0 + 5.0 | -5.0 || 315.0 + } + + def "A time based weather value factory for ICON column scheme determines wind velocity correctly"() { + given: + def data = new TimeBasedWeatherValueData([ + "u131m": u.toString(), + "v131m": v.toString(), + ], Mock(Point)) + def expected = Quantities.getQuantity(expectedValue, PowerSystemUnits.METRE_PER_SECOND) + + when: + def actual = IconTimeBasedWeatherValueFactory.getWindVelocity(data) + + then: + actual.getUnit() == StandardUnits.WIND_VELOCITY + QuantityUtil.isEquivalentAbs(actual, expected, 1E-10.doubleValue()) + + where: + u | v | w || expectedValue + 0.0 | -5.0 | 0.0 || 5.0 + -5.0 | -5.0 | 10.0 || 7.071067811865 + -5.0 | 0.0 | 20.0 || 5.0 + -5.0 | 5.0 | 30.0 || 7.071067811865 + 0.0 | 5.0 | 40.0 || 5.0 + 5.0 | 5.0 | 50.0 || 7.071067811865 + 5.0 | 0.0 | 60.0 || 5.0 + 5.0 | -5.0 | 70.0 || 7.071067811865 + } + + def "A time based weather value factory for ICON column scheme builds a single time based value correctly"() { + given: + def factory = new IconTimeBasedWeatherValueFactory() + def coordinate = CosmoWeatherTestData.COORDINATE_67775 + + def parameter = [ + "time" : "2019-08-01T01:00:00Z", + "albRad" : "13.015240669", + "asobS" : "3.555093673828124", + "aswdifdS" : "1.8088226191406245", + "aswdifuS" : "0.5713421484374998", + "aswdirS" : "2.317613203124999", + "t2m" : "289.1179319051744", + "tg1" : "288.4101691197649", + "tg2" : "288.4101691197649", + "u10m" : "0.3021732864307963", + "u131m" : "2.6058700426057797", + "u20m" : "0.32384365019387784", + "u216m" : "3.9015497418041756", + "u65m" : "1.2823686334340363", + "v10m" : "1.3852550649486943", + "v131m" : "3.8391590569599927", + "v20m" : "1.3726831152710628", + "v216m" : "4.339362039492466", + "v65m" : "2.809877942347672", + "w131m" : "-0.02633474740256081", + "w20m" : "-0.0100060345167524", + "w216m" : "-0.030348050471342078", + "w65m" : "-0.01817112027569893", + "z0" : "0.955323922526438", + "coordinateId": "67775", + "p131m" : "", + "p20m" : "", + "p65m" : "", + "sobsRad" : "", + "t131m" : "" + ] + def data = new TimeBasedWeatherValueData(parameter, coordinate) + + when: + def actual = factory.buildModel(data) + + then: + actual.with { + assert it.time == TimeUtil.withDefaults.toZonedDateTime("2019-08-01T01:00:00Z") + assert it.value.coordinate == coordinate + assert it.value.solarIrradiance.directIrradiance.present + assert it.value.solarIrradiance.directIrradiance.get() == Quantities.getQuantity(0.002317613203124999, PowerSystemUnits.KILOWATT_PER_SQUAREMETRE) + assert it.value.solarIrradiance.diffuseIrradiance.present + assert it.value.solarIrradiance.diffuseIrradiance.get() == Quantities.getQuantity(0.0018088226191406245, PowerSystemUnits.KILOWATT_PER_SQUAREMETRE) + assert it.value.temperature.temperature.present + assert QuantityUtil.isEquivalentAbs(it.value.temperature.temperature.get(), Quantities.getQuantity(15.9679319051744, Units.CELSIUS)) + assert it.value.wind.direction.present + assert QuantityUtil.isEquivalentAbs(it.value.wind.direction.get(), Quantities.getQuantity(214.16711674907722, PowerSystemUnits.DEGREE_GEOM)) + assert it.value.wind.velocity.present + assert QuantityUtil.isEquivalentAbs(it.value.wind.velocity.get(), Quantities.getQuantity(4.640010877529081, PowerSystemUnits.METRE_PER_SECOND)) + assert it.value.groundTemperatureLevel1.present + assert QuantityUtil.isEquivalentAbs(it.value.groundTemperatureLevel1.get().temperature.get(), Quantities.getQuantity(15.2601691197649, Units.CELSIUS)) + assert it.value.groundTemperatureLevel2.present + assert QuantityUtil.isEquivalentAbs(it.value.groundTemperatureLevel2.get().temperature.get(), Quantities.getQuantity(15.2601691197649, Units.CELSIUS)) + } + } + + def "A IconTimeBasedWeatherValueFactory should throw FactoryException if required field is missing"() { + given: + def factory = new IconTimeBasedWeatherValueFactory() + def coordinate = CosmoWeatherTestData.COORDINATE_67775 + def time = TimeUtil.withDefaults.toZonedDateTime("2019-01-01T00:00:00Z") + + // Missing 'aswdirS' (Direct Irradiance) + Map parameter = [ + "time" : TimeUtil.withDefaults.toString(time), + "aswdifdS" : "1.0", + "t2m" : "2.0", + "u131m" : "3.0", + "v131m" : "4.0", + "coordinateId": "67775" + ] + + def data = new TimeBasedWeatherValueData(parameter, coordinate) + + when: + factory.buildModel(data) + + then: + thrown(FactoryException) + } + + def "Smoke Test: This IconTimeBasedWeatherValueFactory should fail since expected results doesn't match input"() { + given: + def factory = new IconTimeBasedWeatherValueFactory() + def coordinate = CosmoWeatherTestData.COORDINATE_67775 + def time = TimeUtil.withDefaults.toZonedDateTime("2019-01-01T00:00:00Z") + + Map parameter = [ + "time" : TimeUtil.withDefaults.toString(time), + "aswdifdS" : "1.0", + "aswdirS" : "2.0", + "t2m" : "3.0", + "u131m" : "4.0", + "v131m" : "5.0", + "coordinateId": "50000" + ] + + def data = new TimeBasedWeatherValueData(parameter, coordinate) + + def expectedResults = new TimeBasedValue( + time, new WeatherValue(coordinate, + Quantities.getQuantity(5d, StandardUnits.SOLAR_IRRADIANCE), + Quantities.getQuantity(4d, StandardUnits.SOLAR_IRRADIANCE), + Quantities.getQuantity(3d, StandardUnits.TEMPERATURE), + Quantities.getQuantity(2d, StandardUnits.WIND_DIRECTION), + Quantities.getQuantity(1d, StandardUnits.WIND_VELOCITY), + Optional.empty(), + Optional.empty())) + + when: + def model = factory.buildModel(data) + + then: + !model.equals(expectedResults) + } + + def "A IconTimeBasedWeatherValueFactory should throw an Exception if the required field 't2m' is missing"() { + given: + def factory = new IconTimeBasedWeatherValueFactory() + def coordinate = CosmoWeatherTestData.COORDINATE_67775 + + Map parameter = [ + "time" : "2019-01-01T00:00:00Z", + "aswdifdS" : "1.0", + "aswdirS" : "2.0", + "t2m" : "", + "u131m" : "4.0", + "v131m" : "5.0", + "coordinateId": "67775" + ] + + def data = new TimeBasedWeatherValueData(parameter, coordinate) + + when: + factory.buildModel(data) + + then: + def exception = thrown(FactoryException) + exception.message.toLowerCase().contains("t2m") } - } - - def "A IconTimeBasedWeatherValueFactory should throw FactoryException if required field is missing"() { - given: - def factory = new IconTimeBasedWeatherValueFactory() - def coordinate = CosmoWeatherTestData.COORDINATE_67775 - def time = TimeUtil.withDefaults.toZonedDateTime("2019-01-01T00:00:00Z") - - // Missing 'aswdirS' (Direct Irradiance) - Map parameter = [ - "time" : TimeUtil.withDefaults.toString(time), - "aswdifdS" : "1.0", - "t2m" : "2.0", - "u131m" : "3.0", - "v131m" : "4.0", - "coordinateId": "67775" - ] - - def data = new TimeBasedWeatherValueData(parameter, coordinate) - - when: - factory.buildModel(data) - - then: - thrown(FactoryException) - } - - def "Smoke Test: This IconTimeBasedWeatherValueFactory should fail since expected results doesn't match input"() { - given: - def factory = new IconTimeBasedWeatherValueFactory() - def coordinate = CosmoWeatherTestData.COORDINATE_67775 - def time = TimeUtil.withDefaults.toZonedDateTime("2019-01-01T00:00:00Z") - - Map parameter = [ - "time" : TimeUtil.withDefaults.toString(time), - "aswdifdS" : "1.0", - "aswdirS" : "2.0", - "t2m" : "3.0", - "u131m" : "4.0", - "v131m" : "5.0", - "coordinateId": "50000" - ] - - def data = new TimeBasedWeatherValueData(parameter, coordinate) - - def expectedResults = new TimeBasedValue( - time, new WeatherValue(coordinate, - Quantities.getQuantity(5d, StandardUnits.SOLAR_IRRADIANCE), - Quantities.getQuantity(4d, StandardUnits.SOLAR_IRRADIANCE), - Quantities.getQuantity(3d, StandardUnits.TEMPERATURE), - Quantities.getQuantity(2d, StandardUnits.WIND_DIRECTION), - Quantities.getQuantity(1d, StandardUnits.WIND_VELOCITY), - Optional.empty(), - Optional.empty())) - - when: - def model = factory.buildModel(data) - - then: - !model.equals(expectedResults) - } } \ No newline at end of file From b2376f1707d4e0bfa75443e40e2fe64682076fa6 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier <155652256+pierrepetersmeier@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:22:29 +0100 Subject: [PATCH 2/8] Clarify required and optional ground temperature parameters in weather source documentation --- docs/readthedocs/models/input/additionaldata/weathersource.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/readthedocs/models/input/additionaldata/weathersource.md b/docs/readthedocs/models/input/additionaldata/weathersource.md index a59e79c55..ea8c8437b 100644 --- a/docs/readthedocs/models/input/additionaldata/weathersource.md +++ b/docs/readthedocs/models/input/additionaldata/weathersource.md @@ -71,5 +71,7 @@ Weather data is comprised of five key components: - Ground temperature at level 2 for this coordinate. - K (Kelvin) ``` +All components listed above are required, except groundTemperatureLevel1 and groundTemperatureLevel2, which are optional. + Weather data in COSMO and ICON formats is supported. Additional optional weather data can also be provided. The ground temperature measurements at level 1 and level 2 depth are used. Underground cables are typically laid at around 80 cm depth. \ No newline at end of file From 34cebaff0e5c4bcda36dfd6c007c1c9102e50a18 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier <155652256+pierrepetersmeier@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:30:26 +0100 Subject: [PATCH 3/8] fmt --- ...conTimeBasedWeatherValueFactoryTest.groovy | 400 +++++++++--------- 1 file changed, 200 insertions(+), 200 deletions(-) diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy index 4f4a7dbdd..303f60b28 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy @@ -19,205 +19,205 @@ import tech.units.indriya.quantity.Quantities import tech.units.indriya.unit.Units class IconTimeBasedWeatherValueFactoryTest extends Specification { - def "A time based weather value factory for ICON column scheme determines wind velocity angle correctly"() { - given: - def data = new TimeBasedWeatherValueData([ - "u131m": u.toString(), - "v131m": v.toString(), - ], Mock(Point)) - def expected = Quantities.getQuantity(expectedValue, PowerSystemUnits.DEGREE_GEOM) - - when: - def actual = IconTimeBasedWeatherValueFactory.getWindDirection(data) - - then: - actual.getUnit() == StandardUnits.WIND_DIRECTION - QuantityUtil.isEquivalentAbs(actual, expected, 1E-10.doubleValue()) - - where: - u | v || expectedValue - 0.0 | -5.0 || 0.0 - -5.0 | -5.0 || 45.0 - -5.0 | 0.0 || 90.0 - -5.0 | 5.0 || 135.0 - 0.0 | 5.0 || 180.0 - 5.0 | 5.0 || 225.0 - 5.0 | 0.0 || 270.0 - 5.0 | -5.0 || 315.0 - } - - def "A time based weather value factory for ICON column scheme determines wind velocity correctly"() { - given: - def data = new TimeBasedWeatherValueData([ - "u131m": u.toString(), - "v131m": v.toString(), - ], Mock(Point)) - def expected = Quantities.getQuantity(expectedValue, PowerSystemUnits.METRE_PER_SECOND) - - when: - def actual = IconTimeBasedWeatherValueFactory.getWindVelocity(data) - - then: - actual.getUnit() == StandardUnits.WIND_VELOCITY - QuantityUtil.isEquivalentAbs(actual, expected, 1E-10.doubleValue()) - - where: - u | v | w || expectedValue - 0.0 | -5.0 | 0.0 || 5.0 - -5.0 | -5.0 | 10.0 || 7.071067811865 - -5.0 | 0.0 | 20.0 || 5.0 - -5.0 | 5.0 | 30.0 || 7.071067811865 - 0.0 | 5.0 | 40.0 || 5.0 - 5.0 | 5.0 | 50.0 || 7.071067811865 - 5.0 | 0.0 | 60.0 || 5.0 - 5.0 | -5.0 | 70.0 || 7.071067811865 - } - - def "A time based weather value factory for ICON column scheme builds a single time based value correctly"() { - given: - def factory = new IconTimeBasedWeatherValueFactory() - def coordinate = CosmoWeatherTestData.COORDINATE_67775 - - def parameter = [ - "time" : "2019-08-01T01:00:00Z", - "albRad" : "13.015240669", - "asobS" : "3.555093673828124", - "aswdifdS" : "1.8088226191406245", - "aswdifuS" : "0.5713421484374998", - "aswdirS" : "2.317613203124999", - "t2m" : "289.1179319051744", - "tg1" : "288.4101691197649", - "tg2" : "288.4101691197649", - "u10m" : "0.3021732864307963", - "u131m" : "2.6058700426057797", - "u20m" : "0.32384365019387784", - "u216m" : "3.9015497418041756", - "u65m" : "1.2823686334340363", - "v10m" : "1.3852550649486943", - "v131m" : "3.8391590569599927", - "v20m" : "1.3726831152710628", - "v216m" : "4.339362039492466", - "v65m" : "2.809877942347672", - "w131m" : "-0.02633474740256081", - "w20m" : "-0.0100060345167524", - "w216m" : "-0.030348050471342078", - "w65m" : "-0.01817112027569893", - "z0" : "0.955323922526438", - "coordinateId": "67775", - "p131m" : "", - "p20m" : "", - "p65m" : "", - "sobsRad" : "", - "t131m" : "" - ] - def data = new TimeBasedWeatherValueData(parameter, coordinate) - - when: - def actual = factory.buildModel(data) - - then: - actual.with { - assert it.time == TimeUtil.withDefaults.toZonedDateTime("2019-08-01T01:00:00Z") - assert it.value.coordinate == coordinate - assert it.value.solarIrradiance.directIrradiance.present - assert it.value.solarIrradiance.directIrradiance.get() == Quantities.getQuantity(0.002317613203124999, PowerSystemUnits.KILOWATT_PER_SQUAREMETRE) - assert it.value.solarIrradiance.diffuseIrradiance.present - assert it.value.solarIrradiance.diffuseIrradiance.get() == Quantities.getQuantity(0.0018088226191406245, PowerSystemUnits.KILOWATT_PER_SQUAREMETRE) - assert it.value.temperature.temperature.present - assert QuantityUtil.isEquivalentAbs(it.value.temperature.temperature.get(), Quantities.getQuantity(15.9679319051744, Units.CELSIUS)) - assert it.value.wind.direction.present - assert QuantityUtil.isEquivalentAbs(it.value.wind.direction.get(), Quantities.getQuantity(214.16711674907722, PowerSystemUnits.DEGREE_GEOM)) - assert it.value.wind.velocity.present - assert QuantityUtil.isEquivalentAbs(it.value.wind.velocity.get(), Quantities.getQuantity(4.640010877529081, PowerSystemUnits.METRE_PER_SECOND)) - assert it.value.groundTemperatureLevel1.present - assert QuantityUtil.isEquivalentAbs(it.value.groundTemperatureLevel1.get().temperature.get(), Quantities.getQuantity(15.2601691197649, Units.CELSIUS)) - assert it.value.groundTemperatureLevel2.present - assert QuantityUtil.isEquivalentAbs(it.value.groundTemperatureLevel2.get().temperature.get(), Quantities.getQuantity(15.2601691197649, Units.CELSIUS)) - } - } - - def "A IconTimeBasedWeatherValueFactory should throw FactoryException if required field is missing"() { - given: - def factory = new IconTimeBasedWeatherValueFactory() - def coordinate = CosmoWeatherTestData.COORDINATE_67775 - def time = TimeUtil.withDefaults.toZonedDateTime("2019-01-01T00:00:00Z") - - // Missing 'aswdirS' (Direct Irradiance) - Map parameter = [ - "time" : TimeUtil.withDefaults.toString(time), - "aswdifdS" : "1.0", - "t2m" : "2.0", - "u131m" : "3.0", - "v131m" : "4.0", - "coordinateId": "67775" - ] - - def data = new TimeBasedWeatherValueData(parameter, coordinate) - - when: - factory.buildModel(data) - - then: - thrown(FactoryException) - } - - def "Smoke Test: This IconTimeBasedWeatherValueFactory should fail since expected results doesn't match input"() { - given: - def factory = new IconTimeBasedWeatherValueFactory() - def coordinate = CosmoWeatherTestData.COORDINATE_67775 - def time = TimeUtil.withDefaults.toZonedDateTime("2019-01-01T00:00:00Z") - - Map parameter = [ - "time" : TimeUtil.withDefaults.toString(time), - "aswdifdS" : "1.0", - "aswdirS" : "2.0", - "t2m" : "3.0", - "u131m" : "4.0", - "v131m" : "5.0", - "coordinateId": "50000" - ] - - def data = new TimeBasedWeatherValueData(parameter, coordinate) - - def expectedResults = new TimeBasedValue( - time, new WeatherValue(coordinate, - Quantities.getQuantity(5d, StandardUnits.SOLAR_IRRADIANCE), - Quantities.getQuantity(4d, StandardUnits.SOLAR_IRRADIANCE), - Quantities.getQuantity(3d, StandardUnits.TEMPERATURE), - Quantities.getQuantity(2d, StandardUnits.WIND_DIRECTION), - Quantities.getQuantity(1d, StandardUnits.WIND_VELOCITY), - Optional.empty(), - Optional.empty())) - - when: - def model = factory.buildModel(data) - - then: - !model.equals(expectedResults) - } - - def "A IconTimeBasedWeatherValueFactory should throw an Exception if the required field 't2m' is missing"() { - given: - def factory = new IconTimeBasedWeatherValueFactory() - def coordinate = CosmoWeatherTestData.COORDINATE_67775 - - Map parameter = [ - "time" : "2019-01-01T00:00:00Z", - "aswdifdS" : "1.0", - "aswdirS" : "2.0", - "t2m" : "", - "u131m" : "4.0", - "v131m" : "5.0", - "coordinateId": "67775" - ] - - def data = new TimeBasedWeatherValueData(parameter, coordinate) - - when: - factory.buildModel(data) - - then: - def exception = thrown(FactoryException) - exception.message.toLowerCase().contains("t2m") + def "A time based weather value factory for ICON column scheme determines wind velocity angle correctly"() { + given: + def data = new TimeBasedWeatherValueData([ + "u131m": u.toString(), + "v131m": v.toString(), + ], Mock(Point)) + def expected = Quantities.getQuantity(expectedValue, PowerSystemUnits.DEGREE_GEOM) + + when: + def actual = IconTimeBasedWeatherValueFactory.getWindDirection(data) + + then: + actual.getUnit() == StandardUnits.WIND_DIRECTION + QuantityUtil.isEquivalentAbs(actual, expected, 1E-10.doubleValue()) + + where: + u | v || expectedValue + 0.0 | -5.0 || 0.0 + -5.0 | -5.0 || 45.0 + -5.0 | 0.0 || 90.0 + -5.0 | 5.0 || 135.0 + 0.0 | 5.0 || 180.0 + 5.0 | 5.0 || 225.0 + 5.0 | 0.0 || 270.0 + 5.0 | -5.0 || 315.0 + } + + def "A time based weather value factory for ICON column scheme determines wind velocity correctly"() { + given: + def data = new TimeBasedWeatherValueData([ + "u131m": u.toString(), + "v131m": v.toString(), + ], Mock(Point)) + def expected = Quantities.getQuantity(expectedValue, PowerSystemUnits.METRE_PER_SECOND) + + when: + def actual = IconTimeBasedWeatherValueFactory.getWindVelocity(data) + + then: + actual.getUnit() == StandardUnits.WIND_VELOCITY + QuantityUtil.isEquivalentAbs(actual, expected, 1E-10.doubleValue()) + + where: + u | v | w || expectedValue + 0.0 | -5.0 | 0.0 || 5.0 + -5.0 | -5.0 | 10.0 || 7.071067811865 + -5.0 | 0.0 | 20.0 || 5.0 + -5.0 | 5.0 | 30.0 || 7.071067811865 + 0.0 | 5.0 | 40.0 || 5.0 + 5.0 | 5.0 | 50.0 || 7.071067811865 + 5.0 | 0.0 | 60.0 || 5.0 + 5.0 | -5.0 | 70.0 || 7.071067811865 + } + + def "A time based weather value factory for ICON column scheme builds a single time based value correctly"() { + given: + def factory = new IconTimeBasedWeatherValueFactory() + def coordinate = CosmoWeatherTestData.COORDINATE_67775 + + def parameter = [ + "time" : "2019-08-01T01:00:00Z", + "albRad" : "13.015240669", + "asobS" : "3.555093673828124", + "aswdifdS" : "1.8088226191406245", + "aswdifuS" : "0.5713421484374998", + "aswdirS" : "2.317613203124999", + "t2m" : "289.1179319051744", + "tg1" : "288.4101691197649", + "tg2" : "288.4101691197649", + "u10m" : "0.3021732864307963", + "u131m" : "2.6058700426057797", + "u20m" : "0.32384365019387784", + "u216m" : "3.9015497418041756", + "u65m" : "1.2823686334340363", + "v10m" : "1.3852550649486943", + "v131m" : "3.8391590569599927", + "v20m" : "1.3726831152710628", + "v216m" : "4.339362039492466", + "v65m" : "2.809877942347672", + "w131m" : "-0.02633474740256081", + "w20m" : "-0.0100060345167524", + "w216m" : "-0.030348050471342078", + "w65m" : "-0.01817112027569893", + "z0" : "0.955323922526438", + "coordinateId": "67775", + "p131m" : "", + "p20m" : "", + "p65m" : "", + "sobsRad" : "", + "t131m" : "" + ] + def data = new TimeBasedWeatherValueData(parameter, coordinate) + + when: + def actual = factory.buildModel(data) + + then: + actual.with { + assert it.time == TimeUtil.withDefaults.toZonedDateTime("2019-08-01T01:00:00Z") + assert it.value.coordinate == coordinate + assert it.value.solarIrradiance.directIrradiance.present + assert it.value.solarIrradiance.directIrradiance.get() == Quantities.getQuantity(0.002317613203124999, PowerSystemUnits.KILOWATT_PER_SQUAREMETRE) + assert it.value.solarIrradiance.diffuseIrradiance.present + assert it.value.solarIrradiance.diffuseIrradiance.get() == Quantities.getQuantity(0.0018088226191406245, PowerSystemUnits.KILOWATT_PER_SQUAREMETRE) + assert it.value.temperature.temperature.present + assert QuantityUtil.isEquivalentAbs(it.value.temperature.temperature.get(), Quantities.getQuantity(15.9679319051744, Units.CELSIUS)) + assert it.value.wind.direction.present + assert QuantityUtil.isEquivalentAbs(it.value.wind.direction.get(), Quantities.getQuantity(214.16711674907722, PowerSystemUnits.DEGREE_GEOM)) + assert it.value.wind.velocity.present + assert QuantityUtil.isEquivalentAbs(it.value.wind.velocity.get(), Quantities.getQuantity(4.640010877529081, PowerSystemUnits.METRE_PER_SECOND)) + assert it.value.groundTemperatureLevel1.present + assert QuantityUtil.isEquivalentAbs(it.value.groundTemperatureLevel1.get().temperature.get(), Quantities.getQuantity(15.2601691197649, Units.CELSIUS)) + assert it.value.groundTemperatureLevel2.present + assert QuantityUtil.isEquivalentAbs(it.value.groundTemperatureLevel2.get().temperature.get(), Quantities.getQuantity(15.2601691197649, Units.CELSIUS)) } + } + + def "A IconTimeBasedWeatherValueFactory should throw FactoryException if required field is missing"() { + given: + def factory = new IconTimeBasedWeatherValueFactory() + def coordinate = CosmoWeatherTestData.COORDINATE_67775 + def time = TimeUtil.withDefaults.toZonedDateTime("2019-01-01T00:00:00Z") + + // Missing 'aswdirS' (Direct Irradiance) + Map parameter = [ + "time" : TimeUtil.withDefaults.toString(time), + "aswdifdS" : "1.0", + "t2m" : "2.0", + "u131m" : "3.0", + "v131m" : "4.0", + "coordinateId": "67775" + ] + + def data = new TimeBasedWeatherValueData(parameter, coordinate) + + when: + factory.buildModel(data) + + then: + thrown(FactoryException) + } + + def "Smoke Test: This IconTimeBasedWeatherValueFactory should fail since expected results doesn't match input"() { + given: + def factory = new IconTimeBasedWeatherValueFactory() + def coordinate = CosmoWeatherTestData.COORDINATE_67775 + def time = TimeUtil.withDefaults.toZonedDateTime("2019-01-01T00:00:00Z") + + Map parameter = [ + "time" : TimeUtil.withDefaults.toString(time), + "aswdifdS" : "1.0", + "aswdirS" : "2.0", + "t2m" : "3.0", + "u131m" : "4.0", + "v131m" : "5.0", + "coordinateId": "50000" + ] + + def data = new TimeBasedWeatherValueData(parameter, coordinate) + + def expectedResults = new TimeBasedValue( + time, new WeatherValue(coordinate, + Quantities.getQuantity(5d, StandardUnits.SOLAR_IRRADIANCE), + Quantities.getQuantity(4d, StandardUnits.SOLAR_IRRADIANCE), + Quantities.getQuantity(3d, StandardUnits.TEMPERATURE), + Quantities.getQuantity(2d, StandardUnits.WIND_DIRECTION), + Quantities.getQuantity(1d, StandardUnits.WIND_VELOCITY), + Optional.empty(), + Optional.empty())) + + when: + def model = factory.buildModel(data) + + then: + !model.equals(expectedResults) + } + + def "A IconTimeBasedWeatherValueFactory should throw an Exception if the required field 't2m' is missing"() { + given: + def factory = new IconTimeBasedWeatherValueFactory() + def coordinate = CosmoWeatherTestData.COORDINATE_67775 + + Map parameter = [ + "time" : "2019-01-01T00:00:00Z", + "aswdifdS" : "1.0", + "aswdirS" : "2.0", + "t2m" : "", + "u131m" : "4.0", + "v131m" : "5.0", + "coordinateId": "67775" + ] + + def data = new TimeBasedWeatherValueData(parameter, coordinate) + + when: + factory.buildModel(data) + + then: + def exception = thrown(FactoryException) + exception.message.toLowerCase().contains("t2m") + } } \ No newline at end of file From 774650640c2d96ea203162003f2950229756412d Mon Sep 17 00:00:00 2001 From: pierrepetersmeier <155652256+pierrepetersmeier@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:02:30 +0100 Subject: [PATCH 4/8] Add test to validate required fields in CosmoTimeBasedWeatherValueFactory --- ...smoTimeBasedWeatherValueFactoryTest.groovy | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy index c6d44564c..60ba34ef4 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy @@ -13,17 +13,16 @@ import edu.ie3.test.common.CosmoWeatherTestData import edu.ie3.util.TimeUtil import spock.lang.Specification import tech.units.indriya.quantity.Quantities - +def class CosmoTimeBasedWeatherValueFactoryTest extends Specification { - def "A PsdmTimeBasedWeatherValueFactory should be able to create time series with missing values"() { + def "A PsdmTimeBasedWeatherValueFactory should throw an Exception if a required field is empty"() { given: def factory = new CosmoTimeBasedWeatherValueFactory() def coordinate = CosmoWeatherTestData.COORDINATE_193186 - def time = TimeUtil.withDefaults.toZonedDateTime("2019-01-01T00:00:00Z") Map parameter = [ - "time" : TimeUtil.withDefaults.toString(time), + "time" : "2019-01-01T00:00:00Z", "diffuseIrradiance" : "282.671997070312", "directIrradiance" : "286.872985839844", "temperature" : "", @@ -35,23 +34,15 @@ class CosmoTimeBasedWeatherValueFactoryTest extends Specification { def data = new TimeBasedWeatherValueData(parameter, coordinate) - def expectedResults = new TimeBasedValue( - time, new WeatherValue(coordinate, - Quantities.getQuantity(286.872985839844d, StandardUnits.SOLAR_IRRADIANCE), - Quantities.getQuantity(282.671997070312d, StandardUnits.SOLAR_IRRADIANCE), - null, - Quantities.getQuantity(0d, StandardUnits.WIND_DIRECTION), - Quantities.getQuantity(1.66103506088257d, StandardUnits.WIND_VELOCITY), - Optional.empty(), - Optional.empty())) - when: - def model = factory.buildModel(data) + factory.buildModel(data) then: - Objects.equals(model,expectedResults) + def exception = thrown(FactoryException) + exception.message.toLowerCase().contains("temperature") } + def "A PsdmTimeBasedWeatherValueFactory should be able to create time series values"() { given: def factory = new CosmoTimeBasedWeatherValueFactory() From b76feb0907151a008647a175f8a9edaf47c7bbed Mon Sep 17 00:00:00 2001 From: pierrepetersmeier <155652256+pierrepetersmeier@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:06:19 +0100 Subject: [PATCH 5/8] fmt --- .../timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy index 60ba34ef4..22b252a75 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy @@ -13,7 +13,7 @@ import edu.ie3.test.common.CosmoWeatherTestData import edu.ie3.util.TimeUtil import spock.lang.Specification import tech.units.indriya.quantity.Quantities -def + class CosmoTimeBasedWeatherValueFactoryTest extends Specification { def "A PsdmTimeBasedWeatherValueFactory should throw an Exception if a required field is empty"() { From d28bdffb7143fb86549e61fd7de36fc52db282e9 Mon Sep 17 00:00:00 2001 From: Pierre <155652256+pierrepetersmeier@users.noreply.github.com> Date: Thu, 5 Feb 2026 09:43:28 +0100 Subject: [PATCH 6/8] Update CHANGELOG.md Co-authored-by: Daniel Feismann <98817556+danielfeismann@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbe610091..88fd24b66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added converters documentation [#1139](https://github.com/ie3-institute/PowerSystemDataModel/issues/1139) - Added abstraction for power value sources [#1438](https://github.com/ie3-institute/PowerSystemDataModel/issues/1438) - Add ground temperatures level 1 and 2 as option to weather data. [#1343](https://github.com/ie3-institute/PowerSystemDataModel/issues/1343) -- Add validation for required fields in Icon and Cosmo time-based weather value factories [#1537](https://github.com/ie3-institute/PowerSystemDataModel/issues/1537) +- Add validation for required fields in ICON and COSMO `TimebasedWeatherValueFactories` [#1537](https://github.com/ie3-institute/PowerSystemDataModel/issues/1537) ### Fixed - Fixed small issues in tests [#1400](https://github.com/ie3-institute/PowerSystemDataModel/issues/1400) From d0ccf2e12a369f8e7d2ba4002de3c6724668614e Mon Sep 17 00:00:00 2001 From: pierrepetersmeier <155652256+pierrepetersmeier@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:09:05 +0100 Subject: [PATCH 7/8] Update exception messages in weather value factory tests for clarity --- .../timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy | 2 +- .../timeseries/IconTimeBasedWeatherValueFactoryTest.groovy | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy index 22b252a75..fb30156b5 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/CosmoTimeBasedWeatherValueFactoryTest.groovy @@ -39,7 +39,7 @@ class CosmoTimeBasedWeatherValueFactoryTest extends Specification { then: def exception = thrown(FactoryException) - exception.message.toLowerCase().contains("temperature") + exception.message == 'The field "temperature" is missing or empty.' } diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy index 303f60b28..85f8b3659 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy @@ -218,6 +218,6 @@ class IconTimeBasedWeatherValueFactoryTest extends Specification { then: def exception = thrown(FactoryException) - exception.message.toLowerCase().contains("t2m") + exception.message == 'The field "t2m" is missing or empty.' } } \ No newline at end of file From 26b7c7c07f9f8f36375ffb410bde195ba65b5bb6 Mon Sep 17 00:00:00 2001 From: Pierre <155652256+pierrepetersmeier@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:14:04 +0100 Subject: [PATCH 8/8] Update test case for missing 't2m' field to empty --- .../timeseries/IconTimeBasedWeatherValueFactoryTest.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy index 85f8b3659..e80771825 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/timeseries/IconTimeBasedWeatherValueFactoryTest.groovy @@ -196,7 +196,7 @@ class IconTimeBasedWeatherValueFactoryTest extends Specification { !model.equals(expectedResults) } - def "A IconTimeBasedWeatherValueFactory should throw an Exception if the required field 't2m' is missing"() { + def "A IconTimeBasedWeatherValueFactory should throw an Exception if the required field 't2m' is empty"() { given: def factory = new IconTimeBasedWeatherValueFactory() def coordinate = CosmoWeatherTestData.COORDINATE_67775 @@ -220,4 +220,4 @@ class IconTimeBasedWeatherValueFactoryTest extends Specification { def exception = thrown(FactoryException) exception.message == 'The field "t2m" is missing or empty.' } -} \ No newline at end of file +}