test/src/lib/server/CreateNodeStream.spec.js
import {
DataType,
NodeClass,
Variant,
VariantArrayType,
StatusCodes,
NodeId as OpcNodeId,
} from 'node-opcua';
import { spy } from 'sinon';
import Logger from 'gulplog';
import expect from '../../../expect';
import CreateNodeStream from '../../../../src/lib/server/CreateNodeStream';
import AtviseFile from '../../../../src/lib/server/AtviseFile';
import Session from '../../../../src/lib/server/Session';
import NodeId from '../../../../src/lib/model/opcua/NodeId';
import { FileNode } from '../../../../src/lib/gulp/src';
/** @test {CreateNodeStream} */
describe('CreateNodeStream', function () {
/** @test {CreateNodeStream#scriptId} */
describe('#scriptId', function () {
it('should return the atscm CreateNode script', function () {
return expect(
new CreateNodeStream().scriptId,
'to equal',
new NodeId('ns=1;s=SYSTEM.LIBRARY.ATVISE.SERVERSCRIPTS.atscm.CreateNode')
);
});
});
/** @test {CreateNodeStream#scriptParameters} */
describe('#scriptParameters', function () {
it.skip('should return a single JSON encoded parameter', function () {
const file = new FileNode({
name: 'Test',
parent: null,
nodeClass: 'Variable',
nodeId: 'AGENT.OBJECTS.Testing',
referneces: {
HasTypeDefinition: 'VariableTypes.Project.Test',
},
dataType: DataType.String,
arrayType: VariantArrayType.Scalar,
});
this.value.value = 'Just testing...';
const params = new CreateNodeStream().scriptParameters(file);
expect(params, 'to only have keys', ['paramObjString']);
expect(params.paramObjString, 'to only have keys', ['dataType', 'value']);
expect(params.paramObjString.dataType, 'to equal', DataType.String);
expect(params.paramObjString.value, 'to be a string');
expect(() => JSON.parse(params.paramObjString.value), 'not to throw');
});
it.skip('should include nodeId, parentNodeId, nodeClass, typeDefinition and browseName', () => {
const typeDefId = new NodeId('Type.Def.Id');
const file = new AtviseFile({
path: './src/path/to/node/.Object.json',
base: './src/',
contents: Buffer.from(
JSON.stringify({
references: {
HasTypeDefinition: [typeDefId],
},
})
),
});
const params = new CreateNodeStream().scriptParameters(file);
const decoded = JSON.parse(params.paramObjString.value);
expect(decoded.nodeId, 'to equal', 'ns=1;s=path.to.node');
expect(decoded.parentNodeId, 'to equal', 'ns=1;s=path.to');
expect(decoded.nodeClass, 'to equal', NodeClass.Object.value);
expect(decoded.typeDefinition, 'to equal', typeDefId.value);
expect(decoded.browseName, 'to equal', 'node');
expect(decoded.dataType, 'to be undefined');
expect(decoded.valueRank, 'to be undefined');
expect(decoded.value, 'to be undefined');
});
it.skip('should include dataType, arrayType and value for variables', function () {
const file = new AtviseFile({
path: './src/AGENT/OBJECTS/Test.prop.float.array',
base: './src/',
contents: Buffer.from('[0.13,14]'),
});
const params = new CreateNodeStream().scriptParameters(file);
const decoded = JSON.parse(params.paramObjString.value);
expect(decoded.nodeId, 'to equal', 'ns=1;s=AGENT.OBJECTS.Test');
expect(decoded.parentNodeId, 'to equal', 'ns=1;s=AGENT.OBJECTS');
expect(decoded.nodeClass, 'to equal', NodeClass.Variable.value);
expect(decoded.typeDefinition, 'to equal', 68); // Property
expect(decoded.browseName, 'to equal', 'Test');
expect(decoded.dataType, 'to equal', DataType.Float.value);
expect(decoded.valueRank, 'to equal', VariantArrayType.Array.value);
expect(decoded.value, 'to equal', [0.13, 14]);
});
});
/** @test {CreateNodeStream#processErrorMessage} */
describe('#processErrorMessage', function () {
it('should tell which node failed to create', function () {
return expect(
CreateNodeStream.prototype.processErrorMessage,
'when called with',
[{ nodeId: new NodeId('Failed.to.create') }],
'to match',
/creating node/i
);
});
});
/** @test {CreateNodeStream#handleOutputArguments} */
describe('#handleOutputArguments', function () {
it('should forward script errors', function () {
const stream = new CreateNodeStream();
return expect(
(cb) =>
stream.handleOutputArguments(
{},
[{ value: StatusCodes.Bad }, { value: 'Test error' }],
cb
),
'to call the callback with error',
'Test error'
);
});
it('should warn if creating the node failed', async function () {
const stream = new CreateNodeStream();
const warnSpy = spy();
Logger.on('warn', warnSpy);
await expect(
(cb) =>
stream.handleOutputArguments(
{ nodeId: 'Test' },
[{ value: StatusCodes.Good }, {}, {}, { value: [{ value: false }, { value: true }] }],
cb
),
'to call the callback without error'
);
expect(warnSpy, 'was called once');
return expect(warnSpy, 'to have a call satisfying', { args: [/Failed to create.*Test/] });
});
it('should log if a node was created', async function () {
const stream = new CreateNodeStream();
const debugSpy = spy();
Logger.on('debug', debugSpy);
await expect(
(cb) =>
stream.handleOutputArguments(
{ nodeId: 'Test' },
[{ value: StatusCodes.Good }, {}, {}, { value: [{ value: true }, { value: false }] }],
cb
),
'to call the callback without error'
);
expect(debugSpy, 'was called once');
return expect(debugSpy, 'to have a call satisfying', { args: [/Created node.*Test/] });
});
it.skip('should log if node already existed', async function () {
const stream = new CreateNodeStream();
const debugSpy = spy();
Logger.on('debug', debugSpy);
await expect(
(cb) =>
stream.handleOutputArguments(
{ nodeId: 'Test' },
[{ value: StatusCodes.Good }, {}, {}, { value: [{ value: false }, { value: false }] }],
cb
),
'to call the callback without error'
);
expect(debugSpy, 'was called once');
return expect(debugSpy, 'to have a call satisfying', { args: [/already exists/] });
});
});
/** @test {CreateNodeStream#processChunk} */
describe.skip('#processChunk', function () {
const testTime = Date.now();
const testFolderNodePath = `src/AGENT/OBJECTS/TestCreate-${testTime}`;
const nodeIdBase = `AGENT.OBJECTS.TestCreate-${testTime}`;
before('create test node folder', function () {
const stream = new CreateNodeStream();
return expect(
[
new FileNode({
path: `${testFolderNodePath}/.Object.json`,
base: './src',
contents: Buffer.from(
JSON.stringify({
references: {
HasTypeDefinition: ['ns=0;i=61'],
},
})
),
}),
],
'when piped through',
stream,
'to yield objects satisfying',
'to have length',
1
);
});
const dateValue = new Date();
dateValue.setMilliseconds(0);
const tests = [
{
nodeClass: NodeClass.Variable,
value: {
value: true,
dataType: DataType.Boolean,
arrayType: VariantArrayType.Scalar,
},
},
{
nodeClass: NodeClass.Variable,
value: {
value: 3,
dataType: DataType.Int16,
arrayType: VariantArrayType.Scalar,
},
},
{
nodeClass: NodeClass.Variable,
value: {
value: 1.0,
dataType: DataType.Float,
arrayType: VariantArrayType.Scalar,
},
},
{
nodeClass: NodeClass.Variable,
value: {
value: 'Test',
dataType: DataType.String,
arrayType: VariantArrayType.Scalar,
},
},
{
nodeClass: NodeClass.Variable,
value: {
value: dateValue,
dataType: DataType.DateTime,
arrayType: VariantArrayType.Scalar,
},
},
{
nodeClass: NodeClass.Variable,
value: {
value: '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><test />',
dataType: DataType.XmlElement,
arrayType: VariantArrayType.Scalar,
},
},
{
nodeClass: NodeClass.Variable,
value: {
value: new OpcNodeId(NodeId.NodeIdType.STRING, 'Testing'),
dataType: DataType.NodeId,
arrayType: VariantArrayType.Scalar,
},
},
];
tests.forEach(({ nodeClass, value }) => {
it(`should be able to create ${value.dataType.key} ${nodeClass.key}s`, async function () {
const stream = new CreateNodeStream();
const nodeId = new NodeId(`${nodeIdBase}.Test${value.dataType.key}`);
const variant = new Variant(value);
expect(variant.isValid(), 'to be', true);
const file = AtviseFile.fromReadResult({
nodeId,
nodeClass,
value: variant,
mtime: new Date(),
references: {
HasTypeDefinition: [new NodeId('ns=0;i=62')],
},
});
await expect([file], 'when piped through', stream, 'to yield objects satisfying', [file]);
const session = await Session.create();
const [[{ value: readValue }]] = await expect(
(cb) => session.readVariableValue([nodeId.toString()], cb),
'to call the callback without error'
);
expect(readValue, 'to equal', variant);
});
});
});
});