diff --git a/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java b/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java index e7d5e5da..15bb978e 100644 --- a/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java +++ b/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java @@ -8,8 +8,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Builder; -import lombok.EqualsAndHashCode; -import lombok.Getter; +import lombok.Data; +import lombok.With; import org.springframework.util.CollectionUtils; import java.util.List; @@ -17,24 +17,23 @@ /** * @author David Braquart - * == a powsybl OperationalLimitsGroup == a LimitSet */ @Builder -@Getter -@EqualsAndHashCode +@Data +@With // get rule to not have setter in DTOs but mergeCurrentLimits() need to get then set data... public class CurrentLimitsData { // may be null in case we just need the selected limit set and don't really need its name/id @JsonInclude(JsonInclude.Include.NON_NULL) - private String id; + private final String id; @JsonInclude(JsonInclude.Include.NON_NULL) - private Double permanentLimit; + private final Double permanentLimit; @JsonInclude(JsonInclude.Include.NON_NULL) - private List temporaryLimits; + private final List temporaryLimits; @JsonInclude - private Applicability applicability; + private final Applicability applicability; public enum Applicability { EQUIPMENT, // applied to both sides @@ -51,4 +50,3 @@ public boolean limitsEquals(CurrentLimitsData other) { && Objects.equals(temporaryLimits, other.temporaryLimits); } } - diff --git a/src/main/java/org/gridsuite/network/map/dto/mapper/LineInfosMapper.java b/src/main/java/org/gridsuite/network/map/dto/mapper/LineInfosMapper.java index b661276f..bbd1a2a3 100644 --- a/src/main/java/org/gridsuite/network/map/dto/mapper/LineInfosMapper.java +++ b/src/main/java/org/gridsuite/network/map/dto/mapper/LineInfosMapper.java @@ -65,14 +65,12 @@ private static LineFormInfos toFormInfos(Identifiable identifiable) { .b1(line.getB1()) .g2(line.getG2()) .b2(line.getB2()) - .properties(getProperties(line)) .selectedOperationalLimitsGroup1(line.getSelectedOperationalLimitsGroupId1().orElse(null)) - .selectedOperationalLimitsGroup2(line.getSelectedOperationalLimitsGroupId2().orElse(null)); - - mergeCurrentLimits(line.getOperationalLimitsGroups1(), line.getOperationalLimitsGroups2(), builder::currentLimits); - - buildCurrentLimits(line.getOperationalLimitsGroups1(), builder::currentLimits1); - buildCurrentLimits(line.getOperationalLimitsGroups2(), builder::currentLimits2); + .selectedOperationalLimitsGroup2(line.getSelectedOperationalLimitsGroupId2().orElse(null)) + .currentLimits1(buildCurrentLimits(line.getOperationalLimitsGroups1())) + .currentLimits2(buildCurrentLimits(line.getOperationalLimitsGroups2())) + .currentLimits(mergeCurrentLimits(line.getOperationalLimitsGroups1(), line.getOperationalLimitsGroups2())) + .properties(getProperties(line)); builder.busOrBusbarSectionId1(getBusOrBusbarSection(terminal1)) .busOrBusbarSectionId2(getBusOrBusbarSection(terminal2)); @@ -207,8 +205,8 @@ private static LineTooltipInfos toTooltipInfos(Identifiable identifiable, Dou .b1(line.getB1()) .b2(line.getB2()); - line.getCurrentLimits1().ifPresent(limits1 -> builder.currentLimits1(toMapDataCurrentLimits(limits1))); - line.getCurrentLimits2().ifPresent(limits2 -> builder.currentLimits2(toMapDataCurrentLimits(limits2))); + line.getCurrentLimits1().ifPresent(limits1 -> builder.currentLimits1(toCurrentLimitsData(limits1))); + line.getCurrentLimits2().ifPresent(limits2 -> builder.currentLimits2(toCurrentLimitsData(limits2))); return builder.build(); } diff --git a/src/main/java/org/gridsuite/network/map/dto/mapper/TieLineInfosMapper.java b/src/main/java/org/gridsuite/network/map/dto/mapper/TieLineInfosMapper.java index 572fa1a7..0e3fa176 100644 --- a/src/main/java/org/gridsuite/network/map/dto/mapper/TieLineInfosMapper.java +++ b/src/main/java/org/gridsuite/network/map/dto/mapper/TieLineInfosMapper.java @@ -56,8 +56,8 @@ private static TieLineMapInfos toMapInfos(Identifiable identifiable, Double d .i2(nullIfNan(ElementUtils.computeIntensity(terminal2, dcPowerFactor))) .operatingStatus(toOperatingStatus(tieLine)); - tieLine.getCurrentLimits1().ifPresent(limits1 -> builder.currentLimits1(toMapDataCurrentLimits(limits1))); - tieLine.getCurrentLimits2().ifPresent(limits2 -> builder.currentLimits2(toMapDataCurrentLimits(limits2))); + tieLine.getCurrentLimits1().ifPresent(limits1 -> builder.currentLimits1(toCurrentLimitsData(limits1))); + tieLine.getCurrentLimits2().ifPresent(limits2 -> builder.currentLimits2(toCurrentLimitsData(limits2))); return builder.build(); } diff --git a/src/main/java/org/gridsuite/network/map/dto/mapper/TwoWindingsTransformerInfosMapper.java b/src/main/java/org/gridsuite/network/map/dto/mapper/TwoWindingsTransformerInfosMapper.java index 870beffb..ef1c85c0 100644 --- a/src/main/java/org/gridsuite/network/map/dto/mapper/TwoWindingsTransformerInfosMapper.java +++ b/src/main/java/org/gridsuite/network/map/dto/mapper/TwoWindingsTransformerInfosMapper.java @@ -66,6 +66,11 @@ private static TwoWindingsTransformerFormInfos toFormInfos(Identifiable ident .g(twoWT.getG()) .ratedU1(twoWT.getRatedU1()) .ratedU2(twoWT.getRatedU2()) + .selectedOperationalLimitsGroup1(twoWT.getSelectedOperationalLimitsGroupId1().orElse(null)) + .selectedOperationalLimitsGroup2(twoWT.getSelectedOperationalLimitsGroupId2().orElse(null)) + .currentLimits1(buildCurrentLimits(twoWT.getOperationalLimitsGroups1())) + .currentLimits2(buildCurrentLimits(twoWT.getOperationalLimitsGroups2())) + .currentLimits(mergeCurrentLimits(twoWT.getOperationalLimitsGroups1(), twoWT.getOperationalLimitsGroups2())) .properties(getProperties(twoWT)); builder.busOrBusbarSectionId1(getBusOrBusbarSection(terminal1)) @@ -77,13 +82,6 @@ private static TwoWindingsTransformerFormInfos toFormInfos(Identifiable ident builder.q2(nullIfNan(terminal2.getQ())); builder.i1(nullIfNan(terminal1.getI())); builder.i2(nullIfNan(terminal2.getI())); - builder.selectedOperationalLimitsGroup1(twoWT.getSelectedOperationalLimitsGroupId1().orElse(null)); - builder.selectedOperationalLimitsGroup2(twoWT.getSelectedOperationalLimitsGroupId2().orElse(null)); - - mergeCurrentLimits(twoWT.getOperationalLimitsGroups1(), twoWT.getOperationalLimitsGroups2(), builder::currentLimits); - - buildCurrentLimits(twoWT.getOperationalLimitsGroups1(), builder::currentLimits1); - buildCurrentLimits(twoWT.getOperationalLimitsGroups2(), builder::currentLimits2); builder.operatingStatus(toOperatingStatus(twoWT)); builder.connectablePosition1(toMapConnectablePosition(twoWT, 1)) @@ -210,8 +208,8 @@ private static TwoWindingsTransformerTooltipInfos toTooltipInfos(Identifiable .x(twoWindingsTransformer.getX()) .b(twoWindingsTransformer.getB()); - twoWindingsTransformer.getCurrentLimits1().ifPresent(limits1 -> builder.currentLimits1(toMapDataCurrentLimits(limits1))); - twoWindingsTransformer.getCurrentLimits2().ifPresent(limits2 -> builder.currentLimits2(toMapDataCurrentLimits(limits2))); + twoWindingsTransformer.getCurrentLimits1().ifPresent(limits1 -> builder.currentLimits1(toCurrentLimitsData(limits1))); + twoWindingsTransformer.getCurrentLimits2().ifPresent(limits2 -> builder.currentLimits2(toCurrentLimitsData(limits2))); return builder.build(); } diff --git a/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java b/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java index 0bcdae1d..2fc2389a 100644 --- a/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java +++ b/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java @@ -11,8 +11,11 @@ import com.powsybl.iidm.network.extensions.*; import com.powsybl.math.graph.TraversalType; import org.gridsuite.network.map.dto.common.*; +import org.gridsuite.network.map.dto.common.CurrentLimitsData.CurrentLimitsDataBuilder; import org.gridsuite.network.map.dto.definition.extension.*; import org.gridsuite.network.map.dto.definition.threewindingstransformer.ThreeWindingsTransformerTabInfos; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; import java.util.ArrayList; @@ -20,8 +23,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; -import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -84,94 +87,67 @@ public static Optional toHvdcOperatorActivePo .oprFromCS2toCS1(hvdcOperatorActivePowerRange.getOprFromCS2toCS1()).build()); } - public static void buildCurrentLimits(Collection currentLimits, Consumer> build) { - List currentLimitsData = currentLimits.stream() + @Nullable + public static List buildCurrentLimits(Collection currentLimits) { + final List currentLimitsData = currentLimits.stream() .map(ElementUtils::operationalLimitsGroupToMapDataCurrentLimits) + .filter(Objects::nonNull) + .map(CurrentLimitsDataBuilder::build) .toList(); - if (!currentLimitsData.isEmpty()) { - build.accept(currentLimitsData); - } - } - - private static CurrentLimitsData copyCurrentLimitsData(CurrentLimitsData currentLimitsData, CurrentLimitsData.Applicability applicability) { - return CurrentLimitsData.builder() - .id(currentLimitsData.getId()) - .applicability(applicability) - .temporaryLimits(currentLimitsData.getTemporaryLimits()) - .permanentLimit(currentLimitsData.getPermanentLimit()).build(); + return currentLimitsData.isEmpty() ? null : currentLimitsData; } /** + * Combine 2 sides in one list. * @return id of the selected operation limits group 1 and 2 if they have been renamed */ - public static void mergeCurrentLimits(Collection operationalLimitsGroups1, - Collection operationalLimitsGroups2, - Consumer> build) { - List mergedLimitsData = new ArrayList<>(); - + @Nullable + public static List mergeCurrentLimits(final Collection operationalLimitsGroups1, + final Collection operationalLimitsGroups2) { // Build temporary limit from side 1 and 2 - List currentLimitsData1 = operationalLimitsGroups1.stream() - .map(ElementUtils::operationalLimitsGroupToMapDataCurrentLimits).toList(); - ArrayList currentLimitsData2 = new ArrayList<>(operationalLimitsGroups2.stream() - .map(ElementUtils::operationalLimitsGroupToMapDataCurrentLimits).toList()); - - // combine 2 sides in one list + final List currentLimitsData1 = operationalLimitsGroups1.stream() + .map(ElementUtils::operationalLimitsGroupToMapDataCurrentLimits) + .filter(Objects::nonNull) + .map(cld -> cld.applicability(SIDE1).build()) + .toList(); + final List currentLimitsData2 = operationalLimitsGroups2.stream() + .map(ElementUtils::operationalLimitsGroupToMapDataCurrentLimits) + .filter(Objects::nonNull) + .map(cld -> cld.applicability(SIDE2).build()) + .collect(Collectors.toCollection(ArrayList::new)); // simple case : one of the arrays are empty if (currentLimitsData2.isEmpty() && !currentLimitsData1.isEmpty()) { - for (CurrentLimitsData currentLimitsData : currentLimitsData1) { - mergedLimitsData.add(copyCurrentLimitsData(currentLimitsData, SIDE1)); - } - build.accept(mergedLimitsData); - return; + return currentLimitsData1; } if (currentLimitsData1.isEmpty() && !currentLimitsData2.isEmpty()) { - for (CurrentLimitsData currentLimitsData : currentLimitsData2) { - mergedLimitsData.add(copyCurrentLimitsData(currentLimitsData, SIDE2)); - } - build.accept(mergedLimitsData); - return; + return currentLimitsData2; } // more complex case - for (CurrentLimitsData limitsData : currentLimitsData1) { - Optional l2 = currentLimitsData2.stream().filter(l -> l.getId().equals(limitsData.getId())).findFirst(); - + List mergedLimitsData = new ArrayList<>(currentLimitsData1.size() + currentLimitsData2.size()); + for (final CurrentLimitsData limitsData : currentLimitsData1) { + final Optional l2 = currentLimitsData2.stream().filter(l -> l.getId().equals(limitsData.getId())).findFirst(); if (l2.isPresent()) { CurrentLimitsData limitsData2 = l2.get(); - // Only side one has limits - if (limitsData.hasLimits() && !limitsData2.hasLimits()) { - mergedLimitsData.add(copyCurrentLimitsData(limitsData, SIDE1)); - // only side two has limits - } else if (limitsData2.hasLimits() && !limitsData.hasLimits()) { - mergedLimitsData.add(copyCurrentLimitsData(limitsData2, SIDE2)); - } else { - // both sides have limits and limits are equals - if (limitsData.limitsEquals(limitsData2)) { - mergedLimitsData.add(copyCurrentLimitsData(limitsData, EQUIPMENT)); - // both side have limits and they are different : create 2 different limit sets - } else { - // Side 1 - mergedLimitsData.add(copyCurrentLimitsData(limitsData, SIDE1)); - // Side 2 - mergedLimitsData.add(copyCurrentLimitsData(limitsData2, SIDE2)); - } + if (limitsData.hasLimits() && !limitsData2.hasLimits()) { // Only side one has limits + mergedLimitsData.add(limitsData); + } else if (limitsData2.hasLimits() && !limitsData.hasLimits()) { // only side two has limits + mergedLimitsData.add(limitsData2); + } else if (limitsData.limitsEquals(limitsData2)) { // both sides have limits and limits are equals + mergedLimitsData.add(limitsData.withApplicability(EQUIPMENT)); + } else { // both side have limits and are different : create 2 different limit sets + mergedLimitsData.add(limitsData); + mergedLimitsData.add(limitsData2); } - // remove processed limits from side 2 - currentLimitsData2.remove(l2.get()); + currentLimitsData2.remove(l2.get()); // remove processed limits from side 2 } else { - mergedLimitsData.add(copyCurrentLimitsData(limitsData, SIDE1)); + mergedLimitsData.add(limitsData); } } - // add remaining limits from side 2 - for (CurrentLimitsData limitsData : currentLimitsData2) { - mergedLimitsData.add(copyCurrentLimitsData(limitsData, SIDE2)); - } - - if (!mergedLimitsData.isEmpty()) { - build.accept(mergedLimitsData); - } + mergedLimitsData.addAll(currentLimitsData2); + return mergedLimitsData.isEmpty() ? null : mergedLimitsData; } public static Optional toStandbyAutomaton(StaticVarCompensator staticVarCompensator) { @@ -247,8 +223,15 @@ public static Optional toIdentifiableShortCircuit .ipMax(identifiableShortCircuit.getIpMax()).build()); } - public static CurrentLimitsData toMapDataCurrentLimits(CurrentLimits limits) { - CurrentLimitsData.CurrentLimitsDataBuilder builder = CurrentLimitsData.builder(); + @Nullable + public static CurrentLimitsData toCurrentLimitsData(@NonNull CurrentLimits limits) { + final CurrentLimitsDataBuilder builder = toCurrentLimitsDataBuilder(limits); + return builder == null ? null : builder.build(); + } + + @Nullable + private static CurrentLimitsDataBuilder toCurrentLimitsDataBuilder(@NonNull CurrentLimits limits) { + CurrentLimitsDataBuilder builder = CurrentLimitsData.builder(); boolean empty = true; if (!Double.isNaN(limits.getPermanentLimit())) { builder.permanentLimit(limits.getPermanentLimit()); @@ -258,28 +241,18 @@ public static CurrentLimitsData toMapDataCurrentLimits(CurrentLimits limits) { builder.temporaryLimits(toMapDataTemporaryLimit(limits.getTemporaryLimits())); empty = false; } - return empty ? null : builder.build(); + return empty ? null : builder; } - public static CurrentLimitsData operationalLimitsGroupToMapDataCurrentLimits(OperationalLimitsGroup operationalLimitsGroup) { - if (operationalLimitsGroup == null || operationalLimitsGroup.getCurrentLimits().isEmpty()) { + @Nullable + public static CurrentLimitsDataBuilder operationalLimitsGroupToMapDataCurrentLimits(@Nullable final OperationalLimitsGroup operationalLimitsGroup) { + if (operationalLimitsGroup == null) { return null; } - CurrentLimitsData.CurrentLimitsDataBuilder builder = CurrentLimitsData.builder(); - boolean containsLimitsData = false; - - CurrentLimits currentLimits = operationalLimitsGroup.getCurrentLimits().get(); - builder.id(operationalLimitsGroup.getId()); - if (!Double.isNaN(currentLimits.getPermanentLimit())) { - builder.permanentLimit(currentLimits.getPermanentLimit()); - containsLimitsData = true; - } - if (!CollectionUtils.isEmpty(currentLimits.getTemporaryLimits())) { - builder.temporaryLimits(toMapDataTemporaryLimit(currentLimits.getTemporaryLimits())); - containsLimitsData = true; - } - - return containsLimitsData ? builder.build() : null; + return operationalLimitsGroup.getCurrentLimits() + .map(ElementUtils::toCurrentLimitsDataBuilder) + .map(builder -> builder.id(operationalLimitsGroup.getId())) + .orElse(null); } private static List toMapDataTemporaryLimit(Collection limits) { @@ -574,7 +547,7 @@ public static Map buildCurrentLimitsMap(Collection res = new HashMap<>(); if (!CollectionUtils.isEmpty(operationalLimitsGroups)) { operationalLimitsGroups.forEach(operationalLimitsGroup -> operationalLimitsGroup.getCurrentLimits().ifPresent(limits -> { - CurrentLimitsData limitsData = toMapDataCurrentLimits(limits); + CurrentLimitsData limitsData = toCurrentLimitsData(limits); res.put(operationalLimitsGroup.getId(), limitsData); })); } diff --git a/src/test/java/org/gridsuite/network/map/utils/ElementUtilsTest.java b/src/test/java/org/gridsuite/network/map/utils/ElementUtilsTest.java new file mode 100644 index 00000000..7353b851 --- /dev/null +++ b/src/test/java/org/gridsuite/network/map/utils/ElementUtilsTest.java @@ -0,0 +1,281 @@ +package org.gridsuite.network.map.utils; + +import com.powsybl.iidm.network.OperationalLimitsGroup; +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.WithAssertions; +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; +import org.gridsuite.network.map.dto.common.CurrentLimitsData; +import org.gridsuite.network.map.dto.common.CurrentLimitsData.Applicability; +import org.gridsuite.network.map.dto.common.TemporaryLimitData; +import org.gridsuite.network.map.dto.utils.ElementUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; + +@ExtendWith({ SoftAssertionsExtension.class, MockitoExtension.class }) +class ElementUtilsTest implements WithAssertions { + /** Tests for {@link ElementUtils#operationalLimitsGroupToMapDataCurrentLimits(OperationalLimitsGroup)} */ + @Nested + @DisplayName("fn operationalLimitsGroupToMapDataCurrentLimits(…)") + class OperationalLimitsGroupToMapDataCurrentLimits { + private Collection mocks = new ArrayList<>(); + + @AfterEach + void setDown() { + if (!mocks.isEmpty()) { + Mockito.verifyNoMoreInteractions(mocks.toArray(Object[]::new)); + mocks.clear(); + } + } + + /* All cases possibles: + * null -> null + * operationalLimitsGroup(currentLimits=empty, *=any) -> null + * operationalLimitsGroup(currentLimits={PermanentLimit=NaN, TemporaryLimits=null, *=any}, *=any) -> null + * operationalLimitsGroup(currentLimits={PermanentLimit=NaN, TemporaryLimits=[], *=any}, *=any) -> null + * operationalLimitsGroup(currentLimits={permanentLimit=NaN, temporaryLimits=[any], *=any}, *=any) -> (id=*, applicability=null, permanentLimit=null, temporaryLimits=[any]) + * operationalLimitsGroup(currentLimits={PermanentLimit=_non_NaN_, TemporaryLimits=null, *=any}, *=any) -> (id=*, applicability=null, permanentLimit=*, temporaryLimits=null) + * operationalLimitsGroup(currentLimits={PermanentLimit=_non_NaN_, TemporaryLimits=[], *=any}, *=any) -> (id=*, applicability=null, permanentLimit=*, temporaryLimits=null) + * operationalLimitsGroup(currentLimits={PermanentLimit=_non_NaN_, TemporaryLimits=[any], *=any}, *=any) -> (id=*, applicability=null, permanentLimit=*, temporaryLimits=[any]) + */ + + @Test + void nullInput() { + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(null)).isNull(); + } + + @Test + void emptyCurrentLimits() { + final var mock = MockUtils.mockOperationalLimitsGroup("id", null); + mocks.add(mock); + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(mock)).isNull(); + Mockito.verify(mock).getCurrentLimits(); + } + + @Test + void nanPermLimitAndNullTempLimit() { + final var clMock = MockUtils.mockCurrentLimits(Double.NaN, null); + mocks.add(clMock); + final var olgMock = MockUtils.mockOperationalLimitsGroup("id", clMock); + mocks.add(olgMock); + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isNull(); + Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); + Mockito.verify(olgMock).getId(); + Mockito.verify(clMock).getPermanentLimit(); + Mockito.verify(clMock).getTemporaryLimits(); + } + + @Test + void nanPermLimitAndEmptyTempLimit() { + final var clMock = MockUtils.mockCurrentLimits(Double.NaN, List.of()); + mocks.add(clMock); + final var olgMock = MockUtils.mockOperationalLimitsGroup("id", clMock); + mocks.add(olgMock); + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isNull(); + Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); + Mockito.verify(olgMock).getId(); + Mockito.verify(clMock).getPermanentLimit(); + Mockito.verify(clMock).getTemporaryLimits(); + } + + @Test + void nanPermLimitAndNonEmptyTempLimit() { + final var tlMock = MockUtils.mockTemporaryLimits(123, "testLimit", 456.789); + mocks.add(tlMock); + final var clMock = MockUtils.mockCurrentLimits(Double.NaN, List.of(tlMock)); + mocks.add(clMock); + final var olgMock = MockUtils.mockOperationalLimitsGroup("my id", clMock); + mocks.add(olgMock); + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isEqualTo(CurrentLimitsData.builder() + .id("my id").applicability(null).permanentLimit(null).temporaryLimits(List.of(TemporaryLimitData.builder() + .acceptableDuration(123).name("testLimit").value(456.789).build())).build()); + Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); + Mockito.verify(olgMock).getId(); + Mockito.verify(clMock).getPermanentLimit(); + Mockito.verify(clMock, Mockito.times(2)).getTemporaryLimits(); + Mockito.verify(tlMock, Mockito.times(2)).getAcceptableDuration(); + Mockito.verify(tlMock).getName(); + Mockito.verify(tlMock, Mockito.times(2)).getValue(); + } + + @Test + void nonNanPermLimitAndNullTempLimit() { + final var clMock = MockUtils.mockCurrentLimits(0.123, null); + mocks.add(clMock); + final var olgMock = MockUtils.mockOperationalLimitsGroup("my id", clMock); + mocks.add(olgMock); + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isEqualTo(CurrentLimitsData.builder() + .id("my id").applicability(null).permanentLimit(0.123).temporaryLimits(null).build()); + Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); + Mockito.verify(olgMock).getId(); + Mockito.verify(clMock, Mockito.times(2)).getPermanentLimit(); + Mockito.verify(clMock).getTemporaryLimits(); + } + + @Test + void nonNanPermLimitAndEmptyTempLimit() { + final var clMock = MockUtils.mockCurrentLimits(0.123, List.of()); + mocks.add(clMock); + final var olgMock = MockUtils.mockOperationalLimitsGroup("my id", clMock); + mocks.add(olgMock); + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isEqualTo(CurrentLimitsData.builder() + .id("my id").applicability(null).permanentLimit(0.123).temporaryLimits(null).build()); + Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); + Mockito.verify(olgMock).getId(); + Mockito.verify(clMock, Mockito.times(2)).getPermanentLimit(); + Mockito.verify(clMock).getTemporaryLimits(); + } + + @Test + void nonNanPermLimitAndNonEmptyTempLimit() { + final var tlMock = MockUtils.mockTemporaryLimits(123, "testLimit", 456.789); + mocks.add(tlMock); + final var clMock = MockUtils.mockCurrentLimits(0.0, List.of(tlMock)); + mocks.add(clMock); + final var olgMock = MockUtils.mockOperationalLimitsGroup("my id", clMock); + mocks.add(olgMock); + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isEqualTo(CurrentLimitsData.builder() + .id("my id").applicability(null).permanentLimit(0.0).temporaryLimits(List.of(TemporaryLimitData.builder() + .acceptableDuration(123).name("testLimit").value(456.789).build())).build()); + Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); + Mockito.verify(olgMock).getId(); + Mockito.verify(clMock, Mockito.times(2)).getPermanentLimit(); + Mockito.verify(clMock, Mockito.times(2)).getTemporaryLimits(); + Mockito.verify(tlMock, Mockito.times(2)).getAcceptableDuration(); + Mockito.verify(tlMock).getName(); + Mockito.verify(tlMock, Mockito.times(2)).getValue(); + } + } + + /** Tests for {@link ElementUtils#mergeCurrentLimits(Collection, Collection)} */ + @Nested + @DisplayName("fn mergeCurrentLimits(…, …)") + class MergeCurrentLimitsTest { + @ParameterizedTest(name = ParameterizedTest.INDEX_PLACEHOLDER) + @MethodSource("mergeCurrentLimitsTestData") + void shouldNotThrow( + final Collection olg1, + final Collection olg2, + final List expected) { + assertThat(ElementUtils.mergeCurrentLimits(olg1, olg2)).as("Result").isEqualTo(expected); + } + + private static Stream mergeCurrentLimitsTestData() { + return Stream.of( + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.EQUIPMENT).build() + )), + Arguments.of(List.of(), List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE2).build() + )), + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of(), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE1).build() + )), + // TODO get two dto but because hasLimit() condition always false + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of()) + ), List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE2).build() + )), + // TODO java.lang.NullPointerException: Cannot invoke "org.gridsuite.network.map.dto.common.CurrentLimitsData.getId()" because "limitsData" is null + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", Double.NaN, List.of()) + ), List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE2).build() + )), + // TODO java.lang.NullPointerException: Cannot invoke "java.lang.Double.doubleValue()" because "this.permanentLimit" is null + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", Double.NaN, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE2).build() + )), + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of()) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE1).build() + )), + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + MockUtils.mockOperationalLimitsGroup("group1", Double.NaN, List.of()) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE1).build() + )), + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + MockUtils.mockOperationalLimitsGroup("group1", Double.NaN, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE1).build() + )) + ); + } + + @Test + void shouldThrowOnNanLimit(final SoftAssertions softly) { + final var l1 = List.of(MockUtils.mockOperationalLimitsGroup("group1", Double.NaN, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0)))); + final var l2 = List.of(MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0)))); + AtomicReference> results = new AtomicReference<>(); + softly.assertThatNullPointerException().isThrownBy(() -> ElementUtils.mergeCurrentLimits(l1, l2)); + softly.assertThatNullPointerException().isThrownBy(() -> ElementUtils.mergeCurrentLimits(l2, l1)); + } + + // TODO what to do when one side has duplicate ID? + } +} diff --git a/src/test/java/org/gridsuite/network/map/utils/MockUtils.java b/src/test/java/org/gridsuite/network/map/utils/MockUtils.java new file mode 100644 index 00000000..77a4e276 --- /dev/null +++ b/src/test/java/org/gridsuite/network/map/utils/MockUtils.java @@ -0,0 +1,43 @@ +package org.gridsuite.network.map.utils; + +import com.powsybl.iidm.network.CurrentLimits; +import com.powsybl.iidm.network.LoadingLimits.TemporaryLimit; +import com.powsybl.iidm.network.OperationalLimitsGroup; +import org.mockito.Mockito; +import org.mockito.quality.Strictness; + +import java.util.List; +import java.util.Optional; + +public final class MockUtils { + private MockUtils() { + throw new InstantiationError("Utility Class cannot be instantiated."); + } + + public static TemporaryLimit mockTemporaryLimits(final int acceptableDuration, final String name, final double value) { + TemporaryLimit mock = Mockito.mock(TemporaryLimit.class); + Mockito.when(mock.getAcceptableDuration()).thenReturn(acceptableDuration); + Mockito.when(mock.getName()).thenReturn(name); + Mockito.when(mock.getValue()).thenReturn(value); + return mock; + } + + public static CurrentLimits mockCurrentLimits(double permanentLimit, List temporaryLimits) { + CurrentLimits mock = Mockito.mock(CurrentLimits.class); + //Mockito.when(mock.getLimitType()).thenCallRealMethod(); + Mockito.when(mock.getPermanentLimit()).thenReturn(permanentLimit); + Mockito.when(mock.getTemporaryLimits()).thenReturn(temporaryLimits); + return mock; + } + + public static OperationalLimitsGroup mockOperationalLimitsGroup(final String id, double permanentLimit, List temporaryLimits) { + return mockOperationalLimitsGroup(id, mockCurrentLimits(permanentLimit, temporaryLimits)); + } + + public static OperationalLimitsGroup mockOperationalLimitsGroup(final String id, final CurrentLimits cl) { + OperationalLimitsGroup mock = Mockito.mock(OperationalLimitsGroup.class, Mockito.withSettings().strictness(Strictness.LENIENT)); + Mockito.when(mock.getId()).thenReturn(id); + Mockito.when(mock.getCurrentLimits()).thenReturn(Optional.ofNullable(cl)); + return mock; + } +}