Home Manual Reference Source Test

test/src/lib/model/opcua/NodeId.spec.js

import { join } from 'path';
import expect from 'unexpected';
import { spy } from 'sinon';
import { NodeId as OpcNodeId } from 'node-opcua';
import NodeId from '../../../../../src/lib/model/opcua/NodeId';

/** @test {NodeId} */
describe('NodeId', function () {
  const path = join('AGENT/DISPLAYS/Main');
  const id = 'AGENT.DISPLAYS.Main';

  /** @test {NodeId#constructor} */
  describe('#constructor', function () {
    function expectType(type, ...args) {
      expect(new NodeId(...args).identifierType, 'to equal', type);
    }

    context('when called with value only', function () {
      it('should work', function () {
        expect(() => new NodeId('AGENT.DISPLAYS'), 'not to throw');
      });

      it('should set namespace to 1', function () {
        expect(new NodeId('AGENT.DISPLAYS').namespace, 'to equal', 1);
      });

      it('should set indentifierType to string if string is passed', function () {
        expectType(NodeId.NodeIdType.STRING, 'AGENT.DISPLAYS');
      });

      it('should set indentifierType to number if number is passed', function () {
        expectType(NodeId.NodeIdType.NUMERIC, 123);
      });
    });

    context('when called with a nodeid string', function () {
      it('should work', function () {
        expect(() => new NodeId('ns=1;s=AGENT.DISPLAYS'), 'not to throw');
      });

      it('should get namespace from string', function () {
        expect(new NodeId('ns=13;s=AGENT.DISPLAYS').namespace, 'to equal', 13);
      });

      it('should get identifierType from string', function () {
        expectType(NodeId.NodeIdType.STRING, 'ns=1;s=AGENT.DISPLAYS');
        expectType(NodeId.NodeIdType.NUMERIC, 'ns=1;i=13');
        // FIXME: Missing tests for type GUID
        expectType(NodeId.NodeIdType.BYTESTRING, 'ns=1;b=13Ac');
      });
    });

    context('when called with type, value, namespace', function () {
      it('should work', function () {
        expect(() => new NodeId(NodeId.NodeIdType.STRING, 'AGENT.DISPLAYS', 1), 'not to throw');
      });

      it('should set namespace to 1 if omitted', function () {
        expect(() => new NodeId(NodeId.NodeIdType.STRING, 'AGENT.DISPLAYS'), 'not to throw');
        expect(new NodeId(NodeId.NodeIdType.STRING, 'AGENT.DISPLAYS').namespace, 'to equal', 1);
      });
    });

    it("should extend node-opcua's NodeId", function () {
      expect(new NodeId(NodeId.NodeIdType.NUMERIC, 123, 1), 'to be a', OpcNodeId);
    });
  });

  /** @test {NodeId#fromFilePath} */
  describe('#fromFilePath', function () {
    it('should return a NodeId', function () {
      expect(NodeId.fromFilePath(path), 'to be a', NodeId);
    });

    it('should return file path again', function () {
      const nodeId = NodeId.fromFilePath(path);
      expect(nodeId.filePath, 'to be a', 'string');
      expect(join(nodeId.filePath), 'to equal', path);
    });

    it('should work with resource paths', function () {
      expect(
        NodeId.fromFilePath(
          join('SYSTEM/LIBRARY/ATVISE/RESOURCES/timer/imgs_glossy/top-separator.gif')
        ).value,
        'to equal',
        'SYSTEM.LIBRARY.ATVISE.RESOURCES/timer/imgs_glossy/top-separator.gif'
      );
    });

    it('should work with multi extension resource paths', function () {
      expect(
        NodeId.fromFilePath(join('SYSTEM/LIBRARY/PROJECT/RESOURCES/styles/bootstrap.min.css'))
          .value,
        'to equal',
        'SYSTEM.LIBRARY.PROJECT.RESOURCES/styles/bootstrap.min.css'
      );
    });

    it('should unescape slashes', function () {
      return expect(
        NodeId.fromFilePath(join('test/no%2Ffolder')).value,
        'to equal',
        'test.no/folder'
      );
    });
  });

  /** @test {NodeId#filePath} */
  describe('#filePath', function () {
    it('should return a valid file path', function () {
      const nodeId = new NodeId(NodeId.NodeIdType.STRING, id, 1);

      expect(nodeId.filePath, 'to be a', 'string');
      expect(join(nodeId.filePath), 'to equal', path);
    });

    it('should handle resource paths', function () {
      const nodeId = new NodeId(NodeId.NodeIdType.STRING, 'SYSTEM.LIBRARY.RESOURCES/dir/test.e', 1);

      expect(nodeId.filePath, 'to be a', 'string');
      expect(nodeId.filePath, 'to equal', 'SYSTEM/LIBRARY/RESOURCES/dir/test.e');
    });

    it('should escape slashes', function () {
      return expect(
        new NodeId('AGENT.OBJECTS.Test/node').filePath,
        'to equal',
        'AGENT/OBJECTS/Test%2Fnode'
      );
    });
  });

  /** @test {_lastSeparator} */
  describe('#_lastSeparator', function () {
    it('should return null for non-string node ids', function () {
      expect(new NodeId(NodeId.NodeIdType.NUMERIC, 123, 1)._lastSeparator, 'to be', null);
    });

    it('should return `/` for resource paths', function () {
      expect(new NodeId(NodeId.NodeIdType.STRING, 'Test/Resource', 1)._lastSeparator, 'to be', '/');
    });

    it('should return `.` for regular node ids', function () {
      expect(new NodeId(NodeId.NodeIdType.STRING, 'Test.Node', 1)._lastSeparator, 'to be', '.');
    });
  });

  /** @test {NodeId#parent} */
  describe('#parent', function () {
    it('should return null for non-string node ids', function () {
      const child = new NodeId(NodeId.NodeIdType.NUMERIC, 123, 1);
      const parent = child.parent;

      expect(parent, 'to be', null);
    });

    it('should inherit identifier type', function () {
      const child = new NodeId(NodeId.NodeIdType.STRING, 'AGENT.DISPLAYS.Main', 13);
      const parent = child.parent;

      expect(parent.identifierType, 'to equal', NodeId.NodeIdType.STRING);
    });

    it('should inherit namespace', function () {
      const child = new NodeId(NodeId.NodeIdType.STRING, 'AGENT.DISPLAYS.Main', 13);
      const parent = child.parent;

      expect(parent.namespace, 'to equal', 13);
    });

    it('should return parent nodes for dot separated node ids', function () {
      const child = new NodeId(NodeId.NodeIdType.STRING, 'AGENT.DISPLAYS.Main', 13);
      const parent = child.parent;

      expect(parent.value, 'to equal', 'AGENT.DISPLAYS');
    });

    it('should return parent nodes for slash separated node ids', function () {
      const child = new NodeId(NodeId.NodeIdType.STRING, 'SYSTEM.LIBRARY.RESOURCES/dir/test.e', 13);
      const parent = child.parent;

      expect(parent.value, 'to equal', 'SYSTEM.LIBRARY.RESOURCES/dir');
    });
  });

  /** @test {NodeId#isChildOf} */
  describe('#isChildOf', function () {
    it('should return false for non-string ids', function () {
      const invalid = new NodeId(NodeId.NodeIdType.NUMERIC, 123, 13);
      const valid = new NodeId(NodeId.NodeIdType.STRING, 'Node.Path', 13);

      expect(invalid.isChildOf(valid), 'to be false');
      expect(valid.isChildOf(invalid), 'to be false');
    });

    it('should return false for different namespaces', function () {
      const first = new NodeId(NodeId.NodeIdType.STRING, 'Path.To.Node', 1);
      const second = new NodeId(NodeId.NodeIdType.STRING, 'Path.To', 2);

      expect(first.isChildOf(second), 'to be false');
    });

    it('should return false for same nodes values', function () {
      const first = new NodeId(NodeId.NodeIdType.STRING, 'Path.To.Node', 1);
      const second = new NodeId(NodeId.NodeIdType.STRING, 'Path.To.Node', 1);

      expect(first.isChildOf(second), 'to be false');
    });

    it('should return false for similar node values', function () {
      const base = new NodeId(NodeId.NodeIdType.STRING, 'Path.To.Node', 1);
      const postfixed = new NodeId(NodeId.NodeIdType.STRING, 'Path.To.Node1', 1);
      const prefixed = new NodeId(NodeId.NodeIdType.STRING, 'Another.Path.To.Node', 1);

      expect(base.isChildOf(postfixed), 'to be false');
      expect(postfixed.isChildOf(base), 'to be false');

      expect(base.isChildOf(prefixed), 'to be false');
      expect(prefixed.isChildOf(base), 'to be false');
    });

    it('should return true for real parents', function () {
      const first = new NodeId(NodeId.NodeIdType.STRING, 'Path.To.Node', 1);
      const second = new NodeId(NodeId.NodeIdType.STRING, 'Path.To', 1);

      expect(first.isChildOf(second), 'to be true');
    });

    it('should return true for parent resource nodes', function () {
      const first = new NodeId(NodeId.NodeIdType.STRING, 'Path/to/Node', 1);
      const second = new NodeId(NodeId.NodeIdType.STRING, 'Path/to', 1);

      expect(first.isChildOf(second), 'to be true');
    });

    it('should return true for resource child nodes', function () {
      const first = new NodeId(NodeId.NodeIdType.STRING, 'Path/to/Node.html.Translate', 1);
      const second = new NodeId(NodeId.NodeIdType.STRING, 'Path/to/Node.html', 1);

      expect(first.isChildOf(second), 'to be true');
    });
  });

  /** @test {NodeId#browseName} */
  describe('#browseName', function () {
    it('should return null for non-string node ids', function () {
      const node = new NodeId(NodeId.NodeIdType.NUMERIC, 123, 1);

      expect(node.browseName, 'to be', null);
    });

    it('should return the last node path component', function () {
      const node = new NodeId(NodeId.NodeIdType.STRING, 'Path.to.node', 1);
      expect(node.browseName, 'to be', 'node');
    });

    it('should return the last resource path component', function () {
      const node = new NodeId(NodeId.NodeIdType.STRING, 'Path/to/Node', 1);
      expect(node.browseName, 'to be', 'Node');
    });
  });

  /** @test {NodeId#inspect} */
  describe('#inspect', function () {
    const opts = {
      stylize: spy((t) => t),
    };

    beforeEach(() => opts.stylize.resetHistory());

    it('should return "namespace value"', function () {
      const nodeId = new NodeId(NodeId.NodeIdType.STRING, 'AGENT.DISPLAYS', 1);

      expect(nodeId.inspect(0, opts), 'to match', /1 AGENT\.DISPLAYS/);
    });

    it('should style string id as string', function () {
      new NodeId(NodeId.NodeIdType.STRING, 'AGENT.DISPLAYS', 1).inspect(0, opts);

      expect(opts.stylize.calledTwice, 'to be true');
      expect(opts.stylize.lastCall.args, 'to equal', ['AGENT.DISPLAYS', 'string']);
    });

    it('should style numeric id as number', function () {
      new NodeId(NodeId.NodeIdType.NUMERIC, 123, 1).inspect(0, opts);

      expect(opts.stylize.calledTwice, 'to be true');
      expect(opts.stylize.lastCall.args, 'to equal', [123, 'number']);
    });
  });
});