diff --git a/pom.xml b/pom.xml index caaaded0e..c1c3695ad 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ sct-coverage/** ../sct-coverage/target/site/jacoco-aggregate/jacoco.xml ${basedir}/${aggregate.report.dir} - 0.12.0 + 0.13.0 0.0.4 3.4.1 3.2.1 diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ControlBlockNetworkSettings.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ControlBlockNetworkSettings.java new file mode 100644 index 000000000..1755d29f9 --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/ControlBlockNetworkSettings.java @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2023 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons.dto; + +import org.lfenergy.compas.scl2007b4.model.TDurationInMilliSec; +import org.lfenergy.compas.sct.commons.scl.ied.ControlBlockAdapter; + +/** + * This interface has a single method which provides network settings for a ControlBlock. + * These are used to create: + * - the Communication/SubNetwork/ConnectedAP/GSE element, which is the network configuration of a GSEControl block + * - the Communication/SubNetwork/ConnectedAP/SMV element, which is the network configuration of a SampledValueControl block + * It is a FunctionalInterface, so it can be implemented with a lambda expression. + * + * @see org.lfenergy.compas.sct.commons.util.ControlBlockNetworkSettingsCsvHelper + */ +@FunctionalInterface +public interface ControlBlockNetworkSettings { + + /** + * This method provides a vlanId, vlanPriority, minTime, maxTime for this ControlBlock. + * vlanPriority will be ignored when vlanId is null. + * + * @param controlBlockAdapter ControlBlock for which we want to configure the communication section + * @return network settings : All fields are optional (meaning fields can be null). + * When the return value itself is null, the communication section will not be configured for this ControlBlock. + */ + Settings getNetworkSettings(ControlBlockAdapter controlBlockAdapter); + + /** + * Network settings for ControlBlock communication + * + * @param vlanId id of the vlan + * @param vlanPriority priority for the vlan + * @param minTime minTime for GSE communication element + * @param maxTime maxTime for GSE communication element + */ + record Settings(Integer vlanId, Byte vlanPriority, TDurationInMilliSec minTime, TDurationInMilliSec maxTime) { + } + + /** + * NetworkRanges for GSEControl and SampledValueControl + * + * @param gse NetworkRanges for GSEControl + * @param sampledValue NetworkRanges for SampledValueControl + */ + record RangesPerCbType(NetworkRanges gse, NetworkRanges sampledValue) { + } + + /** + * Range of APPID and range of MAC-Address + * + * @param appIdStart range start for APPID (inclusive) + * @param appIdEnd range end for APPID (inclusive) + * @param macAddressStart range start for MAC-Addresses (inclusive). Ex: "01-0C-CD-01-00-00" + * @param macAddressEnd range end for MAC-Addresses (inclusive). Ex: "01-0C-CD-01-01-FF" + */ + record NetworkRanges(long appIdStart, long appIdEnd, String macAddressStart, String macAddressEnd) { + } +} diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ExtRefService.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ExtRefService.java index 6e6f0f9f2..b4740a201 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ExtRefService.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ExtRefService.java @@ -9,24 +9,31 @@ import org.lfenergy.compas.scl2007b4.model.SCL; import org.lfenergy.compas.scl2007b4.model.TCompasICDHeader; import org.lfenergy.compas.scl2007b4.model.TIED; +import org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings; +import org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings.Settings; import org.lfenergy.compas.sct.commons.dto.SclReport; import org.lfenergy.compas.sct.commons.dto.SclReportItem; import org.lfenergy.compas.sct.commons.exception.ScdException; +import org.lfenergy.compas.sct.commons.scl.ied.ControlBlockAdapter; import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter; import org.lfenergy.compas.sct.commons.scl.ied.LDeviceAdapter; import org.lfenergy.compas.sct.commons.scl.ied.LN0Adapter; +import org.lfenergy.compas.sct.commons.util.ControlBlockEnum; import org.lfenergy.compas.sct.commons.util.PrivateEnum; +import org.lfenergy.compas.sct.commons.util.Utils; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings.NetworkRanges; +import static org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings.RangesPerCbType; + @UtilityClass public class ExtRefService { private static final String MESSAGE_MISSING_IED_NAME_PARAMETER = "IED.name parameter is missing"; - private static final String CLIENT_IED_NAME = "The Client IED "; /** * Updates iedName attribute of all ExtRefs in the Scd. @@ -40,21 +47,21 @@ public static SclReport updateAllExtRefIedNames(SCL scd) { return new SclReport(sclRootAdapter, iedErrors); } Map icdSystemVersionToIed = sclRootAdapter.streamIEDAdapters() - .collect(Collectors.toMap( - iedAdapter -> PrivateService.extractCompasPrivate(iedAdapter.getCurrentElem(), TCompasICDHeader.class) - .map(TCompasICDHeader::getICDSystemVersionUUID) - .orElseThrow(), // Value presence is checked by method validateIed called above - Function.identity() - )); + .collect(Collectors.toMap( + iedAdapter -> iedAdapter.getCompasICDHeader() + .map(TCompasICDHeader::getICDSystemVersionUUID) + .orElseThrow(), // Value presence is checked by method validateIed called above + Function.identity() + )); List extRefErrors = sclRootAdapter.streamIEDAdapters() - .flatMap(IEDAdapter::streamLDeviceAdapters) - .filter(LDeviceAdapter::hasLN0) - .map(LDeviceAdapter::getLN0Adapter) - .filter(LN0Adapter::hasInputs) - .map(LN0Adapter::getInputsAdapter) - .map(inputsAdapter -> inputsAdapter.updateAllExtRefIedNames(icdSystemVersionToIed)) - .flatMap(List::stream).toList(); + .flatMap(IEDAdapter::streamLDeviceAdapters) + .filter(LDeviceAdapter::hasLN0) + .map(LDeviceAdapter::getLN0Adapter) + .filter(LN0Adapter::hasInputs) + .map(LN0Adapter::getInputsAdapter) + .map(inputsAdapter -> inputsAdapter.updateAllExtRefIedNames(icdSystemVersionToIed)) + .flatMap(List::stream).toList(); return new SclReport(sclRootAdapter, extRefErrors); } @@ -67,46 +74,59 @@ private static List validateIed(SclRootAdapter sclRootAdapter) { private static List checkIedCompasIcdHeaderAttributes(SclRootAdapter sclRootAdapter) { return sclRootAdapter.streamIEDAdapters() - .map(iedAdapter -> { - Optional compasPrivate = PrivateService.extractCompasPrivate(iedAdapter.getCurrentElem(), TCompasICDHeader.class); - if (compasPrivate.isEmpty()) { - return iedAdapter.buildFatalReportItem(String.format("IED has no Private %s element", PrivateEnum.COMPAS_ICDHEADER.getPrivateType())); - } - if (StringUtils.isBlank(compasPrivate.get().getICDSystemVersionUUID()) - || StringUtils.isBlank(compasPrivate.get().getIEDName())) { - return iedAdapter.buildFatalReportItem(String.format("IED private %s as no icdSystemVersionUUID or iedName attribute", - PrivateEnum.COMPAS_ICDHEADER.getPrivateType())); - } - return null; - } - ).filter(Objects::nonNull) - .toList(); + .map(iedAdapter -> { + Optional compasPrivate = iedAdapter.getCompasICDHeader(); + if (compasPrivate.isEmpty()) { + return iedAdapter.buildFatalReportItem(String.format("IED has no Private %s element", PrivateEnum.COMPAS_ICDHEADER.getPrivateType())); + } + if (StringUtils.isBlank(compasPrivate.get().getICDSystemVersionUUID()) + || StringUtils.isBlank(compasPrivate.get().getIEDName())) { + return iedAdapter.buildFatalReportItem(String.format("IED private %s as no icdSystemVersionUUID or iedName attribute", + PrivateEnum.COMPAS_ICDHEADER.getPrivateType())); + } + return null; + } + ).filter(Objects::nonNull) + .toList(); } private static List checkIedUnityOfIcdSystemVersionUuid(SclRootAdapter sclRootAdapter) { Map> systemVersionToIedList = sclRootAdapter.getCurrentElem().getIED().stream() - .collect(Collectors.groupingBy(ied -> PrivateService.extractCompasPrivate(ied, TCompasICDHeader.class) - .map(TCompasICDHeader::getICDSystemVersionUUID) - .orElse(""))); + .collect(Collectors.groupingBy(ied -> PrivateService.extractCompasPrivate(ied, TCompasICDHeader.class) + .map(TCompasICDHeader::getICDSystemVersionUUID) + .orElse(""))); return systemVersionToIedList.entrySet().stream() - .filter(entry -> StringUtils.isNotBlank(entry.getKey())) - .filter(entry -> entry.getValue().size() > 1) - .map(entry -> SclReportItem.fatal(entry.getValue().stream() - .map(tied -> new IEDAdapter(sclRootAdapter, tied)) - .map(IEDAdapter::getXPath) - .collect(Collectors.joining(", ")), - "/IED/Private/compas:ICDHeader[@ICDSystemVersionUUID] must be unique" + - " but the same ICDSystemVersionUUID was found on several IED.")) - .toList(); + .filter(entry -> StringUtils.isNotBlank(entry.getKey())) + .filter(entry -> entry.getValue().size() > 1) + .map(entry -> SclReportItem.fatal(entry.getValue().stream() + .map(tied -> new IEDAdapter(sclRootAdapter, tied)) + .map(IEDAdapter::getXPath) + .collect(Collectors.joining(", ")), + "/IED/Private/compas:ICDHeader[@ICDSystemVersionUUID] must be unique" + + " but the same ICDSystemVersionUUID was found on several IED.")) + .toList(); } + /** + * Create All DataSet and ControlBlock in the SCL based on the ExtRef + * + * @param scd input SCD object. It could be modified by adding new DataSet and ControlBlocks + * @return a report with all errors encountered + */ public static SclReport createDataSetAndControlBlocks(SCL scd) { SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); Stream lDeviceAdapters = sclRootAdapter.streamIEDAdapters().flatMap(IEDAdapter::streamLDeviceAdapters); return createDataSetAndControlBlocks(sclRootAdapter, lDeviceAdapters); } + /** + * Create All DataSet and ControlBlock for the ExtRef in given IED + * + * @param scd input SCD object. The object will be modified with the new DataSet and ControlBlocks + * @param targetIedName the name of the IED where the ExtRef are + * @return a report with all the errors encountered + */ public static SclReport createDataSetAndControlBlocks(SCL scd, String targetIedName) { SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName(targetIedName); @@ -114,6 +134,14 @@ public static SclReport createDataSetAndControlBlocks(SCL scd, String targetIedN } + /** + * Create All DataSet and ControlBlock for the ExtRef in this LDevice + * + * @param scd input SCD object. The object will be modified with the new DataSet and ControlBlocks + * @param targetIedName the name of the IED where the ExtRef are + * @param targetLDeviceInst the name of the LDevice where the ExtRef are + * @return a report with all encountered errors + */ public static SclReport createDataSetAndControlBlocks(SCL scd, String targetIedName, String targetLDeviceInst) { if (StringUtils.isBlank(targetIedName)) { throw new ScdException(MESSAGE_MISSING_IED_NAME_PARAMETER); @@ -126,9 +154,74 @@ public static SclReport createDataSetAndControlBlocks(SCL scd, String targetIedN private static SclReport createDataSetAndControlBlocks(SclRootAdapter sclRootAdapter, Stream lDeviceAdapters) { List sclReportItems = lDeviceAdapters - .map(LDeviceAdapter::createDataSetAndControlBlocks) - .flatMap(List::stream) - .toList(); + .map(LDeviceAdapter::createDataSetAndControlBlocks) + .flatMap(List::stream) + .toList(); return new SclReport(sclRootAdapter, sclReportItems); } + + /** + * Configure the network for all the ControlBlocks. + * Create (or update if already existing) these elements + * - the Communication/SubNetwork/ConnectedAP/GSE element, for the GSEControl blocks + * - the Communication/SubNetwork/ConnectedAP/SMV element, for the SampledValueControl blocks + * + * @param scd input SCD object. The object will be modified with the new DataGSESet and SMV elements + * @param controlBlockNetworkSettings a method tha gives the network configuration information for a given ControlBlock + * @param rangesPerCbType provide NetworkRanges for GSEControl and SampledValueControl. NetworkRanges contains : + * start-end app APPID range (long value), start-end MAC-Addresses (Mac-Addresses values: Ex: "01-0C-CD-01-01-FF") + * @return a report with all the errors encountered + * @see Utils#macAddressToLong(String) for the expected MAC address format + * @see ControlBlockNetworkSettings + * @see ControlBlockNetworkSettings.RangesPerCbType + * @see ControlBlockNetworkSettings.NetworkRanges + */ + public static SclReport configureNetworkForAllControlBlocks(SCL scd, ControlBlockNetworkSettings controlBlockNetworkSettings, + RangesPerCbType rangesPerCbType) { + List sclReportItems = new ArrayList<>(); + sclReportItems.addAll(configureNetworkForControlBlocks(scd, controlBlockNetworkSettings, rangesPerCbType.gse(), ControlBlockEnum.GSE)); + sclReportItems.addAll(configureNetworkForControlBlocks(scd, controlBlockNetworkSettings, rangesPerCbType.sampledValue(), ControlBlockEnum.SAMPLED_VALUE)); + return new SclReport(new SclRootAdapter(scd), sclReportItems); + } + + private static List configureNetworkForControlBlocks(SCL scd, ControlBlockNetworkSettings controlBlockNetworkSettings, + NetworkRanges networkRanges, ControlBlockEnum controlBlockEnum) { + PrimitiveIterator.OfLong appIdIterator = Utils.sequence(networkRanges.appIdStart(), networkRanges.appIdEnd()); + Iterator macAddressIterator = Utils.macAddressSequence(networkRanges.macAddressStart(), networkRanges.macAddressEnd()); + + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + return sclRootAdapter.streamIEDAdapters() + .flatMap(iedAdapter -> + iedAdapter.streamLDeviceAdapters() + .filter(LDeviceAdapter::hasLN0) + .map(LDeviceAdapter::getLN0Adapter) + .flatMap(ln0Adapter -> ln0Adapter.streamControlBlocks(controlBlockEnum)) + .map(controlBlockAdapter -> configureControlBlockNetwork(controlBlockNetworkSettings, appIdIterator, macAddressIterator, controlBlockAdapter))) + .flatMap(Optional::stream) + .toList(); + } + + private static Optional configureControlBlockNetwork(ControlBlockNetworkSettings controlBlockNetworkSettings, PrimitiveIterator.OfLong appIdIterator, Iterator macAddressIterator, ControlBlockAdapter controlBlockAdapter) { + Settings settings = controlBlockNetworkSettings.getNetworkSettings(controlBlockAdapter); + + if (settings == null) { + return Optional.of(controlBlockAdapter.buildFatalReportItem( + "Cannot configure network for this ControlBlock because no settings was provided")); + } + if (settings.vlanId() == null) { + return Optional.of(controlBlockAdapter.buildFatalReportItem( + "Cannot configure network for this ControlBlock because no Vlan Id was provided in the settings")); + } + if (!appIdIterator.hasNext()) { + return Optional.of(controlBlockAdapter.buildFatalReportItem( + "Cannot configure network for this ControlBlock because range of appId is exhausted")); + } + if (!macAddressIterator.hasNext()) { + return Optional.of(controlBlockAdapter.buildFatalReportItem( + "Cannot configure network for this ControlBlock because range of MAC Address is exhausted")); + } + + return controlBlockAdapter.configureNetwork(appIdIterator.nextLong(), macAddressIterator.next(), settings.vlanId(), settings.vlanPriority(), + settings.minTime(), settings.maxTime()); + } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclRootAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclRootAdapter.java index 07934c74d..2876173ea 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclRootAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/SclRootAdapter.java @@ -12,6 +12,7 @@ import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.com.CommunicationAdapter; +import org.lfenergy.compas.sct.commons.scl.com.ConnectedAPAdapter; import org.lfenergy.compas.sct.commons.scl.dtt.DataTypeTemplateAdapter; import org.lfenergy.compas.sct.commons.scl.header.HeaderAdapter; import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter; @@ -291,4 +292,20 @@ public IEDAdapter checkObjRef(String val) throws ScdException { throw new ScdException("Invalid ObjRef: " + val); } + /** + * Find a ConnectedAp element withe given iedName and apName + * @param iedName iedName + * @param apName apName + * @return the first ConnectedAp which match the given iedName and apName, or empty Optional if none found + */ + public Optional findConnectedApAdapter(String iedName, String apName) { + if (!currentElem.isSetCommunication()) { + return Optional.empty(); + } + CommunicationAdapter communicationAdapter = new CommunicationAdapter(this, currentElem.getCommunication()); + return communicationAdapter.getSubNetworkAdapters().stream() + .map(subNetworkAdapter -> subNetworkAdapter.findConnectedAPAdapter(iedName, apName)) + .flatMap(Optional::stream) + .findFirst(); + } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapter.java index 3f4a076c3..f2fcfb0e9 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapter.java @@ -4,13 +4,16 @@ package org.lfenergy.compas.sct.commons.scl.com; -import org.lfenergy.compas.scl2007b4.model.SCL; -import org.lfenergy.compas.scl2007b4.model.TConnectedAP; +import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; import org.lfenergy.compas.sct.commons.util.Utils; +import java.util.List; +import java.util.Objects; import java.util.Optional; +import static org.lfenergy.compas.sct.commons.util.SclConstructorHelper.newAddress; + /** * A representation of the model object * {@link org.lfenergy.compas.scl2007b4.model.TConnectedAP ConnectedAP}. @@ -49,7 +52,7 @@ protected boolean amChildElementRef() { protected String elementXPath() { return String.format("ConnectedAP[%s and %s]", Utils.xpathAttributeFilter("apName", currentElem.isSetApName() ? currentElem.getApName() : null), - Utils.xpathAttributeFilter("iedName", currentElem.isSetIedName() ? currentElem.getApName() : null)); + Utils.xpathAttributeFilter("iedName", currentElem.isSetIedName() ? currentElem.getIedName() : null)); } /** @@ -86,7 +89,66 @@ public void copyAddressAndPhysConnFromIcd(Optional icd) { currentElem.setAddress(connectedAP.getAddress()); currentElem.getPhysConn().addAll(connectedAP.getPhysConn()); }); + } + } + + private Optional findGse(String ldInst, String cbName){ + if (!currentElem.isSetGSE()){ + return Optional.empty(); + } + return currentElem.getGSE().stream().filter(gse -> Objects.equals(ldInst, gse.getLdInst()) && Objects.equals(cbName, gse.getCbName())) + .findFirst(); + } + private Optional findSmv(String ldInst, String cbName){ + if (!currentElem.isSetSMV()){ + return Optional.empty(); } + return currentElem.getSMV().stream().filter(smv -> Objects.equals(ldInst, smv.getLdInst()) && Objects.equals(cbName, smv.getCbName())) + .findFirst(); } + + /** + * Create A GSE Section or update an existing GSE Section (the network configuration of a GSEControl block). + * + * @param ldInst ldInst + * @param cbName cbName + * @param listOfP list of P elements + * @param minTime minTime + * @param maxTime maxTime + */ + public void updateGseOrCreateIfNotExists(String ldInst, String cbName, List listOfP, TDurationInMilliSec minTime, TDurationInMilliSec maxTime) { + TGSE gse = findGse(ldInst, cbName) + .orElseGet(() -> { + TGSE newGse = new TGSE(); + newGse.setLdInst(ldInst); + newGse.setCbName(cbName); + currentElem.getGSE().add(newGse); + return newGse; + } + ); + gse.setAddress(newAddress(listOfP)); + gse.setMinTime(minTime); + gse.setMaxTime(maxTime); + } + + /** + * Create A SMV Section or update an existing SMV Section (the network configuration of a SampledValueControl block).. + * @param ldInst ldInst + * @param cbName cbName + * @param listOfP list of P elements + */ + public void updateSmvOrCreateIfNotExists(String ldInst, String cbName, List listOfP) { + TSMV smv = findSmv(ldInst, cbName) + .orElseGet(() -> { + TSMV newSmv = new TSMV(); + newSmv.setLdInst(ldInst); + newSmv.setCbName(cbName); + currentElem.getSMV().add(newSmv); + return newSmv; + } + ); + smv.setAddress(newAddress(listOfP)); + } + } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapter.java index efe7a9838..90dbc5b8e 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapter.java @@ -9,11 +9,12 @@ import org.lfenergy.compas.scl2007b4.model.TSubNetwork; import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; +import org.lfenergy.compas.sct.commons.util.SclConstructorHelper; import org.lfenergy.compas.sct.commons.util.Utils; import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; +import java.util.Optional; /** * A representation of the model object @@ -72,9 +73,7 @@ public ConnectedAPAdapter addConnectedAP(@NonNull String iedName, @NonNull Strin .orElse(null); if(tConnectedAP == null){ - tConnectedAP = new TConnectedAP(); - tConnectedAP.setApName(apName); - tConnectedAP.setIedName(iedName); + tConnectedAP = SclConstructorHelper.newConnectedAp(iedName, apName); currentElem.getConnectedAP().add(tConnectedAP); } return new ConnectedAPAdapter(this,tConnectedAP); @@ -104,28 +103,40 @@ public List getConnectedAPAdapters() { return currentElem.getConnectedAP() .stream() .map(ap -> new ConnectedAPAdapter(this,ap)) - .collect(Collectors.toList()); + .toList(); } /** * Gets ConnectedAP from Subnetwork + * * @param iedName IED name - * @param apName AccessPoint name + * @param apName AccessPoint name * @return the ConnectedAPAdapter object - * @throws ScdException + * @throws ScdException when ConnectedAP not found */ public ConnectedAPAdapter getConnectedAPAdapter(String iedName, String apName) throws ScdException { - return currentElem.getConnectedAP() - .stream() - .filter(ap -> ap.getIedName().equals(iedName) && ap.getApName().equals(apName)) - .map(ap -> new ConnectedAPAdapter(this,ap)) - .findFirst() - .orElseThrow( - () -> new ScdException( - String.format( - "Unknown connected AP (%s,%s) for subnetwork %s", iedName, apName, getName() - ) + return findConnectedAPAdapter(iedName, apName) + .orElseThrow( + () -> new ScdException( + String.format( + "Unknown connected AP (%s,%s) for subnetwork %s", iedName, apName, getName() ) - ); + ) + ); + } + + /** + * Find ConnectedAP from Subnetwork + * + * @param iedName IED name + * @param apName AccessPoint name + * @return the ConnectedAPAdapter object + */ + public Optional findConnectedAPAdapter(String iedName, String apName) throws ScdException { + return currentElem.getConnectedAP() + .stream() + .filter(ap -> ap.getIedName().equals(iedName) && ap.getApName().equals(apName)) + .map(ap -> new ConnectedAPAdapter(this, ap)) + .findFirst(); } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapter.java index c085c6057..ffe1396b1 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapter.java @@ -344,7 +344,7 @@ public String getId() { } /** - * Find binded DOType info + * Find bound DOType info * @param signalInfo extRef signal info for binding * @return DOType info as object contening name, id and adapter * @throws ScdException throws when DO unknown diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AbstractLNAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AbstractLNAdapter.java index bc79c4b94..37cc26a11 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AbstractLNAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AbstractLNAdapter.java @@ -371,15 +371,15 @@ private List getControlBlocksByDataSetRef(String dataSetRef, TServ * @return all Control Blocks matching a Service Type * @param inference parameter for Type of Control Block to find */ - public List getTControlsByType(Class cls) { + public List getTControlsByType(Class cls) { if (TGSEControl.class.equals(cls) && isLN0()) { - return ((LN0) currentElem).getGSEControl(); + return (List) ((LN0) currentElem).getGSEControl(); } else if (TSampledValueControl.class.equals(cls) && isLN0()) { - return ((LN0) currentElem).getSampledValueControl(); + return (List) ((LN0) currentElem).getSampledValueControl(); } else if (TReportControl.class.equals(cls)) { - return currentElem.getReportControl(); + return (List) currentElem.getReportControl(); } - throw new IllegalArgumentException("Unsupported ControlBlock " + cls.getSimpleName()); + throw new IllegalArgumentException("Unsupported ControlBlock %s for %s element".formatted(cls.getSimpleName(), elementXPath())); } /** @@ -952,6 +952,11 @@ public Optional findControlBlock(String name, ControlBlockE .map(tControl -> new ControlBlockAdapter(this, tControl)); } + public Stream streamControlBlocks(ControlBlockEnum controlBlockEnum) { + return getTControlsByType(controlBlockEnum.getControlBlockClass()).stream() + .map(tControl -> new ControlBlockAdapter(this, tControl)); + } + public ControlBlockAdapter createControlBlockIfNotExists(String cbName, String id, String datSet, ControlBlockEnum controlBlockEnum) { return findControlBlock(cbName, controlBlockEnum) .orElseGet(() -> addControlBlock( diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapter.java index 4e12b6658..a0fb6bacc 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapter.java @@ -89,8 +89,10 @@ public List checkFCDALimitations() { .map(abstractLNAdapter -> abstractLNAdapter.getCurrentElem().getDataSet()) .flatMap(Collection::stream) .filter(tDataSet -> tDataSet.getFCDA().size() > max) - .map(tDataSet -> SclReportItem.fatal(getXPath(), String.format("There are too much FCDA for the DataSet %S for the LDevice %S in IED %S", - tDataSet.getName(), lDeviceAdapter.getInst(), parentAdapter.getName()))).toList() + .map(tDataSet -> SclReportItem.fatal(getXPath(), String.format("There are too much FCDA for the DataSet %s for the LDevice %s" + + " in IED %s: %d > %d max", tDataSet.getName(), lDeviceAdapter.getInst(), parentAdapter.getName(), + tDataSet.getFCDA().size(), max)) + ).toList() ).flatMap(Collection::stream).toList(); } @@ -98,13 +100,13 @@ public List checkFCDALimitations() { * Checks if occurrences of specified tpe (DataSet, Controls) exceeds config limitation * * @param servicesConfigEnum type of element for which limitation is checked - * @param msg error message to display * @return Optional of encountered error or empty */ - public Optional checkControlsLimitation(ServicesConfigEnum servicesConfigEnum, String msg) { + public Optional checkControlsLimitation(ServicesConfigEnum servicesConfigEnum) { long max = getMaxInstanceAuthorized(servicesConfigEnum); long value = getNumberOfItems(servicesConfigEnum); - return max == MAX_OCCURRENCE_NO_LIMIT_VALUE || value <= max ? Optional.empty() : Optional.of(SclReportItem.fatal(getXPath(), String.format("%s %s", msg, parentAdapter.getName()))); + return max == MAX_OCCURRENCE_NO_LIMIT_VALUE || value <= max ? Optional.empty() : Optional.of(SclReportItem.fatal(getXPath(), + String.format("There are too much %ss for the IED %s: %d > %d max", servicesConfigEnum.getDisplayName(), parentAdapter.getName(), value, max))); } /** @@ -145,36 +147,48 @@ private Class getControlTypeClass(ServicesConfigEnum service } /** - * Gets max number authorized in configuration of each element DataSets, FCDAs, Control Blocks) into an AccessPoint + * Gets max number authorized in configuration of each element (DataSets, FCDAs, Control Blocks) into an AccessPoint * * @param servicesConfigEnum element type * @return max number authorized by config */ private long getMaxInstanceAuthorized(ServicesConfigEnum servicesConfigEnum) { - if (currentElem.getServices() == null || currentElem.getServices().getConfDataSet() == null) + if (currentElem.getServices() == null) return MAX_OCCURRENCE_NO_LIMIT_VALUE; TServices tServices = currentElem.getServices(); return switch (servicesConfigEnum) { case DATASET -> - tServices.getConfDataSet().isSetMax() ? tServices.getConfDataSet().getMax() : MAX_OCCURRENCE_NO_LIMIT_VALUE; + tServices.isSetConfDataSet() && tServices.getConfDataSet().isSetMax() ? + tServices.getConfDataSet().getMax() : MAX_OCCURRENCE_NO_LIMIT_VALUE; case FCDA -> - tServices.getConfDataSet().isSetMaxAttributes() ? tServices.getConfDataSet().getMaxAttributes() : MAX_OCCURRENCE_NO_LIMIT_VALUE; + tServices.isSetConfDataSet() && tServices.getConfDataSet().isSetMaxAttributes() ? + tServices.getConfDataSet().getMaxAttributes() : MAX_OCCURRENCE_NO_LIMIT_VALUE; case REPORT -> - tServices.getConfReportControl().isSetMax() ? tServices.getConfReportControl().getMax() : MAX_OCCURRENCE_NO_LIMIT_VALUE; - case GSE -> tServices.getGOOSE().isSetMax() ? tServices.getGOOSE().getMax() : MAX_OCCURRENCE_NO_LIMIT_VALUE; - case SMV -> tServices.getSMVsc().isSetMax() ? tServices.getSMVsc().getMax() : MAX_OCCURRENCE_NO_LIMIT_VALUE; + tServices.isSetConfReportControl() && tServices.getConfReportControl().isSetMax() ? + tServices.getConfReportControl().getMax() : MAX_OCCURRENCE_NO_LIMIT_VALUE; + case GSE -> tServices.isSetGOOSE() && tServices.getGOOSE().isSetMax() ? tServices.getGOOSE().getMax() : MAX_OCCURRENCE_NO_LIMIT_VALUE; + case SMV -> tServices.isSetSMVsc() && tServices.getSMVsc().isSetMax() ? tServices.getSMVsc().getMax() : MAX_OCCURRENCE_NO_LIMIT_VALUE; }; } /** - * Checks FCDA number limitation for binded IED + * Checks FCDA number limitation for bound IED * - * @param msg message to display hen error occurred * @return Optional of encountered error or empty */ - public Optional checkLimitationForBoundIEDFCDAs(List tExtRefs, String msg) { + public Optional checkLimitationForBoundIedFcdas(List tExtRefs) { + long max; + if (currentElem.getServices() == null) { + max = MAX_OCCURRENCE_NO_LIMIT_VALUE; + } else { + TClientServices tClientServices = currentElem.getServices().getClientServices(); + max = tClientServices != null && tClientServices.isSetMaxAttributes() ? tClientServices.getMaxAttributes() : MAX_OCCURRENCE_NO_LIMIT_VALUE; + } + if (max == MAX_OCCURRENCE_NO_LIMIT_VALUE){ + return Optional.empty(); + } long value = tExtRefs.stream() .map(tExtRef -> { IEDAdapter iedAdapter = getParentAdapter().getParentAdapter().getIEDAdapterByName(tExtRef.getIedName()); @@ -195,17 +209,10 @@ public Optional checkLimitationForBoundIEDFCDAs(List tEx .flatMap(Collection::stream) .toList() .size(); - long max; - if (currentElem.getServices() == null) { - max = AccessPointAdapter.MAX_OCCURRENCE_NO_LIMIT_VALUE; - } else { - TClientServices tClientServices = currentElem.getServices().getClientServices(); - max = tClientServices != null && tClientServices.isSetMaxAttributes() ? tClientServices.getMaxAttributes() : AccessPointAdapter.MAX_OCCURRENCE_NO_LIMIT_VALUE; - } - - return max == AccessPointAdapter.MAX_OCCURRENCE_NO_LIMIT_VALUE || value <= max ? Optional.empty() : - Optional.of(SclReportItem.fatal(getParentAdapter().getXPath(), msg)); + return value <= max ? Optional.empty() : + Optional.of(SclReportItem.fatal(getParentAdapter().getXPath(), + "The Client IED %s subscribes to too much FCDA: %d > %d max".formatted(getParentAdapter().getName(), value, max))); } /** @@ -225,7 +232,7 @@ public ExtRefAnalyzeRecord getAllCoherentExtRefForAnalyze() { List tExtRefList = new ArrayList<>(); streamLDeviceAdapters().map(lDeviceAdapter -> { List extRefs = lDeviceAdapter.getLN0Adapter().getExtRefs().stream().filter(TExtRef::isSetSrcCBName).collect(Collectors.toCollection(ArrayList::new)); - sclReportItems.addAll(checkExtRefWithoutServiceType(extRefs, lDeviceAdapter.getXPath())); + sclReportItems.addAll(checkExtRefWithoutServiceType(extRefs, lDeviceAdapter.getLN0Adapter().getXPath())); extRefs.removeIf(tExtRef -> !tExtRef.isSetServiceType()); return extRefs; }).flatMap(Collection::stream).forEach(tExtRef -> { @@ -249,7 +256,9 @@ private List checkExtRefWithoutServiceType(List tExtRefs return tExtRefs.stream() .filter(tExtRef -> !tExtRef.isSetServiceType()) .map(tExtRef -> - SclReportItem.fatal(xPath, "ExtRef signal without ServiceType : " + tExtRef.getDesc())) + SclReportItem.fatal("%s/Inputs/ExtRef[%s]".formatted(xPath, + Utils.xpathAttributeFilter("desc", tExtRef.getDesc())), + "ExtRef is missing ServiceType attribute")) .toList(); } @@ -273,7 +282,7 @@ private static boolean isExtRefFeedBySameControlBlock(TExtRef t1, TExtRef t2) { } /** - * Checks Control Blocks (Report, Goose, SMV) number limitation for binded IED + * Checks Control Blocks (Report, Goose, SMV) number limitation for bound IED * * @return List of errors encountered */ @@ -281,34 +290,27 @@ public List checkLimitationForBoundIEDControls(List tExt Map> extRefsByServiceType = tExtRefs.stream() .filter(TExtRef::isSetServiceType) .collect(Collectors.groupingBy(TExtRef::getServiceType, Collectors.toSet())); - return extRefsByServiceType.keySet().stream() - .map(tServiceType -> { - Set tExtRefSet = extRefsByServiceType.get(tServiceType); - return switch (tServiceType) { - case REPORT -> - checkLimitationForOneControlType(tExtRefSet, CLIENT_IED_NAME + getParentAdapter().getName() + " subscribes to too much REPORT Control Blocks.", ServicesConfigEnum.REPORT); - case GOOSE -> - checkLimitationForOneControlType(tExtRefSet, CLIENT_IED_NAME + getParentAdapter().getName() + " subscribes to too much GOOSE Control Blocks.", ServicesConfigEnum.GSE); - case SMV -> - checkLimitationForOneControlType(tExtRefSet, CLIENT_IED_NAME + getParentAdapter().getName() + " subscribes to too much SMV Control Blocks.", ServicesConfigEnum.SMV); - default -> throw new ScdException("Unsupported value: " + tServiceType); - }; - }).flatMap(Optional::stream) + return extRefsByServiceType.entrySet().stream() + .map(entry -> checkLimitationForOneControlType(entry.getValue(), ServicesConfigEnum.from(entry.getKey()))) + .flatMap(Optional::stream) .toList(); } /** - * Checks Control Block number limitation for binded IED + * Checks Control Block number limitation for bound IED * * @param tExtRefs list of ExtRefs referenced same ied * @param msg message to display hen error occured * @param servicesConfigEnum type of Control Block for which check is done * @return Optional of encountered error or empty */ - private Optional checkLimitationForOneControlType(Set tExtRefs, String msg, ServicesConfigEnum servicesConfigEnum) { + private Optional checkLimitationForOneControlType(Set tExtRefs, ServicesConfigEnum servicesConfigEnum) { long max = getMaxInstanceAuthorizedForBoundIED(servicesConfigEnum); long value = tExtRefs.size(); - return max == AccessPointAdapter.MAX_OCCURRENCE_NO_LIMIT_VALUE || value <= max ? Optional.empty() : Optional.of(SclReportItem.fatal(getParentAdapter().getXPath(), msg)); + return max == AccessPointAdapter.MAX_OCCURRENCE_NO_LIMIT_VALUE || value <= max ? Optional.empty() : + Optional.of(SclReportItem.fatal(getParentAdapter().getXPath(), + "The Client IED %s subscribes to too much %ss: %d > %d max".formatted(getParentAdapter().getName(), servicesConfigEnum.getDisplayName(), + value, max))); } /** diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapter.java index 4b7167f6b..d7259ca8a 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapter.java @@ -7,8 +7,17 @@ import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.dto.ControlBlockTarget; +import org.lfenergy.compas.sct.commons.dto.SclReportItem; import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; +import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; +import org.lfenergy.compas.sct.commons.scl.com.ConnectedAPAdapter; import org.lfenergy.compas.sct.commons.util.ControlBlockEnum; +import org.lfenergy.compas.sct.commons.util.SclConstructorHelper; +import org.lfenergy.compas.sct.commons.util.Utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import static org.lfenergy.compas.sct.commons.util.Utils.xpathAttributeFilter; @@ -40,6 +49,12 @@ public class ControlBlockAdapter extends SclElementAdapter, TControl> { private static final long RPT_ENABLED_MAX_DEFAULT = 1L; + private static final String APPID_P_TYPE = "APPID"; + private static final String MAC_ADDRESS_P_TYPE = "MAC-Address"; + private static final String VLAN_ID_P_TYPE = "VLAN-ID"; + private static final String VLAN_PRIORITY_P_TYPE = "VLAN-PRIORITY"; + private static final int APPID_LENGTH = 4; + private static final int VLAN_ID_LENGTH = 3; public ControlBlockAdapter(AbstractLNAdapter parentAdapter, TControl tControl) { super(parentAdapter, tControl); @@ -75,6 +90,14 @@ protected String elementXPath() { return String.format("%s[%s]", tag, xpathAttributeFilter("name", currentElem.getName())); } + /** + * Get the name of this ControlBlock + * @return name of ControlBlock + */ + public String getName(){ + return currentElem.getName(); + } + /** * Add a ClientLN to ReportControl or IEDName to GSEControl/SampleValueControl, if it does not already exist. * @param targetLn target LN (where the target ExtRef is) @@ -102,4 +125,74 @@ public void addTargetIfNotExists(AbstractLNAdapter targetLn) { } } + /** + * Configure the Communication section for this ControlBlock + * - Communication/SubNetwork/ConnectedAP/GSE for GSEControl block + * - Communication/SubNetwork/ConnectedAP/SMV for SampledValueControl block + * @param appId value for P type APPID + * @param macAddress value for P type MAC-Address + * @param vlanId value for P type VLAN-ID + * @param vlanPriority value for P type VLAN-PRIORITY + * @param minTime MinTime Element + * @param maxTime MaxTime Element + * @return An empty Optional if network have been configured, else a SclReportItem. + */ + public Optional configureNetwork(long appId, String macAddress, Integer vlanId, Byte vlanPriority, TDurationInMilliSec minTime, + TDurationInMilliSec maxTime) { + String accessPointName = getParentLDeviceAdapter().getAccessPoint().getName(); + + Optional optConApAdapter = getSclRootAdapter().findConnectedApAdapter(getParentIedAdapter().getName(), accessPointName); + if (optConApAdapter.isEmpty()) { + return Optional.of(buildFatalReportItem("Cannot configure network for ControlBlock because no ConnectAP found for parent AccessPoint")); + } + ConnectedAPAdapter connectedAPAdapter = optConApAdapter.get(); + List listOfPs = new ArrayList<>(); + listOfPs.add(SclConstructorHelper.newP(APPID_P_TYPE, Utils.toHex(appId, APPID_LENGTH))); + listOfPs.add(SclConstructorHelper.newP(MAC_ADDRESS_P_TYPE, macAddress)); + if (vlanId != null) { + listOfPs.add(SclConstructorHelper.newP(VLAN_ID_P_TYPE, Utils.toHex(vlanId, VLAN_ID_LENGTH))); + if (vlanPriority != null) { + listOfPs.add(SclConstructorHelper.newP(VLAN_PRIORITY_P_TYPE, String.valueOf(vlanPriority))); + } + } + switch (getControlBlockEnum()) { + case GSE -> connectedAPAdapter.updateGseOrCreateIfNotExists(getParentLDeviceAdapter().getInst(), currentElem.getName(), listOfPs, clone(minTime), clone(maxTime)); + case SAMPLED_VALUE -> connectedAPAdapter.updateSmvOrCreateIfNotExists(getParentLDeviceAdapter().getInst(), currentElem.getName(), listOfPs); + default -> { + return Optional.of(buildFatalReportItem("configureNetwork not yet implemented for %s ControlBlocks".formatted(getControlBlockEnum()))); + } + } + return Optional.empty(); + } + + private TDurationInMilliSec clone(TDurationInMilliSec tDurationInMilliSec){ + if (tDurationInMilliSec == null) { + return null; + } + return SclConstructorHelper.newDurationInMilliSec(tDurationInMilliSec.getValue(), tDurationInMilliSec.getUnit(), tDurationInMilliSec.getMultiplier()); + } + + /** + * Get parent LDevice + * @return ControlBlock's parent lDeviceAdapter + */ + private LDeviceAdapter getParentLDeviceAdapter() { + return getParentAdapter().getParentAdapter(); + } + + /** + * Get parent IED + * @return ControlBlock's parent IEDAdapter + */ + public IEDAdapter getParentIedAdapter() { + return getParentAdapter().getParentIed(); + } + + /** + * Get SCL Root + * @return sclRootAdapter + */ + private SclRootAdapter getSclRootAdapter() { + return getParentIedAdapter().getParentAdapter(); + } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DOIAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DOIAdapter.java index a12c63ec4..573d21d50 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DOIAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DOIAdapter.java @@ -189,7 +189,7 @@ public Optional updateDaiFromExtRef(List tExtRefs) { .updateVal(createInRefValTestString(tExtRefMin)); } - Optional tExtRefMaxOptional = tExtRefs.stream().max(Comparator.comparingInt(o -> Integer.parseInt(Objects.requireNonNull(Utils.extractField(o.getDesc(), "_", -1))))); + Optional tExtRefMaxOptional = tExtRefs.stream().max(EXTREF_DESC_SUFFIX_COMPARATOR); if (tExtRefMaxOptional.isPresent() && extractDescSuffix(tExtRefMaxOptional.get().getDesc()) > 1) { TExtRef tExtRefMax = tExtRefMaxOptional.get(); findDataAdapterByName(DA_NAME_SET_TST_REF) @@ -209,7 +209,7 @@ public Optional updateDaiFromExtRef(List tExtRefs) { } private static int extractDescSuffix(String desc) throws NumberFormatException { - return Integer.parseInt(Utils.extractField(desc, "_", -1)); + return Integer.parseInt(Objects.requireNonNull(Utils.extractField(desc, "_", -1))); } private String createInRefValNominalString(TExtRef extRef) { diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapter.java index df3934aac..e24f8da89 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapter.java @@ -18,7 +18,11 @@ import org.lfenergy.compas.sct.commons.util.ServicesConfigEnum; import org.lfenergy.compas.sct.commons.util.Utils; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; import java.util.stream.Stream; /** @@ -325,20 +329,18 @@ public Optional getPrivateCompasBay() { * @return empty list if all IED respect limits, otherwise list of errors */ public List checkDataGroupCoherence() { - return currentElem.getAccessPoint().stream() - .map(tAccessPoint -> { - AccessPointAdapter accessPointAdapter = new AccessPointAdapter(this, tAccessPoint); - List sclReportItems = new ArrayList<>(accessPointAdapter.checkFCDALimitations()); - accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.DATASET, "There are too much DataSets for the IED") - .ifPresent(sclReportItems::add); - accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.REPORT, "There are too much Report Control Blocks for the IED") - .ifPresent(sclReportItems::add); - accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.GSE, "There are too much GOOSE Control Blocks for the IED") - .ifPresent(sclReportItems::add); - accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.SMV, "There are too much SMV Control Blocks for the IED") - .ifPresent(sclReportItems::add); - return sclReportItems; - }).flatMap(Collection::stream) + return streamAccessPointAdapters() + .flatMap(accessPointAdapter -> + Stream.concat( + accessPointAdapter.checkFCDALimitations().stream(), + Stream.of( + accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.DATASET), + accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.REPORT), + accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.GSE), + accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.SMV)) + .flatMap(Optional::stream) + ) + ) .toList(); } @@ -346,21 +348,37 @@ public List checkDataGroupCoherence() { * Checks if Controls and FCDAs of source IEDs respect config limitation * @return empty list if all IED respect limits, otherwise list of errors */ - public List checkBindingDataGroupCoherence() { - return currentElem.getAccessPoint().stream() - .map(tAccessPoint -> { - AccessPointAdapter accessPointAdapter = new AccessPointAdapter(this, tAccessPoint); + public List checkBindingDataGroupCoherence() { + return streamAccessPointAdapters() + .flatMap(accessPointAdapter -> { AccessPointAdapter.ExtRefAnalyzeRecord extRefAnalyzeRecord = accessPointAdapter.getAllCoherentExtRefForAnalyze(); - List sclReportItems = new ArrayList<>(extRefAnalyzeRecord.sclReportItems()); - accessPointAdapter.checkLimitationForBoundIEDFCDAs(extRefAnalyzeRecord.tExtRefs(), "There are too much FCDA for the Client IED " + getName()) - .ifPresent(sclReportItems::add); - sclReportItems.addAll(accessPointAdapter.checkLimitationForBoundIEDControls(extRefAnalyzeRecord.tExtRefs())); - return sclReportItems; - }).flatMap(List::stream).toList(); - + return Stream.of( + extRefAnalyzeRecord.sclReportItems().stream(), + accessPointAdapter.checkLimitationForBoundIedFcdas(extRefAnalyzeRecord.tExtRefs()).stream(), + accessPointAdapter.checkLimitationForBoundIEDControls(extRefAnalyzeRecord.tExtRefs()).stream()) + .flatMap(Function.identity()); + }).toList(); } + private Stream streamAccessPointAdapters() { + return currentElem.getAccessPoint().stream() + .map(tAccessPoint -> new AccessPointAdapter(this, tAccessPoint)); + } + /** + * Get value of private type COMPAS-ICDHeader + * @return COMPAS-ICDHeader private value if present, else empty Optional + */ + public Optional getCompasICDHeader() { + return PrivateService.extractCompasPrivate(currentElem, TCompasICDHeader.class); + } + /** + * Get value of private type COMPAS-SystemVersion + * @return COMPAS-SystemVersion private value if present, else empty Optional + */ + public Optional getCompasSystemVersion() { + return PrivateService.extractCompasPrivate(currentElem, TCompasSystemVersion.class); + } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapter.java index 89e91a3dc..50cbcbb22 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapter.java @@ -171,13 +171,7 @@ public List getLNAdapters(){ * @throws ScdException thros when specified LNode not found in current IED */ public LNAdapter getLNAdapter(String lnClass, String lnInst, String prefix) throws ScdException { - return currentElem.getLN() - .stream() - .filter(tln -> Utils.lnClassEquals(tln.getLnClass(), lnClass) - && tln.getInst().equals(lnInst) - && Utils.equalsOrBothBlank(prefix, tln.getPrefix())) - .map(tln -> new LNAdapter(this,tln)) - .findFirst() + return findLnAdapter(lnClass, lnInst, prefix) .orElseThrow( ()-> new ScdException( String.format( @@ -187,6 +181,27 @@ public LNAdapter getLNAdapter(String lnClass, String lnInst, String prefix) thro } + /** + * Find a specific LN from current LDevice + * @param lnClass LNode lnClass value + * @param lnInst LNode lnInst value + * @param prefix LNode prefix value + * @return LNAdapter object + * @throws ScdException thros when specified LNode not found in current IED + */ + public Optional findLnAdapter(String lnClass, String lnInst, String prefix) { + if (!currentElem.isSetLN()){ + return Optional.empty(); + } + return currentElem.getLN() + .stream() + .filter(tln -> Utils.lnClassEquals(tln.getLnClass(), lnClass) + && tln.getInst().equals(lnInst) + && Utils.equalsOrBothBlank(prefix, tln.getPrefix())) + .map(tln -> new LNAdapter(this, tln)) + .findFirst(); + } + /** * Checks all possible ExtRef in current LDevice which could be bound to given ExtRef as parameter * @param signalInfo ExtRef to bind data @@ -234,13 +249,13 @@ public List getExtRefInfo() { * @throws ScdException SCD illegal arguments exception */ public Set getDAI(ResumedDataTemplate rDtt, boolean updatableOnly) throws ScdException { - List> lnAdapters = new ArrayList<>(); + List> lnAdapters; if(StringUtils.isBlank(rDtt.getLnClass())){ lnAdapters = getLNAdaptersIncludingLN0(); } else if(rDtt.getLnClass().equals(TLLN0Enum.LLN_0.value())){ - lnAdapters.add(getLN0Adapter()); + lnAdapters = hasLN0() ? Collections.singletonList(getLN0Adapter()) : Collections.emptyList(); } else { - lnAdapters.add(getLNAdapter(rDtt.getLnClass(),rDtt.getLnInst(),rDtt.getPrefix())); + lnAdapters = findLnAdapter(rDtt.getLnClass(),rDtt.getLnInst(),rDtt.getPrefix()).stream().toList(); } Set resumedDataTemplateSet = new HashSet<>(); diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LN0Adapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LN0Adapter.java index e3399ad5d..fc010cf5d 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LN0Adapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LN0Adapter.java @@ -264,15 +264,16 @@ public List updateDoInRef() { && doiAdapter.findDataAdapterByName(DAI_NAME_PURPOSE).isPresent()) .map(doiAdapter -> doiAdapter.getDataAdapterByName(DAI_NAME_PURPOSE).getCurrentElem().getVal().stream() .findFirst() - .map(tVal -> doiAdapter.updateDaiFromExtRef(getExtRefsByDesc(tVal.getValue()))) + .map(tVal -> doiAdapter.updateDaiFromExtRef(getBoundExtRefsByDesc(tVal.getValue()))) .orElse(Optional.of(SclReportItem.warning(getXPath(), "The DOI %s can't be bound with an ExtRef".formatted(getXPath()))))) .flatMap(Optional::stream) .toList(); } - private List getExtRefsByDesc(String desc) { + private List getBoundExtRefsByDesc(String desc) { return getExtRefs().stream() - .filter(tExtRef -> tExtRef.isSetDesc() && tExtRef.getDesc().contains(desc)) + .filter(tExtRef -> tExtRef.isSetIedName() && tExtRef.isSetLdInst() && tExtRef.isSetLnClass() && tExtRef.isSetDoName() && + tExtRef.isSetDesc() && tExtRef.getDesc().contains(desc)) .toList(); } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ControlBlockNetworkSettingsCsvHelper.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ControlBlockNetworkSettingsCsvHelper.java new file mode 100644 index 000000000..b62e27ad0 --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ControlBlockNetworkSettingsCsvHelper.java @@ -0,0 +1,193 @@ +// SPDX-FileCopyrightText: 2023 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons.util; + +import com.opencsv.bean.CsvBindByPosition; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; +import org.lfenergy.compas.scl2007b4.model.TCompasICDHeader; +import org.lfenergy.compas.scl2007b4.model.TCompasIEDRedundancy; +import org.lfenergy.compas.scl2007b4.model.TCompasIEDType; +import org.lfenergy.compas.scl2007b4.model.TDurationInMilliSec; +import org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings; +import org.lfenergy.compas.sct.commons.scl.ied.ControlBlockAdapter; +import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter; + +import java.io.Reader; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * This class is an implementation example for interface ControlBlockNetworkSettings. + * It relies on a CSV file. + * The first columns of the CSV file are the criteria to match the ControlBlock (controlBlockEnum, systemVersionWithoutV, iedType, iedRedundancy, + * isBayInternal), + * The last columns are the network settings for the matched ControlBlock (as described in {@link ControlBlockNetworkSettings.Settings}). + * + * @see CsvUtils + */ +public class ControlBlockNetworkSettingsCsvHelper implements ControlBlockNetworkSettings { + + private static final int MAX_VLAN_ID = 0x0FFF; + private static final int MAX_VLAN_PRIORITY = 7; + private static final String NONE = "none"; + + private final Map settings; + + /** + * Constructor + * Provide the CSV file as a Reader. For example, you can create a reader like this : + * new InputStreamReader(getClass().getClassLoader().getResourceAsStream(fileName), StandardCharsets.UTF_8); + * + * @param csvSource a reader that provides the data as CSV. For example : + */ + public ControlBlockNetworkSettingsCsvHelper(Reader csvSource) { + settings = readCsvFile(csvSource); + } + + private Map readCsvFile(Reader csvSource) { + return CsvUtils.parseRows(csvSource, Row.class).stream() + .distinct() + .collect(Collectors.toMap( + ControlBlockNetworkSettingsCsvHelper::rowToCriteria, + ControlBlockNetworkSettingsCsvHelper::rowToSetting + )); + } + + @Override + public Settings getNetworkSettings(ControlBlockAdapter controlBlockAdapter) { + ControlBlockEnum controlBlockEnum = controlBlockAdapter.getControlBlockEnum(); + IEDAdapter iedAdapter = controlBlockAdapter.getParentIedAdapter(); + String systemVersion = iedAdapter.getCompasSystemVersion() + .map(version -> version.getMainSystemVersion() + "." + version.getMinorSystemVersion()) + .orElse(null); + String systemVersionWithoutV = removeVFromSystemVersion(systemVersion); + TCompasIEDType iedType = iedAdapter.getCompasICDHeader().map(TCompasICDHeader::getIEDType).orElse(null); + TCompasIEDRedundancy iedRedundancy = iedAdapter.getCompasICDHeader().map(TCompasICDHeader::getIEDredundancy).orElse(null); + boolean isBayInternal = controlBlockAdapter.getName().endsWith("I"); + return findSettings(new Criteria(controlBlockEnum, systemVersionWithoutV, iedType, iedRedundancy, isBayInternal)); + } + + private Settings findSettings(Criteria criteria) { + Objects.requireNonNull(criteria); + if (criteria.systemVersionWithoutV() == null + || criteria.iedType() == null + || criteria.iedRedundancy() == null) { + return null; + } + return settings.get(criteria); + } + + private static String removeVFromSystemVersion(String systemVersion) { + if (systemVersion == null) { + return null; + } + String[] systemVersionParts = systemVersion.split("\\."); + if (systemVersionParts.length < 4) { + return systemVersion; + } + return systemVersionParts[0] + "." + systemVersionParts[1] + "." + systemVersionParts[2] + "." + systemVersionParts[3]; + } + + private static Criteria rowToCriteria(Row row) { + if (StringUtils.isBlank(row.cbType) + || StringUtils.isBlank(row.xy) + || StringUtils.isBlank(row.zw) + || StringUtils.isBlank(row.iedType) + || StringUtils.isBlank(row.bindingType) + ) { + throw new IllegalArgumentException("At least one criteria (cbType, xy, zw, iedType, bindingType) is blank"); + } + ControlBlockEnum controlBlockEnum = switch (row.cbType) { + case "GOOSE" -> ControlBlockEnum.GSE; + case "SV" -> ControlBlockEnum.SAMPLED_VALUE; + default -> throw new IllegalArgumentException("Unsupported Control Block Type : " + row.cbType); + }; + return new Criteria( + controlBlockEnum, + row.xy + "." + row.zw, + TCompasIEDType.fromValue(row.iedType), + TCompasIEDRedundancy.fromValue(row.iedRedundancy), + row.bindingType.equals("BAY_INTERNAL") + ); + } + + private static Settings rowToSetting(Row row) { + Integer vlanId = toVLanId(row.vlanId); + Byte vlanPriority = toVlanPriority(row.vlanPriority); + TDurationInMilliSec minTime = toDurationInMilliSec(row.minTime); + TDurationInMilliSec maxTime = toDurationInMilliSec(row.maxTime); + return new Settings(vlanId, vlanPriority, minTime, maxTime); + } + + private static Byte toVlanPriority(String strVlanPriority) { + if (StringUtils.isBlank(strVlanPriority) || NONE.equalsIgnoreCase(strVlanPriority)) { + return null; + } + byte vlanPriority = Byte.parseByte(strVlanPriority); + if (vlanPriority < 0 || vlanPriority > MAX_VLAN_PRIORITY) { + throw new IllegalArgumentException("VLAN PRIORITY must be between 0 and %d, but got : %d".formatted(MAX_VLAN_PRIORITY, vlanPriority)); + } + return vlanPriority; + } + + private static Integer toVLanId(String strVlanId) { + if (StringUtils.isBlank(strVlanId) || NONE.equalsIgnoreCase(strVlanId)) { + return null; + } + int vlanId = Integer.parseInt(strVlanId); + if (vlanId < 0 || vlanId > MAX_VLAN_ID) { + throw new IllegalArgumentException("VLAN ID must be between 0 and %d, but got : %d".formatted(MAX_VLAN_ID, vlanId)); + } + return vlanId; + } + + private static TDurationInMilliSec toDurationInMilliSec(String duration) { + if (StringUtils.isBlank(duration) || NONE.equalsIgnoreCase(duration)) { + return null; + } + return SclConstructorHelper.newDurationInMilliSec(Long.parseLong(duration)); + } + + private record Criteria( + ControlBlockEnum controlBlockEnum, + String systemVersionWithoutV, + TCompasIEDType iedType, + TCompasIEDRedundancy iedRedundancy, + boolean isBayInternal) { + } + + @NoArgsConstructor + @Getter + @Setter + @EqualsAndHashCode + public static class Row { + @CsvBindByPosition(position = 0) + private String cbType; + @CsvBindByPosition(position = 1) + private String xy; + @CsvBindByPosition(position = 2) + private String zw; + @CsvBindByPosition(position = 3) + private String iedType; + @CsvBindByPosition(position = 4) + private String iedRedundancy; + @CsvBindByPosition(position = 5) + private String bindingType; + @CsvBindByPosition(position = 6) + private String vlanId; + @CsvBindByPosition(position = 7) + private String vlanPriority; + @CsvBindByPosition(position = 8) + private String minTime; + @CsvBindByPosition(position = 9) + private String maxTime; + } + +} diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/CsvUtils.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/CsvUtils.java new file mode 100644 index 000000000..23a8d4377 --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/CsvUtils.java @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2023 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons.util; + +import com.opencsv.bean.ColumnPositionMappingStrategy; +import com.opencsv.bean.CsvToBeanBuilder; +import com.opencsv.enums.CSVReaderNullFieldIndicator; +import lombok.experimental.UtilityClass; + +import java.io.*; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Objects; + +/** + * Utility class to parse CSV files. + * This utility class intention is to normalize all CSV inputs in the project: + * - Separator is {@link CsvUtils#SEPARATOR}. + * - Lines starting with {@link CsvUtils#COMMENT_PREFIX} will be ignored. Allow to write copyright and headers at the beginning of the file for example. + * - blank lines are ignored + */ +@UtilityClass +public class CsvUtils { + private static final char SEPARATOR = ';'; + private static final String COMMENT_PREFIX = "#"; + + /** + * Read CSV from a resource + * + * @param resourcePath path of the resource + * @param charset charset of the resource + * @param targetClass Each row will be mapped to this class. + * @return list of rows, mapped as targetClass + */ + public static List parseRows(String resourcePath, Charset charset, Class targetClass) { + InputStream inputStream = Objects.requireNonNull(CsvUtils.class.getClassLoader().getResourceAsStream(resourcePath), "Resource not found: " + resourcePath); + InputStreamReader csvReader = new InputStreamReader(inputStream, charset); + return parseRows(csvReader, targetClass); + } + + /** + * Read CSV from a Reader. + * Reader will be automatically closed when the method returns or throw an exception. + * @param csvSource CSV input + * @param targetClass Each row will be mapped to this class. + * @return list of rows, mapped as targetClass + */ + public static List parseRows(Reader csvSource, Class targetClass) { + ColumnPositionMappingStrategy columnPositionMappingStrategy = new ColumnPositionMappingStrategy<>(); + columnPositionMappingStrategy.setType(targetClass); + try (csvSource) { + return new CsvToBeanBuilder(csvSource) + .withType(targetClass) + .withSeparator(SEPARATOR) + .withIgnoreLeadingWhiteSpace(true) + .withIgnoreEmptyLine(true) + .withFieldAsNull(CSVReaderNullFieldIndicator.EMPTY_SEPARATORS) + .withFilter(line -> line != null && line.length > 0 && (line[0] == null || !line[0].stripLeading().startsWith(COMMENT_PREFIX))) + .withMappingStrategy(columnPositionMappingStrategy) + .build() + .parse(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/FcdaCandidates.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/FcdaCandidates.java index 32d4b17f9..0858f28e4 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/FcdaCandidates.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/FcdaCandidates.java @@ -4,28 +4,19 @@ package org.lfenergy.compas.sct.commons.util; -import com.opencsv.bean.ColumnPositionMappingStrategy; import com.opencsv.bean.CsvBindByPosition; -import com.opencsv.bean.CsvToBeanBuilder; -import com.opencsv.enums.CSVReaderNullFieldIndicator; import lombok.*; import org.apache.commons.lang3.StringUtils; import org.lfenergy.compas.sct.commons.dto.ResumedDataTemplate; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.util.HashSet; -import java.util.List; import java.util.Set; public enum FcdaCandidates { SINGLETON; private static final String FCDA_CONSTRAINTS_FILE_NAME = "FcdaCandidates.csv"; - private static final char SEPARATOR = ';'; - public static final int HEADER_LINES = 5; private Set candidates; @@ -51,30 +42,11 @@ boolean contains(String lnClass, String doName, String daName, String fc) { if (candidates == null) { // using a HashSet because "HashSet.contains" is faster than "ArrayList.contains" - candidates = new HashSet<>(readFcdaCandidatesFile()); + candidates = new HashSet<>(CsvUtils.parseRows(FCDA_CONSTRAINTS_FILE_NAME, StandardCharsets.UTF_8, FcdaCandidate.class)); } return candidates.contains(new FcdaCandidate(lnClass, doName, daName, fc)); } - private List readFcdaCandidatesFile() { - try (InputStreamReader inputStreamReader = new InputStreamReader( - getClass().getClassLoader().getResourceAsStream(FCDA_CONSTRAINTS_FILE_NAME), StandardCharsets.UTF_8)) { - ColumnPositionMappingStrategy columnPositionMappingStrategy = new ColumnPositionMappingStrategy<>(); - columnPositionMappingStrategy.setType(FcdaCandidate.class); - return new CsvToBeanBuilder(inputStreamReader) - .withSeparator(SEPARATOR) - .withIgnoreLeadingWhiteSpace(true) - .withSkipLines(HEADER_LINES) - .withType(FcdaCandidate.class) - .withFieldAsNull(CSVReaderNullFieldIndicator.EMPTY_SEPARATORS) - .withMappingStrategy(columnPositionMappingStrategy) - .build() - .parse(); - } catch (IOException e) { - throw new UncheckedIOException("Error when closing file " + FCDA_CONSTRAINTS_FILE_NAME, e); - } - } - @NoArgsConstructor @AllArgsConstructor @Getter diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/SclConstructorHelper.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/SclConstructorHelper.java new file mode 100644 index 000000000..ce0d5af4e --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/SclConstructorHelper.java @@ -0,0 +1,93 @@ +/* + * // SPDX-FileCopyrightText: 2022 RTE FRANCE + * // + * // SPDX-License-Identifier: Apache-2.0 + */ + +package org.lfenergy.compas.sct.commons.util; + +import lombok.experimental.UtilityClass; +import org.lfenergy.compas.scl2007b4.model.TAddress; +import org.lfenergy.compas.scl2007b4.model.TConnectedAP; +import org.lfenergy.compas.scl2007b4.model.TDurationInMilliSec; +import org.lfenergy.compas.scl2007b4.model.TP; + +import java.math.BigDecimal; +import java.util.List; + +/** + * A helper class that provides constructors to easily create instances of Jaxb classes generated by xjc. + * xjc only provides NoArgConstructors on generated classes. + * This utility class provides constructors with the most relevant attributes. + * + */ +@UtilityClass +public class SclConstructorHelper { + + private static final String SECOND = "s"; + private static final String MILLI = "m"; + + /** + * Create new instance of TP + * @param type type attribute + * @param value value attribute + * @return new instance of TP initialized with the given parameters + */ + public static TP newP(String type, String value){ + TP tp = new TP(); + tp.setType(type); + tp.setValue(value); + return tp; + } + + /** + * Create new instance of TDurationInMilliSec + * @param value value attribute + * @param unit unit attribute + * @param multiplier multiplier attribute + * @return new instance of TDurationInMilliSec initialized with the given parameters + */ + public static TDurationInMilliSec newDurationInMilliSec(BigDecimal value, String unit, String multiplier) { + TDurationInMilliSec minTime = new TDurationInMilliSec(); + minTime.setUnit(unit); + minTime.setMultiplier(multiplier); + minTime.setValue(value); + return minTime; + } + + /** + * Create new instance of TDurationInMilliSec with unit SECOND and multiplier MILLI. + * @param value value attribute + * @return new instance of TDurationInMilliSec initialized with the given parameters + * @see #newDurationInMilliSec(BigDecimal, String, String) + */ + public static TDurationInMilliSec newDurationInMilliSec(long value) { + return newDurationInMilliSec(new BigDecimal(value), SECOND, MILLI); + } + + /** + * Create new instance of TAddress + * @param listOfP list of TP for setting the P attribute of the new TAddress + * @return new instance of TAddress initialized with the given parameters. + * P attribute of TAddress is a new list which contains the elements of listOfP (the given TP are not cloned). + */ + public static TAddress newAddress(List listOfP) { + TAddress address = new TAddress(); + address.getP().addAll(listOfP); + return address; + } + + /** + * Create new instance of TConnectedAP + * @param iedName iedName attribute + * @param apName apName attribute + * @return new instance of TConnectedAP initialized with the given parameters + */ + public static TConnectedAP newConnectedAp(String iedName, String apName) { + TConnectedAP tConnectedAP = new TConnectedAP(); + tConnectedAP.setIedName(iedName); + tConnectedAP.setApName(apName); + return tConnectedAP; + } + +} diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ServicesConfigEnum.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ServicesConfigEnum.java index cfa6755e0..d976a3603 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ServicesConfigEnum.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ServicesConfigEnum.java @@ -4,11 +4,29 @@ package org.lfenergy.compas.sct.commons.util; +import lombok.Getter; +import org.lfenergy.compas.scl2007b4.model.TServiceType; + +@Getter public enum ServicesConfigEnum { - GSE, - SMV, - REPORT, - DATASET, - FCDA; + GSE("GOOSE Control Block"), + SMV("SMV Control Block"), + REPORT("Report Control Block"), + DATASET("DataSet"), + FCDA("FCDA"); + + private final String displayName; + + ServicesConfigEnum(String displayName) { + this.displayName = displayName; + } + public static ServicesConfigEnum from(TServiceType tServiceType){ + return switch (tServiceType){ + case GOOSE -> GSE; + case SMV -> SMV; + case REPORT -> REPORT; + default -> throw new IllegalArgumentException("Unsupported TServiceType " + tServiceType); + }; + } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/Utils.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/Utils.java index 0b5da5b75..f69176a2d 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/Utils.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/Utils.java @@ -6,13 +6,12 @@ import org.apache.commons.lang3.StringUtils; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.function.Function; import java.util.function.Predicate; +import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.stream.LongStream; public final class Utils { @@ -21,6 +20,8 @@ public final class Utils { private static final int S1_CONSIDERED_EQUALS_TO_S2 = 0; private static final int S1_LOWER_THAN_S2 = -1; private static final int S1_GREATER_THAN_S2 = 1; + private static final long MAC_ADDRESS_MAX_VALUE = 0xFFFFFFFFFFFFL; + private static final Pattern MAC_ADDRESS_PATTERN = Pattern.compile("[0-9A-F]{2}([-:][0-9A-F]{2}){5}", Pattern.CASE_INSENSITIVE); /** * Private Constructor, should not be instanced @@ -253,4 +254,103 @@ public static boolean lnClassEquals(List lnClass1, String lnClass2){ } return equalsOrBothBlank(lnClass1.get(0), lnClass2); } + + /** + * Create a new Iterator that provides all ordered long value in the given range + * @param startInclusive the first long in the range (inclusive) + * @param endInclusive the last long in the range (inclusive). Cannot exceed Long.MAX_VALUE - 1. + * @return new Iterator. When endInclusive < startInclusive, return an empty iterator. + */ + public static PrimitiveIterator.OfLong sequence(long startInclusive, long endInclusive) { + if (endInclusive >= Long.MAX_VALUE){ + throw new IllegalArgumentException("End cannot exceed Long.MAX_VALUE - 1"); + } + return LongStream.range(startInclusive, endInclusive + 1).iterator(); + } + + /** + * Create a new Iterator that provides all ordered MAC-Addresses value included in given range. + * See macAddressToLong for the format of MAC-Addresses range, + * and see longToMacAddress for the format of MAC-Addresses output + * @param startInclusive the first MAC-Address in the range (inclusive) + * @param endInclusive the last MAC-Address in the range (inclusive) + * @return new Iterator + * + * @see Utils#macAddressToLong(String) + * @see Utils#longToMacAddress(long) + */ + public static Iterator macAddressSequence(String startInclusive, String endInclusive) { + PrimitiveIterator.OfLong iterator = sequence(macAddressToLong(startInclusive), macAddressToLong(endInclusive)); + return new Iterator<>() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public String next() { + return longToMacAddress(iterator.next()); + } + }; + } + + /** + * Converts long representation of a MAC-Address, by converting it to hexadecimal and separating every 2 characters by a hyphen(-). + * See macAddressToLong for the reversing method. + * @param macAddress a long between 0 and 0xFFFFFFFFFFFF + * @return MAC address separated by hyphens(-). Letters are uppercase (A to F) + * + * @see Utils#macAddressToLong(String) + */ + public static String longToMacAddress(long macAddress){ + if (macAddress < 0){ + throw new IllegalArgumentException("macAddress must be a positive integer but got : %d".formatted(macAddress)); + } + if (macAddress > MAC_ADDRESS_MAX_VALUE){ + throw new IllegalArgumentException("macAddress cannot exceed %d but got : %d".formatted(MAC_ADDRESS_MAX_VALUE, macAddress)); + } + String macText = toHex(macAddress, 12); + return + macText.substring(0, 2) + "-" + + macText.substring(2, 4) + "-" + + macText.substring(4, 6) + "-" + + macText.substring(6, 8) + "-" + + macText.substring(8, 10) + "-" + + macText.substring(10, 12); + } + + /** + * Converts a MAC-Address to its long representation, by concatenating the digits and converting it from hexadecimal to long. + * See longToMacAddress for the reversing method. + * @param macAddress macAddress should be 6 groups of 2 hexadecimal digits (0 to 9 and A to F or a to f) separated by hyphens(–) or + * colons(:) + * @return long between 0 and 0xFFFFFFFFFFFF representing this MAC-Address + * + * @see Utils#longToMacAddress(long) + */ + public static long macAddressToLong(String macAddress){ + if (!MAC_ADDRESS_PATTERN.matcher(macAddress).matches()) { + throw new IllegalArgumentException(("macAddress should be 6 groups of 2 hexadecimal digits (0 to 9 and A to F) separated by hyphens(–) " + + "or colons(:), but got : %s").formatted(macAddress)); + } + String hex = macAddress.substring(0, 2) + + macAddress.substring(3, 5) + + macAddress.substring(6, 8) + + macAddress.substring(9, 11) + + macAddress.substring(12, 14) + + macAddress.substring(15, 17); + return Long.valueOf(hex, 16); + } + + /** + * Convert number to hexadecimal, with uppercase letters (A to F) and a minimum length (using left padding with zero when necessary). + * @param number number to be converted in hexadecimal + * @param length minimum length of resulting string. + * When hexadecimal form of number does not reach length, left padding with "0" is done. + * @return hexadecimal, with uppercase letters (A to F) and minimum length of length. + * Note that the length of return value can exceed "length" parameter when number hexadecimal form is longer than "length" parameter. + */ + public static String toHex(long number, int length) { + return StringUtils.leftPad(Long.toHexString(number).toUpperCase(), length, "0"); + } } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/dto/ControlBlockNetworkSettingsTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/dto/ControlBlockNetworkSettingsTest.java new file mode 100644 index 000000000..6987bd3c8 --- /dev/null +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/dto/ControlBlockNetworkSettingsTest.java @@ -0,0 +1,178 @@ +// SPDX-FileCopyrightText: 2023 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons.dto; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.lfenergy.compas.scl2007b4.model.SCL; +import org.lfenergy.compas.scl2007b4.model.TDurationInMilliSec; +import org.lfenergy.compas.sct.commons.scl.PrivateService; +import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; +import org.lfenergy.compas.sct.commons.scl.ied.ControlBlockAdapter; +import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; +import org.lfenergy.compas.sct.commons.util.ControlBlockEnum; +import org.lfenergy.compas.sct.commons.util.ControlBlockNetworkSettingsCsvHelper; +import org.lfenergy.compas.sct.commons.util.CsvUtils; +import org.lfenergy.compas.sct.commons.util.PrivateEnum; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.math.BigDecimal; +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings.Settings; +import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.findControlBlock; +import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.findIed; + +class ControlBlockNetworkSettingsTest { + + private ControlBlockNetworkSettings controlBlockNetworkSettings; + + @BeforeEach + public void setUp() { + String fileName = "ControlBlockCommunicationTemplates.csv"; + InputStream inputStream = Objects.requireNonNull(CsvUtils.class.getClassLoader().getResourceAsStream(fileName), "Resource not found: " + fileName); + InputStreamReader inputStreamReader = new InputStreamReader(inputStream); + controlBlockNetworkSettings = new ControlBlockNetworkSettingsCsvHelper(inputStreamReader); + } + + @ParameterizedTest + @ValueSource(strings = { + ";01.00;009.001;BCU;A;BAY_INTERNAL;300;4;10;2000", + "GOOSE;;009.001;BCU;A;BAY_INTERNAL;300;4;10;2000", + "GOOSE;01.00;;BCU;A;BAY_INTERNAL;300;4;10;2000", + "GOOSE;01.00;009.001;;A;BAY_INTERNAL;300;4;10;2000", + "GOOSE;01.00;009.001;BCU;;BAY_INTERNAL;300;4;10;2000", + "GOOSE;01.00;009.001;BCU;A;;300;4;10;2000" + }) + void constructor_when_csv_has_blank_criteria_cells_should_throw_exception(String row) { + //Given + StringReader stringReader = new StringReader(row); + //When & Then + assertThatThrownBy(() -> new ControlBlockNetworkSettingsCsvHelper(stringReader)) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @ValueSource(strings = { + "GOOSE;01.00;009.001;BCU;A;BAY_INTERNAL;XXX;4;10;2000", + "GOOSE;01.00;009.001;BCU;A;BAY_INTERNAL;300;XXX;10;2000", + "GOOSE;01.00;009.001;BCU;A;BAY_INTERNAL;300;4;XXX;2000", + "GOOSE;01.00;009.001;BCU;A;BAY_INTERNAL;300;4;10;XXX" + }) + void constructor_when_csv_has_malformed_numbers_should_throw_exception(String row) { + //Given + StringReader stringReader = new StringReader(row); + //When & Then + assertThatThrownBy(() -> new ControlBlockNetworkSettingsCsvHelper(stringReader)) + .isInstanceOf(NumberFormatException.class); + } + + @ParameterizedTest + @ValueSource(strings = { + "GOOSE;01.00;009.001;BCU;A;BAY_INTERNAL;4096;4;10;2000", // VlanId > MAX_VLAN_ID + "GOOSE;01.00;009.001;BCU;A;BAY_INTERNAL;300;8;10;2000" // VlanPriority > MAX_VLAN_PRIORITY + }) + void constructor_when_csv_has_numbers_our_out_of_bound_should_throw_exception(String row) { + //Given + StringReader stringReader = new StringReader(row); + //When & Then + assertThatThrownBy(() -> new ControlBlockNetworkSettingsCsvHelper(stringReader)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void getNetworkSettings_should_return_settings_for_bay_internal_controlBlock() { + //Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + ControlBlockAdapter controlBlockAdapter = findControlBlock(sclRootAdapter, "IED_NAME2", "LD_INST21", "CB_LD_INST21_GSI", ControlBlockEnum.GSE); + + //When + ControlBlockNetworkSettings.Settings networkSettings = controlBlockNetworkSettings.getNetworkSettings(controlBlockAdapter); + + //Then + assertThat(networkSettings) + .extracting(Settings::vlanId, Settings::vlanPriority) + .containsExactly(300, (byte) 4); + assertThat(networkSettings.minTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) + .containsExactly("s", "m", new BigDecimal("10")); + assertThat(networkSettings.maxTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) + .containsExactly("s", "m", new BigDecimal("2000")); + } + + @Test + void getNetworkSettings_should_return_settings_for_bay_external_controlBlock() { + //Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + ControlBlockAdapter controlBlockAdapter = findControlBlock(sclRootAdapter, "IED_NAME3", "LD_INST31", "CB_LD_INST31_GSE", ControlBlockEnum.GSE); + + //When + ControlBlockNetworkSettings.Settings networkSettings = controlBlockNetworkSettings.getNetworkSettings(controlBlockAdapter); + + //Then + assertThat(networkSettings) + .extracting(Settings::vlanId, Settings::vlanPriority) + .containsExactly(301, (byte) 5); + assertThat(networkSettings.minTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) + .containsExactly("s", "m", new BigDecimal("15")); + assertThat(networkSettings.maxTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) + .containsExactly("s", "m", new BigDecimal("5000")); + } + + @Test + void getNetworkSettings_should_return_vlanId_null_when_column_contains_none() { + //Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + ControlBlockAdapter controlBlockAdapter = findControlBlock(sclRootAdapter, "IED_NAME2", "LD_INST21", "CB_LD_INST21_SVI", ControlBlockEnum.SAMPLED_VALUE); + + //When + ControlBlockNetworkSettings.Settings networkSettings = controlBlockNetworkSettings.getNetworkSettings(controlBlockAdapter); + + //Then + assertThat(networkSettings.vlanId()).isNull(); + assertThat(networkSettings.vlanPriority()).isNull(); + } + + @Test + void getNetworkSettings_should_return_null_when_row_not_found_in_csv_file() { + //Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + findIed(sclRootAdapter, "IED_NAME2").getCompasSystemVersion().get().setMainSystemVersion("99.99"); + ControlBlockAdapter controlBlockAdapter = findControlBlock(sclRootAdapter, "IED_NAME2", "LD_INST21", "CB_LD_INST21_GSI", ControlBlockEnum.GSE); + + //When + ControlBlockNetworkSettings.Settings networkSettings = controlBlockNetworkSettings.getNetworkSettings(controlBlockAdapter); + + //Then + assertThat(networkSettings).isNull(); + } + + @ParameterizedTest + @EnumSource(value = PrivateEnum.class, mode = EnumSource.Mode.INCLUDE, names = {"COMPAS_ICDHEADER", "COMPAS_SYSTEM_VERSION"}) + void getNetworkSettings_should_return_null_when_missing_ied_private(PrivateEnum privateEnum) { + //Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + PrivateService.removePrivates(findIed(sclRootAdapter, "IED_NAME2").getCurrentElem(), privateEnum); + ControlBlockAdapter controlBlockAdapter = findControlBlock(sclRootAdapter, "IED_NAME2", "LD_INST21", "CB_LD_INST21_GSI", ControlBlockEnum.GSE); + + //When + ControlBlockNetworkSettings.Settings networkSettings = controlBlockNetworkSettings.getNetworkSettings(controlBlockAdapter); + + //Then + assertThat(networkSettings).isNull(); + } + +} diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/dto/DTO.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/dto/DTO.java index 194be6745..fb54f52c0 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/dto/DTO.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/dto/DTO.java @@ -18,6 +18,7 @@ public class DTO { /* ConnectedAPDTO */ /*-----------------------------------------------*/ public static final String AP_NAME = "AP_NAME"; + public static final String AP_NAME_2 = "AP_NAME_2"; public static ConnectedApDTO createCapDTO() { @@ -32,6 +33,7 @@ public static ConnectedApDTO createCapDTO() { /* ExtRefInfo */ /*-----------------------------------------------*/ public static final String HOLDER_IED_NAME = "IED_NAME"; + public static final String HOLDER_IED_NAME_2 = "IED_NAME_2"; public static final String HOLDER_LD_INST = "LD_INST_H"; public static final String HOLDER_LN_INST = "1"; public static final String HOLDER_LN_CLASS = "LN_CLASS_H"; diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ExtRefServiceTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ExtRefServiceTest.java index 7855773f3..46d59cf8d 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ExtRefServiceTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ExtRefServiceTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.lfenergy.compas.scl2007b4.model.*; +import org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings; import org.lfenergy.compas.sct.commons.dto.ControlBlockTarget; import org.lfenergy.compas.sct.commons.dto.SclReport; import org.lfenergy.compas.sct.commons.dto.SclReportItem; @@ -21,7 +22,7 @@ import org.lfenergy.compas.sct.commons.testhelpers.MarshallerWrapper; import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; -import java.util.Collections; +import java.math.BigDecimal; import java.util.List; import java.util.Optional; import java.util.stream.Stream; @@ -29,11 +30,21 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.lfenergy.compas.scl2007b4.model.TFCEnum.ST; +import static org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings.*; import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.*; import static org.lfenergy.compas.sct.commons.util.ControlBlockEnum.*; +import static org.lfenergy.compas.sct.commons.util.SclConstructorHelper.newDurationInMilliSec; class ExtRefServiceTest { + private static final long GSE_APP_ID_MIN = 0x9; + private static final long SMV_APP_ID_MIN = 0x400A; + private static final String GSE_MAC_ADDRESS_PREFIX = "01-02-03-04-"; + private static final String SMV_MAC_ADDRESS_PREFIX = "0A-0B-0C-0D-"; + private static final NetworkRanges GSE_NETWORK_RANGES = new NetworkRanges(GSE_APP_ID_MIN, GSE_APP_ID_MIN + 10, GSE_MAC_ADDRESS_PREFIX + "00-FF", GSE_MAC_ADDRESS_PREFIX + "01-AA"); + private static final NetworkRanges SMV_NETWORK_RANGES = new NetworkRanges(SMV_APP_ID_MIN, SMV_APP_ID_MIN + 10, SMV_MAC_ADDRESS_PREFIX + "00-FF", SMV_MAC_ADDRESS_PREFIX + "01-AA"); + private static final RangesPerCbType RANGES_PER_CB_TYPE = new RangesPerCbType(GSE_NETWORK_RANGES, SMV_NETWORK_RANGES); + @Test void updateAllExtRefIedNames_should_update_iedName_and_ExtRefIedName() { // Given : An ExtRef with a matching compas:Flow @@ -45,12 +56,12 @@ void updateAllExtRefIedNames_should_update_iedName_and_ExtRefIedName() { assertThat(extRef.getIedName()).isEqualTo("IED_NAME2"); TInputs inputs = findLDevice(sclReport, "IED_NAME1", "LD_INST11") - .getLN0Adapter() - .getCurrentElem() - .getInputs(); + .getLN0Adapter() + .getCurrentElem() + .getInputs(); assertThat(PrivateService.extractCompasPrivate(inputs, TCompasFlow.class)) - .map(TCompasFlow::getExtRefiedName) - .hasValue("IED_NAME2"); + .map(TCompasFlow::getExtRefiedName) + .hasValue("IED_NAME2"); } @Test @@ -61,8 +72,8 @@ void updateAllExtRefIedNames_should_return_success_status() { SclReport sclReport = ExtRefService.updateAllExtRefIedNames(scd); // Then assertThat(sclReport.isSuccess()) - .overridingErrorMessage(String.valueOf(sclReport.getSclReportItems())) - .isTrue(); + .overridingErrorMessage(String.valueOf(sclReport.getSclReportItems())) + .isTrue(); } @ParameterizedTest(name = "{0}") @@ -79,64 +90,64 @@ void updateAllExtRefIedNames_should_report_errors(String testCase, SCL scl, SclR public static Stream updateAllExtRefIedNamesErrors() { return - Stream.of(Arguments.of( - "Errors on ExtRefs", - SclTestMarshaller.getSCLFromFile("/scd-extref-iedname/scd_set_extref_iedname_with_extref_errors.xml"), - new SclReportItem[]{ - SclReportItem.fatal( - "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + - "/LN0/Inputs/ExtRef[@desc=\"No matching compas:Flow\"]", - "The signal ExtRef has no matching compas:Flow Private"), - SclReportItem.fatal( - "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + - "/LN0/Inputs/ExtRef[@desc=\"Matching two compas:Flow\"]", - "The signal ExtRef has more than one matching compas:Flow Private"), - SclReportItem.fatal( - "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST13\"]", - "The LDevice status is neither \"on\" nor \"off\""), - SclReportItem.fatal( - "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST14\"]", - "The LDevice status is undefined"), - SclReportItem.warning( - "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + - "/LN0/Inputs/ExtRef[@desc=\"ExtRef does not match any ICDSystemVersionUUID\"]", - "The signal ExtRef iedName does not match any IED/Private/compas:ICDHeader@ICDSystemVersionUUID"), - SclReportItem.warning( - "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + - "/LN0/Inputs/ExtRef[@desc=\"ExtRefldinst does not match any LDevice inst in source IED\"]", - "The signal ExtRef ExtRefldinst does not match any LDevice with same inst attribute in source IED /SCL/IED[@name=\"IED_NAME2\"]"), - SclReportItem.warning( - "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + - "/LN0/Inputs/ExtRef[@desc=\"ExtRef does not match any LN in source LDevice\"]", - "The signal ExtRef lninst, doName or daName does not match any source in LDevice " + - "/SCL/IED[@name=\"IED_NAME2\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST21\"]"), - SclReportItem.warning( - "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + - "/LN0/Inputs/ExtRef[@desc=\"Source LDevice is off for this ExtRef\"]", - "The signal ExtRef source LDevice /SCL/IED[@name=\"IED_NAME2\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST22\"] status is off"), - SclReportItem.fatal( - "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + - "/LN0/Inputs/ExtRef[@desc=\"Source LDevice is undefined for this ExtRef\"]", - "The signal ExtRef source LDevice /SCL/IED[@name=\"IED_NAME2\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST23\"] status is " + - "undefined"), - SclReportItem.fatal( - "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + - "/LN0/Inputs/ExtRef[@desc=\"Source LDevice is neither on nor off for this ExtRef\"]", - "The signal ExtRef source LDevice /SCL/IED[@name=\"IED_NAME2\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST24\"] " + - "status is neither \"on\" nor \"off\"") - }), - Arguments.of( - "Errors on IEDs", - SclTestMarshaller.getSCLFromFile("/scd-extref-iedname/scd_set_extref_iedname_with_ied_errors.xml"), - new SclReportItem[]{ - SclReportItem.fatal( - "/SCL/IED[@name=\"IED_NAME1\"], /SCL/IED[@name=\"IED_NAME2\"]", - "/IED/Private/compas:ICDHeader[@ICDSystemVersionUUID] must be unique but the same ICDSystemVersionUUID was found on several IED."), - SclReportItem.fatal("/SCL/IED[@name=\"IED_NAME3\"]", "IED has no Private COMPAS-ICDHeader element"), - SclReportItem.fatal("/SCL/IED[@name=\"IED_NAME4\"]", "IED private COMPAS-ICDHeader as no icdSystemVersionUUID or iedName attribute"), - SclReportItem.fatal("/SCL/IED[@name=\"IED_NAME5\"]", "IED private COMPAS-ICDHeader as no icdSystemVersionUUID or iedName attribute") - }) - ); + Stream.of(Arguments.of( + "Errors on ExtRefs", + SclTestMarshaller.getSCLFromFile("/scd-extref-iedname/scd_set_extref_iedname_with_extref_errors.xml"), + new SclReportItem[]{ + SclReportItem.fatal( + "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + + "/LN0/Inputs/ExtRef[@desc=\"No matching compas:Flow\"]", + "The signal ExtRef has no matching compas:Flow Private"), + SclReportItem.fatal( + "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + + "/LN0/Inputs/ExtRef[@desc=\"Matching two compas:Flow\"]", + "The signal ExtRef has more than one matching compas:Flow Private"), + SclReportItem.fatal( + "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST13\"]", + "The LDevice status is neither \"on\" nor \"off\""), + SclReportItem.fatal( + "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST14\"]", + "The LDevice status is undefined"), + SclReportItem.warning( + "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + + "/LN0/Inputs/ExtRef[@desc=\"ExtRef does not match any ICDSystemVersionUUID\"]", + "The signal ExtRef iedName does not match any IED/Private/compas:ICDHeader@ICDSystemVersionUUID"), + SclReportItem.warning( + "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + + "/LN0/Inputs/ExtRef[@desc=\"ExtRefldinst does not match any LDevice inst in source IED\"]", + "The signal ExtRef ExtRefldinst does not match any LDevice with same inst attribute in source IED /SCL/IED[@name=\"IED_NAME2\"]"), + SclReportItem.warning( + "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + + "/LN0/Inputs/ExtRef[@desc=\"ExtRef does not match any LN in source LDevice\"]", + "The signal ExtRef lninst, doName or daName does not match any source in LDevice " + + "/SCL/IED[@name=\"IED_NAME2\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST21\"]"), + SclReportItem.warning( + "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + + "/LN0/Inputs/ExtRef[@desc=\"Source LDevice is off for this ExtRef\"]", + "The signal ExtRef source LDevice /SCL/IED[@name=\"IED_NAME2\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST22\"] status is off"), + SclReportItem.fatal( + "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + + "/LN0/Inputs/ExtRef[@desc=\"Source LDevice is undefined for this ExtRef\"]", + "The signal ExtRef source LDevice /SCL/IED[@name=\"IED_NAME2\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST23\"] status is " + + "undefined"), + SclReportItem.fatal( + "/SCL/IED[@name=\"IED_NAME1\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST11\"]" + + "/LN0/Inputs/ExtRef[@desc=\"Source LDevice is neither on nor off for this ExtRef\"]", + "The signal ExtRef source LDevice /SCL/IED[@name=\"IED_NAME2\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST24\"] " + + "status is neither \"on\" nor \"off\"") + }), + Arguments.of( + "Errors on IEDs", + SclTestMarshaller.getSCLFromFile("/scd-extref-iedname/scd_set_extref_iedname_with_ied_errors.xml"), + new SclReportItem[]{ + SclReportItem.fatal( + "/SCL/IED[@name=\"IED_NAME1\"], /SCL/IED[@name=\"IED_NAME2\"]", + "/IED/Private/compas:ICDHeader[@ICDSystemVersionUUID] must be unique but the same ICDSystemVersionUUID was found on several IED."), + SclReportItem.fatal("/SCL/IED[@name=\"IED_NAME3\"]", "IED has no Private COMPAS-ICDHeader element"), + SclReportItem.fatal("/SCL/IED[@name=\"IED_NAME4\"]", "IED private COMPAS-ICDHeader as no icdSystemVersionUUID or iedName attribute"), + SclReportItem.fatal("/SCL/IED[@name=\"IED_NAME5\"]", "IED private COMPAS-ICDHeader as no icdSystemVersionUUID or iedName attribute") + }) + ); } @Test @@ -146,28 +157,12 @@ void updateAllExtRefIedNames_when_not_bindable_should_clear_binding() { // When SclReport sclReport = ExtRefService.updateAllExtRefIedNames(scd); // Then - assertThatExtRefBindingInfoIsMissing(findExtRef(sclReport, "IED_NAME1", "LD_INST12", "ExtRef target LDevice status is off")); - assertThatExtRefBindingInfoIsMissing(findExtRef(sclReport, "IED_NAME1", "LD_INST11", "Match compas:Flow but FlowStatus is INACTIVE")); - assertThatExtRefBindingInfoIsMissing(findExtRef(sclReport, "IED_NAME1", "LD_INST11", "ExtRef does not match any ICDSystemVersionUUID")); - assertThatExtRefBindingInfoIsMissing(findExtRef(sclReport, "IED_NAME1", "LD_INST11", "ExtRefldinst does not match any LDevice inst in source IED")); - assertThatExtRefBindingInfoIsMissing(findExtRef(sclReport, "IED_NAME1", "LD_INST11", "ExtRef does not match any LN in source LDevice")); - assertThatExtRefBindingInfoIsMissing(findExtRef(sclReport, "IED_NAME1", "LD_INST11", "Source LDevice is off for this ExtRef")); - } - - private void assertThatExtRefBindingInfoIsMissing(TExtRef extRef) { - assertThat(extRef.isSetIedName()).isFalse(); - assertThat(extRef.isSetLdInst()).isFalse(); - assertThat(extRef.isSetPrefix()).isFalse(); - assertThat(extRef.isSetLnClass()).isFalse(); - assertThat(extRef.isSetLnInst()).isFalse(); - assertThat(extRef.isSetDoName()).isFalse(); - assertThat(extRef.isSetDaName()).isFalse(); - assertThat(extRef.isSetServiceType()).isFalse(); - assertThat(extRef.isSetSrcLDInst()).isFalse(); - assertThat(extRef.isSetPrefix()).isFalse(); - assertThat(extRef.isSetSrcLNClass()).isFalse(); - assertThat(extRef.isSetLnInst()).isFalse(); - assertThat(extRef.isSetSrcCBName()).isFalse(); + assertExtRefIsNotBound(findExtRef(sclReport, "IED_NAME1", "LD_INST12", "ExtRef target LDevice status is off")); + assertExtRefIsNotBound(findExtRef(sclReport, "IED_NAME1", "LD_INST11", "Match compas:Flow but FlowStatus is INACTIVE")); + assertExtRefIsNotBound(findExtRef(sclReport, "IED_NAME1", "LD_INST11", "ExtRef does not match any ICDSystemVersionUUID")); + assertExtRefIsNotBound(findExtRef(sclReport, "IED_NAME1", "LD_INST11", "ExtRefldinst does not match any LDevice inst in source IED")); + assertExtRefIsNotBound(findExtRef(sclReport, "IED_NAME1", "LD_INST11", "ExtRef does not match any LN in source LDevice")); + assertExtRefIsNotBound(findExtRef(sclReport, "IED_NAME1", "LD_INST11", "Source LDevice is off for this ExtRef")); } @Test @@ -181,7 +176,7 @@ void updateAllExtRefIedNames_when_lDevice_off_should_remove_binding() { LDeviceAdapter lDeviceAdapter = findLDeviceByLdName(sclReport.getSclRootAdapter(), "IED_NAME1LD_INST12"); assertThat(lDeviceAdapter.getLDeviceStatus()).hasValue("off"); assertThat(lDeviceAdapter.getLN0Adapter().getInputsAdapter().getCurrentElem().getExtRef()) - .allSatisfy(this::assertExtRefIsNotBound); + .allSatisfy(this::assertExtRefIsNotBound); } @Test @@ -195,8 +190,8 @@ void updateAllExtRefIedNames_when_FlowStatus_INACTIVE_should_remove_binding() { LDeviceAdapter lDeviceAdapter = findLDeviceByLdName(sclReport.getSclRootAdapter(), "IED_NAME1LD_INST11"); assertThat(lDeviceAdapter.getLDeviceStatus()).hasValue("on"); Optional optionalTExtRef = lDeviceAdapter.getCurrentElem().getLN0().getInputs().getExtRef().stream() - .filter(tExtRef -> "Match compas:Flow but FlowStatus is INACTIVE".equals(tExtRef.getDesc())) - .findFirst(); + .filter(tExtRef -> "Match compas:Flow but FlowStatus is INACTIVE".equals(tExtRef.getDesc())) + .findFirst(); assertThat(optionalTExtRef).isPresent(); TExtRef extRef = optionalTExtRef.get(); assertExtRefIsNotBound(extRef); @@ -224,12 +219,12 @@ void createDataSetAndControlBlocks_should_create_DataSet() { DataSetAdapter aDataSet = findDataSet(sclReport.getSclRootAdapter(), "IED_NAME2", "LD_INST21", "DS_LD_INST21_GSI"); assertThat(aDataSet.getCurrentElem().getFCDA()).hasSize(4); assertThat(aDataSet.getCurrentElem().getFCDA().stream().map(FCDARecord::toFCDARecord)) - .containsExactly( - new FCDARecord("LD_INST21", "ANCR", "1", "", "DoName", "daNameST", ST), - new FCDARecord("LD_INST21", "ANCR", "1", "", "DoWithInst1", "daNameST", ST), - new FCDARecord("LD_INST21", "ANCR", "1", "", "DoWithInst2.subDo", "daNameST", ST), - new FCDARecord("LD_INST21", "ANCR", "1", "", "OtherDoName", "daNameST", ST) - ); + .containsExactly( + new FCDARecord("LD_INST21", "ANCR", "1", "", "DoName", "daNameST", ST), + new FCDARecord("LD_INST21", "ANCR", "1", "", "DoWithInst1", "daNameST", ST), + new FCDARecord("LD_INST21", "ANCR", "1", "", "DoWithInst2.subDo", "daNameST", ST), + new FCDARecord("LD_INST21", "ANCR", "1", "", "OtherDoName", "daNameST", ST) + ); } @@ -255,17 +250,17 @@ void createDataSetAndControlBlocks_should_create_ControlBlocks() { assertThat(reportControlBlock.getCurrentElem()).isInstanceOf(TReportControl.class); TReportControl tReportControl = (TReportControl) reportControlBlock.getCurrentElem(); assertThat(tReportControl).extracting(TReportControl::getConfRev, TReportControl::isBuffered, TReportControl::getBufTime, TReportControl::isIndexed, - TControlWithTriggerOpt::getIntgPd) - .containsExactly(1L, true, 0L, true, 60000L); + TControlWithTriggerOpt::getIntgPd) + .containsExactly(1L, true, 0L, true, 60000L); assertThat(tReportControl.getTrgOps()) - .extracting(TTrgOps::isDchg, TTrgOps::isQchg, TTrgOps::isDupd, TTrgOps::isPeriod, TTrgOps::isGi) - .containsExactly(false, false, false, true, true); + .extracting(TTrgOps::isDchg, TTrgOps::isQchg, TTrgOps::isDupd, TTrgOps::isPeriod, TTrgOps::isGi) + .containsExactly(false, false, false, true, true); assertThat(tReportControl.getRptEnabled().getMax()).isEqualTo(1L); assertThat(tReportControl.getRptEnabled().getClientLN().stream().map(ControlBlockTarget::from)) - .containsExactly( - new ControlBlockTarget("AP_NAME", "IED_NAME1", "LD_INST11", "", "LLN0", "", "")); + .containsExactly( + new ControlBlockTarget("AP_NAME", "IED_NAME1", "LD_INST11", "", "LLN0", "", "")); } @Test @@ -279,22 +274,22 @@ void createDataSetAndControlBlocks_should_set_ExtRef_srcXXX_attributes() { // assert all ExtRef.srcPrefix srcLNClass srcLNInst are not set assertThat(streamAllExtRef(sclReport.getSclRootAdapter())) - .extracting(TExtRef::getSrcPrefix, TExtRef::getSrcLNClass, TExtRef::getSrcLNInst) - .containsOnly(Tuple.tuple(null, Collections.emptyList(), null)); + .extracting(TExtRef::getSrcPrefix, TExtRef::isSetSrcLNClass, TExtRef::getSrcLNInst) + .containsOnly(Tuple.tuple(null, false, null)); // check some ExtRef assertThat(findExtRef(sclReport, "IED_NAME1", "LD_INST11", "test bay internal")) - .extracting(TExtRef::getSrcCBName, TExtRef::getSrcLDInst) - .containsExactly("CB_LD_INST21_GSI", "LD_INST21"); + .extracting(TExtRef::getSrcCBName, TExtRef::getSrcLDInst) + .containsExactly("CB_LD_INST21_GSI", "LD_INST21"); assertThat(findExtRef(sclReport, "IED_NAME1", "LD_INST11", "test bay external")) - .extracting(TExtRef::getSrcCBName, TExtRef::getSrcLDInst) - .containsExactly("CB_LD_INST31_GSE", "LD_INST31"); + .extracting(TExtRef::getSrcCBName, TExtRef::getSrcLDInst) + .containsExactly("CB_LD_INST31_GSE", "LD_INST31"); assertThat(findExtRef(sclReport, "IED_NAME1", "LD_INST11", "test ServiceType is SMV, no daName and DO contains ST and MX, but only ST is FCDA candidate")) - .extracting(TExtRef::getSrcCBName, TExtRef::getSrcLDInst) - .containsExactly("CB_LD_INST21_SVI", "LD_INST21"); + .extracting(TExtRef::getSrcCBName, TExtRef::getSrcLDInst) + .containsExactly("CB_LD_INST21_SVI", "LD_INST21"); assertThat(findExtRef(sclReport, "IED_NAME1", "LD_INST11", "test ServiceType is Report_daReportMX_1")) - .extracting(TExtRef::getSrcCBName, TExtRef::getSrcLDInst) - .containsExactly("CB_LD_INST21_CYCI", "LD_INST21"); + .extracting(TExtRef::getSrcCBName, TExtRef::getSrcLDInst) + .containsExactly("CB_LD_INST21_CYCI", "LD_INST21"); } @Test @@ -330,8 +325,8 @@ void createDataSetAndControlBlocks_when_targetIedName_is_not_found_should_throw_ SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When & Then assertThatThrownBy(() -> ExtRefService.createDataSetAndControlBlocks(scd, "non_existing_IED_name")) - .isInstanceOf(ScdException.class) - .hasMessage("IED.name 'non_existing_IED_name' not found in SCD"); + .isInstanceOf(ScdException.class) + .hasMessage("IED.name 'non_existing_IED_name' not found in SCD"); } @Test @@ -350,8 +345,8 @@ void createDataSetAndControlBlocks_when_targetIedName_is_not_found_and_targetLDe SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When & Then assertThatThrownBy(() -> ExtRefService.createDataSetAndControlBlocks(scd, "non_existing_IED_name", "LD_INST11")) - .isInstanceOf(ScdException.class) - .hasMessage("IED.name 'non_existing_IED_name' not found in SCD"); + .isInstanceOf(ScdException.class) + .hasMessage("IED.name 'non_existing_IED_name' not found in SCD"); } @Test @@ -360,8 +355,8 @@ void createDataSetAndControlBlocks_when_targetIedName_and_targetLDeviceInst_is_n SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When & Then assertThatThrownBy(() -> ExtRefService.createDataSetAndControlBlocks(scd, "IED_NAME1", "non_existing_LDevice_inst")) - .isInstanceOf(ScdException.class) - .hasMessage("LDevice.inst 'non_existing_LDevice_inst' not found in IED 'IED_NAME1'"); + .isInstanceOf(ScdException.class) + .hasMessage("LDevice.inst 'non_existing_LDevice_inst' not found in IED 'IED_NAME1'"); } @Test @@ -370,8 +365,8 @@ void createDataSetAndControlBlocks_when_targetLDeviceInst_is_provided_without_ta SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); // When & Then assertThatThrownBy(() -> ExtRefService.createDataSetAndControlBlocks(scd, null, "LD_INST11")) - .isInstanceOf(ScdException.class) - .hasMessage("IED.name parameter is missing"); + .isInstanceOf(ScdException.class) + .hasMessage("IED.name parameter is missing"); } private void assertExtRefIsNotBound(TExtRef extRef) { @@ -401,13 +396,114 @@ void updateAllSourceDataSetsAndControlBlocks_should_sort_FCDA_inside_DataSet_and assertThat(sclReport.getSclReportItems()).isEmpty(); DataSetAdapter dataSetAdapter = findDataSet(sclRootAdapter, "IED_NAME2", "LD_INST21", "DS_LD_INST21_GSI"); assertThat(dataSetAdapter.getCurrentElem().getFCDA()) - .map(TFCDA::getLnInst, TFCDA::getDoName) - .containsExactly( - Tuple.tuple("1", "FirstDo"), - Tuple.tuple("1", "SecondDo"), - Tuple.tuple("1", "ThirdDo"), - Tuple.tuple("02", "FirstDo") - ); + .map(TFCDA::getLnInst, TFCDA::getDoName) + .containsExactly( + Tuple.tuple("1", "FirstDo"), + Tuple.tuple("1", "SecondDo"), + Tuple.tuple("1", "ThirdDo"), + Tuple.tuple("02", "FirstDo") + ); + } + + @Test + void configureNetworkForAllControlBlocks_should_create_GSE_and_SMV_elements() { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + + TDurationInMilliSec minTime = newDurationInMilliSec(10); + TDurationInMilliSec maxTime = newDurationInMilliSec(2000); + ControlBlockNetworkSettings controlBlockNetworkSettings = controlBlockAdapter -> new Settings(0x1D6, (byte) 4, minTime, maxTime); + + // When + SclReport sclReport = ExtRefService.configureNetworkForAllControlBlocks(scd, controlBlockNetworkSettings, RANGES_PER_CB_TYPE); + // Then + assertThat(sclReport.isSuccess()).isTrue(); + TConnectedAP connectedAP = sclReport.getSclRootAdapter().findConnectedApAdapter("IED_NAME2", "AP_NAME").get().getCurrentElem(); + TGSE gse = connectedAP.getGSE().stream() + .filter(tgse -> "CB_LD_INST21_GSI".equals(tgse.getCbName())) + .findFirst().get(); + assertThat(gse.getLdInst()).isEqualTo("LD_INST21"); + assertThat(gse.getMinTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) + .containsExactly("s", "m", new BigDecimal("10")); + assertThat(gse.getMaxTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) + .containsExactly("s", "m", new BigDecimal("2000")); + assertThat(gse.getAddress().getP()).extracting(TP::getType, TP::getValue) + .containsExactlyInAnyOrder( + Tuple.tuple("VLAN-PRIORITY", "4"), + Tuple.tuple("APPID", "0009"), + Tuple.tuple("MAC-Address", "01-02-03-04-00-FF"), + Tuple.tuple("VLAN-ID", "1D6") + ); + TSMV smv = connectedAP.getSMV().stream() + .filter(tsmv -> "CB_LD_INST21_SVI".equals(tsmv.getCbName())) + .findFirst().get(); + assertThat(smv.getLdInst()).isEqualTo("LD_INST21"); + assertThat(smv.getAddress().getP()).extracting(TP::getType, TP::getValue) + .containsExactlyInAnyOrder( + Tuple.tuple("VLAN-PRIORITY", "4"), + Tuple.tuple("APPID", "400A"), + Tuple.tuple("MAC-Address", "0A-0B-0C-0D-00-FF"), + Tuple.tuple("VLAN-ID", "1D6") + ); + MarshallerWrapper.assertValidateXmlSchema(scd); + } + + @Test + void configureNetworkForAllControlBlocks_should_create_GSE_with_incremental_appid_and_mac_addresses() { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + + TDurationInMilliSec minTime = newDurationInMilliSec(10); + TDurationInMilliSec maxTime = newDurationInMilliSec(2000); + ControlBlockNetworkSettings controlBlockNetworkSettings = controlBlockAdapter -> new Settings(0x1D6, (byte) 4, minTime, maxTime); + // When + SclReport sclReport = ExtRefService.configureNetworkForAllControlBlocks(scd, controlBlockNetworkSettings, RANGES_PER_CB_TYPE); + // Then + assertThat(sclReport.isSuccess()).isTrue(); + assertThat(streamAllConnectedApGseP(scd, "APPID")) + .containsExactlyInAnyOrder("0009", "000A", "000B"); + assertThat(streamAllConnectedApGseP(scd, "MAC-Address")) + .containsExactlyInAnyOrder("01-02-03-04-00-FF", "01-02-03-04-01-00", "01-02-03-04-01-01"); + } + + @ParameterizedTest + @MethodSource("provideConfigureNetworkForAllControlBlocksErrors") + void configureNetworkForAllControlBlocks_should_fail_when_no_settings_for_this_controlBlock(ControlBlockNetworkSettings controlBlockNetworkSettings, + RangesPerCbType rangesPerCbType, + String expectedMessage) { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml"); + // When + SclReport sclReport = ExtRefService.configureNetworkForAllControlBlocks(scd, controlBlockNetworkSettings, rangesPerCbType); + // Then + assertThat(sclReport.isSuccess()).isFalse(); + assertThat(sclReport.getSclReportItems()) + .extracting(SclReportItem::getMessage, SclReportItem::getXpath) + .contains(Tuple.tuple(expectedMessage, + "/SCL/IED[@name=\"IED_NAME2\"]/AccessPoint/Server/LDevice[@inst=\"LD_INST21\"]/LN0/GSEControl[@name=\"CB_LD_INST21_GMI\"]")); + } + + public static Stream provideConfigureNetworkForAllControlBlocksErrors() { + Settings settingsWithNullVlanId = new Settings(null, (byte) 1, newDurationInMilliSec(1), newDurationInMilliSec(2)); + Settings settings = new Settings(1, (byte) 1, newDurationInMilliSec(1), newDurationInMilliSec(2)); + return Stream.of( + Arguments.of((ControlBlockNetworkSettings) controlBlockAdapter -> null, + RANGES_PER_CB_TYPE, + "Cannot configure network for this ControlBlock because no settings was provided"), + Arguments.of((ControlBlockNetworkSettings) controlBlockAdapter -> settingsWithNullVlanId, + RANGES_PER_CB_TYPE, + "Cannot configure network for this ControlBlock because no Vlan Id was provided in the settings"), + Arguments.of((ControlBlockNetworkSettings) controlBlockAdapter -> settings, + new RangesPerCbType( + new NetworkRanges(GSE_APP_ID_MIN, GSE_APP_ID_MIN, GSE_MAC_ADDRESS_PREFIX + "00-FF", GSE_MAC_ADDRESS_PREFIX + "01-AA"), + SMV_NETWORK_RANGES), + "Cannot configure network for this ControlBlock because range of appId is exhausted"), + Arguments.of((ControlBlockNetworkSettings) controlBlockAdapter -> settings, + new RangesPerCbType( + new NetworkRanges(GSE_APP_ID_MIN, GSE_APP_ID_MIN + 10, GSE_MAC_ADDRESS_PREFIX + "00-FF", GSE_MAC_ADDRESS_PREFIX + "00-FF"), + SMV_NETWORK_RANGES), + "Cannot configure network for this ControlBlock because range of MAC Address is exhausted") + ); } } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/PrivateServiceTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/PrivateServiceTest.java index 0fd7e179c..456293765 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/PrivateServiceTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/PrivateServiceTest.java @@ -17,6 +17,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.math.BigInteger; import java.util.*; import java.util.stream.Stream; @@ -479,7 +480,7 @@ void comparePrivateCompasICDHeaders_should_return_true_equality_not_check_for_IE TCompasICDHeader compasICDHeader1 = new TCompasICDHeader(); compasICDHeader1.setIEDName("IED-1"); compasICDHeader1.setBayLabel("BAY-1"); - compasICDHeader1.setIEDinstance("1"); + compasICDHeader1.setIEDSubstationinstance(BigInteger.ONE); TCompasICDHeader compasICDHeader2 = new TCompasICDHeader(); TPrivate tPrivate1 = PrivateService.createPrivate(compasICDHeader1); TPrivate tPrivate2 = PrivateService.createPrivate(compasICDHeader2); @@ -496,7 +497,7 @@ void comparePrivateCompasICDHeaders_should_return_false_equality_not_check_for_I TCompasICDHeader compasICDHeader1 = new TCompasICDHeader(); compasICDHeader1.setIEDName("IED-1"); compasICDHeader1.setBayLabel("BAY-1"); - compasICDHeader1.setIEDinstance("1"); + compasICDHeader1.setIEDSubstationinstance(BigInteger.ONE); compasICDHeader1.setICDSystemVersionUUID("UUID-1"); TCompasICDHeader compasICDHeader2 = new TCompasICDHeader(); compasICDHeader2.setICDSystemVersionUUID("UUID-2"); @@ -515,7 +516,7 @@ void comparePrivateCompasICDHeaders_should_return_true() { TCompasICDHeader compasICDHeader1 = new TCompasICDHeader(); compasICDHeader1.setIEDName("IED-1"); compasICDHeader1.setBayLabel("BAY-1"); - compasICDHeader1.setIEDinstance("1"); + compasICDHeader1.setIEDSubstationinstance(BigInteger.ONE); compasICDHeader1.setICDSystemVersionUUID("UUID-1"); TCompasICDHeader compasICDHeader2 = new TCompasICDHeader(); compasICDHeader2.setICDSystemVersionUUID("UUID-1"); @@ -537,17 +538,17 @@ void copyCompasICDHeaderFromLNodePrivateIntoSTDPrivate() { lNodeCompasICDHeader.setICDSystemVersionUUID("UUID-2"); lNodeCompasICDHeader.setIEDName("IED-1"); lNodeCompasICDHeader.setBayLabel("BAY-1"); - lNodeCompasICDHeader.setIEDinstance("1"); + lNodeCompasICDHeader.setIEDSubstationinstance(BigInteger.ONE); TPrivate stdTPrivate = PrivateService.createPrivate(stdCompasICDHeader); TPrivate lNodePrivate = PrivateService.createPrivate(lNodeCompasICDHeader); + assertThat(stdTPrivate).isNotEqualTo(lNodePrivate); // When - assertThat(stdTPrivate).isNotEqualTo(lNodePrivate); PrivateService.copyCompasICDHeaderFromLNodePrivateIntoSTDPrivate(stdTPrivate,lNodePrivate); // Then TCompasICDHeader result = PrivateService.extractCompasICDHeader(stdTPrivate).get(); assertThat(result).extracting(TCompasICDHeader::getICDSystemVersionUUID, TCompasICDHeader::getIEDName, - TCompasICDHeader::getIEDinstance, TCompasICDHeader::getBayLabel) - .containsExactlyInAnyOrder("UUID-2", "IED-1", "1", "BAY-1"); + TCompasICDHeader::getIEDSubstationinstance, TCompasICDHeader::getBayLabel) + .containsExactlyInAnyOrder("UUID-2", "IED-1", BigInteger.ONE, "BAY-1"); } } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SclRootAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SclRootAdapterTest.java index aea4e215f..3b7b5e720 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SclRootAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SclRootAdapterTest.java @@ -6,11 +6,11 @@ import org.junit.jupiter.api.Test; import org.lfenergy.compas.scl2007b4.model.SCL; -import org.lfenergy.compas.scl2007b4.model.THeader; -import org.lfenergy.compas.scl2007b4.model.TIED; import org.lfenergy.compas.scl2007b4.model.TPrivate; import org.lfenergy.compas.sct.commons.exception.ScdException; +import org.lfenergy.compas.sct.commons.scl.com.ConnectedAPAdapter; import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter; +import org.lfenergy.compas.sct.commons.testhelpers.SclHelper; import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; import java.util.Optional; @@ -46,7 +46,7 @@ void testConstruction() { } @Test - void addIED() throws Exception { + void addIED() { SCL scd = SclTestMarshaller.getSCLFromFile("/scl-root-test-schema-conf/add_ied_test.xml"); SCL icd1 = SclTestMarshaller.getSCLFromFile("/scl-root-test-schema-conf/icd1_to_add_test.xml"); @@ -62,7 +62,7 @@ void addIED() throws Exception { } @Test - void addPrivate() throws Exception { + void addPrivate() { SCL scd = SclTestMarshaller.getSCLFromFile("/scl-root-test-schema-conf/add_ied_test.xml"); SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); TPrivate tPrivate = new TPrivate(); @@ -77,7 +77,7 @@ void addPrivate() throws Exception { @Test void getIEDAdapterByName_should_return_ied(){ // Given - SclRootAdapter sclRootAdapter = createSclRootAdapterWithIed("IED_NAME"); + SclRootAdapter sclRootAdapter = SclHelper.createSclRootAdapterWithIed("IED_NAME"); // When IEDAdapter resultIed = sclRootAdapter.getIEDAdapterByName("IED_NAME"); // Then @@ -87,7 +87,7 @@ void getIEDAdapterByName_should_return_ied(){ @Test void getIEDAdapterByName_should_throw_exception(){ // Given - SclRootAdapter sclRootAdapter = createSclRootAdapterWithIed("IED_NAME"); + SclRootAdapter sclRootAdapter = SclHelper.createSclRootAdapterWithIed("IED_NAME"); // When & Then assertThatThrownBy(() -> sclRootAdapter.getIEDAdapterByName("NON_EXISTING_IED")) .isInstanceOf(ScdException.class) @@ -97,7 +97,7 @@ void getIEDAdapterByName_should_throw_exception(){ @Test void findIEDAdapterByName_should_return_ied(){ // Given - SclRootAdapter sclRootAdapter = createSclRootAdapterWithIed("IED_NAME"); + SclRootAdapter sclRootAdapter = SclHelper.createSclRootAdapterWithIed("IED_NAME"); // When Optional resultOptionalIed = sclRootAdapter.findIedAdapterByName("IED_NAME"); // Then @@ -108,19 +108,32 @@ void findIEDAdapterByName_should_return_ied(){ @Test void findIEDAdapterByName_should_return_empty(){ // Given - SclRootAdapter sclRootAdapter = createSclRootAdapterWithIed("IED_NAME"); + SclRootAdapter sclRootAdapter = SclHelper.createSclRootAdapterWithIed("IED_NAME"); // When Optional resultOptionalIed = sclRootAdapter.findIedAdapterByName("NON_EXISTING_IED"); // Then assertThat(resultOptionalIed).isEmpty(); } - private SclRootAdapter createSclRootAdapterWithIed(String iedName) { - SCL scl = new SCL(); - scl.setHeader(new THeader()); - TIED ied = new TIED(); - ied.setName(iedName); - scl.getIED().add(ied); - return new SclRootAdapter(scl); + @Test + void findConnectedApAdapter_should_return_adapter(){ + // Given + SclRootAdapter sclRootAdapter = SclHelper.createSclRootWithConnectedAp("iedName", "apName"); + // When + Optional result = sclRootAdapter.findConnectedApAdapter("iedName", "apName"); + // Then + assertThat(result).get().extracting(ConnectedAPAdapter::getIedName, ConnectedAPAdapter::getApName) + .containsExactly("iedName", "apName"); } + + @Test + void findConnectedApAdapter_should_return_empty(){ + // Given + SclRootAdapter sclRootAdapter = SclHelper.createSclRootWithConnectedAp("iedName", "apName"); + // When + Optional result = sclRootAdapter.findConnectedApAdapter("iedName2", "apName2"); + // Then + assertThat(result).isEmpty(); + } + } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SclServiceTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SclServiceTest.java index 94a38dcf4..ca831e518 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SclServiceTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/SclServiceTest.java @@ -1108,14 +1108,18 @@ void updateDoInRef_shouldReturnUpdatedFile(String testName, String ldInst, Strin assertThat(getValFromDaiName(sclReport.getSclRootAdapter().getCurrentElem(), "IED_NAME1", ldInst, doName, daName) .map(TVal::getValue)) .hasValue(expected); + assertIsMarshallable(sclReport.getSclRootAdapter().getCurrentElem()); } @ParameterizedTest(name = "{0}") @CsvSource({ "Test with only 1 ExtRef should not update srcTstCB,LD_WITH_1_InRef,InRef2,setTstRef", - "Test with only 1 ExtRef should not update setTstCB Value,LD_WITH_1_InRef,InRef2,setTstCB" + "Test with only 1 ExtRef should not update setTstCB Value,LD_WITH_1_InRef,InRef2,setTstCB", + "Test with only 1 ExtRef should not update DO when IedName not present,LD_WITH_1_InRef_ExtRef_Without_IedName,InRef4,setSrcRef", + "Test with only 1 ExtRef should not update DO when LdInst not present,LD_WITH_1_InRef_ExtRef_Without_LdInst,InRef5,setSrcRef", + "Test with only 1 ExtRef should not update DO when lnClass not present,LD_WITH_1_InRef_ExtRef_Without_LnClass,InRef6,setSrcRef" }) - void updateDoInRef_should_not_update_tst_DAI_When_only_1_ExtRef(String testName, String ldInst, String doName, String daName) { + void updateDoInRef_should_not_update_DAI(String testName, String ldInst, String doName, String daName) { // Given SCL givenScl = SclTestMarshaller.getSCLFromFile("/scd-test-update-inref/scd_update_inref_issue_231_test_ok.xml"); @@ -1160,7 +1164,7 @@ private Optional getValFromDaiName(SCL scl, String iedName, String ldInst, @Test void analyzeDataGroups_should_success() { // Given - SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_binded_ied_controls_fcda.xml"); + SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_bound_ied_controls_fcda.xml"); SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); IEDAdapter iedAdapter1 = sclRootAdapter.getIEDAdapterByName("IED_NAME1"); iedAdapter1.getCurrentElem().getAccessPoint().get(0).getServices().getClientServices().setMaxAttributes(9L); @@ -1178,7 +1182,7 @@ void analyzeDataGroups_should_success() { void analyzeDataGroups_should_return_errors_messages() { // Given - SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_binded_ied_controls_fcda.xml"); + SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_bound_ied_controls_fcda.xml"); SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName("IED_NAME2"); iedAdapter.getCurrentElem().getAccessPoint().get(0).getServices().getConfDataSet().setMaxAttributes(1L); @@ -1192,17 +1196,17 @@ void analyzeDataGroups_should_return_errors_messages() { assertThat(sclReport.getSclReportItems()).hasSize(11) .extracting(SclReportItem::getMessage) .containsExactlyInAnyOrder( - "There are too much FCDA for the Client IED IED_NAME1", - "The Client IED IED_NAME1 subscribes to too much GOOSE Control Blocks.", - "The Client IED IED_NAME1 subscribes to too much REPORT Control Blocks.", - "The Client IED IED_NAME1 subscribes to too much SMV Control Blocks.", - "There are too much FCDA for the DataSet DATASET6 for the LDevice LD_INST21 in IED IED_NAME2", - "There are too much FCDA for the DataSet DATASET6 for the LDevice LD_INST22 in IED IED_NAME2", - "There are too much FCDA for the DataSet DATASET5 for the LDevice LD_INST22 in IED IED_NAME2", - "There are too much DataSets for the IED IED_NAME2", - "There are too much Report Control Blocks for the IED IED_NAME2", - "There are too much GOOSE Control Blocks for the IED IED_NAME2", - "There are too much SMV Control Blocks for the IED IED_NAME2"); + "The Client IED IED_NAME1 subscribes to too much FCDA: 9 > 8 max", + "The Client IED IED_NAME1 subscribes to too much GOOSE Control Blocks: 3 > 2 max", + "The Client IED IED_NAME1 subscribes to too much Report Control Blocks: 1 > 0 max", + "The Client IED IED_NAME1 subscribes to too much SMV Control Blocks: 2 > 1 max", + "There are too much FCDA for the DataSet dataset6 for the LDevice LD_INST21 in IED IED_NAME2: 2 > 1 max", + "There are too much FCDA for the DataSet dataset6 for the LDevice LD_INST22 in IED IED_NAME2: 2 > 1 max", + "There are too much FCDA for the DataSet dataset5 for the LDevice LD_INST22 in IED IED_NAME2: 2 > 1 max", + "There are too much DataSets for the IED IED_NAME2: 6 > 3 max", + "There are too much Report Control Blocks for the IED IED_NAME2: 1 > 0 max", + "There are too much GOOSE Control Blocks for the IED IED_NAME2: 3 > 2 max", + "There are too much SMV Control Blocks for the IED IED_NAME2: 3 > 1 max"); } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapterTest.java index 01229afbe..987b8b268 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/ConnectedAPAdapterTest.java @@ -4,22 +4,24 @@ package org.lfenergy.compas.sct.commons.scl.com; +import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import org.lfenergy.compas.scl2007b4.model.SCL; -import org.lfenergy.compas.scl2007b4.model.TConnectedAP; -import org.lfenergy.compas.scl2007b4.model.TPrivate; -import org.lfenergy.compas.scl2007b4.model.TSubNetwork; +import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.dto.DTO; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; +import org.lfenergy.compas.sct.commons.util.SclConstructorHelper; +import java.math.BigDecimal; +import java.util.List; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; +import static org.lfenergy.compas.sct.commons.util.SclConstructorHelper.*; class ConnectedAPAdapterTest { @@ -28,9 +30,7 @@ class ConnectedAPAdapterTest { @BeforeEach void setUp() { subNetworkAdapter = new SubNetworkAdapter(null, new TSubNetwork()); - TConnectedAP tConnectedAP = new TConnectedAP(); - tConnectedAP.setIedName(DTO.HOLDER_IED_NAME); - tConnectedAP.setApName(DTO.AP_NAME); + TConnectedAP tConnectedAP = newConnectedAp(DTO.HOLDER_IED_NAME, DTO.AP_NAME); subNetworkAdapter.getCurrentElem().getConnectedAP().add(tConnectedAP); } @@ -56,7 +56,7 @@ void addPrivate() { } @Test - void testCopyAddressAndPhysConnFromIcd_withFilledCommunication() throws Exception { + void testCopyAddressAndPhysConnFromIcd_withFilledCommunication() { // GIVEN ConnectedAPAdapter connectedAPAdapter = assertDoesNotThrow( () -> subNetworkAdapter.getConnectedAPAdapter(DTO.HOLDER_IED_NAME, DTO.AP_NAME) @@ -93,18 +93,92 @@ void testCopyAddressAndPhysConnFromIcd_withEmptyIcd() { } @ParameterizedTest - @CsvSource(value = {"IED_NAME;AP_NAME;ConnectedAP[@apName=\"IED_NAME\" and @iedName=\"IED_NAME\"]", ";;ConnectedAP[not(@apName) and not(@iedName)]"} + @CsvSource(value = {"IED_NAME;AP_NAME;ConnectedAP[@apName=\"AP_NAME\" and @iedName=\"IED_NAME\"]", ";;ConnectedAP[not(@apName) and not(@iedName)]"} , delimiter = ';') void elementXPath(String iedName, String apName, String message) { // Given - TConnectedAP tConnectedAP = new TConnectedAP(); - tConnectedAP.setApName(iedName); - tConnectedAP.setIedName(apName); - ConnectedAPAdapter connectedAPAdapter = new ConnectedAPAdapter(null, tConnectedAP); + ConnectedAPAdapter connectedAPAdapter = newConnectedApAdapter(iedName, apName); // When String elementXPath = connectedAPAdapter.elementXPath(); // Then assertThat(elementXPath).isEqualTo(message); } + @Test + void updateGseOrCreateIfNotExists_should_create_GSE(){ + // Given + ConnectedAPAdapter connectedAPAdapter = newConnectedApAdapter("IED_NAME", "AP_NAME"); + // When + connectedAPAdapter.updateGseOrCreateIfNotExists("ldinst", "cbName", List.of(SclConstructorHelper.newP("APPID", "0001")), + newDurationInMilliSec(5), newDurationInMilliSec(10)); + // Then + assertThat(connectedAPAdapter.getCurrentElem().getGSE()).hasSize(1); + TGSE gse = connectedAPAdapter.getCurrentElem().getGSE().get(0); + assertThat(gse.getLdInst()).isEqualTo("ldinst"); + assertThat(gse.getMinTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) + .containsExactly("s", "m", new BigDecimal("5")); + assertThat(gse.getMaxTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) + .containsExactly("s", "m", new BigDecimal("10")); + assertThat(gse.getAddress().getP()).extracting(TP::getType, TP::getValue) + .containsExactly(Tuple.tuple("APPID", "0001")); + } + + @Test + void updateGseOrCreateIfNotExists_when_exists_should_update_GSE(){ + // Given + ConnectedAPAdapter connectedAPAdapter = newConnectedApAdapter("IED_NAME", "AP_NAME"); + connectedAPAdapter.updateGseOrCreateIfNotExists("ldinst", "cbName", List.of(SclConstructorHelper.newP("APPID", "0001")), + newDurationInMilliSec(5), newDurationInMilliSec(10)); + // When + connectedAPAdapter.updateGseOrCreateIfNotExists("ldinst", "cbName", List.of(SclConstructorHelper.newP("APPID", "0004")), + newDurationInMilliSec(30), newDurationInMilliSec(50)); + // Then + assertThat(connectedAPAdapter.getCurrentElem().getGSE()).hasSize(1); + TGSE gse = connectedAPAdapter.getCurrentElem().getGSE().get(0); + assertThat(gse.getLdInst()).isEqualTo("ldinst"); + assertThat(gse.getMinTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) + .containsExactly("s", "m", new BigDecimal("30")); + assertThat(gse.getMaxTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) + .containsExactly("s", "m", new BigDecimal("50")); + assertThat(gse.getAddress().getP()).extracting(TP::getType, TP::getValue) + .containsExactly(Tuple.tuple("APPID", "0004")); + } + + @Test + void updateSmvOrCreateIfNotExists_should_create_SMV(){ + // Given + ConnectedAPAdapter connectedAPAdapter = newConnectedApAdapter("IED_NAME", "AP_NAME"); + // When + connectedAPAdapter.updateSmvOrCreateIfNotExists("ldinst", "cbName", List.of(SclConstructorHelper.newP("APPID", "0001"))); + // Then + assertThat(connectedAPAdapter.getCurrentElem().getSMV()).hasSize(1); + TSMV smv = connectedAPAdapter.getCurrentElem().getSMV().get(0); + assertThat(smv.getLdInst()).isEqualTo("ldinst"); + assertThat(smv.getAddress().getP()).extracting(TP::getType, TP::getValue) + .containsExactly(Tuple.tuple("APPID", "0001")); + } + + @Test + void updateSmvOrCreateIfNotExists_when_exists_should_update_SMV(){ + // Given + ConnectedAPAdapter connectedAPAdapter = newConnectedApAdapter("IED_NAME", "AP_NAME"); + TSMV newSmv = new TSMV(); + newSmv.setLdInst("ldinst"); + newSmv.setCbName("cbName"); + newSmv.setAddress(newAddress(List.of(SclConstructorHelper.newP("APPID", "0001")))); + connectedAPAdapter.getCurrentElem().getSMV().add(newSmv); + // When + connectedAPAdapter.updateSmvOrCreateIfNotExists("ldinst", "cbName", List.of(SclConstructorHelper.newP("APPID", "0004"))); + // Then + assertThat(connectedAPAdapter.getCurrentElem().getSMV()).hasSize(1); + TSMV smv = connectedAPAdapter.getCurrentElem().getSMV().get(0); + assertThat(smv.getLdInst()).isEqualTo("ldinst"); + assertThat(smv.getAddress().getP()).extracting(TP::getType, TP::getValue) + .containsExactly(Tuple.tuple("APPID", "0004")); + } + + private ConnectedAPAdapter newConnectedApAdapter(String iedName, String apName){ + return new ConnectedAPAdapter(null, newConnectedAp(iedName, apName)); + } + } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapterTest.java index 494968162..9e339474c 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/com/SubNetworkAdapterTest.java @@ -4,6 +4,8 @@ package org.lfenergy.compas.sct.commons.scl.com; +import org.assertj.core.api.Assertions; +import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -14,6 +16,9 @@ import org.lfenergy.compas.sct.commons.dto.DTO; import org.lfenergy.compas.sct.commons.exception.ScdException; +import java.util.List; +import java.util.Optional; + import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; @@ -51,20 +56,63 @@ void testAddConnectedAP() { assertEquals(1,subNetworkAdapter.getCurrentElem().getConnectedAP().size()); } + @Test + void getConnectedAPAdapters_should_return_all_ConnectedAP() { + // Given + SubNetworkAdapter subNetworkAdapter = new SubNetworkAdapter(null, new TSubNetwork()); + subNetworkAdapter.addConnectedAP(DTO.HOLDER_IED_NAME, DTO.AP_NAME); + subNetworkAdapter.addConnectedAP(DTO.HOLDER_IED_NAME_2, DTO.AP_NAME_2); + // When + List connectedAPAdapter = subNetworkAdapter.getConnectedAPAdapters(); + // Then + assertThat(connectedAPAdapter).extracting(ConnectedAPAdapter::getIedName, ConnectedAPAdapter::getApName) + .containsExactly( + Tuple.tuple(DTO.HOLDER_IED_NAME, DTO.AP_NAME), + Tuple.tuple(DTO.HOLDER_IED_NAME_2, DTO.AP_NAME_2) + ); + } @Test - void getConnectedAPAdapters() { + void getConnectedAPAdapter_should_get_element() { + // Given SubNetworkAdapter subNetworkAdapter = new SubNetworkAdapter(null, new TSubNetwork()); - assertTrue(subNetworkAdapter.getCurrentElem().getConnectedAP().isEmpty()); subNetworkAdapter.addConnectedAP(DTO.HOLDER_IED_NAME,DTO.AP_NAME); - assertEquals(1,subNetworkAdapter.getConnectedAPAdapters().size()); + // When + ConnectedAPAdapter connectedAPAdapter = subNetworkAdapter.getConnectedAPAdapter(DTO.HOLDER_IED_NAME, DTO.AP_NAME); + // Then + assertThat(connectedAPAdapter).extracting(ConnectedAPAdapter::getIedName, ConnectedAPAdapter::getApName) + .containsExactly(DTO.HOLDER_IED_NAME, DTO.AP_NAME); + } + + @Test + void getConnectedAPAdapter_when_not_found_should_throw_exception() { + // Given + SubNetworkAdapter subNetworkAdapter = new SubNetworkAdapter(null, new TSubNetwork()); + // When & Then + Assertions.assertThatThrownBy(() -> subNetworkAdapter.getConnectedAPAdapter(DTO.HOLDER_IED_NAME, DTO.AP_NAME)) + .isInstanceOf(ScdException.class); + } - assertDoesNotThrow( () -> subNetworkAdapter.getConnectedAPAdapter(DTO.HOLDER_IED_NAME,DTO.AP_NAME)); + @Test + void findConnectedAPAdapter_should_get_element() { + // Given + SubNetworkAdapter subNetworkAdapter = new SubNetworkAdapter(null, new TSubNetwork()); + subNetworkAdapter.addConnectedAP(DTO.HOLDER_IED_NAME,DTO.AP_NAME); + // When + Optional connectedAPAdapter = subNetworkAdapter.findConnectedAPAdapter(DTO.HOLDER_IED_NAME, DTO.AP_NAME); + // Then + assertThat(connectedAPAdapter).get().extracting(ConnectedAPAdapter::getIedName, ConnectedAPAdapter::getApName) + .containsExactly(DTO.HOLDER_IED_NAME, DTO.AP_NAME); + } - assertThrows( - ScdException.class, - () -> subNetworkAdapter.getConnectedAPAdapter(DTO.HOLDER_IED_NAME,DTO.AP_NAME + "1") - ); + @Test + void findConnectedAPAdapter_when_not_found_shouldreturn_empty() { + // Given + SubNetworkAdapter subNetworkAdapter = new SubNetworkAdapter(null, new TSubNetwork()); + // When + Optional connectedAPAdapter = subNetworkAdapter.findConnectedAPAdapter(DTO.HOLDER_IED_NAME, DTO.AP_NAME); + // Then + assertThat(connectedAPAdapter).isEmpty(); } @Test @@ -94,4 +142,4 @@ void elementXPath(String sName, String sType, String message) { } -} \ No newline at end of file +} diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapterTest.java index 1ecfc9daf..4f0e805a7 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/AccessPointAdapterTest.java @@ -73,7 +73,7 @@ void getXPath() { } @Test - void checkFCDALimitations_should_succed_no_error_message() throws Exception { + void checkFCDALimitations_should_succeed_no_error_message() { //Given AccessPointAdapter accessPointAdapter = provideAPForCheckLimitationForIED(); //When @@ -83,7 +83,7 @@ void checkFCDALimitations_should_succed_no_error_message() throws Exception { } @Test - void checkFCDALimitations_should_fail_with_one_error_messages() throws Exception { + void checkFCDALimitations_should_fail_with_one_error_messages() { //Given AccessPointAdapter accessPointAdapter = provideAPForCheckLimitationForIED(); accessPointAdapter.getCurrentElem().getServices().getConfDataSet().setMaxAttributes(2L); @@ -92,10 +92,10 @@ void checkFCDALimitations_should_fail_with_one_error_messages() throws Exception //Then assertThat(sclReportItems).hasSize(1) .extracting(SclReportItem::getMessage) - .containsExactlyInAnyOrder("There are too much FCDA for the DataSet DATASET6 for the LDevice LD_INST21 in IED IED_NAME"); + .containsExactlyInAnyOrder("There are too much FCDA for the DataSet dataset6 for the LDevice LD_INST21 in IED IED_NAME: 3 > 2 max"); } @Test - void checkFCDALimitations_should_fail_with_four_error_messages() throws Exception { + void checkFCDALimitations_should_fail_with_four_error_messages() { //Given AccessPointAdapter accessPointAdapter = provideAPForCheckLimitationForIED(); accessPointAdapter.getCurrentElem().getServices().getConfDataSet().setMaxAttributes(1L); @@ -104,66 +104,62 @@ void checkFCDALimitations_should_fail_with_four_error_messages() throws Exceptio //Then assertThat(sclReportItems).hasSize(4) .extracting(SclReportItem::getMessage) - .containsExactlyInAnyOrder("There are too much FCDA for the DataSet DATASET3 for the LDevice LD_INST21 in IED IED_NAME", - "There are too much FCDA for the DataSet DATASET6 for the LDevice LD_INST21 in IED IED_NAME", - "There are too much FCDA for the DataSet DATASET6 for the LDevice LD_INST22 in IED IED_NAME", - "There are too much FCDA for the DataSet DATASET5 for the LDevice LD_INST22 in IED IED_NAME"); + .containsExactlyInAnyOrder("There are too much FCDA for the DataSet dataset3 for the LDevice LD_INST21 in IED IED_NAME: 2 > 1 max", + "There are too much FCDA for the DataSet dataset6 for the LDevice LD_INST21 in IED IED_NAME: 3 > 1 max", + "There are too much FCDA for the DataSet dataset6 for the LDevice LD_INST22 in IED IED_NAME: 2 > 1 max", + "There are too much FCDA for the DataSet dataset5 for the LDevice LD_INST22 in IED IED_NAME: 2 > 1 max"); } @Test - void checkControlsLimitation_should_fail_for_dataset_with_one_error_messages() throws Exception { + void checkControlsLimitation_should_fail_for_dataset_with_one_error_messages() { //Given AccessPointAdapter accessPointAdapter = provideAPForCheckLimitationForIED(); accessPointAdapter.getCurrentElem().getServices().getConfDataSet().setMax(5L); - String message = "Too much DataSet for"; //When - Optional sclReportItem = accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.DATASET,message); + Optional sclReportItem = accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.DATASET); //Then assertThat(sclReportItem).isPresent() - .get().extracting(SclReportItem::getMessage).isEqualTo(message +" IED_NAME"); + .get().extracting(SclReportItem::getMessage).isEqualTo("There are too much DataSets for the IED IED_NAME: 6 > 5 max"); } @Test - void checkControlsLimitation_should_fail_for_smv_with_one_error_messages() throws Exception { + void checkControlsLimitation_should_fail_for_smv_with_one_error_messages() { //Given AccessPointAdapter accessPointAdapter = provideAPForCheckLimitationForIED(); accessPointAdapter.getCurrentElem().getServices().getSMVsc().setMax(2L); - String message = "Too much SMV Control for"; //When - Optional sclReportItem = accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.SMV,message); + Optional sclReportItem = accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.SMV); //Then assertThat(sclReportItem).isPresent() - .get().extracting(SclReportItem::getMessage).isEqualTo(message +" IED_NAME"); + .get().extracting(SclReportItem::getMessage).isEqualTo("There are too much SMV Control Blocks for the IED IED_NAME: 3 > 2 max"); } @Test - void checkControlsLimitation_should_fail_for_goose_with_one_error_messages() throws Exception { + void checkControlsLimitation_should_fail_for_goose_with_one_error_messages() { //Given AccessPointAdapter accessPointAdapter = provideAPForCheckLimitationForIED(); accessPointAdapter.getCurrentElem().getServices().getGOOSE().setMax(2L); - String message = "Too much Goose Control for"; //When - Optional sclReportItem = accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.GSE,message); + Optional sclReportItem = accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.GSE); //Then assertThat(sclReportItem).isPresent() - .get().extracting(SclReportItem::getMessage).isEqualTo(message +" IED_NAME"); + .get().extracting(SclReportItem::getMessage).isEqualTo("There are too much GOOSE Control Blocks for the IED IED_NAME: 3 > 2 max"); } @Test - void checkControlsLimitation_should_fail_for_report_with_one_error_messages() throws Exception { + void checkControlsLimitation_should_fail_for_report_with_one_error_messages() { //Given AccessPointAdapter accessPointAdapter = provideAPForCheckLimitationForIED(); accessPointAdapter.getCurrentElem().getServices().getConfReportControl().setMax(0L); - String message = "Too much Report Control for"; //When - Optional sclReportItem = accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.REPORT,message); + Optional sclReportItem = accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.REPORT); //Then assertThat(sclReportItem).isPresent() - .get().extracting(SclReportItem::getMessage).isEqualTo(message +" IED_NAME"); + .get().extracting(SclReportItem::getMessage).isEqualTo("There are too much Report Control Blocks for the IED IED_NAME: 1 > 0 max"); } - public static AccessPointAdapter provideAPForCheckLimitationForIED() throws Exception { + public static AccessPointAdapter provideAPForCheckLimitationForIED() { SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_ied_controls_dataset.xml"); SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName("IED_NAME"); @@ -171,47 +167,89 @@ public static AccessPointAdapter provideAPForCheckLimitationForIED() throws Exce } @Test - void checkLimitationForBindedIEDFCDAs_should_success_no_error() throws Exception { + void checkControlsLimitation_should_succeed_when_ConfReportControl_is_missing() { //Given - SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_binded_ied_controls_fcda.xml"); + AccessPointAdapter accessPointAdapter = provideAPForCheckLimitationForIED(); + accessPointAdapter.getCurrentElem().getServices().setConfReportControl(null); + //When + Optional sclReportItem = accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.REPORT); + //Then + assertThat(sclReportItem).isEmpty(); + } + + @Test + void checkControlsLimitation_should_succeed_when_GOOSE_is_missing() { + //Given + AccessPointAdapter accessPointAdapter = provideAPForCheckLimitationForIED(); + accessPointAdapter.getCurrentElem().getServices().setGOOSE(null); + //When + Optional sclReportItem = accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.GSE); + //Then + assertThat(sclReportItem).isEmpty(); + } + + @Test + void checkControlsLimitation_should_succeed_when_ConfDataSet_is_missing() { + //Given + AccessPointAdapter accessPointAdapter = provideAPForCheckLimitationForIED(); + accessPointAdapter.getCurrentElem().getServices().setConfDataSet(null); + //When + Optional sclReportItem = accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.DATASET); + //Then + assertThat(sclReportItem).isEmpty(); + } + + @Test + void checkControlsLimitation_should_succeed_when_Services_is_missing() { + //Given + AccessPointAdapter accessPointAdapter = provideAPForCheckLimitationForIED(); + accessPointAdapter.getCurrentElem().setServices(null); + //When + Optional sclReportItem = accessPointAdapter.checkControlsLimitation(ServicesConfigEnum.SMV); + //Then + assertThat(sclReportItem).isEmpty(); + } + + @Test + void checkLimitationForBoundIEDFCDAs_should_success_no_error() { + //Given + SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_bound_ied_controls_fcda.xml"); SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName("IED_NAME1"); iedAdapter.getCurrentElem().getAccessPoint().get(0).getServices().getClientServices().setMaxAttributes(11L); AccessPointAdapter accessPointAdapter = new AccessPointAdapter(iedAdapter, iedAdapter.getCurrentElem().getAccessPoint().get(0)); - String message = "Too much FCDA"; List tExtRefs = accessPointAdapter.getAllCoherentExtRefForAnalyze().tExtRefs(); //When - Optional sclReportItem = accessPointAdapter.checkLimitationForBoundIEDFCDAs(tExtRefs, message); + Optional sclReportItem = accessPointAdapter.checkLimitationForBoundIedFcdas(tExtRefs); //Then assertThat(sclReportItem).isEmpty(); } @Test - void checkLimitationForBindedIEDFCDAs_should_fail_one_error_message() throws Exception { + void checkLimitationForBoundIEDFCDAs_should_fail_one_error_message() { //Given - SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_binded_ied_controls_fcda.xml"); + SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_bound_ied_controls_fcda.xml"); SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName("IED_NAME1"); AccessPointAdapter accessPointAdapter = new AccessPointAdapter(iedAdapter, iedAdapter.getCurrentElem().getAccessPoint().get(0)); accessPointAdapter.getCurrentElem().getServices().getClientServices().setMaxAttributes(4L); List tExtRefs = accessPointAdapter.getAllCoherentExtRefForAnalyze().tExtRefs(); - String message = "Too much FCDA for IED_NAME1"; //When - Optional sclReportItem = accessPointAdapter.checkLimitationForBoundIEDFCDAs(tExtRefs, message); + Optional sclReportItem = accessPointAdapter.checkLimitationForBoundIedFcdas(tExtRefs); //Then assertThat(sclReportItem).isPresent() .get() .extracting(SclReportItem::getMessage) - .isEqualTo(message); + .isEqualTo("The Client IED IED_NAME1 subscribes to too much FCDA: 9 > 4 max"); } @Test - void checkLimitationForBindedIEDControls_should_fail_three_error_messages() throws Exception { + void checkLimitationForBoundIEDControls_should_fail_three_error_messages() { //Given - SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_binded_ied_controls_fcda.xml"); + SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_bound_ied_controls_fcda.xml"); SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName("IED_NAME1"); AccessPointAdapter accessPointAdapter = new AccessPointAdapter(iedAdapter, iedAdapter.getCurrentElem().getAccessPoint().get(0)); @@ -222,15 +260,15 @@ void checkLimitationForBindedIEDControls_should_fail_three_error_messages() thro //Then assertThat(sclReportItems).hasSize(3) .extracting(SclReportItem::getMessage) - .containsExactlyInAnyOrder("The Client IED IED_NAME1 subscribes to too much GOOSE Control Blocks.", - "The Client IED IED_NAME1 subscribes to too much SMV Control Blocks.", - "The Client IED IED_NAME1 subscribes to too much REPORT Control Blocks."); + .containsExactlyInAnyOrder("The Client IED IED_NAME1 subscribes to too much GOOSE Control Blocks: 3 > 2 max", + "The Client IED IED_NAME1 subscribes to too much SMV Control Blocks: 2 > 1 max", + "The Client IED IED_NAME1 subscribes to too much Report Control Blocks: 1 > 0 max"); } @Test - void checkLimitationForBindedIEDControls_should_succed_no_error_message() throws Exception { + void checkLimitationForBoundIEDControls_should_succeed_no_error_message() { //Given - SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_binded_ied_controls_fcda.xml"); + SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_bound_ied_controls_fcda.xml"); SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName("IED_NAME1"); iedAdapter.getCurrentElem().getAccessPoint().get(0).getServices().getClientServices().setMaxAttributes(11L); @@ -247,9 +285,9 @@ void checkLimitationForBindedIEDControls_should_succed_no_error_message() throws } @Test - void getAllCoherentExtRefForAnalyze_succed() throws Exception { + void getAllCoherentExtRefForAnalyze_succeed() { //Given - SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_binded_ied_controls_fcda.xml"); + SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_bound_ied_controls_fcda.xml"); SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName("IED_NAME1"); AccessPointAdapter accessPointAdapter = new AccessPointAdapter(iedAdapter, iedAdapter.getCurrentElem().getAccessPoint().get(0)); @@ -262,7 +300,7 @@ void getAllCoherentExtRefForAnalyze_succed() throws Exception { } @Test - void getAllCoherentExtRefForAnalyze_fail_with_one_error() throws Exception { + void getAllCoherentExtRefForAnalyze_fail_with_one_error() { //Given SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_coherent_extRefs.xml"); SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); @@ -275,4 +313,5 @@ void getAllCoherentExtRefForAnalyze_fail_with_one_error() throws Exception { .extracting(AccessPointAdapter.ExtRefAnalyzeRecord::sclReportItems) .asList().hasSize(1); } -} \ No newline at end of file +} + diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapterTest.java index 7589dd415..5e7abdcf8 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/ControlBlockAdapterTest.java @@ -4,16 +4,19 @@ package org.lfenergy.compas.sct.commons.scl.ied; +import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.Test; -import org.lfenergy.compas.scl2007b4.model.SCL; -import org.lfenergy.compas.scl2007b4.model.TControl; -import org.lfenergy.compas.scl2007b4.model.TControlWithIEDName; -import org.lfenergy.compas.scl2007b4.model.TGSEControl; +import org.lfenergy.compas.scl2007b4.model.*; +import org.lfenergy.compas.sct.commons.dto.SclReportItem; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; +import org.lfenergy.compas.sct.commons.testhelpers.SclHelper; import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; import org.lfenergy.compas.sct.commons.util.ControlBlockEnum; +import org.lfenergy.compas.sct.commons.util.SclConstructorHelper; +import java.math.BigDecimal; import java.util.List; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.findLn; @@ -21,6 +24,18 @@ class ControlBlockAdapterTest { + @Test + void getName_should_return_name(){ + // Given + TGSEControl tgseControl = new TGSEControl(); + tgseControl.setName("cbName"); + ControlBlockAdapter controlBlockAdapter = new ControlBlockAdapter(null, tgseControl); + // When + String result = controlBlockAdapter.getName(); + // Then + assertThat(result).isEqualTo("cbName"); + } + @Test void addTargetIfNotExists_should_add_target(){ // Given @@ -45,4 +60,75 @@ void addTargetIfNotExists_should_add_target(){ .containsExactly("AP_NAME", "IED_NAME2", "LD_INST21", "1", List.of("ANCR"), "prefix"); } + @Test + void configureNetwork_should_add_GSE_element() { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scl-ln-adapter/scd_with_ln.xml"); + TConnectedAP connectedAP = SclHelper.addConnectedAp(scd, "SUB_NETWORK_NAME", "AP_NAME", "IED_NAME1"); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + LN0Adapter ln0 = findLn0(sclRootAdapter, "IED_NAME1", "LD_INST11"); + ln0.createDataSetIfNotExists("datSet", ControlBlockEnum.GSE); + ControlBlockAdapter controlBlockAdapter = ln0.createControlBlockIfNotExists("cbName", "cbId", "datSet", ControlBlockEnum.GSE); + // When + Optional sclReportItem = controlBlockAdapter.configureNetwork(10L, "00-01-02-04-05", 11, (byte) 12, SclConstructorHelper.newDurationInMilliSec(3), + SclConstructorHelper.newDurationInMilliSec(20)); + // Then + assertThat(sclReportItem).isEmpty(); + assertThat(connectedAP.getGSE()).hasSize(1); + TGSE gse = connectedAP.getGSE().get(0); + assertThat(gse.getLdInst()).isEqualTo("LD_INST11"); + assertThat(gse.getMinTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) + .containsExactly("s", "m", new BigDecimal("3")); + assertThat(gse.getMaxTime()).extracting(TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier, TDurationInMilliSec::getValue) + .containsExactly("s", "m", new BigDecimal("20")); + assertThat(gse.getAddress().getP()).extracting(TP::getType, TP::getValue) + .containsExactlyInAnyOrder( + Tuple.tuple("APPID", "000A"), + Tuple.tuple("MAC-Address", "00-01-02-04-05"), + Tuple.tuple("VLAN-ID", "00B"), + Tuple.tuple("VLAN-PRIORITY", "12") + ); + } + + @Test + void configureNetwork_should_add_SMV_element() { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scl-ln-adapter/scd_with_ln.xml"); + TConnectedAP connectedAP = SclHelper.addConnectedAp(scd, "SUB_NETWORK_NAME", "AP_NAME", "IED_NAME1"); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + LN0Adapter ln0 = findLn0(sclRootAdapter, "IED_NAME1", "LD_INST11"); + ln0.createDataSetIfNotExists("datSet", ControlBlockEnum.SAMPLED_VALUE); + ControlBlockAdapter controlBlockAdapter = ln0.createControlBlockIfNotExists("cbName", "cbId", "datSet", ControlBlockEnum.SAMPLED_VALUE); + // When + Optional sclReportItem = controlBlockAdapter.configureNetwork(10L, "00-01-02-04-05", 11, (byte) 12, null, null); + // Then + assertThat(sclReportItem).isEmpty(); + assertThat(connectedAP.getSMV()).hasSize(1); + TSMV smv = connectedAP.getSMV().get(0); + assertThat(smv.getLdInst()).isEqualTo("LD_INST11"); + assertThat(smv.getAddress().getP()).extracting(TP::getType, TP::getValue) + .containsExactlyInAnyOrder( + Tuple.tuple("APPID", "000A"), + Tuple.tuple("MAC-Address", "00-01-02-04-05"), + Tuple.tuple("VLAN-ID", "00B"), + Tuple.tuple("VLAN-PRIORITY", "12") + ); + } + + @Test + void configureNetwork_when_connectApNotFound_should_return_sclReportItem() { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scl-ln-adapter/scd_with_ln.xml"); + SclHelper.addConnectedAp(scd, "SUB_NETWORK_NAME", "AP_NAME", "IED_NAME2"); // ConnectedAp for IED_NAME2 instead of IED_NAME1 + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + LN0Adapter ln0 = findLn0(sclRootAdapter, "IED_NAME1", "LD_INST11"); + ln0.createDataSetIfNotExists("datSet", ControlBlockEnum.SAMPLED_VALUE); + ControlBlockAdapter controlBlockAdapter = ln0.createControlBlockIfNotExists("cbName", "cbId", "datSet", ControlBlockEnum.SAMPLED_VALUE); + // When + Optional sclReportItem = controlBlockAdapter.configureNetwork(10L, "00-01-02-04-05", 11, (byte) 12, null, null); + // Then + assertThat(sclReportItem).isPresent(); + } + + } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapterTest.java index 88f867311..7a3878135 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/IEDAdapterTest.java @@ -11,6 +11,7 @@ import org.lfenergy.compas.sct.commons.dto.SclReportItem; import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.ObjectReference; +import org.lfenergy.compas.sct.commons.scl.PrivateService; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; import org.mockito.Mockito; @@ -204,7 +205,7 @@ void elementXPath() { } @Test - void checkDataGroupCoherence_should_succed_no_error_message() throws Exception { + void checkDataGroupCoherence_should_succeed_no_error_message() throws Exception { //Given IEDAdapter iedAdapter = provideIEDForCheckLimitationForIED(); //When @@ -225,19 +226,19 @@ void checkDataGroupCoherence_should_fail_five_error_message() throws Exception { //When List sclReportItems = iedAdapter.checkDataGroupCoherence(); //Then - assertThat(sclReportItems).hasSize(5) + assertThat(sclReportItems) .extracting(SclReportItem::getMessage) - .containsExactlyInAnyOrder("There are too much FCDA for the DataSet DATASET6 for the LDevice LD_INST21 in IED IED_NAME", - "There are too much DataSets for the IED IED_NAME", - "There are too much Report Control Blocks for the IED IED_NAME", - "There are too much GOOSE Control Blocks for the IED IED_NAME", - "There are too much SMV Control Blocks for the IED IED_NAME"); + .containsExactlyInAnyOrder("There are too much FCDA for the DataSet dataset6 for the LDevice LD_INST21 in IED IED_NAME: 3 > 2 max", + "There are too much DataSets for the IED IED_NAME: 6 > 5 max", + "There are too much Report Control Blocks for the IED IED_NAME: 1 > 0 max", + "There are too much GOOSE Control Blocks for the IED IED_NAME: 3 > 2 max", + "There are too much SMV Control Blocks for the IED IED_NAME: 3 > 2 max"); } @Test - void checkBindingDataGroupCoherence_should_succed_no_error_message() throws Exception { + void checkBindingDataGroupCoherence_should_succeed_no_error_message() { //Given - IEDAdapter iedAdapter = provideIEDForCheckLimitationForBindedIED(); + IEDAdapter iedAdapter = provideIEDForCheckLimitationForBoundIED(); TClientServices tClientServices = iedAdapter.getParentAdapter().getIEDAdapterByName("IED_NAME1").getCurrentElem().getAccessPoint().get(0).getServices().getClientServices(); tClientServices.setMaxAttributes(11L); tClientServices.setMaxGOOSE(5L); @@ -250,30 +251,62 @@ void checkBindingDataGroupCoherence_should_succed_no_error_message() throws Exce } @Test - void checkBindingDataGroupCoherence_should_fail_five_error_message() throws Exception { + void checkBindingDataGroupCoherence_should_fail_five_error_message() { //Given - IEDAdapter iedAdapter = provideIEDForCheckLimitationForBindedIED(); + IEDAdapter iedAdapter = provideIEDForCheckLimitationForBoundIED(); //When List sclReportItems = iedAdapter.checkBindingDataGroupCoherence(); //Then assertThat(sclReportItems).hasSize(4) .extracting(SclReportItem::getMessage) - .containsExactlyInAnyOrder("There are too much FCDA for the Client IED IED_NAME1", - "The Client IED IED_NAME1 subscribes to too much SMV Control Blocks.", - "The Client IED IED_NAME1 subscribes to too much REPORT Control Blocks.", - "The Client IED IED_NAME1 subscribes to too much GOOSE Control Blocks."); + .containsExactlyInAnyOrder("The Client IED IED_NAME1 subscribes to too much FCDA: 9 > 8 max", + "The Client IED IED_NAME1 subscribes to too much Report Control Blocks: 1 > 0 max", + "The Client IED IED_NAME1 subscribes to too much SMV Control Blocks: 2 > 1 max", + "The Client IED IED_NAME1 subscribes to too much GOOSE Control Blocks: 3 > 2 max"); } - public static IEDAdapter provideIEDForCheckLimitationForBindedIED() throws Exception { - SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_binded_ied_controls_fcda.xml"); + public static IEDAdapter provideIEDForCheckLimitationForBoundIED() { + SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_bound_ied_controls_fcda.xml"); SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); return sclRootAdapter.getIEDAdapterByName("IED_NAME1"); } - public static IEDAdapter provideIEDForCheckLimitationForIED() throws Exception { + public static IEDAdapter provideIEDForCheckLimitationForIED() { SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_limitation_ied_controls_dataset.xml"); SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); return sclRootAdapter.getIEDAdapterByName("IED_NAME"); } + + @Test + void getCompasICDHeader_should_return_compas_icd_header(){ + // Given + SCL scd = SclTestMarshaller.getSCLFromFile(SCD_IED_U_TEST); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName("IED_NAME"); + TCompasICDHeader tCompasICDHeader = new TCompasICDHeader(); + tCompasICDHeader.setHeaderId("HEADER_ID"); + iedAdapter.getCurrentElem().getPrivate().add(PrivateService.createPrivate(tCompasICDHeader)); + + // When + Optional compasICDHeader = iedAdapter.getCompasICDHeader(); + // Then + assertThat(compasICDHeader).map(TCompasICDHeader::getHeaderId).hasValue("HEADER_ID"); + } + + @Test + void getCompasSystemVersion_should_return_compas_icd_header(){ + // Given + SCL scd = SclTestMarshaller.getSCLFromFile(SCD_IED_U_TEST); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName("IED_NAME"); + TCompasSystemVersion tCompasSystemVersion = new TCompasSystemVersion(); + tCompasSystemVersion.setMainSystemVersion("01.00"); + iedAdapter.getCurrentElem().getPrivate().add(PrivateService.createPrivate(tCompasSystemVersion)); + + // When + Optional compasSystemVersion = iedAdapter.getCompasSystemVersion(); + // Then + assertThat(compasSystemVersion).map(TCompasSystemVersion::getMainSystemVersion).hasValue("01.00"); + } } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapterTest.java index a23706f4d..feb993360 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapterTest.java @@ -258,30 +258,5 @@ private static InputsAdapter keepOnlyThisExtRef(SclRootAdapter sclRootAdapter, S foundInputsAdapter.getCurrentElem().getExtRef().removeIf(Predicate.not(extref -> extRefDesc.equals(extref.getDesc()))); return foundInputsAdapter; } - /* @Test - void checkSourceDataGroupCoherence_should_fail_one_error_messages() throws Exception { - //Given - SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_coherent_extRefs.xml"); - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - InputsAdapter inputsAdapter = keepOnlyThisExtRef(sclRootAdapter, "a"); - //When - List sclReportItems = inputsAdapter.checkSourceDataGroupCoherence(); - //Then - assertThat(sclReportItems).hasSize(1) - .extracting(SclReportItem::getMessage) - .containsExactlyInAnyOrder("The Client IED IED_NAME1 subscribes to much GOOSE Control Blocks."); - } - @Test - void checkSourceDataGroupCoherence_should_succed_no_error_message() throws Exception { - //Given - SCL scd = SclTestMarshaller.getSCLFromFile("/limitation_cb_dataset_fcda/scd_check_coherent_extRefs.xml"); - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - InputsAdapter inputsAdapter = keepOnlyThisExtRef(sclRootAdapter, "a"); - sclRootAdapter.getIEDAdapterByName("IED_NAME1").getCurrentElem().getAccessPoint().get(0).getServices().getClientServices().setMaxGOOSE(1L); - //When - List sclReportItems = inputsAdapter.checkSourceDataGroupCoherence(); - //Then - assertThat(sclReportItems).isEmpty(); - }*/ } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapterTest.java index 835c2c2b4..f70c4fe04 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LDeviceAdapterTest.java @@ -62,6 +62,17 @@ void testGetLNAdapters() { assertThrows(ScdException.class, () -> lDeviceAdapter.getLNAdapter("ANCR","1","pr")); } + @Test + void findLnAdapter_should_return_adapter(){ + // Given + LDeviceAdapter lDeviceAdapter = iAdapter.findLDeviceAdapterByLdInst("LD_INS2").get(); + // When + Optional lnAdapter = lDeviceAdapter.findLnAdapter("ANCR", "1", null); + // Then + assertThat(lnAdapter).get().extracting(LNAdapter::getLNClass, LNAdapter::getLNInst, LNAdapter::getPrefix) + .containsExactly("ANCR", "1", ""); + } + @Test void getExtRefBinders_shouldReturnExtRefBindingInfo_whenExist() { //Given diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LN0AdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LN0AdapterTest.java index e48d8d48e..829a8f8eb 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LN0AdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LN0AdapterTest.java @@ -999,4 +999,31 @@ void updateDoInRef_should_update_setSrcRef_and_setSrcCB_and_setTstRef_and_setTst .isEqualTo(expectedTstCB); assertThat(sclReportItems).isEmpty(); } + + @Test + void streamControlBlocks_should_return_all_GSEControlBlocks(){ + // Given + IEDAdapter iedAdapter = mock(IEDAdapter.class); + TLDevice tlDevice = new TLDevice(); + LDeviceAdapter lDeviceAdapter = mock(LDeviceAdapter.class); + when(lDeviceAdapter.hasDataSetCreationCapability(any())).thenReturn(true); + when(lDeviceAdapter.hasControlBlockCreationCapability(any())).thenReturn(true); + when(lDeviceAdapter.getParentAdapter()).thenReturn(iedAdapter); + LN0 ln0 = new LN0(); + tlDevice.setLN0(ln0); + when(lDeviceAdapter.getCurrentElem()).thenReturn(tlDevice); + LN0Adapter ln0Adapter = new LN0Adapter(lDeviceAdapter, ln0); + ln0Adapter.createDataSetIfNotExists("datSet1", ControlBlockEnum.GSE); + ln0Adapter.createDataSetIfNotExists("datSet2", ControlBlockEnum.SAMPLED_VALUE); + ln0Adapter.createControlBlockIfNotExists("cbNameGSE", "cbId1", "datSet1", ControlBlockEnum.GSE); + ln0Adapter.createControlBlockIfNotExists("cbNameSMV", "cbId2", "datSet2", ControlBlockEnum.SAMPLED_VALUE); + // When + Stream result = ln0Adapter.streamControlBlocks(ControlBlockEnum.GSE); + // Then + assertThat(result) + .hasSize(1) + .extracting(ControlBlockAdapter::getName) + .containsExactly("cbNameGSE"); + } + } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapterTest.java index 199b61e32..69b06a6e2 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapterTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapterTest.java @@ -23,6 +23,7 @@ import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; +import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.findLn; class LNAdapterTest { @@ -650,4 +651,34 @@ void getControlBlocks_should_find_ControlBlock_by_name() { assertThat(tControls).isNotEmpty() .hasSize(1); } + + @Test + void getTControlsByType_should_return_list_of_report_control_blocks() { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-ied-dtt-com-import-stds/std.xml"); + LNAdapter lnAdapter= findLn(new SclRootAdapter(scd), "IED4d4fe1a8cda64cf88a5ee4176a1a0eef", "LDSUIED", "LPAI", "1", null); + // When + List tControlsByType = lnAdapter.getTControlsByType(TReportControl.class); + // Then + assertThat(tControlsByType).isSameAs(lnAdapter.getCurrentElem().getReportControl()); + } + + @ParameterizedTest + @MethodSource("provideGetTControlsByTypeException") + void getTControlsByType_should_throw_when_unsupported_controlBlock_for_ln(Class tControlClass) { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-ied-dtt-com-import-stds/std.xml"); + LNAdapter lnAdapter= findLn(new SclRootAdapter(scd), "IED4d4fe1a8cda64cf88a5ee4176a1a0eef", "LDSUIED", "LPAI", "1", null); + // When & Then + assertThatThrownBy(() -> lnAdapter.getTControlsByType(tControlClass)) + .isInstanceOf(IllegalArgumentException.class); + } + + private static Stream provideGetTControlsByTypeException() { + return Stream.of( + Arguments.of(TGSEControl.class), + Arguments.of(TSampledValueControl.class) + ); + } + } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/testhelpers/SclHelper.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/testhelpers/SclHelper.java index 71d3d110a..44358ad88 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/testhelpers/SclHelper.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/testhelpers/SclHelper.java @@ -17,6 +17,7 @@ import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.lfenergy.compas.sct.commons.util.SclConstructorHelper.newConnectedAp; /** * Provides static methods to quickly retrieve SCL elements, to be used in writing tests. @@ -25,9 +26,14 @@ */ @UtilityClass public class SclHelper { - public static LDeviceAdapter findLDevice(SclRootAdapter sclRootAdapter, String iedName, String ldInst) { + + public static IEDAdapter findIed(SclRootAdapter sclRootAdapter, String iedName) { return sclRootAdapter.findIedAdapterByName(iedName) - .orElseThrow(() -> new AssertionFailedError(String.format("IED.name=%s not found", iedName))).findLDeviceAdapterByLdInst(ldInst).orElseThrow(() -> new AssertionFailedError(String.format("LDevice.inst=%s not found in IED.name=%s", ldInst, iedName))); + .orElseThrow(() -> new AssertionFailedError(String.format("IED.name=%s not found", iedName))); + } + + public static LDeviceAdapter findLDevice(SclRootAdapter sclRootAdapter, String iedName, String ldInst) { + return findIed(sclRootAdapter, iedName).findLDeviceAdapterByLdInst(ldInst).orElseThrow(() -> new AssertionFailedError(String.format("LDevice.inst=%s not found in IED.name=%s", ldInst, iedName))); } public static InputsAdapter findInputs(SclRootAdapter sclRootAdapter, String iedName, String ldInst) { @@ -159,4 +165,46 @@ public static Stream streamAllExtRef(SclRootAdapter sclRootAdapter) { public static String getDaiValue(AbstractLNAdapter ln, String doiName, String daiName) { return ln.getDOIAdapterByName(doiName).getDataAdapterByName(daiName).getCurrentElem().getVal().get(0).getValue(); } + + public static Stream streamAllConnectedApGseP(SCL scd, String pType) { + return scd.getCommunication().getSubNetwork().stream() + .map(TSubNetwork::getConnectedAP) + .flatMap(List::stream) + .map(TConnectedAP::getGSE) + .flatMap(List::stream) + .map(TControlBlock::getAddress) + .map(TAddress::getP) + .flatMap(List::stream) + .filter(tp -> pType.equals(tp.getType())) + .map(TP::getValue); + } + + public static TConnectedAP addConnectedAp(SCL scd, String subNetworkName, String apName, String iedName) { + scd.setCommunication(new TCommunication()); + TSubNetwork subNetwork = new TSubNetwork(); + subNetwork.setName(subNetworkName); + scd.getCommunication().getSubNetwork().add(subNetwork); + TConnectedAP connectedAP = newConnectedAp(iedName, apName); + subNetwork.getConnectedAP().add(connectedAP); + return connectedAP; + } + + public static SclRootAdapter createSclRootAdapterWithIed(String iedName) { + SCL scl = new SCL(); + scl.setHeader(new THeader()); + TIED ied = new TIED(); + ied.setName(iedName); + scl.getIED().add(ied); + return new SclRootAdapter(scl); + } + + public static SclRootAdapter createSclRootWithConnectedAp(String iedName, String apName) { + SclRootAdapter sclRootAdapter = createSclRootAdapterWithIed(iedName); + SCL scl = sclRootAdapter.getCurrentElem(); + scl.setCommunication(new TCommunication()); + TSubNetwork subNetwork = new TSubNetwork(); + scl.getCommunication().getSubNetwork().add(subNetwork); + subNetwork.getConnectedAP().add(newConnectedAp(iedName, apName)); + return sclRootAdapter; + } } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/CsvUtilsTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/CsvUtilsTest.java new file mode 100644 index 000000000..d245b7b3f --- /dev/null +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/CsvUtilsTest.java @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: 2023 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons.util; + +import com.opencsv.bean.CsvBindByPosition; +import lombok.*; +import org.assertj.core.groups.Tuple; +import org.junit.jupiter.api.Test; + +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class CsvUtilsTest { + + private static final Tuple ROW_1 = Tuple.tuple("cel1x1", "cel1x2", "cel1x3"); + private static final Tuple ROW_2 = Tuple.tuple("cel2x1", "cel2x2", "cel2x3"); + + @Test + void parseRows_should_parse_rows() { + //Given + StringReader csvReader = new StringReader(""" + cel1x1;cel1x2;cel1x3 + cel2x1;cel2x2;cel2x3 + """); + //When + List rows = CsvUtils.parseRows(csvReader, Row.class); + //Then + assertThat(rows).extracting(Row::getCol1, Row::getCol2, Row::getCol3) + .containsExactly( + ROW_1, + ROW_2 + ); + } + + @Test + void parseRows_should_ignore_empty_lines() { + //Given + StringReader csvReader = new StringReader(""" + cel1x1;cel1x2;cel1x3 + + cel2x1;cel2x2;cel2x3 + """); + //When + List rows = CsvUtils.parseRows(csvReader, Row.class); + //Then + assertThat(rows).extracting(Row::getCol1, Row::getCol2, Row::getCol3) + .containsExactly( + ROW_1, + ROW_2 + ); + } + + @Test + void parseRows_should_treat_empty_string_as_null() { + //Given + StringReader csvReader = new StringReader(""" + ;cel1x2;cel1x3 + cel2x1;;cel2x3 + cel3x1;cel3x2; + """); + //When + List rows = CsvUtils.parseRows(csvReader, Row.class); + //Then + assertThat(rows).extracting(Row::getCol1, Row::getCol2, Row::getCol3) + .containsExactly( + Tuple.tuple(null, "cel1x2", "cel1x3"), + Tuple.tuple("cel2x1", null, "cel2x3"), + Tuple.tuple("cel3x1", "cel3x2", null) + ); + } + + @Test + void parseRows_should_ignore_comment_lines() { + //Given + StringReader csvReader = new StringReader(""" + cel1x1;cel1x2;cel1x3 + + # other comment line with indentation + line with # in the middle should not be ignored;a;b + """); + //When + List rows = CsvUtils.parseRows(csvReader, Row.class); + //Then + assertThat(rows).extracting(Row::getCol1, Row::getCol2, Row::getCol3) + .containsExactly( + ROW_1, + Tuple.tuple("line with # in the middle should not be ignored", "a", "b") + ); + } + + @Test + void parseRows_with_resource_path_should_parse_rows() { + //Given + String resourcePath = "csvutils/csv_utils_test_file.csv"; + //When + List rows = CsvUtils.parseRows(resourcePath, StandardCharsets.UTF_8, Row.class); + //Then + assertThat(rows).extracting(Row::getCol1, Row::getCol2, Row::getCol3) + .containsExactly( + ROW_1, + ROW_2 + ); + } + + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + @EqualsAndHashCode + public static class Row { + @CsvBindByPosition(position = 0) + private String col1; + @CsvBindByPosition(position = 1) + private String col2; + @CsvBindByPosition(position = 2) + private String col3; + } + +} diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/FcdaCandidatesTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/FcdaCandidatesTest.java index 35eedbf73..16b73210c 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/FcdaCandidatesTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/FcdaCandidatesTest.java @@ -59,13 +59,4 @@ void contains_when_a_parameter_is_blank_should_throw_exception() { .isInstanceOf(IllegalArgumentException.class); } - @Test - void contains_should_ignore_first_lines() { - //Given - FcdaCandidates fcdaCandidates = FcdaCandidates.SINGLETON; - //When - boolean result = fcdaCandidates.contains("FCDA.lnClass", "FCDA.doName", "FCDA.daName", "FCDA.fc"); - //Then - assertThat(result).isFalse(); - } } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/SclConstructorHelperTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/SclConstructorHelperTest.java new file mode 100644 index 000000000..4c3b9ad98 --- /dev/null +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/SclConstructorHelperTest.java @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2023 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons.util; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.support.ReflectionSupport; +import org.lfenergy.compas.scl2007b4.model.TAddress; +import org.lfenergy.compas.scl2007b4.model.TConnectedAP; +import org.lfenergy.compas.scl2007b4.model.TDurationInMilliSec; +import org.lfenergy.compas.scl2007b4.model.TP; + +import java.math.BigDecimal; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class SclConstructorHelperTest { + + @Test + void constructor_should_throw_exception() { + // Given + Class testedClass = SclConstructorHelper.class; + // When & Then + assertThatThrownBy(() -> ReflectionSupport.newInstance(testedClass)) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + void newP_should_create_new_instance_with_given_parameters() { + //Given + String pType = "pType"; + String pValue = "pValue"; + //When + TP tp = SclConstructorHelper.newP(pType, pValue); + //Then + assertThat(tp).extracting(TP::getType, TP::getValue) + .containsExactly(pType, pValue); + } + + @Test + void newDurationInMilliSec_should_create_new_instance_with_given_parameters() { + //Given + BigDecimal value = new BigDecimal(5); + String unit = "unit"; + String multiplier = "multiplier"; + //When + TDurationInMilliSec durationInMilliSec = SclConstructorHelper.newDurationInMilliSec(value, unit, multiplier); + //Then + assertThat(durationInMilliSec).extracting(TDurationInMilliSec::getValue, TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier) + .containsExactly(value, unit, multiplier); + } + + @Test + void testNewDurationInMilliSec_should_create_new_instance_with_given_parameters() { + //Given + long value = 5L; + //When + TDurationInMilliSec durationInMilliSec = SclConstructorHelper.newDurationInMilliSec(value); + //Then + assertThat(durationInMilliSec).extracting(TDurationInMilliSec::getValue, TDurationInMilliSec::getUnit, TDurationInMilliSec::getMultiplier) + .containsExactly(new BigDecimal(value), "s", "m"); + } + + @Test + void newAddress_should_create_new_instance_with_given_parameters() { + //Given + List listOfP = List.of( + SclConstructorHelper.newP("type1", "value1"), + SclConstructorHelper.newP("type2", "value2")); + //When + TAddress tAddress = SclConstructorHelper.newAddress(listOfP); + //Then + assertThat(tAddress.getP()) + .containsExactlyInAnyOrder(listOfP.toArray(new TP[0])); + } + + @Test + void newConnectedAp_should_create_new_instance_with_given_parameters() { + //Given + String iedName = "iedName"; + String apName = "apName"; + //When + TConnectedAP tConnectedAP = SclConstructorHelper.newConnectedAp(iedName, apName); + //Then + assertThat(tConnectedAP).extracting(TConnectedAP::getIedName, TConnectedAP::getApName) + .containsExactly(iedName, apName); + } +} diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/UtilsTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/UtilsTest.java index ec66b9a3e..525239889 100644 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/UtilsTest.java +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/util/UtilsTest.java @@ -7,7 +7,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.support.ReflectionSupport; import java.util.*; @@ -436,4 +438,110 @@ void lnClassEquals_when_lnClass_has_more_than_one_value_should_throw_exception() assertThatThrownBy(() -> Utils.lnClassEquals(lnClass1, lnClass2)) .isInstanceOf(IllegalArgumentException.class); } + + @Test + void sequence_should_return_range_of_numbers(){ + // Given + long startInclusive = 1; + long endInclusive = 3; + // When + PrimitiveIterator.OfLong result = Utils.sequence(startInclusive, endInclusive); + // Then + assertThat(result.next()).isEqualTo(1); + assertThat(result.next()).isEqualTo(2); + assertThat(result.next()).isEqualTo(3); + assertThat(result.hasNext()).isFalse(); + assertThatThrownBy(result::nextLong).isInstanceOf(NoSuchElementException.class); + } + + @Test + void sequence_should_return_empty_iterator(){ + // Given + long startInclusive = 1; + long endInclusive = 0; + // When + PrimitiveIterator.OfLong result = Utils.sequence(startInclusive, endInclusive); + // Then + assertThat(result.hasNext()).isFalse(); + assertThatThrownBy(result::nextLong).isInstanceOf(NoSuchElementException.class); + } + + @Test + void sequence_should_throw_exception_when_MAX_VALUE(){ + // Given + long startInclusive = 1; + long endInclusive = Long.MAX_VALUE; + // When & Then + assertThatThrownBy(() -> Utils.sequence(startInclusive, endInclusive)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("End cannot exceed Long.MAX_VALUE - 1"); + } + + @Test + void macAddressSequence_should_return_range_of_macAddresses(){ + // Given + String startInclusive = "01-0C-CD-01-00-FE"; + String endInclusive = "01-0C-CD-01-01-01"; + // When + Iterator result = Utils.macAddressSequence(startInclusive, endInclusive); + // Then + assertThat(result.next()).isEqualTo("01-0C-CD-01-00-FE"); + assertThat(result.next()).isEqualTo("01-0C-CD-01-00-FF"); + assertThat(result.next()).isEqualTo("01-0C-CD-01-01-00"); + assertThat(result.next()).isEqualTo("01-0C-CD-01-01-01"); + assertThat(result.hasNext()).isFalse(); + assertThatThrownBy(result::next).isInstanceOf(NoSuchElementException.class); + } + + @ParameterizedTest + @CsvSource({"0x123456789ABC,12-34-56-78-9A-BC", "0xAA,00-00-00-00-00-AA"}) + void longToMacAddress_should_convert_long_to_mac_address(long macAddress, String expected){ + // Given : parameter + // When + String result = Utils.longToMacAddress(macAddress); + // Then + assertThat(result.toUpperCase()).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource(value = {"-1,macAddress must be a positive integer but got : -1", // Negative number + "281474976710656,macAddress cannot exceed 281474976710655 but got : 281474976710656", //FF-FF-FF-FF-FF-FF + "9223372036854775807,macAddress cannot exceed 281474976710655 but got : 9223372036854775807"}) //Long MAX_VALUE + void longToMacAddress_when_long_out_of_range_should_throw_exception(long macAddress, String expectedMessage){ + // Given : parameter + // When & Then + assertThatThrownBy(() -> Utils.longToMacAddress(macAddress)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(expectedMessage); + } + + @ParameterizedTest + @CsvSource({"00-00-00-00-00-00,0", "12-34-56-78-9A-BC,20015998343868", "FF-FF-FF-FF-FF-FF,281474976710655", + "11:ab:FF:01:c8:2A,19430415386666"}) + void macAddressToLong_should_convert_mac_address_to_long(String macAddress, long expected){ + // Given : parameters + // When + long result = Utils.macAddressToLong(macAddress); + // Then + assertThat(result).isEqualTo(expected); + } + + @ParameterizedTest + @ValueSource(strings = {"", "123", "AB-CD", "FF_FF_FF_FF_FF_FF", "LK-JI-HG-FE-DC-AB", "AB-AB-AB-AB-AB-AB-AB"}) + void macAddressToLong_when_malformed_macAddress_should_throw_exception(String macAddress){ + // Given : parameters + // When & Then + assertThatThrownBy(() -> Utils.macAddressToLong(macAddress)) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @CsvSource({"0,6,000000", "255,1,FF", "255,2,FF", "255,3,0FF", "10,2,0A"}) + void toHex_should_return_hexadecimal(long number, int length, String expected){ + // Given + // When + String result = Utils.toHex(number, length); + // Then + assertThat(result).isEqualTo(expected); + } } diff --git a/sct-commons/src/test/resources/ControlBlockCommunicationTemplates.csv b/sct-commons/src/test/resources/ControlBlockCommunicationTemplates.csv new file mode 100644 index 000000000..37184ecb7 --- /dev/null +++ b/sct-commons/src/test/resources/ControlBlockCommunicationTemplates.csv @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2022 RTE FRANCE +# +# SPDX-License-Identifier: Apache-2.0 + +#CB Type;X.Y;Z.W;IedType;IedRedundancy;Bay Internal OR External;VLAN-ID;VLAN-PRIORITY;MINTIME;MAXTIME +GOOSE;01.00;009.001;BCU;A;BAY_INTERNAL;300;4;10;2000 +SV;01.00;009.001;BCU;A;BAY_INTERNAL;None;None;; +GOOSE;01.00;009.001;BCU;A;BAY_EXTERNAL;301;5;15;5000 +SV;01.00;009.001;BCU;A;BAY_EXTERNAL;None;None;; diff --git a/sct-commons/src/test/resources/FcdaCandidates.csv b/sct-commons/src/test/resources/FcdaCandidates.csv index 6596b7fb6..1cdfa1861 100644 --- a/sct-commons/src/test/resources/FcdaCandidates.csv +++ b/sct-commons/src/test/resources/FcdaCandidates.csv @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2022 RTE FRANCE # # SPDX-License-Identifier: Apache-2.0 -# !!! WARNING !!! The 5 first lines are skipped + FCDA.lnClass;FCDA.doName;FCDA.daName;FCDA.fc ANCR;DoName;daNameST;ST ANCR;DoName;daNameMX;MX diff --git a/sct-commons/src/test/resources/csvutils/csv_utils_test_file.csv b/sct-commons/src/test/resources/csvutils/csv_utils_test_file.csv new file mode 100644 index 000000000..77151a08a --- /dev/null +++ b/sct-commons/src/test/resources/csvutils/csv_utils_test_file.csv @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2022 RTE FRANCE +# +# SPDX-License-Identifier: Apache-2.0 + +#col1;col2;col3 +cel1x1;cel1x2;cel1x3 +cel2x1;cel2x2;cel2x3 diff --git a/sct-commons/src/test/resources/limitation_cb_dataset_fcda/scd_check_limitation_binded_ied_controls_fcda.xml b/sct-commons/src/test/resources/limitation_cb_dataset_fcda/scd_check_limitation_bound_ied_controls_fcda.xml similarity index 100% rename from sct-commons/src/test/resources/limitation_cb_dataset_fcda/scd_check_limitation_binded_ied_controls_fcda.xml rename to sct-commons/src/test/resources/limitation_cb_dataset_fcda/scd_check_limitation_bound_ied_controls_fcda.xml diff --git a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml new file mode 100644 index 000000000..5e9f4d991 --- /dev/null +++ b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_controlblock_network_configuration.xml @@ -0,0 +1,170 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + on + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + + + + IED_NAME1 + + + IED_NAME1 + + + IED_NAME1 + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + + + + IED_NAME1 + + + IED_NAME1 + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + on + off + test + + + diff --git a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_extref_errors.xml b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_extref_errors.xml index 8abf4bb0c..0aa4abf82 100644 --- a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_extref_errors.xml +++ b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_extref_errors.xml @@ -7,7 +7,7 @@
- + @@ -39,7 +39,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_ied_errors.xml b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_ied_errors.xml index 410fbc21f..2eac0b60f 100644 --- a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_ied_errors.xml +++ b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_ied_errors.xml @@ -8,7 +8,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -57,7 +57,7 @@ - + @@ -85,7 +85,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml index 041e1f8d7..1c6acbf12 100644 --- a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml +++ b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml @@ -7,7 +7,7 @@
- + @@ -91,7 +91,7 @@ - + @@ -122,7 +122,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success_analyze.xml b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success_analyze.xml index 4cd987c58..177659525 100644 --- a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success_analyze.xml +++ b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success_analyze.xml @@ -7,7 +7,7 @@
- + @@ -41,7 +41,7 @@ - + @@ -75,7 +75,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success_test_fcda_sort.xml b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success_test_fcda_sort.xml index adf03d363..567eb21ac 100644 --- a/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success_test_fcda_sort.xml +++ b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success_test_fcda_sort.xml @@ -7,7 +7,7 @@
- + @@ -41,7 +41,7 @@ - + @@ -75,7 +75,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_success.xml b/sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_success.xml index 66c8cc057..c7869dfd7 100644 --- a/sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_success.xml +++ b/sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_success.xml @@ -7,7 +7,7 @@
- + @@ -33,7 +33,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_with_extref_errors.xml b/sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_with_extref_errors.xml index 424308740..3fd8ae780 100644 --- a/sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_with_extref_errors.xml +++ b/sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_with_extref_errors.xml @@ -7,7 +7,7 @@
- + @@ -104,7 +104,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_with_ied_errors.xml b/sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_with_ied_errors.xml index b8571944a..5955cca60 100644 --- a/sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_with_ied_errors.xml +++ b/sct-commons/src/test/resources/scd-extref-iedname/scd_set_extref_iedname_with_ied_errors.xml @@ -8,7 +8,7 @@ - + @@ -21,7 +21,7 @@ - + @@ -46,7 +46,7 @@ - + @@ -60,7 +60,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/scd.xml b/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/scd.xml index 1248f24fb..61c829f27 100644 --- a/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/scd.xml +++ b/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/scd.xml @@ -25,7 +25,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/scd_lnode_with_many_compas_icdheader.xml b/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/scd_lnode_with_many_compas_icdheader.xml index 5542476fb..e89ea6852 100644 --- a/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/scd_lnode_with_many_compas_icdheader.xml +++ b/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/scd_lnode_with_many_compas_icdheader.xml @@ -25,13 +25,13 @@ - + - + - + diff --git a/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/scd_with_same_compas_icd_header_in_different_functions.xml b/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/scd_with_same_compas_icd_header_in_different_functions.xml index c2d4c04d1..d06f64392 100644 --- a/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/scd_with_same_compas_icd_header_in_different_functions.xml +++ b/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/scd_with_same_compas_icd_header_in_different_functions.xml @@ -16,14 +16,14 @@ - + - + diff --git a/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/ssd.xml b/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/ssd.xml index d6f0de482..591e03dd0 100644 --- a/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/ssd.xml +++ b/sct-commons/src/test/resources/scd-ied-dtt-com-import-stds/ssd.xml @@ -16,7 +16,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test1_LD_STATUS_INACTIVE.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test1_LD_STATUS_INACTIVE.scd index 9aa92348e..cad226b1d 100644 --- a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test1_LD_STATUS_INACTIVE.scd +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test1_LD_STATUS_INACTIVE.scd @@ -30,12 +30,12 @@ - + - + @@ -75,7 +75,7 @@ - + @@ -112,7 +112,7 @@ - + @@ -149,7 +149,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test2_LD_STATUS_INACTIVE.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test2_LD_STATUS_INACTIVE.scd index 316933867..76dd3fce0 100644 --- a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test2_LD_STATUS_INACTIVE.scd +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test2_LD_STATUS_INACTIVE.scd @@ -30,7 +30,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -107,7 +107,7 @@ - + @@ -144,7 +144,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Dai_Not_Updatable.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Dai_Not_Updatable.scd index 48c81222d..730ca6fd7 100644 --- a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Dai_Not_Updatable.scd +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Dai_Not_Updatable.scd @@ -30,7 +30,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -107,7 +107,7 @@ - + @@ -144,7 +144,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingBeh.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingBeh.scd index ee8ac2930..b29dcd927 100644 --- a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingBeh.scd +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingBeh.scd @@ -30,7 +30,7 @@ - + @@ -60,7 +60,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivate.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivate.scd index eba19585c..09f288ffa 100644 --- a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivate.scd +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivate.scd @@ -30,7 +30,7 @@ - + @@ -60,7 +60,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivateAttribute.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivateAttribute.scd index c2e919b1b..83961dcac 100644 --- a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivateAttribute.scd +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingLDevicePrivateAttribute.scd @@ -30,7 +30,7 @@ - + @@ -60,7 +60,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingMod.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingMod.scd index 1c6850178..41c519502 100644 --- a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingMod.scd +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_KO_MissingMod.scd @@ -30,7 +30,7 @@ - + @@ -60,7 +60,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_ACTIVE.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_ACTIVE.scd index 85a71fb25..5dc4b89f4 100644 --- a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_ACTIVE.scd +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_ACTIVE.scd @@ -30,7 +30,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -107,7 +107,7 @@ - + @@ -144,7 +144,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_UNTESTED.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_UNTESTED.scd index 205bcff51..3cf0dad8e 100644 --- a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_UNTESTED.scd +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_LD_STATUS_UNTESTED.scd @@ -30,7 +30,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -107,7 +107,7 @@ - + @@ -144,7 +144,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Template.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Template.scd index 651c5eff3..7a79a5be2 100644 --- a/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Template.scd +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue68_Test_Template.scd @@ -30,7 +30,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -107,7 +107,7 @@ - + @@ -144,7 +144,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-refresh-lnode/issue_165_enhance_68_Test_Dai_Updatable.scd b/sct-commons/src/test/resources/scd-refresh-lnode/issue_165_enhance_68_Test_Dai_Updatable.scd index 68a260d66..e3fae2ae9 100644 --- a/sct-commons/src/test/resources/scd-refresh-lnode/issue_165_enhance_68_Test_Dai_Updatable.scd +++ b/sct-commons/src/test/resources/scd-refresh-lnode/issue_165_enhance_68_Test_Dai_Updatable.scd @@ -30,7 +30,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -107,7 +107,7 @@ - + @@ -144,7 +144,7 @@ - + @@ -176,7 +176,7 @@ - + @@ -213,7 +213,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-substation-import-ssd/scd_with_substation.xml b/sct-commons/src/test/resources/scd-substation-import-ssd/scd_with_substation.xml index 49e15f457..60df1a663 100644 --- a/sct-commons/src/test/resources/scd-substation-import-ssd/scd_with_substation.xml +++ b/sct-commons/src/test/resources/scd-substation-import-ssd/scd_with_substation.xml @@ -27,7 +27,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-substation-import-ssd/ssd.xml b/sct-commons/src/test/resources/scd-substation-import-ssd/ssd.xml index c4688b459..37b4f304d 100644 --- a/sct-commons/src/test/resources/scd-substation-import-ssd/ssd.xml +++ b/sct-commons/src/test/resources/scd-substation-import-ssd/ssd.xml @@ -23,14 +23,14 @@ - + - + @@ -57,7 +57,7 @@ - + diff --git a/sct-commons/src/test/resources/scd-test-update-inref/scd_update_inref_issue_231_test_ok.xml b/sct-commons/src/test/resources/scd-test-update-inref/scd_update_inref_issue_231_test_ok.xml index 9b15fc0b5..fbbbd0dfd 100644 --- a/sct-commons/src/test/resources/scd-test-update-inref/scd_update_inref_issue_231_test_ok.xml +++ b/sct-commons/src/test/resources/scd-test-update-inref/scd_update_inref_issue_231_test_ok.xml @@ -65,13 +65,79 @@ - + + + + + + LD_WITH_1_Bad_InRef_DOI_InRef4 + + + + OLD_VAL + + + + + + + + + + + + + + + LD_WITH_1_Bad_InRef_DOI_InRef4 + + + + OLD_VAL + + + + + + + + + + + + + + + LD_WITH_1_Bad_InRef_DOI_InRef4 + + + + OLD_VAL + + + + + + + + + + @@ -79,6 +145,13 @@ + + + + + + + diff --git a/sct-commons/src/test/resources/scl-ln-adapter/scd_with_ln.xml b/sct-commons/src/test/resources/scl-ln-adapter/scd_with_ln.xml index d40d1cc89..d1493e76f 100644 --- a/sct-commons/src/test/resources/scl-ln-adapter/scd_with_ln.xml +++ b/sct-commons/src/test/resources/scl-ln-adapter/scd_with_ln.xml @@ -8,7 +8,7 @@ - + @@ -42,7 +42,7 @@ - + diff --git a/sct-commons/src/test/resources/scl-remove-controlBlocks-dataSet-extRefSrc/scl-with-control-blocks.xml b/sct-commons/src/test/resources/scl-remove-controlBlocks-dataSet-extRefSrc/scl-with-control-blocks.xml index 94db16aa8..d395939f3 100644 --- a/sct-commons/src/test/resources/scl-remove-controlBlocks-dataSet-extRefSrc/scl-with-control-blocks.xml +++ b/sct-commons/src/test/resources/scl-remove-controlBlocks-dataSet-extRefSrc/scl-with-control-blocks.xml @@ -33,7 +33,7 @@ - +