From 545062aa537227b5cb8b8aafa9bc51035e697422 Mon Sep 17 00:00:00 2001 From: massifben <105049157+massifben@users.noreply.github.com> Date: Thu, 29 Dec 2022 18:07:53 +0100 Subject: [PATCH] feat(84): RSR-433 create datasets from extRef Signed-off-by: massifben <105049157+massifben@users.noreply.github.com> --- .../compas/sct/commons/dto/FCDAInfo.java | 2 +- .../compas/sct/commons/scl/ExtRefService.java | 8 +- .../commons/scl/ied/AbstractLNAdapter.java | 69 +++++--- .../sct/commons/scl/ied/DataSetAdapter.java | 142 ++++++++++++++++ .../sct/commons/scl/ied/IEDAdapter.java | 31 +--- .../sct/commons/scl/ied/InputsAdapter.java | 73 +++++--- .../sct/commons/scl/ied/LN0Adapter.java | 4 +- .../compas/sct/commons/scl/ied/LNAdapter.java | 5 +- .../sct/commons/util/ServiceSettingsType.java | 20 ++- .../compas/sct/commons/util/Utils.java | 43 ++++- .../sct/commons/scl/ExtRefServiceTest.java | 66 +++++++- .../commons/scl/ied/DataSetAdapterTest.java | 113 +++++++++++++ .../sct/commons/scl/ied/IEDAdapterTest.java | 62 ------- .../scl/ied/InputsAdapterForDemoTest.java | 121 -------------- .../commons/scl/ied/InputsAdapterTest.java | 156 +++++++++++++++++- .../sct/commons/scl/ied/LN0AdapterTest.java | 45 +++++ .../sct/commons/scl/ied/LNAdapterTest.java | 25 ++- .../sct/commons/testhelpers/FCDARecord.java | 24 +++ .../sct/commons/testhelpers/SclHelper.java | 34 +++- .../compas/sct/commons/util/UtilsTest.java | 74 +++++++++ .../src/test/resources/FcdaCandidates.csv | 4 + .../src/test/resources/logback-test.xml | 4 +- ...eate_dataset_and_controlblocks_success.xml | 30 +++- ...d_controlblocks_success_test_fcda_sort.xml | 130 +++++++++++++++ .../resources/scl-ln-adapter/scd_with_ln.xml | 72 ++++++++ 25 files changed, 1050 insertions(+), 307 deletions(-) create mode 100644 sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DataSetAdapter.java create mode 100644 sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/DataSetAdapterTest.java delete mode 100644 sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapterForDemoTest.java create mode 100644 sct-commons/src/test/java/org/lfenergy/compas/sct/commons/testhelpers/FCDARecord.java create mode 100644 sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success_test_fcda_sort.xml create mode 100644 sct-commons/src/test/resources/scl-ln-adapter/scd_with_ln.xml diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/FCDAInfo.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/FCDAInfo.java index 921e43424..453f5b590 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/FCDAInfo.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/dto/FCDAInfo.java @@ -94,7 +94,7 @@ public TFCDA getFCDA(){ } if(doName != null && doName.isDefined()){ - tfcda.setDaName(doName.toString()); + tfcda.setDoName(doName.toString()); } if(daName != null && daName.isDefined()){ 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 b97ea6999..57f516127 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 @@ -53,7 +53,7 @@ public static SclReport updateAllExtRefIedNames(SCL scd) { .filter(LN0Adapter::hasInputs) .map(LN0Adapter::getInputsAdapter) .map(inputsAdapter -> inputsAdapter.updateAllExtRefIedNames(icdSystemVersionToIed)) - .flatMap(List::stream).collect(Collectors.toList()); + .flatMap(List::stream).toList(); return new SclReport(sclRootAdapter, extRefErrors); } @@ -79,7 +79,7 @@ private static List checkIedCompasIcdHeaderAttributes(SclRootAdap return null; } ).filter(Objects::nonNull) - .collect(Collectors.toList()); + .toList(); } private static List checkIedUnityOfIcdSystemVersionUuid(SclRootAdapter sclRootAdapter) { @@ -97,7 +97,7 @@ private static List checkIedUnityOfIcdSystemVersionUuid(SclRootAd .collect(Collectors.joining(", ")), "/IED/Private/compas:ICDHeader[@ICDSystemVersionUUID] must be unique" + " but the same ICDSystemVersionUUID was found on several IED.")) - .collect(Collectors.toList()); + .toList(); } public static SclReport createDataSetAndControlBlocks(SCL scd) { @@ -127,7 +127,7 @@ private static SclReport createDataSetAndControlBlocks(SclRootAdapter sclRootAda List sclReportItems = lDeviceAdapters .map(LDeviceAdapter::createDataSetAndControlBlocks) .flatMap(List::stream) - .collect(Collectors.toList()); + .toList(); return new SclReport(sclRootAdapter, sclReportItems); } } 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 ac326b2d4..26e1977a1 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 @@ -16,6 +16,7 @@ import org.lfenergy.compas.sct.commons.scl.dtt.DataTypeTemplateAdapter; import org.lfenergy.compas.sct.commons.scl.dtt.EnumTypeAdapter; import org.lfenergy.compas.sct.commons.scl.dtt.LNodeTypeAdapter; +import org.lfenergy.compas.sct.commons.util.ServiceSettingsType; import java.util.*; import java.util.stream.Collectors; @@ -46,7 +47,7 @@ *
  • {@link AbstractLNAdapter#getDAI Returns the value of the ResumedDataTemplate containment reference By filter}
  • *
  • {@link AbstractLNAdapter#getDAIValues(ResumedDataTemplate) Returns DAI (sGroup, value) containment reference list By ResumedDataTemplate filter}
  • * - *
  • {@link AbstractLNAdapter#getDataSetByRef(String) Returns the value of the TDataSet object reference By the value of the name attribute }
  • + *
  • {@link AbstractLNAdapter#getDataSetByName(String) Returns the value of the TDataSet object reference By the value of the name attribute }
  • * *
  • {@link AbstractLNAdapter#getControlBlocks(List, TServiceType) Returns the value of the ControlBlock containment reference list that match datSet value of given TDataSet }
  • *
  • {@link AbstractLNAdapter#addPrivate Add TPrivate under this object}
  • @@ -140,11 +141,12 @@ protected void addControlBlock(ControlBlock controlBlock) throws ScdException } } - public Optional findDataSetByRef(String dataSetRef) { + public Optional findDataSetByName(String dataSetName) { return currentElem.getDataSet() .stream() - .filter(tDataSet -> Objects.equals(tDataSet.getName(), dataSetRef)) - .findFirst(); + .filter(tDataSet -> Objects.equals(tDataSet.getName(), dataSetName)) + .findFirst() + .map(dataSet -> new DataSetAdapter(this, dataSet)); } public DOIAdapter getDOIAdapterByName(String doiName) throws ScdException { @@ -345,9 +347,8 @@ public List> getControlBlocksForMatchingFCDA(@NonNull ExtRefInfo */ protected List> getControlBlocks(List tDataSets, TServiceType serviceType) { return tDataSets.stream() - .map(tDataSet -> getControlBlocksByDataSetRef(tDataSet.getName(), serviceType)) - .flatMap(Collection::stream).toList(); - + .map(tDataSet -> getControlBlocksByDataSetRef(tDataSet.getName(), serviceType)) + .flatMap(Collection::stream).toList(); } /** @@ -655,14 +656,22 @@ private boolean iedHasConfSG() { return iedAdapter.isSettingConfig(this.parentAdapter.getInst()); } + /** + * Gets linked LDevice as parent + * + * @return IEDAdapter object + */ + private LDeviceAdapter getCurrentLDevice() { + return this.parentAdapter; + } + /** * Gets linked IED as parent * * @return IEDAdapter object */ private IEDAdapter getCurrentIed() { - LDeviceAdapter lDeviceAdapter = this.parentAdapter; - return lDeviceAdapter.getParentAdapter(); + return getCurrentLDevice().getParentAdapter(); } /** @@ -854,32 +863,17 @@ protected boolean matchesDataAttributes(String dataAttribute) { /** * Gets Data Set in LNode by its name * - * @param dataSetRef Data Set name + * @param dataSetName Data Set name * @return optional of DataSetInfo */ - public Optional getDataSetByRef(String dataSetRef) { + public Optional getDataSetByName(String dataSetName) { return currentElem.getDataSet() .stream() - .filter(tDataSet -> tDataSet.getName().equals(dataSetRef)) + .filter(tDataSet -> tDataSet.getName().equals(dataSetName)) .map(DataSetInfo::from) .findFirst(); } - /** - * Adds Data Set to LNode Data Sets - * - * @param dataSetInfo data's of Data Set to add - */ - public void addDataSet(DataSetInfo dataSetInfo) { - TDataSet tDataSet = new TDataSet(); - tDataSet.setName(dataSetInfo.getName()); - tDataSet.getFCDA().addAll( - dataSetInfo.getFCDAInfos().stream().map(FCDAInfo::getFCDA).toList() - ); - currentElem.getDataSet().add(tDataSet); - - } - /** * Gets DAI values for specified DA in summaraized Data Type Template * @@ -939,4 +933,25 @@ private void removeExtRefSourceBinding(final TExtRef tExtRef) { tExtRef.unsetSrcLNClass(); } + /** + * Adds DataSet in specified LNode in current IED/AccessPoint. + * The AccessPoint must have DataSet creation capabilities + * + * @param dataSetName Name of the dataSet + * @param serviceSettingsType GOOSE, SMV or REPORT service type + * @throws ScdException throws when IED does not have DataSet creation capabilities + * @see LDeviceAdapter#hasDataSetCreationCapability + */ + public DataSetAdapter createDataSetIfNotExists(String dataSetName, ServiceSettingsType serviceSettingsType) { + return findDataSetByName(dataSetName).orElseGet(() -> { + if (!getCurrentLDevice().hasDataSetCreationCapability(serviceSettingsType)) { + throw new ScdException("IED/AccessPoint does not have capability to create DataSet of type %s in %s" + .formatted(serviceSettingsType, getXPath())); + } + TDataSet newDataSet = new TDataSet(); + newDataSet.setName(dataSetName); + currentElem.getDataSet().add(newDataSet); + return new DataSetAdapter(this, newDataSet); + }); + } } diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DataSetAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DataSetAdapter.java new file mode 100644 index 000000000..229c606c3 --- /dev/null +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/DataSetAdapter.java @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons.scl.ied; + + +import org.apache.commons.lang3.StringUtils; +import org.lfenergy.compas.scl2007b4.model.TAnyLN; +import org.lfenergy.compas.scl2007b4.model.TDataSet; +import org.lfenergy.compas.scl2007b4.model.TFCDA; +import org.lfenergy.compas.scl2007b4.model.TFCEnum; +import org.lfenergy.compas.sct.commons.scl.SclElementAdapter; +import org.lfenergy.compas.sct.commons.util.Utils; + +import java.util.Comparator; +import java.util.Objects; +import java.util.Optional; + +import static org.lfenergy.compas.sct.commons.util.Utils.*; + +/** + * A representation of the model object + * {@link DataSetAdapter DataSetAdapter}. + *

    + * The following features are supported: + *

    + *
      + *
    1. Principal functions
    2. + *
        + *
      • {@link DataSetAdapter#findFCDA look for a FCDA in this DataSet}
      • + *
      • {@link DataSetAdapter#createFCDAIfNotExists create a FCDA in this DataSet
      • + *
      + *
    + *
    + *
    + *      ObjectReference: DataSet
    + *      LDName = "name" key of the DataSet in the parent LN/LN0
    + *  
    + * + * @see TLN0 + * @see AbstractLNAdapter + */ +public class DataSetAdapter extends SclElementAdapter, TDataSet> { + + /** + * IEC 61850 requires FCDA to be oredred inside a DataSet. + * This is the comparator to sort the FCDA inside a DataSet + */ + private static final Comparator fcdaComparator = Comparator + .comparing(TFCDA::getLdInst, Utils::blanksFirstComparator) + .thenComparing(TFCDA::getPrefix, Utils::blanksFirstComparator) + .thenComparing(tfcda -> tfcda.isSetLnClass() ? tfcda.getLnClass().get(0) : null, Utils::blanksFirstComparator) + .thenComparing(tfcda -> tfcda.getLnInst() != null ? Integer.valueOf(tfcda.getLnInst()) : null, Comparator.nullsFirst(Integer::compareTo)) + .thenComparing(TFCDA::getDoName, Utils::blanksFirstComparator) + .thenComparing(TFCDA::getDaName, Utils::blanksFirstComparator); + + public DataSetAdapter(AbstractLNAdapter parentAdapter, TDataSet dataSet) { + super(parentAdapter, dataSet); + } + + /** + * Check if node is child of the reference node + * + * @return link parent child existence + */ + @Override + protected boolean amChildElementRef() { + return parentAdapter.getCurrentElem().isSetDataSet() && parentAdapter.getCurrentElem().getDataSet().stream().anyMatch(dataSet -> + Objects.equals(currentElem.getName(), dataSet.getName())); + } + + /** + * Returns local XPath + * @return XPath for current element (not including parent XPath) + */ + @Override + protected String elementXPath() { + return String.format("DataSet[%s]", xpathAttributeFilter("name", currentElem.getName())); + } + + /** + * Find a FCDA matching all given criteria. + * @param ldInst FCDA ldInst attribute + * @param prefix FCDA prefix attribute + * @param lnClass FCDA lnClass attribute + * @param lnInst FCDA lnInst attribute + * @param doName FCDA doName attribute + * @param daName FCDA daNae attribute + * @param fc FCDA fc attribute + * @return Matching FCDA in this DataSet when found, empty Optional otherwise. + */ + public Optional findFCDA(String ldInst, String prefix, String lnClass, String lnInst, String doName, String daName, TFCEnum fc) { + if (!currentElem.isSetFCDA()) { + return Optional.empty(); + } + return currentElem.getFCDA().stream() + .filter(tfcda -> + Objects.equals(ldInst, tfcda.getLdInst()) + && equalsOrBothBlank(prefix, tfcda.getPrefix()) + && tfcda.getLnClass().contains(lnClass) + && equalsOrBothBlank(lnInst, tfcda.getLnInst()) + && Objects.equals(doName, tfcda.getDoName()) + && Objects.equals(fc, tfcda.getFc()) + && equalsOrBothBlank(daName, tfcda.getDaName())) + .findFirst(); + } + + /** + * Create a new FCDA in this DataSet. + * Does nothing if a FCDA with the given attribute already exists in this DataSet. + * @param ldInst FCDA ldInst attribute + * @param prefix FCDA prefix attribute + * @param lnClass FCDA lnClass attribute + * @param lnInst FCDA lnInst attribute + * @param doName FCDA doName attribute + * @param daName FCDA daNae attribute + * @param fc FCDA fc attribute + * @return created FCDA, or existing FCDA with the given attributes + */ + public TFCDA createFCDAIfNotExists(String ldInst, String prefix, String lnClass, String lnInst, String doName, String daName, TFCEnum fc) { + Objects.requireNonNull(fc); // fc is required by XSD + Optional fcda = findFCDA(ldInst, prefix, lnClass, lnInst, doName, daName, fc); + return fcda + .orElseGet(() -> { + TFCDA newFcda = new TFCDA(); + newFcda.setLdInst(nullIfBlank(ldInst)); + newFcda.setPrefix(nullIfBlank(prefix)); + if (StringUtils.isNotBlank(lnClass)) { + newFcda.getLnClass().add(lnClass); + } + newFcda.setLnInst(nullIfBlank(lnInst)); + newFcda.setDoName(nullIfBlank(doName)); + newFcda.setDaName(nullIfBlank(daName)); + newFcda.setFc(fc); + currentElem.getFCDA().add(newFcda); + currentElem.getFCDA().sort(fcdaComparator); + return newFcda; + }); + } + +} 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 3ac4a9940..a286361c1 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 @@ -8,7 +8,6 @@ import org.apache.commons.lang3.StringUtils; import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.dto.ControlBlock; -import org.lfenergy.compas.sct.commons.dto.DataSetInfo; import org.lfenergy.compas.sct.commons.dto.ExtRefBindingInfo; import org.lfenergy.compas.sct.commons.dto.ExtRefSignalInfo; import org.lfenergy.compas.sct.commons.exception.ScdException; @@ -44,7 +43,6 @@ *
  • {@link IEDAdapter#getServices Returns the value of the Service object}
  • *
  • {@link IEDAdapter#getPrivateHeader Returns the value of the TPrivate containment reference list}
  • *
  • {@link IEDAdapter#getExtRefBinders Returns the value of the ExtRefBindingInfo containment reference list By ExtRefSignalInfo}
  • - *
  • {@link IEDAdapter#createDataSet Add DataSetInfo describing the children TDataSet that can be created under TAnyLN}
  • *
  • {@link IEDAdapter#createControlBlock Add ControlBlock describing the children TControlBlock that can be created under TAnyLN}
  • *
  • {@link IEDAdapter#addPrivate Add TPrivate under this object}
  • *
  • {@link IEDAdapter#updateLDeviceNodesType Update Type describing the value of the children TAnyLN}
  • @@ -304,33 +302,6 @@ public boolean isSettingConfig(String ldInst) { return srv != null && srv.getSettingGroups() != null && srv.getSettingGroups().getConfSG() != null; } - /** - * Adds Data Set in specified LNode in current IED - * - * @param dataSetInfo Data Set data to add (and LNode path) - * @param serviceSettingsType Type of DataSet to create - * @throws ScdException throws when IED is not able to add DataSet - */ - public void createDataSet(DataSetInfo dataSetInfo, ServiceSettingsType serviceSettingsType) throws ScdException { - LDeviceAdapter lDeviceAdapter = findLDeviceAdapterByLdInst(dataSetInfo.getHolderLDInst()) - .orElseThrow( - () -> new ScdException( - String.format( - "Unknow LDevice(%s) in IED(%s)", dataSetInfo.getHolderLDInst(), currentElem.getName()) - ) - ); - if (!lDeviceAdapter.hasDataSetCreationCapability(serviceSettingsType)) { - throw new ScdException("The AccessPoint does not have capability to create DataSet of type " + serviceSettingsType.toString()); - } - AbstractLNAdapter lNodeAdapter = AbstractLNAdapter.builder() - .withLDeviceAdapter(lDeviceAdapter) - .withLnClass(dataSetInfo.getHolderLnClass()) - .withLnInst(dataSetInfo.getHolderLnInst()) - .withLnPrefix(dataSetInfo.getHolderLnPrefix()) - .build(); - lNodeAdapter.addDataSet(dataSetInfo); - } - /** * Creates Control Block in specified LNode in current IED * @@ -366,7 +337,7 @@ public ControlBlock createControlBlock(ControlBlock updateAllExtRefIedNames(Map icdSy return List.of(getLDeviceAdapter().buildFatalReportItem("The LDevice status is undefined")); } String lDeviceStatus = optionalLDeviceStatus.get(); - switch (lDeviceStatus) { - case ON: + return switch (lDeviceStatus) { + case ON -> { List compasFlows = PrivateService.extractCompasPrivates(currentElem, TCompasFlow.class); - return getExtRefs().stream() + yield getExtRefs().stream() .filter(tExtRef -> StringUtils.isNotBlank(tExtRef.getIedName()) && StringUtils.isNotBlank(tExtRef.getDesc())) .map(extRef -> updateExtRefIedName(extRef, compasFlows, icdSystemVersionToIed.get(extRef.getIedName()))) .flatMap(Optional::stream) .toList(); - case OFF: + } + case OFF -> { getExtRefs().forEach(this::clearBinding); - return Collections.emptyList(); - default: - return List.of(getLDeviceAdapter() - .buildFatalReportItem("The LDevice status is neither \"on\" nor \"off\"")); - } + yield Collections.emptyList(); + } + default -> List.of(getLDeviceAdapter().buildFatalReportItem("The LDevice status is neither \"on\" nor \"off\"")); + }; } /** @@ -289,10 +290,8 @@ private Optional updateSourceDataSetsAndControlBlocks(TExtRef ext if (sourceIedBayUuid.isEmpty()) { return fatalReportItem(extRef, "Source IED is missing Private/compas:Bay@UUID attribute"); } - //TODO: use isBayInternal in issue #84 RSR-433 and remove print to console + boolean isBayInternal = targetBayUuid.equals(sourceIedBayUuid.get()); - System.out.print("\nExtRef.desc=" + extRef.getDesc()); - System.out.print(",isBayInternal=" + isBayInternal); Optional optionalSourceLDevice = sourceIed.findLDeviceAdapterByLdInst(extRef.getLdInst()); if (optionalSourceLDevice.isEmpty()) { @@ -305,30 +304,56 @@ private Optional updateSourceDataSetsAndControlBlocks(TExtRef ext } Optional sclReportItem = removeFilteredSourceDas(extRef, sourceDas); - //TODO: #83 follow-up : grouping ExtRef by serviceType, iedName, ldInst, isBayInternal, and DA@fc - // will be done by calculating DataSet Name in #84 RSR-433 - // TODO: map to FCDA in issue #84 RSR-433 and remove print to console - String daToPrint = sourceDas.stream() - .map(da -> da.getFc() + "#" + da.getDataAttributes()) - .collect(Collectors.joining(",")); - System.out.print("," + (daToPrint.isEmpty() ? "--NO_VALID_SOURCE_DA--" : daToPrint)); - return sclReportItem; + if (sclReportItem.isPresent()) { + return sclReportItem; + } + + try { + sourceDas.forEach(sourceDa -> { + String dataSetName = generateDataSetName(extRef, sourceDa, isBayInternal); + ServiceSettingsType serviceSettingsType = ServiceSettingsType.fromTServiceType(extRef.getServiceType()); + DataSetAdapter dataSet = sourceLDevice.getLN0Adapter().createDataSetIfNotExists(dataSetName, serviceSettingsType); + createFCDAInDataSet(extRef, sourceDa, dataSet); + }); + } catch (ScdException e) { + // ScdException can be thrown if AccessPoint does not have DataSet creation capability + log.error(e.getMessage(), e); + return fatalReportItem(extRef, "Could not create DataSet for this ExtRef : " + e.getMessage()); + } + return Optional.empty(); + } + + private static String generateDataSetName(TExtRef extRef, ResumedDataTemplate sourceDa, boolean isBayInternal) { + return "DS_" + extRef.getLdInst() + "_" + + switch (extRef.getServiceType()) { + case GOOSE -> "G" + ((sourceDa.getFc() == TFCEnum.ST) ? "S" : "M"); + case SMV -> "SV"; + case REPORT -> (sourceDa.getFc() == TFCEnum.ST) ? "DQC" : "CYC"; + case POLL -> throw new IllegalArgumentException("only GOOSE, SMV and REPORT ServiceType are allowed"); + } + + (isBayInternal ? "I" : "E"); + } + + private static void createFCDAInDataSet(TExtRef extRef, ResumedDataTemplate sourceDa, DataSetAdapter dataSet) { + dataSet.createFCDAIfNotExists(extRef.getLdInst(), extRef.getPrefix(), extRef.getLnClass().stream().findFirst().orElse(null), extRef.getLnInst(), + sourceDa.getDoRef(), + (extRef.getServiceType() == TServiceType.REPORT ? null : sourceDa.getDaRef()), + sourceDa.getFc()); } private Optional removeFilteredSourceDas(TExtRef extRef, final Set sourceDas) { - //TODO: Don't forget to test this in issue #84 RSR-433 sourceDas.removeIf(da -> da.getFc() != TFCEnum.MX && da.getFc() != TFCEnum.ST); return switch (extRef.getServiceType()) { case GOOSE, SMV -> { sourceDas.removeIf(Predicate.not(FcdaCandidates.SINGLETON::contains)); yield Optional.empty(); } - case REPORT -> filterReportSourceDa(extRef, sourceDas); + case REPORT -> removeFilterSourceDaForReport(extRef, sourceDas); default -> fatalReportItem(extRef, String.format(MESSAGE_INVALID_SERVICE_TYPE, extRef.getServiceType())); }; } - private Optional filterReportSourceDa(TExtRef extRef, Set sourceDas) { + private Optional removeFilterSourceDaForReport(TExtRef extRef, Set sourceDas) { String daName = Utils.extractField(extRef.getDesc(), EXTREF_DESC_DELIMITER, EXTREF_DESC_DA_NAME_POSITION); if (StringUtils.isBlank(daName)) { return fatalReportItem(extRef, MESSAGE_REPORT_EXTREF_DESC_MALFORMED); 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 5173e0656..efc341581 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 @@ -43,10 +43,8 @@ *
  • {@link LN0Adapter#getDAI Returns the value of the ResumedDataTemplate containment reference By filter}
  • *
  • {@link LN0Adapter#getDAIValues(ResumedDataTemplate) Returns DAI (sGroup, value) containment reference list By ResumedDataTemplate filter}
  • * - *
  • {@link LN0Adapter#getDataSet(ExtRefInfo) Returns the value of the TDataSet containment reference list By ExtRefInfo }
  • - *
  • {@link LN0Adapter#getDataSetByRef(String) Returns the value of the TDataSet object reference By the value of the name attribute }
  • + *
  • {@link LN0Adapter#getDataSetByName(String) Returns the value of the TDataSet object reference By the value of the name attribute }
  • * - *
  • {@link LN0Adapter#getControlSetByExtRefInfo(ExtRefInfo) Returns the value of the ControlBlock containment reference list By ExtRefInfo }
  • *
  • {@link LN0Adapter#getControlBlocks(List, TServiceType) Returns the value of the ControlBlock containment reference list that match datSet value of given TDataSet }
  • *
  • {@link LN0Adapter#addPrivate Add TPrivate under this object}
  • *
  • {@link LN0Adapter#removeAllControlBlocksAndDatasets() Remove all ControlBlock}
  • diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapter.java index 9268e214c..d960ea939 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapter.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/ied/LNAdapter.java @@ -6,7 +6,6 @@ import org.lfenergy.compas.scl2007b4.model.TLN; import org.lfenergy.compas.scl2007b4.model.TServiceType; -import org.lfenergy.compas.sct.commons.dto.ExtRefInfo; import org.lfenergy.compas.sct.commons.dto.ExtRefSignalInfo; import org.lfenergy.compas.sct.commons.dto.ResumedDataTemplate; import org.lfenergy.compas.sct.commons.scl.ObjectReference; @@ -40,10 +39,8 @@ *
  • {@link LNAdapter#getDAI Returns the value of the ResumedDataTemplate containment reference By filter}
  • *
  • {@link LNAdapter#getDAIValues(ResumedDataTemplate) Returns DAI (sGroup, value) containment reference list By ResumedDataTemplate filter}
  • - *
  • {@link LNAdapter#getDataSet(ExtRefInfo) Returns the value of the TDataSet containment reference list By ExtRefInfo }
  • - *
  • {@link LNAdapter#getDataSetByRef(String) Returns the value of the TDataSet object reference By the value of the name attribute }
  • + *
  • {@link LNAdapter#getDataSetByName(String) Returns the value of the TDataSet object reference By the value of the name attribute }
  • * - *
  • {@link LNAdapter#getControlSetByExtRefInfo(ExtRefInfo) Returns the value of the ControlBlock containment reference list By ExtRefInfo }
  • *
  • {@link LNAdapter#getControlBlocks(List, TServiceType) Returns the value of the ControlBlock containment reference list that match datSet value of given TDataSet }
  • *
  • {@link LNAdapter#addPrivate Add TPrivate under this object}
  • *
  • {@link LNAdapter#removeAllControlBlocksAndDatasets() Remove all ControlBlock}
  • diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ServiceSettingsType.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ServiceSettingsType.java index 599161c62..33edf3b26 100644 --- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ServiceSettingsType.java +++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/util/ServiceSettingsType.java @@ -4,9 +4,23 @@ package org.lfenergy.compas.sct.commons.util; +import org.lfenergy.compas.scl2007b4.model.TServiceType; + public enum ServiceSettingsType { - REPORT, - LOG, GSE, - SMV + SMV, + REPORT, + LOG; + + public static ServiceSettingsType fromTServiceType(TServiceType tServiceType){ + if (tServiceType == null) { + return null; + } + return switch (tServiceType) { + case GOOSE -> GSE; + case SMV -> SMV; + case REPORT -> REPORT; + default -> null; + }; + } } 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 95f49821f..1ba1cde79 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 @@ -7,6 +7,7 @@ import org.apache.commons.lang3.StringUtils; import java.util.Collection; +import java.util.Comparator; import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; @@ -16,9 +17,12 @@ public final class Utils { private static final String LEAVING_PREFIX = "<<< Leaving: ::"; private static final String ENTERING_PREFIX = ">>> Entering: ::"; + 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 Controlller, should not be instanced + * Private Constructor, should not be instanced */ private Utils() { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); @@ -128,7 +132,7 @@ public static String xpathAttributeFilter(String name, Collection value) /** * Checks if strings are equals or both blank. - * Blank means : null, empty string or whitespaces only string. + * Blank means : null, empty string or whitespaces (as defined by {@link Character#isWhitespace(char)}) only string. * * @param s1 first string * @param s2 seconde string @@ -140,6 +144,37 @@ public static boolean equalsOrBothBlank(String s1, String s2) { || (StringUtils.isBlank(s1) && StringUtils.isBlank(s2)); } + /** + * Comparator for String + * Difference with {@link String#compare(CharSequence, CharSequence)} is that + * blank strings are considered equals and inferior to any not blank String. + * Blank means : null, empty string or whitespaces (as defined by {@link Character#isWhitespace(char)}) only string. + * Note: this comparator imposes orderings that are inconsistent with equals. + * + * @param s1 first String to compare + * @param s2 second String to compare + * @return when s1 and s2 are not blank, same result as {@link String#compare(CharSequence, CharSequence)}, + * zero when s1 and s2 are both blanks, negative integer when s1 is blank and s2 is not, positive integer when s1 is not blank but s2 is. + * @see java.util.Comparator#compare(Object, Object) + * @see org.apache.commons.lang3.StringUtils#isBlank(CharSequence) + * @see java.util.Comparator#nullsFirst(Comparator) + */ + public static int blanksFirstComparator(String s1, String s2) { + if (StringUtils.isBlank(s1)){ + if (StringUtils.isBlank(s2)){ + return S1_CONSIDERED_EQUALS_TO_S2; + } else { + return S1_LOWER_THAN_S2; + } + } else { + if (StringUtils.isBlank(s2)){ + return S1_GREATER_THAN_S2; + } else { + return s1.compareTo(s2); + } + } + } + /** * Remove all digits at the end of the string, if any * @param s input string @@ -175,4 +210,8 @@ public static String extractField(String s, String regexDelimiter, int index) { int column = index < 0 ? split.length + index : index; return 0 <= column && column < split.length ? split[column] : null; } + + public static String nullIfBlank(String s) { + return StringUtils.isBlank(s) ? null : s; + } } 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 0b6b44ed5..eabf3278b 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 @@ -4,19 +4,19 @@ package org.lfenergy.compas.sct.commons.scl; +import org.assertj.core.groups.Tuple; 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.MethodSource; -import org.lfenergy.compas.scl2007b4.model.SCL; -import org.lfenergy.compas.scl2007b4.model.TCompasFlow; -import org.lfenergy.compas.scl2007b4.model.TExtRef; -import org.lfenergy.compas.scl2007b4.model.TInputs; +import org.lfenergy.compas.scl2007b4.model.*; 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.DataSetAdapter; import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter; import org.lfenergy.compas.sct.commons.scl.ied.LDeviceAdapter; +import org.lfenergy.compas.sct.commons.testhelpers.FCDARecord; import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; import java.util.Optional; @@ -24,8 +24,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.findExtRef; -import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.findLDevice; +import static org.lfenergy.compas.scl2007b4.model.TFCEnum.ST; +import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.*; class ExtRefServiceTest { @@ -204,8 +204,27 @@ void createDataSetAndControlBlocks_should_succeed() throws Exception { // When SclReport sclReport = ExtRefService.createDataSetAndControlBlocks(scd); // Then - //TODO: This is only the first part. Test will be updated in issue #84 assertThat(sclReport.getSclReportItems()).isEmpty(); + assertThat(streamAllDataSets(sclReport.getSclRootAdapter())).hasSize(6); + + // Check dataSet names + findDataSet(sclReport.getSclRootAdapter(), "IED_NAME2", "LD_INST21", "DS_LD_INST21_CYCI"); + findDataSet(sclReport.getSclRootAdapter(), "IED_NAME2", "LD_INST21", "DS_LD_INST21_DQCI"); + findDataSet(sclReport.getSclRootAdapter(), "IED_NAME2", "LD_INST21", "DS_LD_INST21_GMI"); + findDataSet(sclReport.getSclRootAdapter(), "IED_NAME2", "LD_INST21", "DS_LD_INST21_SVI"); + findDataSet(sclReport.getSclRootAdapter(), "IED_NAME3", "LD_INST31", "DS_LD_INST31_GSE"); + findDataSet(sclReport.getSclRootAdapter(), "IED_NAME2", "LD_INST21", "DS_LD_INST21_GSI"); + + // Check one DataSet content + 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) + ); } @Test @@ -215,8 +234,19 @@ void createDataSetAndControlBlocks_when_targetIedName_is_provided_should_succeed // When SclReport sclReport = ExtRefService.createDataSetAndControlBlocks(scd, "IED_NAME1"); // Then - //TODO: This is only the first part. Test will be updated in issue #84 assertThat(sclReport.getSclReportItems()).isEmpty(); + assertThat(streamAllDataSets(sclReport.getSclRootAdapter())).hasSize(6); + } + + @Test + void createDataSetAndControlBlocks_when_targetIedName_is_provided_and_no_ext_ref_should_do_nothing() throws Exception { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); + // When + SclReport sclReport = ExtRefService.createDataSetAndControlBlocks(scd, "IED_NAME2"); + // Then + assertThat(sclReport.getSclReportItems()).isEmpty(); + assertThat(streamAllDataSets(sclReport.getSclRootAdapter())).isEmpty(); } @Test @@ -294,4 +324,24 @@ private LDeviceAdapter getLDeviceByLdName(String ldName, SclRootAdapter sclRootA return optionalLDeviceAdapter.get(); } + @Test + void updateAllSourceDataSetsAndControlBlocks_should_sort_FCDA_inside_DataSet_and_avoid_duplicates() throws Exception { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success_test_fcda_sort.xml"); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + // When + SclReport sclReport = ExtRefService.createDataSetAndControlBlocks(scd); + // Then + 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") + ); + } + } diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/DataSetAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/DataSetAdapterTest.java new file mode 100644 index 000000000..30c159b6e --- /dev/null +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/DataSetAdapterTest.java @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons.scl.ied; + +import org.junit.jupiter.api.Test; +import org.lfenergy.compas.scl2007b4.model.LN0; +import org.lfenergy.compas.scl2007b4.model.TDataSet; +import org.lfenergy.compas.scl2007b4.model.TFCDA; +import org.lfenergy.compas.scl2007b4.model.TFCEnum; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +class DataSetAdapterTest { + + @Test + void amChildElementRef_should_succeed() { + //Given + LN0 ln0 = new LN0(); + TDataSet dataSet = new TDataSet(); + ln0.getDataSet().add(dataSet); + LN0Adapter ln0Adapter = new LN0Adapter(null, ln0); + DataSetAdapter dataSetAdapter = new DataSetAdapter(ln0Adapter, dataSet); + //When + boolean result = dataSetAdapter.amChildElementRef(); + //Then + assertThat(result).isTrue(); + } + + @Test + void elementXPath_should_return_relative_xpath() { + //Given + TDataSet dataSet = new TDataSet(); + dataSet.setName("dataSetName"); + DataSetAdapter dataSetAdapter = new DataSetAdapter(null, dataSet); + //When + String elementXPath = dataSetAdapter.elementXPath(); + //Then, + assertThat(elementXPath).isEqualTo("DataSet[@name=\"dataSetName\"]"); + } + + @Test + void findFCDA_should_return_found_FCDA() { + //Given + TFCDA fcda = createFCDA(); + TDataSet dataSet = new TDataSet(); + dataSet.getFCDA().add(fcda); + DataSetAdapter dataSetAdapter = new DataSetAdapter(null, dataSet); + //When + Optional result = dataSetAdapter.findFCDA("LDINST", null, "LLN0", null, "DoName", "daName", TFCEnum.ST); + //Then + assertThat(result).isPresent(); + } + + @Test + void findFCDA_when_no_FCDA_found_should_return_empty() { + //Given + TDataSet dataSet = new TDataSet(); + DataSetAdapter dataSetAdapter = new DataSetAdapter(null, dataSet); + //When + Optional result = dataSetAdapter.findFCDA("any", "any", "any", "any", "any", "any", TFCEnum.ST); + //Then + assertThat(result).isEmpty(); + } + + @Test + void createFCDAIfNotExists_should_create_new_FCDA() { + //Given + TDataSet dataSet = new TDataSet(); + DataSetAdapter dataSetAdapter = new DataSetAdapter(null, dataSet); + //When + TFCDA result = dataSetAdapter.createFCDAIfNotExists("LDINST", null, "LLN0", null, "DoName", "daName", TFCEnum.ST); + //Then + assertThat(dataSet.getFCDA()).hasSize(1) + .first() + .isSameAs(result); + assertThat(dataSet.getFCDA().get(0)) + .extracting(TFCDA::getLdInst, TFCDA::isSetPrefix, TFCDA::getLnClass, TFCDA::isSetLnInst, TFCDA::getDoName, TFCDA::getDaName, TFCDA::getFc) + .containsExactly("LDINST", false, List.of("LLN0"), false, "DoName", "daName", TFCEnum.ST); + } + + @Test + void createFCDAIfNotExists_when_FCDA_already_exists_should_not_create_FCDA() { + //Given + TDataSet dataSet = new TDataSet(); + TFCDA existingFcda = createFCDA(); + dataSet.getFCDA().add(existingFcda); + DataSetAdapter dataSetAdapter = new DataSetAdapter(null, dataSet); + //When + TFCDA result = dataSetAdapter.createFCDAIfNotExists("LDINST", null, "LLN0", null, "DoName", "daName", TFCEnum.ST); + //Then + assertThat(dataSet.getFCDA()).hasSize(1) + .first() + .isSameAs(result) + .isSameAs(existingFcda); + } + + private static TFCDA createFCDA() { + TFCDA existingFcda = new TFCDA(); + existingFcda.setLdInst("LDINST"); + existingFcda.setPrefix(""); + existingFcda.getLnClass().add("LLN0"); + existingFcda.setLnInst(""); + existingFcda.setDoName("DoName"); + existingFcda.setDaName("daName"); + existingFcda.setFc(TFCEnum.ST); + return existingFcda; + } +} 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 cfda76f69..038251791 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 @@ -7,7 +7,6 @@ import org.junit.jupiter.api.Test; import org.lfenergy.compas.scl2007b4.model.*; import org.lfenergy.compas.sct.commons.dto.DTO; -import org.lfenergy.compas.sct.commons.dto.DataSetInfo; import org.lfenergy.compas.sct.commons.dto.ExtRefSignalInfo; import org.lfenergy.compas.sct.commons.dto.ReportControlBlock; import org.lfenergy.compas.sct.commons.exception.ScdException; @@ -173,67 +172,6 @@ void testMatches() throws Exception { assertTrue(iAdapter.matches(objectReference)); } - @Test - void createDataSet_should_create_when_DataSetInfo_is_complete() throws Exception { - // Given - SCL scd = SclTestMarshaller.getSCLFromFile("/ied-test-schema-conf/ied_unit_test.xml"); - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName("IED_NAME"); - - TDataSet tDataSet = new TDataSet(); - tDataSet.setName("dataset"); - TFCDA tfcda = new TFCDA(); - tfcda.setFc(TFCEnum.ST); - tfcda.getLnClass().add("LLN0"); - tDataSet.getFCDA().add(tfcda); - DataSetInfo dataSetInfo = DataSetInfo.from(tDataSet); - dataSetInfo.setHolderIEDName("IED_NAME"); - dataSetInfo.setHolderLDInst("LD_INS2"); - dataSetInfo.setHolderLnClass(TLLN0Enum.LLN_0.value()); - assertIsMarshallable(scd); - - // When - iedAdapter.createDataSet(dataSetInfo, ServiceSettingsType.GSE); - - // Then - assertThat(iedAdapter.getLDeviceAdapterByLdInst("LD_INS2").getLN0Adapter().getDataSetByRef("dataset")) - .isPresent(); - assertIsMarshallable(scd); - } - - @Test - void createDataSet_should_throw_ScdException_when_DataSetInfo_is_incomplete() throws Exception { - // Given - SCL scd = SclTestMarshaller.getSCLFromFile("/ied-test-schema-conf/ied_unit_test.xml"); - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - IEDAdapter iAdapter = assertDoesNotThrow(() -> sclRootAdapter.getIEDAdapterByName("IED_NAME")); - - TDataSet tDataSet = new TDataSet(); - tDataSet.setName("dataset"); - TFCDA tfcda = new TFCDA(); - tfcda.setFc(TFCEnum.ST); - tDataSet.getFCDA().add(tfcda); - DataSetInfo dataSetInfo = DataSetInfo.from(tDataSet); - dataSetInfo.setHolderIEDName("IED_NAME"); - - // When & Then - assertThatThrownBy(() -> iAdapter.createDataSet(dataSetInfo, ServiceSettingsType.GSE)) - .isInstanceOf(ScdException.class); - } - - @Test - void createDataSet_should_throw_ScdException_when_DataSetInfo_is_empty() throws Exception { - // Given - SCL scd = SclTestMarshaller.getSCLFromFile("/ied-test-schema-conf/ied_unit_test.xml"); - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - IEDAdapter iAdapter = sclRootAdapter.getIEDAdapterByName("IED_NAME"); - DataSetInfo dataSetInfo = new DataSetInfo(); - - // When & Then - assertThatThrownBy(() -> iAdapter.createDataSet(dataSetInfo, ServiceSettingsType.GSE)) - .isInstanceOf(ScdException.class); - } - @Test void createControlBlock() throws Exception { // Given diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapterForDemoTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapterForDemoTest.java deleted file mode 100644 index a4d95cab4..000000000 --- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/ied/InputsAdapterForDemoTest.java +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-FileCopyrightText: 2022 RTE FRANCE -// -// SPDX-License-Identifier: Apache-2.0 - -package org.lfenergy.compas.sct.commons.scl.ied; - -import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.lfenergy.compas.scl2007b4.model.SCL; -import org.lfenergy.compas.sct.commons.dto.SclReportItem; -import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; -import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Named.named; -import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.findInputs; - -class InputsAdapterForDemoTest { - public static final String NO_VALID_SOURCE_DA = "--NO_VALID_SOURCE_DA--"; - private final PrintStream originalOut = System.out; - private ByteArrayOutputStream outContent; - - @BeforeEach - public void setUpStreams() { - outContent = new ByteArrayOutputStream(); - System.setOut(new PrintStream(outContent)); - } - - @AfterEach - public void restoreStreams() { - System.setOut(originalOut); - System.out.println(outContent.toString(StandardCharsets.UTF_8)); - } - - @ParameterizedTest(name = "{0}") - @MethodSource("provideUpdateAllSourceDataSetsAndControlBlocks") - void updateAllSourceDataSetsAndControlBlocks_should_filter_expected_value(String... expected) throws Exception { - // Given - SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - InputsAdapter inputsAdapter = findInputs(sclRootAdapter, "IED_NAME1", "LD_INST11"); - // When - List sclReportItems = inputsAdapter.updateAllSourceDataSetsAndControlBlocks(); - // Then - assertThat(sclReportItems).isEmpty(); - assertThat(Arrays.stream(outContent.toString(StandardCharsets.UTF_8).split("\n")).filter(StringUtils::isNotBlank)) - .contains(expected); - } - - @ParameterizedTest(name = "{0}") - @MethodSource("provideUpdateAllSourceDataSetsAndControlBlocksIgnoredExtRef") - void updateAllSourceDataSetsAndControlBlocks_should_ignore_extref(String... unexpectedExtRefDesc) throws Exception { - // Given - SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); - SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); - InputsAdapter inputsAdapter = findInputs(sclRootAdapter, "IED_NAME1", "LD_INST11"); - // When - List sclReportItems = inputsAdapter.updateAllSourceDataSetsAndControlBlocks(); - // Then - assertThat(sclReportItems).isEmpty(); - assertThat(outContent.toString()) - .doesNotContain(unexpectedExtRefDesc); - } - - public static Stream provideUpdateAllSourceDataSetsAndControlBlocks() { - return Stream.of( - Arguments.of(named("should define if signal is internal or external to a Bay", - new String[]{ - "ExtRef.desc=test bay internal,isBayInternal=true," - + "ST#DoName.daNameST", - "ExtRef.desc=test bay external,isBayInternal=false," - + "ST#DoName.daNameST"})), - Arguments.of(named("should keep only fc = ST or MX source DA", - new String[]{ - "ExtRef.desc=test daName ST,isBayInternal=true," - + "ST#DoName.daNameST", - "ExtRef.desc=test daName MX,isBayInternal=true," - + "MX#DoName.daNameMX", - "ExtRef.desc=test daName BL,isBayInternal=true," - + NO_VALID_SOURCE_DA})), - Arguments.of(named("for GOOSE and SMV, should keep only valid fcda candidates", - new String[]{ - "ExtRef.desc=test ServiceType is GOOSE, no daName and DO contains ST and MX, but only ST is FCDA candidate,isBayInternal=true," - + "ST#OtherDoName.daNameST", - "ExtRef.desc=test ServiceType is SMV, no daName and DO contains ST and MX, but only ST is FCDA candidate,isBayInternal=true," - + "ST#OtherDoName.daNameST"})), - Arguments.of(named("for Report, should ignore source DA", - new String[]{ - "ExtRef.desc=test ServiceType is Report_daReportST_1,isBayInternal=true," - + "ST#DoName.daReportST"})), - Arguments.of(named("should ignore instance number when checking FCDA Candidate file", - new String[]{ - "ExtRef.desc=test no daName and doName with instance number,isBayInternal=true," - + "ST#DoWithInst1.daNameST", - "ExtRef.desc=test no daName and doName with instance number and SDO,isBayInternal=true," - + "ST#DoWithInst2.subDo.daNameST"})) - ); - } - - public static Stream provideUpdateAllSourceDataSetsAndControlBlocksIgnoredExtRef() { - return Stream.of( - Arguments.of(named("should ignore binding internal to IED", - new String[]{"test ignore internal binding"})), - Arguments.of(named("should ignore extref with missing binding attributes", - new String[]{"test ignore missing bindings attributes"})), - Arguments.of(named("should ignore extref when compas:Flow.FlowStatus is neither ACTIVE nor UNTESTED", - new String[]{"test ignore when compas:Flow.FlowStatus is neither ACTIVE nor UNTESTED"})) - ); - } -} 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 141617be8..c7682544d 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 @@ -5,18 +5,25 @@ package org.lfenergy.compas.sct.commons.scl.ied; import org.junit.jupiter.api.Test; -import org.lfenergy.compas.scl2007b4.model.LN0; -import org.lfenergy.compas.scl2007b4.model.SCL; -import org.lfenergy.compas.scl2007b4.model.TInputs; +import org.junit.jupiter.params.ParameterizedTest; +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.SclReportItem; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; +import org.lfenergy.compas.sct.commons.testhelpers.FCDARecord; +import org.lfenergy.compas.sct.commons.testhelpers.MarshallerWrapper; import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; +import org.opentest4j.AssertionFailedError; import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.findInputs; +import static org.junit.jupiter.api.Named.named; +import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.*; class InputsAdapterTest { @@ -105,7 +112,148 @@ void updateAllSourceDataSetsAndControlBlocks_should_succeed() throws Exception { List sclReportItems = inputsAdapter.updateAllSourceDataSetsAndControlBlocks(); // Then assertThat(sclReportItems).isEmpty(); + System.out.println(MarshallerWrapper.marshall(scd)); } + @ParameterizedTest + @MethodSource("provideCreateFCDA") + void updateAllSourceDataSetsAndControlBlocks_should_create_dataset_and_fcda_for_valid_extRef(String extRefDesc, String dataSetPath, + List expectedFcda) throws Exception { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + InputsAdapter inputsAdapter = keepOnlyThisExtRef(sclRootAdapter, extRefDesc); + String[] splitPath = dataSetPath.split("/"); + final int IED_NAME_PART = 0; + final int LDEVICE_INST_PART = 1; + final int DATASET_NAME_PART = 2; + String expectedSourceIedName = splitPath[IED_NAME_PART]; + String expectedSourceLDeviceInst = splitPath[LDEVICE_INST_PART]; + String expectedDataSetName = splitPath[DATASET_NAME_PART]; + // When + List sclReportItems = inputsAdapter.updateAllSourceDataSetsAndControlBlocks(); + // Then + assertThat(sclReportItems).isEmpty(); + DataSetAdapter dataSet = findDataSet(sclRootAdapter, expectedSourceIedName, expectedSourceLDeviceInst, expectedDataSetName); + assertThat(dataSet.getCurrentElem().getFCDA()) + .extracting(TFCDA::getLdInst) + .containsOnly(expectedSourceLDeviceInst); + + assertThat(dataSet.getCurrentElem().getFCDA()) + .map(FCDARecord::toFCDARecord) + .containsExactly(expectedFcda.toArray(new FCDARecord[]{})); + } + + public static Stream provideCreateFCDA() { + return Stream.of( + Arguments.of(named("should include signal internal to a Bay", + "test bay internal"), + "IED_NAME2/LD_INST21/DS_LD_INST21_GSI", + List.of(new FCDARecord("LD_INST21", "ANCR", "1", "", "DoName", "daNameST", TFCEnum.ST))), + Arguments.of(named("should include signal external to a Bay", + "test bay external"), + "IED_NAME3/LD_INST31/DS_LD_INST31_GSE", + List.of(new FCDARecord("LD_INST31", "ANCR", "1", "", "DoName", "daNameST", TFCEnum.ST))), + Arguments.of(named("keep source DA with fc = ST", + "test daName ST"), + "IED_NAME2/LD_INST21/DS_LD_INST21_GSI", + List.of(new FCDARecord("LD_INST21", "ANCR", "1", "", "DoName", "daNameST", TFCEnum.ST))), + Arguments.of(named("keep source DA with fc = MX", + "test daName MX"), + "IED_NAME2/LD_INST21/DS_LD_INST21_GMI", + List.of(new FCDARecord("LD_INST21", "ANCR", "1", "", "DoName", "daNameMX", TFCEnum.MX))), + Arguments.of(named("for GOOSE, should keep only valid fcda candidates", + "test ServiceType is GOOSE, no daName and DO contains ST and MX, but only ST is FCDA candidate"), + "IED_NAME2/LD_INST21/DS_LD_INST21_GSI", + List.of(new FCDARecord("LD_INST21", "ANCR", "1", "", "OtherDoName", "daNameST", TFCEnum.ST))), + Arguments.of(named("for SMV, should keep only valid fcda candidates", + "test ServiceType is SMV, no daName and DO contains ST and MX, but only ST is FCDA candidate"), + "IED_NAME2/LD_INST21/DS_LD_INST21_SVI", + List.of(new FCDARecord("LD_INST21", "ANCR", "1", "", "OtherDoName", "daNameST", TFCEnum.ST))), + Arguments.of(named("for Report, should get source daName from ExtRef.desc to deduce FC ST", + "test ServiceType is Report_daReportST_1"), + "IED_NAME2/LD_INST21/DS_LD_INST21_DQCI", + List.of(new FCDARecord("LD_INST21", "ANCR", "1", "", "DoName", null, TFCEnum.ST))), + Arguments.of(named("for Report, should get source daName from ExtRef.desc to deduce FC MX", + "test ServiceType is Report_daReportMX_1"), + "IED_NAME2/LD_INST21/DS_LD_INST21_CYCI", + List.of(new FCDARecord("LD_INST21", "ANCR", "1", "", "DoName", null, TFCEnum.MX))), + Arguments.of(named("should ignore instance number when checking FCDA Candidate file", + "test no daName and doName with instance number"), + "IED_NAME2/LD_INST21/DS_LD_INST21_GSI", + List.of(new FCDARecord("LD_INST21", "ANCR", "1", "", "DoWithInst1", "daNameST", TFCEnum.ST))), + Arguments.of(named("should ignore instance number when checking FCDA Candidate file (DO with SDO)", + "test no daName and doName with instance number and SDO"), + "IED_NAME2/LD_INST21/DS_LD_INST21_GSI", + List.of(new FCDARecord("LD_INST21", "ANCR", "1", "", "DoWithInst2.subDo", "daNameST", TFCEnum.ST))), + Arguments.of(named("hould include UNTESTED FlowStatus", + "test include compas:Flow.FlowStatus UNTESTED"), + "IED_NAME2/LD_INST21/DS_LD_INST21_GSI", + List.of(new FCDARecord("LD_INST21", "ANCR", "1", "", "DoName", "daNameST", TFCEnum.ST))) + ); + } + + @ParameterizedTest + @MethodSource("provideDoNotCreateFCDA") + void updateAllSourceDataSetsAndControlBlocks_should_not_create_FCDA_when_no_valid_source_Da_found(String extRefDesc) throws Exception { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + InputsAdapter inputsAdapter = keepOnlyThisExtRef(sclRootAdapter, extRefDesc); + // When + List sclReportItems = inputsAdapter.updateAllSourceDataSetsAndControlBlocks(); + // Then + assertThat(sclReportItems).isEmpty(); + assertThat(sclRootAdapter.streamIEDAdapters() + .flatMap(IEDAdapter::streamLDeviceAdapters) + .filter(LDeviceAdapter::hasLN0) + .map(LDeviceAdapter::getLN0Adapter)) + .allMatch(ln0Adapter -> !ln0Adapter.getCurrentElem().isSetDataSet()); + } + + public static Stream provideDoNotCreateFCDA() { + return Stream.of( + Arguments.of(named("should not create FCDA for source Da different from MX and ST", + "test daName BL")), + Arguments.of(named("should not create FCDA for extref with a binding internal to the IED", + "test ignore internal binding")), + Arguments.of(named("should not create FCDA for extref with missing binding attributes", + "test ignore missing bindings attributes")), + Arguments.of(named("should not create FCDA for ExtRef with compas:Flow.FlowStatus INACTIVE", + "test ignore when compas:Flow.FlowStatus is neither ACTIVE nor UNTESTED")) + ); + } + + @Test + void updateAllSourceDataSetsAndControlBlocks_when_AceessPoint_does_not_have_dataset_creation_capability_should_report_error() throws Exception { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success.xml"); + SclRootAdapter sclRootAdapter = new SclRootAdapter(scd); + InputsAdapter inputsAdapter = keepOnlyThisExtRef(sclRootAdapter, "test bay internal"); + TExtRef extRef = inputsAdapter.getCurrentElem().getExtRef().get(0); + LDeviceAdapter sourceLDevice = findLDevice(sclRootAdapter, extRef.getIedName(), extRef.getLdInst()); + sourceLDevice.getAccessPoint().setServices(new TServices()); + // When + List sclReportItems = inputsAdapter.updateAllSourceDataSetsAndControlBlocks(); + // Then + assertThat(sclReportItems).hasSize(1) + .first().extracting(SclReportItem::getMessage).asString() + .startsWith("Could not create DataSet for this ExtRef : IED/AccessPoint does not have capability to create DataSet of type GSE"); + } + + private static InputsAdapter keepOnlyThisExtRef(SclRootAdapter sclRootAdapter, String extRefDesc) { + InputsAdapter foundInputsAdapter = sclRootAdapter.streamIEDAdapters() + .flatMap(IEDAdapter::streamLDeviceAdapters) + .filter(LDeviceAdapter::hasLN0) + .map(LDeviceAdapter::getLN0Adapter) + .filter(AbstractLNAdapter::hasInputs) + .map(LN0Adapter::getInputsAdapter) + .filter(inputsAdapter -> + inputsAdapter.getCurrentElem().getExtRef().stream().map(TExtRef::getDesc).anyMatch(extRefDesc::equals)) + .findFirst() + .orElseThrow(() -> new AssertionFailedError("ExtRef not found: " + extRefDesc)); + foundInputsAdapter.getCurrentElem().getExtRef().removeIf(Predicate.not(extref -> extRefDesc.equals(extref.getDesc()))); + return foundInputsAdapter; + } } 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 97ae4b083..312c9566b 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 @@ -10,6 +10,7 @@ import org.lfenergy.compas.sct.commons.exception.ScdException; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; import org.lfenergy.compas.sct.commons.testhelpers.SclTestMarshaller; +import org.lfenergy.compas.sct.commons.util.ServiceSettingsType; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; @@ -20,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.*; +import static org.lfenergy.compas.sct.commons.testhelpers.SclHelper.findLn0; class LN0AdapterTest { @@ -521,4 +523,47 @@ void getLDeviceStatus_should_succeed() throws Exception { .isPresent() .hasValue("test"); } + + @Test + void createDataSetIfNotExists_should_create_dataSet() throws Exception { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scl-ln-adapter/scd_with_ln.xml"); + LN0Adapter ln0 = findLn0(new SclRootAdapter(scd), "IED_NAME1", "LD_INST11"); + assertThat(ln0.getCurrentElem().getDataSet()).isEmpty(); + // When + DataSetAdapter newDataSet = ln0.createDataSetIfNotExists("newDataSet", ServiceSettingsType.GSE); + // Then + assertThat(newDataSet.getCurrentElem().getName()).isEqualTo("newDataSet"); + assertThat(newDataSet.getParentAdapter().getParentAdapter().getInst()).isEqualTo("LD_INST11"); + assertThat(ln0.getCurrentElem().getDataSet()) + .map(TDataSet::getName) + .containsExactly("newDataSet"); + } + + @Test + void createDataSetIfNotExists_when_dataset_exists_should_not_create_dataset() throws Exception { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scl-ln-adapter/scd_with_ln.xml"); + LN0Adapter ln0 = findLn0(new SclRootAdapter(scd), "IED_NAME1", "LD_INST12"); + assertThat(ln0.getCurrentElem().getDataSet()).hasSize(1); + // When + DataSetAdapter newDataSet = ln0.createDataSetIfNotExists("existingDataSet", ServiceSettingsType.GSE); + // Then + assertThat(ln0.getCurrentElem().getDataSet()).hasSize(1) + .map(TDataSet::getName) + .containsExactly("existingDataSet"); + assertThat(newDataSet.getCurrentElem().getName()).isEqualTo("existingDataSet"); + assertThat(newDataSet.getParentAdapter().getParentAdapter().getInst()).isEqualTo("LD_INST12"); + } + + @Test + void createDataSetIfNotExists_when_ied_does_not_have_creation_capabilities_should_throw_exception() throws Exception { + // Given + SCL scd = SclTestMarshaller.getSCLFromFile("/scl-ln-adapter/scd_with_ln.xml"); + LN0Adapter ln0 = findLn0(new SclRootAdapter(scd), "IED_NAME2", "LD_INST21"); + // When & Then + assertThatThrownBy(() -> ln0.createDataSetIfNotExists("existingDataSet", ServiceSettingsType.GSE)) + .isInstanceOf(ScdException.class); + } + } 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 dc5f526ec..62e08e8d2 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 @@ -17,6 +17,7 @@ import org.mockito.Mockito; import java.util.List; +import java.util.Optional; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -64,17 +65,31 @@ void testGetExtRefs() { } @Test - void testFindDataSetByRef() { + void findDataSetByName_should_return_dataset() { + // Given TLN tln = new TLN(); LNAdapter lnAdapter = initLNAdapter(tln); - - assertTrue(lnAdapter.findDataSetByRef(DTO.CB_DATASET_REF).isEmpty()); TDataSet tDataSet = new TDataSet(); tDataSet.setName(DTO.CB_DATASET_REF); tln.getDataSet().add(tDataSet); + // Then + Optional foundDataSet = lnAdapter.findDataSetByName(DTO.CB_DATASET_REF); + // When + assertThat(foundDataSet).isPresent(); + } - assertTrue(lnAdapter.findDataSetByRef(DTO.CB_DATASET_REF).isPresent()); - + @Test + void findDataSetByName_when_not_found_should_return_empty() { + // Given + TLN tln = new TLN(); + LNAdapter lnAdapter = initLNAdapter(tln); + TDataSet tDataSet = new TDataSet(); + tDataSet.setName(DTO.CB_DATASET_REF); + tln.getDataSet().add(tDataSet); + // Then + Optional foundDataSet = lnAdapter.findDataSetByName("Non existent dataset"); + // When + assertThat(foundDataSet).isEmpty(); } @Test diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/testhelpers/FCDARecord.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/testhelpers/FCDARecord.java new file mode 100644 index 000000000..0441846bf --- /dev/null +++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/testhelpers/FCDARecord.java @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2022 RTE FRANCE +// +// SPDX-License-Identifier: Apache-2.0 + +package org.lfenergy.compas.sct.commons.testhelpers; + +import org.lfenergy.compas.scl2007b4.model.TFCDA; +import org.lfenergy.compas.scl2007b4.model.TFCEnum; + +/** + * Record to easily compare FCDA with AssertJ (xjc does not implement an equals method for TFCDA class) + */ +public record FCDARecord(String ldInst, String lnClass, String lnInst, String prefix, String doName, String daName, TFCEnum fc) { + static public FCDARecord toFCDARecord(TFCDA tfcda) { + return new FCDARecord( + tfcda.getLdInst(), + tfcda.isSetLnClass() ? tfcda.getLnClass().get(0) : null, + tfcda.getLnInst(), + tfcda.getPrefix(), + tfcda.getDoName(), + tfcda.getDaName(), + tfcda.getFc()); + } +} 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 7fb0dcca1..f7703b620 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 @@ -5,14 +5,16 @@ package org.lfenergy.compas.sct.commons.testhelpers; import lombok.experimental.UtilityClass; +import org.lfenergy.compas.scl2007b4.model.TDataSet; import org.lfenergy.compas.scl2007b4.model.TExtRef; import org.lfenergy.compas.sct.commons.dto.SclReport; import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; -import org.lfenergy.compas.sct.commons.scl.ied.InputsAdapter; -import org.lfenergy.compas.sct.commons.scl.ied.LDeviceAdapter; -import org.lfenergy.compas.sct.commons.scl.ied.LN0Adapter; +import org.lfenergy.compas.sct.commons.scl.ied.*; import org.opentest4j.AssertionFailedError; +import java.util.List; +import java.util.stream.Stream; + /** * Provides static methods to quickly retrieve SCL elements, to be used in writing tests. * Methods throw AssertionFailedError when element not found (equivalent of calling JUnit Assert.fail()) @@ -20,7 +22,7 @@ */ @UtilityClass public class SclHelper { - private static LDeviceAdapter findLDevice(SclRootAdapter sclRootAdapter, String iedName, String ldInst) { + public static LDeviceAdapter findLDevice(SclRootAdapter sclRootAdapter, String iedName, String ldInst) { 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))); } @@ -55,4 +57,28 @@ public static LDeviceAdapter findLDevice(SclReport sclReport, String iedName, St return findLDevice(sclReport.getSclRootAdapter(), iedName, ldInst); } + public static LN0Adapter findLn0(SclRootAdapter sclRootAdapter, String iedName, String ldInst) { + LDeviceAdapter lDevice = findLDevice(sclRootAdapter, iedName, ldInst); + if (!lDevice.hasLN0()) { + throw new AssertionFailedError(String.format("LN0 not found in IED.name=%s,LDevice.inst=%s", iedName, ldInst)); + } + return lDevice.getLN0Adapter(); + } + + public static DataSetAdapter findDataSet(SclRootAdapter sclRootAdapter, String iedName, String ldInst, String dataSetName) { + LN0Adapter ln0 = findLn0(sclRootAdapter, iedName, ldInst); + return ln0.findDataSetByName(dataSetName) + .orElseThrow(() -> new AssertionFailedError(String.format("DataSet.name=%s not found id in IED.name=%s,LDevice.inst=%s,LN0", + dataSetName, iedName, ldInst))); + } + + public static Stream streamAllDataSets(SclRootAdapter sclRootAdapter) { + return sclRootAdapter + .streamIEDAdapters() + .flatMap(IEDAdapter::streamLDeviceAdapters) + .filter(LDeviceAdapter::hasLN0) + .map(LDeviceAdapter::getLN0Adapter) + .map(ln0Adapter -> ln0Adapter.getCurrentElem().getDataSet()) + .flatMap(List::stream); + } } 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 ec0a5ee97..730c81daf 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 @@ -16,6 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Named.named; class UtilsTest { @@ -283,4 +284,77 @@ public static Stream provideExtractField() { Arguments.of("a_b_c", -4, null) ); } + + @ParameterizedTest + @MethodSource("provideBlankFirstComparatorSource") + void blankFirstComparator_should_be_false(String s1, String s2, int expectedResult) { + // Given : parameters + // When + int result = Utils.blanksFirstComparator(s1, s2); + // Then + assertThat(Integer.signum(result)).isEqualTo(Integer.signum(expectedResult)); + } + + public static Stream provideBlankFirstComparatorSource() { + final int EQUALS = 0; + final int GREATER_THAN = 1; + final int LOWER_THAN = -1; + + return Stream.of( + // Standard String.compare when not blank + Arguments.of("a", "a", EQUALS), + Arguments.of("a", "b", LOWER_THAN), + Arguments.of("b", "a", GREATER_THAN), + + // Blank equality + Arguments.of(" ", " ", EQUALS), + Arguments.of(" ", null, EQUALS), + Arguments.of(" ", "", EQUALS), + Arguments.of("", " ", EQUALS), + Arguments.of("", null, EQUALS), + Arguments.of("", "", EQUALS), + Arguments.of(null, " ", EQUALS), + Arguments.of(null, null, EQUALS), + Arguments.of(null, "", EQUALS), + + // Not blank compared to blank + Arguments.of("a", " ", GREATER_THAN), + Arguments.of(" ", "a", LOWER_THAN), + Arguments.of(" ", "b", LOWER_THAN), // transitivity " " < "a" && "a" < "b" => " " < "b" + Arguments.of("a", "", GREATER_THAN), + Arguments.of("", "a", LOWER_THAN), + Arguments.of("", "b", LOWER_THAN), // transitivity "" < "a" && "a" < "b" => "" < "b" + Arguments.of("a", null, GREATER_THAN), + Arguments.of(null, "a", LOWER_THAN), + Arguments.of(null, "b", LOWER_THAN) // transitivity null < "a" && "a" < "b" => null < "b" + ); + } + + @ParameterizedTest + @MethodSource("provideNullIfBlankSource") + void nullIfBlank_when_input_is_blank_should_return_null(String input){ + // Given : Parameter + // When + String result = Utils.nullIfBlank(input); + // Then + assertThat(result).isNull(); + } + + public static Stream provideNullIfBlankSource() { + return Stream.of( + Arguments.of(named("null", null)), + Arguments.of(""), + Arguments.of(" ") + ); + } + + @Test + void nullIfBlank_when_input_is_not_blank_should_return_same_string(){ + // Given + String input = "a"; + // When + String result = Utils.nullIfBlank(input); + // Then + assertThat(result).isEqualTo(input); + } } diff --git a/sct-commons/src/test/resources/FcdaCandidates.csv b/sct-commons/src/test/resources/FcdaCandidates.csv index 3a38c1ff8..6596b7fb6 100644 --- a/sct-commons/src/test/resources/FcdaCandidates.csv +++ b/sct-commons/src/test/resources/FcdaCandidates.csv @@ -9,6 +9,10 @@ ANCR;OtherDoName;daNameST;ST ANCR;DoWithInst;daNameST;ST ANCR;DoWithInst.subDo;daNameST;ST +ANCR;FirstDo;daNameST;ST +ANCR;SecondDo;daNameST;ST +ANCR;ThirdDo;daNameST;ST + LPHD;PhyHealth;stVal;ST LPHD;PhyHealth;q;ST LPHD;PhyHealth;t;ST diff --git a/sct-commons/src/test/resources/logback-test.xml b/sct-commons/src/test/resources/logback-test.xml index 00154e9de..958da6d33 100644 --- a/sct-commons/src/test/resources/logback-test.xml +++ b/sct-commons/src/test/resources/logback-test.xml @@ -7,7 +7,7 @@ - [%green(%t)] %highlight(%-5level) %yellow(%C{1.}): %msg%n%throwable + [%green(%t)] %highlight(%-5level) %yellow(%C): %msg%n%throwable @@ -19,4 +19,4 @@ - \ No newline at end of file + 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 a1baab1fc..7fb3536a4 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 @@ -32,8 +32,10 @@ + + @@ -48,16 +50,20 @@ - + - + + + - + + + @@ -102,6 +108,13 @@ + + + + 5 + + + @@ -111,6 +124,9 @@ + + + @@ -125,6 +141,13 @@ + + + + 5 + + + @@ -145,6 +168,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 new file mode 100644 index 000000000..4cd987c58 --- /dev/null +++ b/sct-commons/src/test/resources/scd-extref-create-dataset-and-controlblocks/scd_create_dataset_and_controlblocks_success_test_fcda_sort.xml @@ -0,0 +1,130 @@ + + + + + + +
    + + + + + + + + + + + + + + + on + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + on + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + off + test + + + 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 new file mode 100644 index 000000000..cf46334bb --- /dev/null +++ b/sct-commons/src/test/resources/scl-ln-adapter/scd_with_ln.xml @@ -0,0 +1,72 @@ + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + off + test + + +