diff --git a/src/editors/subscription/foundation.ts b/src/editors/subscription/foundation.ts index c32acff61..5867acc3c 100644 --- a/src/editors/subscription/foundation.ts +++ b/src/editors/subscription/foundation.ts @@ -197,6 +197,37 @@ export function canRemoveSubscriptionSupervision( ); } +/** + * Searches DataTypeTemplates for DOType>DA[valKind=Conf/RO][valImport=true] from an LN reference. + * @param lnElement - The LN Element to use for searching the starting DO Element. + * @returns - true if both conditions are found in the DA child element. + */ +function checksDataTypeTemplateConditions(lnElement: Element): boolean { + const rootNode = lnElement?.ownerDocument; + const lNodeType = lnElement.getAttribute('lnType'); + const lnClass = lnElement.getAttribute('lnClass'); + const dObj = rootNode.querySelector( + `DataTypeTemplates > LNodeType[id="${lNodeType}"][lnClass="${lnClass}"] > DO[name="${ + lnClass === 'LGOS' ? 'GoCBRef' : 'SvCBRef' + }"]` + ); + if (dObj) { + const dORef = dObj.getAttribute('type'); + const daObj = rootNode.querySelector( + `DataTypeTemplates > DOType[id="${dORef}"] > DA[name="setSrcRef"]` + ); + if (daObj) { + return ( + (daObj.getAttribute('valKind') === 'Conf' || + daObj.getAttribute('valKind') === 'RO') && + daObj.getAttribute('valImport') === 'true' + ); + } + } + // definition missing + return false; +} + /** * Returns an array with a single Create action to create a new * supervision element for the given GOOSE/SMV message and subscriber IED. @@ -222,7 +253,8 @@ export function instantiateSubscriptionSupervision( subscriberIED, supervisionType ); - if (!availableLN) return []; + if (!availableLN || !checksDataTypeTemplateConditions(availableLN)) return []; + // Then, create the templateStructure array const templateStructure = createTemplateStructure(availableLN, [ controlBlock?.tagName === 'GSEControl' ? 'GoCBRef' : 'SvCBRef', diff --git a/test/testfiles/editors/VS893-LaterBindingSMV-LSVS.scd b/test/testfiles/editors/VS893-LaterBindingSMV-LSVS.scd index b70a82960..98ed7bb1d 100644 --- a/test/testfiles/editors/VS893-LaterBindingSMV-LSVS.scd +++ b/test/testfiles/editors/VS893-LaterBindingSMV-LSVS.scd @@ -73,7 +73,7 @@ - + @@ -128,7 +128,7 @@ - + @@ -162,16 +162,16 @@ - + SMV_PublisherCurrentTransformer/LLN0.currrentOnly - - - + + + @@ -210,6 +210,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SMV_PublisherCurrentTransformer/LLN0.currrentOnly + + + + + + + + + + @@ -524,6 +574,22 @@ + + + + + + + + + + + + + + + + @@ -565,6 +631,12 @@ + + + + + + diff --git a/test/testfiles/editors/VS893_LaterBindingGOOSE-LGOS.scd b/test/testfiles/editors/VS893_LaterBindingGOOSE-LGOS.scd index 93882c69c..a339c36e1 100644 --- a/test/testfiles/editors/VS893_LaterBindingGOOSE-LGOS.scd +++ b/test/testfiles/editors/VS893_LaterBindingGOOSE-LGOS.scd @@ -77,6 +77,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -125,6 +154,14 @@ + + + + + GOOSE_PublisherQB2_Disconnector/LLN0.GOOSE1 + + + @@ -167,37 +204,31 @@ - + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + @@ -270,6 +301,22 @@ + + + + + + + + + + + + + + + + @@ -309,6 +356,12 @@ + + + + + + diff --git a/test/unit/editors/subscription/supervision.test.ts b/test/unit/editors/subscription/supervision.test.ts index dcfc7ce54..fe2c7be11 100644 --- a/test/unit/editors/subscription/supervision.test.ts +++ b/test/unit/editors/subscription/supervision.test.ts @@ -21,87 +21,108 @@ describe('supervision', () => { .then(str => new DOMParser().parseFromString(str, 'application/xml')); }); - it('produces the correct structure where supervision is allowed', () => { - controlElement = doc.querySelector( - 'IED[name="SMV_Publisher4"] SampledValueControl[name="voltageOnly"]' - )!; - subscriberIED = doc.querySelector('IED[name="SMV_Subscriber3"]')!; - const noSupervisionAllowedActions: Create[] = - instantiateSubscriptionSupervision(controlElement, subscriberIED); - expect(noSupervisionAllowedActions).to.have.length(0); - - subscriberIED = doc.querySelector('IED[name="SMV_Subscriber2"]')!; - const createActions: Create[] = instantiateSubscriptionSupervision( - controlElement, - subscriberIED - ); - expect(createActions).to.have.length(1); - const createAction: Create = createActions[0]; - const parentElement: Node = createAction.new.parent; - const newElement: Node = createAction.new.element; - expect(parentElement).to.exist; - expect(newElement).to.exist; - expect(parentElement.nodeName).to.be.equal('LN'); - expect(newElement.nodeName).to.be.equal('DOI'); - - controlElement = doc.querySelector( - 'IED[name="SMV_Publisher3"] SampledValueControl[name="fullSmv"]' - )!; - subscriberIED = doc.querySelector('IED[name="SMV_Subscriber"]')!; - - const createActionsWithNewLn: Create[] = - instantiateSubscriptionSupervision(controlElement, subscriberIED); - expect(createActionsWithNewLn).to.have.length(2); - const newLnAction: Create = createActionsWithNewLn[0]; - const lDeviceParent: Node = newLnAction.new.parent; - const newLnElement: Node = newLnAction.new.element; - expect(lDeviceParent).to.exist; - expect(newLnElement).to.exist; - expect(lDeviceParent.nodeName).to.be.equal('LDevice'); - expect(newLnElement.nodeName).to.be.equal('LN'); - - const newDoiAction: Create = createActionsWithNewLn[1]; - const lnParent: Node = newDoiAction.new.parent; - const newDoiElement: Node = newDoiAction.new.element; - expect(lnParent).to.exist; - expect(newDoiElement).to.exist; - expect(lnParent.nodeName).to.be.equal('LN'); - expect(newDoiElement.nodeName).to.be.equal('DOI'); + describe('produces the correct structure where supervision is allowed', () => { + beforeEach(() => { + controlElement = doc.querySelector( + 'IED[name="SMV_Publisher4"] SampledValueControl[name="voltageOnly"]' + )!; + }); + + it('does not allow supervision because there is no services defined in the IED', () => { + subscriberIED = doc.querySelector('IED[name="SMV_Subscriber3"]')!; + const noSupervisionAllowedActions: Create[] = + instantiateSubscriptionSupervision(controlElement!, subscriberIED); + expect(noSupervisionAllowedActions).to.have.length(0); + }); + + it('does not allow supervision because the DataTypeTemplate>DOType>DA is missing necessary attributes ', () => { + subscriberIED = doc.querySelector('IED[name="SMV_Subscriber4"]')!; + const noSupervisionAllowedActions: Create[] = + instantiateSubscriptionSupervision(controlElement!, subscriberIED); + expect(noSupervisionAllowedActions).to.have.length(0); + }); + + it('allows supervision and an existing LN[class=LSVS] should be used', () => { + subscriberIED = doc.querySelector('IED[name="SMV_Subscriber2"]')!; + const createActions: Create[] = instantiateSubscriptionSupervision( + controlElement!, + subscriberIED + ); + expect(createActions).to.have.length(1); + const createAction: Create = createActions[0]; + const parentElement: Node = createAction.new.parent; + const newElement: Node = createAction.new.element; + expect(parentElement).to.exist; + expect(newElement).to.exist; + expect(parentElement.nodeName).to.be.equal('LN'); + expect(newElement.nodeName).to.be.equal('DOI'); + }); + + it('allows supervision and an a new LN[class=LSVS] should be created as well', () => { + controlElement = doc.querySelector( + 'IED[name="SMV_Publisher3"] SampledValueControl[name="fullSmv"]' + )!; + subscriberIED = doc.querySelector('IED[name="SMV_Subscriber"]')!; + const createActionsWithNewLn: Create[] = + instantiateSubscriptionSupervision(controlElement, subscriberIED); + expect(createActionsWithNewLn).to.have.length(2); + const newLnAction: Create = createActionsWithNewLn[0]; + const lDeviceParent: Node = newLnAction.new.parent; + const newLnElement: Node = newLnAction.new.element; + expect(lDeviceParent).to.exist; + expect(newLnElement).to.exist; + expect(lDeviceParent.nodeName).to.be.equal('LDevice'); + expect(newLnElement.nodeName).to.be.equal('LN'); + + const newDoiAction: Create = createActionsWithNewLn[1]; + const lnParent: Node = newDoiAction.new.parent; + const newDoiElement: Node = newDoiAction.new.element; + expect(lnParent).to.exist; + expect(newDoiElement).to.exist; + expect(lnParent.nodeName).to.be.equal('LN'); + expect(newDoiElement.nodeName).to.be.equal('DOI'); + }); }); it('removes the correct elements when supervision is removed', () => { - controlElement = doc.querySelector( - 'IED[name="SMV_Publisher2"] SampledValueControl[name="fullSmv"]' - )!; - subscriberIED = doc.querySelector('IED[name="SMV_Subscriber"]')!; - const removeActions: Delete[] = removeSubscriptionSupervision( - controlElement, - subscriberIED - ); - expect(removeActions).to.have.length(1); - const removeAction: Delete = removeActions[0]; - const parentElement: Node = removeAction.old.parent; - const element: Node = removeAction.old.element; - expect(parentElement).to.exist; - expect(element).to.exist; - expect(parentElement.nodeName).to.be.equal('LN'); - expect(element.nodeName).to.be.equal('DOI'); - - controlElement = doc.querySelector( - 'IED[name="SMV_Publisher2"] SampledValueControl[name="voltageOnly"]' - )!; - const removeActionsWithRemovedLn: Delete[] = - removeSubscriptionSupervision(controlElement, subscriberIED); - expect(removeActionsWithRemovedLn).to.have.length(1); - const removeLnAction: Delete = removeActionsWithRemovedLn[0]; - const lnParent: Node = removeLnAction.old.parent; - const lnElement: Node = removeLnAction.old.element; - expect(lnParent).to.exist; - expect(lnElement).to.exist; - expect(lnParent.nodeName).to.be.equal('LDevice'); - expect(lnElement.nodeName).to.be.equal('LN'); + beforeEach(() => { + subscriberIED = doc.querySelector('IED[name="SMV_Subscriber"]')!; + }); + it('allows removing supervision in a vendor-created LN', () => { + controlElement = doc.querySelector( + 'IED[name="SMV_Publisher2"] SampledValueControl[name="fullSmv"]' + )!; + const removeActions: Delete[] = removeSubscriptionSupervision( + controlElement, + subscriberIED! + ); + expect(removeActions).to.have.length(1); + const removeAction: Delete = removeActions[0]; + const parentElement: Node = removeAction.old.parent; + const element: Node = removeAction.old.element; + expect(parentElement).to.exist; + expect(element).to.exist; + expect(parentElement.nodeName).to.be.equal('LN'); + expect(element.nodeName).to.be.equal('DOI'); + }); + it('allows removing supervision in an OpenSCD-created LN', () => { + controlElement = doc.querySelector( + 'IED[name="SMV_Publisher2"] SampledValueControl[name="voltageOnly"]' + )!; + const removeActionsWithRemovedLn: Delete[] = + removeSubscriptionSupervision(controlElement, subscriberIED!); + expect(removeActionsWithRemovedLn).to.have.length(1); + const removeLnAction: Delete = removeActionsWithRemovedLn[0]; + const lnParent: Node = removeLnAction.old.parent; + const lnElement: Node = removeLnAction.old.element; + expect(lnParent).to.exist; + expect(lnElement).to.exist; + expect(lnParent.nodeName).to.be.equal('LDevice'); + expect(lnElement.nodeName).to.be.equal('LN'); + }); }); }); + describe('when subscribing to a GSEControl', () => { beforeEach(async () => { doc = await fetch( @@ -111,85 +132,112 @@ describe('supervision', () => { .then(str => new DOMParser().parseFromString(str, 'application/xml')); }); - it('produces the correct structure where supervision is allowed', () => { - controlElement = doc.querySelector( - 'IED[name="GOOSE_Publisher2"] GSEControl[name="GOOSE1"]' - )!; - subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber4"]')!; - const noSupervisionAllowedActions: Create[] = - instantiateSubscriptionSupervision(controlElement, subscriberIED); - expect(noSupervisionAllowedActions).to.have.length(0); - - subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber2"]')!; - const createActions: Create[] = instantiateSubscriptionSupervision( - controlElement, - subscriberIED - ); - expect(createActions).to.have.length(1); - const createAction: Create = createActions[0]; - const parentElement: Node = createAction.new.parent; - const newElement: Node = createAction.new.element; - expect(parentElement).to.exist; - expect(newElement).to.exist; - expect(parentElement.nodeName).to.be.equal('LN'); - expect(newElement.nodeName).to.be.equal('DOI'); - - controlElement = doc.querySelector( - 'IED[name="GOOSE_Publisher"] GSEControl[name="GOOSE1"]' - )!; - subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber1"]')!; - - const createActionsWithNewLn: Create[] = - instantiateSubscriptionSupervision(controlElement, subscriberIED); - expect(createActionsWithNewLn).to.have.length(2); - const newLnAction: Create = createActionsWithNewLn[0]; - const lDeviceParent: Node = newLnAction.new.parent; - const newLnElement: Node = newLnAction.new.element; - expect(lDeviceParent).to.exist; - expect(newLnElement).to.exist; - expect(lDeviceParent.nodeName).to.be.equal('LDevice'); - expect(newLnElement.nodeName).to.be.equal('LN'); - - const newDoiAction: Create = createActionsWithNewLn[1]; - const lnParent: Node = newDoiAction.new.parent; - const newDoiElement: Node = newDoiAction.new.element; - expect(lnParent).to.exist; - expect(newDoiElement).to.exist; - expect(lnParent.nodeName).to.be.equal('LN'); - expect(newDoiElement.nodeName).to.be.equal('DOI'); + describe('produces the correct structure where supervision is allowed', () => { + beforeEach(() => { + controlElement = doc.querySelector( + 'IED[name="GOOSE_Publisher2"] GSEControl[name="GOOSE1"]' + )!; + }); + it('does not allow supervision because there is no services defined in the IED', () => { + subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber"]')!; + const noSupervisionAllowedActions: Create[] = + instantiateSubscriptionSupervision(controlElement!, subscriberIED); + expect(noSupervisionAllowedActions).to.have.length(0); + }); + it('does not allow supervision because the DataTypeTemplate>DOType>DA is missing necessary attributes ', () => { + subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber4"]')!; + const noSupervisionAllowedActions: Create[] = + instantiateSubscriptionSupervision(controlElement!, subscriberIED); + expect(noSupervisionAllowedActions).to.have.length(0); + }); + it('allows supervision and an existing LN[class=LGOS] should be used', () => { + subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber2"]')!; + const createActions: Create[] = instantiateSubscriptionSupervision( + controlElement!, + subscriberIED + ); + expect(createActions).to.have.length(1); + const createAction: Create = createActions[0]; + const parentElement: Node = createAction.new.parent; + const newElement: Node = createAction.new.element; + expect(parentElement).to.exist; + expect(newElement).to.exist; + expect(parentElement.nodeName).to.be.equal('LN'); + expect(newElement.nodeName).to.be.equal('DOI'); + }); + it('allows supervision and an a new LN[class=LGOS] should be created as well', () => { + controlElement = doc.querySelector( + 'IED[name="GOOSE_Publisher2"] GSEControl[name="GOOSE1"]' + )!; + subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber1"]')!; + + const createActionsWithNewLn: Create[] = + instantiateSubscriptionSupervision(controlElement, subscriberIED); + expect(createActionsWithNewLn).to.have.length(2); + const newLnAction: Create = createActionsWithNewLn[0]; + const lDeviceParent: Node = newLnAction.new.parent; + const newLnElement: Node = newLnAction.new.element; + expect(lDeviceParent).to.exist; + expect(newLnElement).to.exist; + expect(lDeviceParent.nodeName).to.be.equal('LDevice'); + expect(newLnElement.nodeName).to.be.equal('LN'); + + const newDoiAction: Create = createActionsWithNewLn[1]; + const lnParent: Node = newDoiAction.new.parent; + const newDoiElement: Node = newDoiAction.new.element; + expect(lnParent).to.exist; + expect(newDoiElement).to.exist; + expect(lnParent.nodeName).to.be.equal('LN'); + expect(newDoiElement.nodeName).to.be.equal('DOI'); + }); }); - it('removes the correct elements when supervision is removed', () => { - controlElement = doc.querySelector( - 'IED[name="GOOSE_Publisher"] GSEControl[name="GOOSE2"]' - )!; - subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber1"]')!; - const removeActions: Delete[] = removeSubscriptionSupervision( - controlElement, - subscriberIED - ); - expect(removeActions).to.have.length(1); - const removeAction: Delete = removeActions[0]; - const parentElement: Node = removeAction.old.parent; - const element: Node = removeAction.old.element; - expect(parentElement).to.exist; - expect(element).to.exist; - expect(parentElement.nodeName).to.be.equal('LN'); - expect(element.nodeName).to.be.equal('DOI'); - - controlElement = doc.querySelector( - 'IED[name="GOOSE_Publisher2"] GSEControl[name="GOOSE2"]' - )!; - const removeActionsWithRemovedLn: Delete[] = - removeSubscriptionSupervision(controlElement, subscriberIED); - expect(removeActionsWithRemovedLn).to.have.length(1); - const removeLnAction: Delete = removeActionsWithRemovedLn[0]; - const lnParent: Node = removeLnAction.old.parent; - const lnElement: Node = removeLnAction.old.element; - expect(lnParent).to.exist; - expect(lnElement).to.exist; - expect(lnParent.nodeName).to.be.equal('LDevice'); - expect(lnElement.nodeName).to.be.equal('LN'); + describe('removes the correct elements when supervision is removed', () => { + beforeEach(() => { + subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber1"]')!; + }); + it('allows removing supervision in a vendor-created LN', () => { + controlElement = doc.querySelector( + 'IED[name="GOOSE_Publisher"] GSEControl[name="GOOSE2"]' + )!; + const removeActions: Delete[] = removeSubscriptionSupervision( + controlElement, + subscriberIED! + ); + expect(removeActions).to.have.length(1); + const removeAction: Delete = removeActions[0]; + const parentElement: Node = removeAction.old.parent; + const element: Node = removeAction.old.element; + expect(parentElement).to.exist; + expect(element).to.exist; + expect(parentElement.nodeName).to.be.equal('LN'); + expect(element.nodeName).to.be.equal('DOI'); + }); + it('allows removing supervision in an OpenSCD-created LN', () => { + controlElement = doc.querySelector( + 'IED[name="GOOSE_Publisher"] GSEControl[name="GOOSE1"]' + )!; + const removeActionsWithRemovedLn: Delete[] = + removeSubscriptionSupervision(controlElement, subscriberIED!); + expect(removeActionsWithRemovedLn).to.have.length(1); + const removeLnAction: Delete = removeActionsWithRemovedLn[0]; + const lnParent: Node = removeLnAction.old.parent; + const lnElement: Node = removeLnAction.old.element; + expect(lnParent).to.exist; + expect(lnElement).to.exist; + expect(lnParent.nodeName).to.be.equal('LDevice'); + expect(lnElement.nodeName).to.be.equal('LN'); + }); + it('does not allow removing supervision because other data attributes are still using it', () => { + controlElement = doc.querySelector( + 'IED[name="GOOSE_Publisher2"] GSEControl[name="GOOSE1"]' + )!; + const removeActions: Delete[] = removeSubscriptionSupervision( + controlElement, + subscriberIED! + ); + expect(removeActions).to.have.length(0); + }); }); }); });