Home Manual Reference Source Test

test/src/lib/server/Session.spec.js

import { spy } from 'sinon';
import proxyquire from 'proxyquire';
import { StatusCodes } from 'node-opcua';
import { ClientSession, OPCUAClient } from 'node-opcua/lib/client/opcua_client';
import Logger from 'gulplog';
import expect from '../../../expect';
import Session from '../../../../src/lib/server/Session';
import ProjectConfig from '../../../../src/config/ProjectConfig';

function sessionWithLogin(login) {
  return proxyquire('../../../../src/lib/server/Session', {
    '../../config/ProjectConfig': {
      default: {
        host: ProjectConfig.host,
        port: ProjectConfig.port,
        login,
        timeout: ProjectConfig.timeout,
      },
    },
  }).default;
}

const FailingClientSession = proxyquire('../../../../src/lib/server/Session', {
  'node-opcua/lib/client/opcua_client': {
    OPCUAClient: class StubClient {
      connect(endpoint, cb) {
        cb(new Error('Client.create error'));
      }
    },
  },
}).default;

const FailingSession = proxyquire('../../../../src/lib/server/Session', {
  'node-opcua/lib/client/opcua_client': {
    OPCUAClient: class StubClient extends OPCUAClient {
      createSession(login, callback) {
        callback(new Error('Client.createSession error'));
      }
    },
  },
}).default;

/** @test {Session} */
describe('Session', function () {
  /** @test {Session.create} */
  describe('.create', function () {
    it('should create a new ClientSession', function () {
      return expect(Session.create(), 'to be fulfilled')
        .then((session) => {
          expect(session, 'to be a', ClientSession);
          return session;
        })
        .then((session) => Session.close(session));
    });

    it('should store newly created session', function () {
      return expect(Session.create(), 'to be fulfilled')
        .then((session) => {
          expect(Session.open, 'to contain', session);
          return session;
        })
        .then((session) => Session.close(session));
    });

    it('should fail with invalid credentials', function () {
      return Promise.all([
        // Missing username
        expect(
          sessionWithLogin({
            username: false,
            password: 'invalid password',
          }).create(),
          'to be rejected with',
          /Invalid login/
        ),

        // Missing password
        expect(
          sessionWithLogin({
            username: 'invalid username',
            password: false,
          }).create(),
          'to be rejected with',
          /Invalid login/
        ),

        // Invalid credentials
        expect(
          sessionWithLogin({
            username: 'invalid username',
            password: 'invalid password',
          }).create(),
          'to be rejected with',
          /Invalid login/
        ),
      ]);
    });

    it('should forward Client.create errors', function () {
      return expect(FailingClientSession.create(), 'to be rejected with', 'Client.create error');
    });

    it('should forward non-login errors', function () {
      return expect(FailingSession.create(), 'to be rejected with', /Client\.createSession error/);
    });
  });

  /** @test {Session.close} */
  describe('.close', function () {
    it('should fail without session', function () {
      return expect(() => Session.close(), 'to be rejected with', 'session is required');
    });

    it('should return if session is already closed', function () {
      return expect(Session.create(), 'to be fulfilled')
        .then((session) => expect(Session.close(session), 'to be fulfilled'))
        .then((session) => expect(Session.close(session), 'to be fulfilled'));
    });

    it('should wait for session to close if already closing', function () {
      return expect(Session.create(), 'to be fulfilled')
        .then((session) => {
          spy(session, 'close');
          return session;
        })
        .then((session) =>
          expect(Promise.all([Session.close(session), Session.close(session)]), 'to be fulfilled')
        )
        .then((sessions) => {
          expect(sessions, 'to have length', 2);
        });
    });

    it.skip('should warn if session does not exist', function () {
      const logListener = spy();
      Logger.on('debug', logListener);

      const session = new ClientSession();
      session._client = {
        closeSession(sess, del, callback) {
          const err = new Error();
          err.response = {
            responseHeader: {
              serviceResult: StatusCodes.BadSessionIdInvalid,
            },
          };

          callback(err);
        },
      };

      return expect(Session.close(session), 'to be fulfilled').then(() => {
        expect(logListener, 'was called once');
        expect(logListener.lastCall, 'to satisfy', [/close a session that does not exist/]);
      });
    });

    it('should do nothing if client is not connected', function () {
      return expect(Session.create(), 'to be fulfilled')
        .then(
          (session) =>
            new Promise((resolve, reject) => {
              session._client.disconnect((err) => {
                if (err) {
                  reject(err);
                } else {
                  resolve(session);
                }
              });
            })
        )
        .then((session) => expect(Session.close(session), 'to be fulfilled'));
    });

    it('should ignore errors closing session', function () {
      const session = new ClientSession();
      session._client = {
        closeSession(sess, del, callback) {
          callback(new Error('Test error'));
        },
        disconnect(cb) {
          cb();
        },
      };

      return expect(Session.close(session), 'to be fulfilled');
    });

    it('should ignore errors disconnecting client', function () {
      const session = new ClientSession();
      session._client = {
        closeSession(sess, del, callback) {
          callback(null);
        },
        disconnect(callback) {
          callback(new Error('Test client error'));
        },
      };

      return expect(Session.close(session), 'to be fulfilled');
    });
  });

  /** @test {Session.closeOpen} */
  describe('.closeOpen', function () {
    it('should return if no sessions are open', function () {
      return expect(Session.closeOpen(), 'to be fulfilled with', []);
    });

    it('should close open sessions', function () {
      expect(Session.open, 'to have length', 0);
      return expect(Session.create(), 'to be fulfilled').then((session) =>
        expect(Session.closeOpen(), 'to be fulfilled with', [session])
      );
    });

    it('should wait for opening sessions to open before closing', function () {
      let session;
      Session.create().then((sess) => (session = sess));

      return expect(Session.closeOpen(), 'to be fulfilled').then((sessions) => {
        expect(sessions, 'to equal', [session]);
      });
    });
  });
});