// transpile:mocha
import { AppiumDriver } from '../lib/appium';
import { FakeDriver } from 'appium-fake-driver';
import { BASE_CAPS, W3C_CAPS } from './helpers';
import _ from 'lodash';
import sinon from 'sinon';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { XCUITestDriver } from 'appium-xcuitest-driver';
import { IosDriver } from 'appium-ios-driver';
import { AndroidUiautomator2Driver } from 'appium-uiautomator2-driver';
import { sleep } from 'asyncbox';
import { insertAppiumPrefixes } from '../lib/utils';
chai.should();
chai.use(chaiAsPromised);
const SESSION_ID = 1;
describe('AppiumDriver', function () {
describe('AppiumDriver', function () {
function getDriverAndFakeDriver () {
const appium = new AppiumDriver({});
const fakeDriver = new FakeDriver();
const mockFakeDriver = sinon.mock(fakeDriver);
appium.getDriverAndVersionForCaps = function (/*args*/) {
return {
driver: function Driver () {
return fakeDriver;
},
version: '1.2.3',
};
};
return [appium, mockFakeDriver];
}
describe('createSession', function () {
let appium;
let mockFakeDriver;
beforeEach(function () {
[appium, mockFakeDriver] = getDriverAndFakeDriver();
});
afterEach(async function () {
mockFakeDriver.restore();
await appium.deleteSession(SESSION_ID);
});
it(`should call inner driver's createSession with desired capabilities`, async function () {
mockFakeDriver.expects('createSession')
.once().withExactArgs(BASE_CAPS, undefined, null, [])
.returns([SESSION_ID, BASE_CAPS]);
await appium.createSession(BASE_CAPS);
mockFakeDriver.verify();
});
it(`should call inner driver's createSession with desired and default capabilities`, async function () {
let defaultCaps = {deviceName: 'Emulator'};
let allCaps = _.extend(_.clone(defaultCaps), BASE_CAPS);
appium.args.defaultCapabilities = defaultCaps;
mockFakeDriver.expects('createSession')
.once().withArgs(allCaps)
.returns([SESSION_ID, allCaps]);
await appium.createSession(BASE_CAPS);
mockFakeDriver.verify();
});
it(`should call inner driver's createSession with desired and default capabilities without overriding caps`, async function () {
// a default capability with the same key as a desired capability
// should do nothing
let defaultCaps = {platformName: 'Ersatz'};
appium.args.defaultCapabilities = defaultCaps;
mockFakeDriver.expects('createSession')
.once().withArgs(BASE_CAPS)
.returns([SESSION_ID, BASE_CAPS]);
await appium.createSession(BASE_CAPS);
mockFakeDriver.verify();
});
it('should kill all other sessions if sessionOverride is on', async function () {
appium.args.sessionOverride = true;
// mock three sessions that should be removed when the new one is created
let fakeDrivers = [
new FakeDriver(),
new FakeDriver(),
new FakeDriver(),
];
let mockFakeDrivers = _.map(fakeDrivers, (fd) => sinon.mock(fd));
mockFakeDrivers[0].expects('deleteSession')
.once();
mockFakeDrivers[1].expects('deleteSession')
.once()
.throws('Cannot shut down Android driver; it has already shut down');
mockFakeDrivers[2].expects('deleteSession')
.once();
appium.sessions['abc-123-xyz'] = fakeDrivers[0];
appium.sessions['xyz-321-abc'] = fakeDrivers[1];
appium.sessions['123-abc-xyz'] = fakeDrivers[2];
let sessions = await appium.getSessions();
sessions.should.have.length(3);
mockFakeDriver.expects('createSession')
.once().withExactArgs(BASE_CAPS, undefined, null, [])
.returns([SESSION_ID, BASE_CAPS]);
await appium.createSession(BASE_CAPS);
sessions = await appium.getSessions();
sessions.should.have.length(1);
for (let mfd of mockFakeDrivers) {
mfd.verify();
}
mockFakeDriver.verify();
});
it('should call "createSession" with W3C capabilities argument, if provided', async function () {
mockFakeDriver.expects('createSession')
.once().withArgs(null, undefined, W3C_CAPS)
.returns([SESSION_ID, BASE_CAPS]);
await appium.createSession(undefined, undefined, W3C_CAPS);
mockFakeDriver.verify();
});
it('should call "createSession" with W3C capabilities argument with additional provided parameters', async function () {
let w3cCaps = {
...W3C_CAPS,
alwaysMatch: {
...W3C_CAPS.alwaysMatch,
'appium:someOtherParm': 'someOtherParm',
},
};
mockFakeDriver.expects('createSession')
.once().withArgs(null, undefined, {
alwaysMatch: {
...w3cCaps.alwaysMatch,
'appium:someOtherParm': 'someOtherParm',
},
firstMatch: [{}],
})
.returns([SESSION_ID, insertAppiumPrefixes(BASE_CAPS)]);
await appium.createSession(undefined, undefined, w3cCaps);
mockFakeDriver.verify();
});
it('should call "createSession" with JSONWP capabilities if W3C has incomplete capabilities', async function () {
let w3cCaps = {
...W3C_CAPS,
alwaysMatch: {
...W3C_CAPS.alwaysMatch,
'appium:someOtherParm': 'someOtherParm',
},
};
let jsonwpCaps = {
...BASE_CAPS,
automationName: 'Fake',
someOtherParam: 'someOtherParam',
};
let expectedW3cCaps = {
...w3cCaps,
alwaysMatch: {
...w3cCaps.alwaysMatch,
'appium:automationName': 'Fake',
'appium:someOtherParam': 'someOtherParam',
},
};
mockFakeDriver.expects('createSession')
.once().withArgs(jsonwpCaps, undefined, expectedW3cCaps)
.returns([SESSION_ID, jsonwpCaps]);
await appium.createSession(jsonwpCaps, undefined, w3cCaps);
mockFakeDriver.verify();
});
});
describe('deleteSession', function () {
let appium;
let mockFakeDriver;
beforeEach(function () {
[appium, mockFakeDriver] = getDriverAndFakeDriver();
});
afterEach(function () {
mockFakeDriver.restore();
});
it('should remove the session if it is found', async function () {
let [sessionId] = (await appium.createSession(BASE_CAPS)).value;
let sessions = await appium.getSessions();
sessions.should.have.length(1);
await appium.deleteSession(sessionId);
sessions = await appium.getSessions();
sessions.should.have.length(0);
});
it('should call inner driver\'s deleteSession method', async function () {
const [sessionId] = (await appium.createSession(BASE_CAPS)).value;
mockFakeDriver.expects('deleteSession')
.once().withExactArgs(sessionId, [])
.returns();
await appium.deleteSession(sessionId);
mockFakeDriver.verify();
// cleanup, since we faked the delete session call
await mockFakeDriver.object.deleteSession();
});
});
describe('getSessions', function () {
let appium;
let sessions;
before(function () {
appium = new AppiumDriver({});
});
afterEach(async function () {
for (let session of sessions) {
await appium.deleteSession(session.id);
}
});
it('should return an empty array of sessions', async function () {
sessions = await appium.getSessions();
sessions.should.be.an('array');
sessions.should.be.empty;
});
it('should return sessions created', async function () {
let session1 = (await appium.createSession(_.extend(_.clone(BASE_CAPS), {cap: 'value'}))).value;
let session2 = (await appium.createSession(_.extend(_.clone(BASE_CAPS), {cap: 'other value'}))).value;
sessions = await appium.getSessions();
sessions.should.be.an('array');
sessions.should.have.length(2);
sessions[0].id.should.equal(session1[0]);
sessions[0].capabilities.should.eql(session1[1]);
sessions[1].id.should.equal(session2[0]);
sessions[1].capabilities.should.eql(session2[1]);
});
});
describe('getStatus', function () {
let appium;
before(function () {
appium = new AppiumDriver({});
});
it('should return a status', async function () {
let status = await appium.getStatus();
status.build.should.exist;
status.build.version.should.exist;
});
});
describe('sessionExists', function () {
});
describe('attachUnexpectedShutdownHandler', function () {
let appium;
let mockFakeDriver;
beforeEach(function () {
[appium, mockFakeDriver] = getDriverAndFakeDriver();
});
afterEach(async function () {
await mockFakeDriver.object.deleteSession();
mockFakeDriver.restore();
appium.args.defaultCapabilities = {};
});
it('should remove session if inner driver unexpectedly exits with an error', async function () {
let [sessionId,] = (await appium.createSession(_.clone(BASE_CAPS))).value; // eslint-disable-line comma-spacing
_.keys(appium.sessions).should.contain(sessionId);
appium.sessions[sessionId].eventEmitter.emit('onUnexpectedShutdown', new Error('Oops'));
// let event loop spin so rejection is handled
await sleep(1);
_.keys(appium.sessions).should.not.contain(sessionId);
});
it('should remove session if inner driver unexpectedly exits with no error', async function () {
let [sessionId,] = (await appium.createSession(_.clone(BASE_CAPS))).value; // eslint-disable-line comma-spacing
_.keys(appium.sessions).should.contain(sessionId);
appium.sessions[sessionId].eventEmitter.emit('onUnexpectedShutdown');
// let event loop spin so rejection is handled
await sleep(1);
_.keys(appium.sessions).should.not.contain(sessionId);
});
});
describe('getDriverAndVersionForCaps', function () {
it('should not blow up if user does not provide platformName', function () {
const appium = new AppiumDriver({});
(() => { appium.getDriverAndVersionForCaps({}); }).should.throw(/platformName/);
});
it('should ignore automationName Appium', function () {
const appium = new AppiumDriver({});
const {driver} = appium.getDriverAndVersionForCaps({
platformName: 'Android',
automationName: 'Appium'
});
driver.should.be.an.instanceof(Function);
driver.should.equal(AndroidUiautomator2Driver);
});
it('should get XCUITestDriver driver for automationName of XCUITest', function () {
const appium = new AppiumDriver({});
const {driver} = appium.getDriverAndVersionForCaps({
platformName: 'iOS',
automationName: 'XCUITest'
});
driver.should.be.an.instanceof(Function);
driver.should.equal(XCUITestDriver);
});
it('should get iosdriver for ios < 10', function () {
const appium = new AppiumDriver({});
const caps = {
platformName: 'iOS',
platformVersion: '8.0',
};
let {driver} = appium.getDriverAndVersionForCaps(caps);
driver.should.be.an.instanceof(Function);
driver.should.equal(IosDriver);
caps.platformVersion = '8.1';
({driver} = appium.getDriverAndVersionForCaps(caps));
driver.should.equal(IosDriver);
caps.platformVersion = '9.4';
({driver} = appium.getDriverAndVersionForCaps(caps));
driver.should.equal(IosDriver);
caps.platformVersion = '';
({driver} = appium.getDriverAndVersionForCaps(caps));
driver.should.equal(IosDriver);
caps.platformVersion = 'foo';
({driver} = appium.getDriverAndVersionForCaps(caps));
driver.should.equal(IosDriver);
delete caps.platformVersion;
({driver} = appium.getDriverAndVersionForCaps(caps));
driver.should.equal(IosDriver);
});
it('should get xcuitestdriver for ios >= 10', function () {
const appium = new AppiumDriver({});
const caps = {
platformName: 'iOS',
platformVersion: '10',
};
let {driver} = appium.getDriverAndVersionForCaps(caps);
driver.should.be.an.instanceof(Function);
driver.should.equal(XCUITestDriver);
caps.platformVersion = '10.0';
({driver} = appium.getDriverAndVersionForCaps(caps));
driver.should.equal(XCUITestDriver);
caps.platformVersion = '10.1';
({driver} = appium.getDriverAndVersionForCaps(caps));
driver.should.equal(XCUITestDriver);
caps.platformVersion = '12.14';
({driver} = appium.getDriverAndVersionForCaps(caps));
driver.should.equal(XCUITestDriver);
});
it('should be able to handle different cases in automationName', function () {
const appium = new AppiumDriver({});
const caps = {
platformName: 'iOS',
platformVersion: '10',
automationName: 'XcUiTeSt',
};
let {driver} = appium.getDriverAndVersionForCaps(caps);
driver.should.be.an.instanceof(Function);
driver.should.equal(XCUITestDriver);
});
it('should be able to handle different case in platformName', function () {
const appium = new AppiumDriver({});
const caps = {
platformName: 'IoS',
platformVersion: '10',
};
let {driver} = appium.getDriverAndVersionForCaps(caps);
driver.should.be.an.instanceof(Function);
driver.should.equal(XCUITestDriver);
});
});
});
});
import { default as BaseDriver, errors } from '../..';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import logger from '../../lib/basedriver/logger';
import sinon from 'sinon';
const should = chai.should();
chai.use(chaiAsPromised);
describe('Desired Capabilities', function () {
let d;
beforeEach(function () {
d = new BaseDriver();
sinon.spy(logger, 'warn');
});
afterEach(function () {
logger.warn.restore();
});
it('should require platformName and deviceName', async function () {
try {
await d.createSession({});
} catch (e) {
e.should.be.instanceof(errors.SessionNotCreatedError);
e.message.should.contain('platformName');
return;
}
should.fail('error should have been thrown');
});
it('should require platformName', async function () {
try {
await d.createSession({'deviceName': 'Delorean'});
} catch (e) {
e.should.be.instanceof(errors.SessionNotCreatedError);
e.message.should.contain('platformName');
return;
}
should.fail('error should have been thrown');
});
it('should not care about cap order', async function () {
await d.createSession({
deviceName: 'Delorean',
platformName: 'iOS'
});
});
it('should check required caps which are added to driver', async function () {
d.desiredCapConstraints = {
necessary: {
presence: true
},
proper: {
presence: true,
isString: true,
inclusion: ['Delorean', 'Reventon']
}
};
try {
await d.createSession({
'platformName': 'iOS',
'deviceName': 'Delorean'
});
} catch (e) {
e.should.be.instanceof(errors.SessionNotCreatedError);
e.message.should.contain('necessary');
e.message.should.contain('proper');
return;
}
should.fail('error should have been thrown');
});
it('should check added required caps in addition to base', async function () {
d.desiredCapConstraints = {
necessary: {
presence: true
},
proper: {
presence: true,
isString: true,
inclusion: ['Delorean', 'Reventon']
}
};
try {
await d.createSession({
necessary: 'yup',
proper: 'yup, your highness'
});
} catch (e) {
e.should.be.instanceof(errors.SessionNotCreatedError);
e.message.should.contain('platformName');
return;
}
should.fail('error should have been thrown');
});
it('should accept extra capabilities', async function () {
await d.createSession({
'platformName': 'iOS',
'deviceName': 'Delorean',
'extra': 'cheese',
'hold the': 'sauce'
});
});
it('should log the use of extra caps', async function () {
this.timeout(500);
await d.createSession({
'platformName': 'iOS',
'deviceName': 'Delorean',
'extra': 'cheese',
'hold the': 'sauce'
});
logger.warn.callCount.should.be.above(0);
});
it('should be sensitive to the case of caps', async function () {
try {
await d.createSession({
'platformname': 'iOS',
'deviceName': 'Delorean'
});
} catch (e) {
e.should.be.instanceof(errors.SessionNotCreatedError);
e.message.should.contain('platformName');
return;
}
should.fail('error should have been thrown');
});
describe('boolean capabilities', function () {
it('should allow a string "false"', async function () {
await d.createSession({
'platformName': 'iOS',
'deviceName': 'Delorean',
'noReset': 'false'
});
logger.warn.callCount.should.be.above(0);
let sessions = await d.getSessions();
sessions[0].capabilities.noReset.should.eql(false);
});
it('should allow a string "true"', async function () {
await d.createSession({
'platformName': 'iOS',
'deviceName': 'Delorean',
'noReset': 'true'
});
logger.warn.callCount.should.be.above(0);
let sessions = await d.getSessions();
sessions[0].capabilities.noReset.should.eql(true);
});
it('should allow a string "true" in string capabilities', async function () {
await d.createSession({
'platformName': 'iOS',
'deviceName': 'Delorean',
'language': 'true'
});
logger.warn.callCount.should.equal(0);
let sessions = await d.getSessions();
sessions[0].capabilities.language.should.eql('true');
});
});
describe('number capabilities', function () {
it('should allow a string "1"', async function () {
await d.createSession({
'platformName': 'iOS',
'deviceName': 'Delorean',
'newCommandTimeout': '1'
});
logger.warn.callCount.should.be.above(0);
let sessions = await d.getSessions();
sessions[0].capabilities.newCommandTimeout.should.eql(1);
});
it('should allow a string "1.1"', async function () {
await d.createSession({
'platformName': 'iOS',
'deviceName': 'Delorean',
'newCommandTimeout': '1.1'
});
logger.warn.callCount.should.be.above(0);
let sessions = await d.getSessions();
sessions[0].capabilities.newCommandTimeout.should.eql(1.1);
});
it('should allow a string "1" in string capabilities', async function () {
await d.createSession({
'platformName': 'iOS',
'deviceName': 'Delorean',
'language': '1'
});
logger.warn.callCount.should.equal(0);
let sessions = await d.getSessions();
sessions[0].capabilities.language.should.eql('1');
});
});
it ('should error if objects in caps', async function () {
try {
await d.createSession({
'platformName': {a: 'iOS'},
'deviceName': 'Delorean'
});
} catch (e) {
e.should.be.instanceof(errors.SessionNotCreatedError);
e.message.should.contain('platformName');
return;
}
should.fail('error should have been thrown');
});
it('should check for deprecated caps', async function () {
this.timeout(500);
d.desiredCapConstraints = {
'lynx-version': {
deprecated: true
}
};
await d.createSession({
'platformName': 'iOS',
'deviceName': 'Delorean',
'lynx-version': 5
});
logger.warn.callCount.should.be.above(0);
});
it('should not warn if deprecated=false', async function () {
this.timeout(500);
d.desiredCapConstraints = {
'lynx-version': {
deprecated: false
}
};
await d.createSession({
'platformName': 'iOS',
'deviceName': 'Delorean',
'lynx-version': 5
});
logger.warn.callCount.should.equal(0);
});
it('should not validate against null/undefined caps', async function () {
d.desiredCapConstraints = {
'foo': {
isString: true
}
};
await d.createSession({
platformName: 'iOS',
deviceName: 'Dumb',
foo: null
});
await d.deleteSession();
await d.createSession({
platformName: 'iOS',
deviceName: 'Dumb',
foo: 1
}).should.eventually.be.rejectedWith(/was not valid/);
await d.createSession({
platformName: 'iOS',
deviceName: 'Dumb',
foo: undefined
});
await d.deleteSession();
await d.createSession({
platformName: 'iOS',
deviceName: 'Dumb',
foo: ''
});
await d.deleteSession();
});
it('should still validate null/undefined/empty caps whose presence is required', async function () {
d.desiredCapConstraints = {
foo: {
presence: true
},
};
await d.createSession({
platformName: 'iOS',
deviceName: 'Dumb',
foo: null
}).should.eventually.be.rejectedWith(/blank/);
await d.createSession({
platformName: 'iOS',
deviceName: 'Dumb',
foo: ''
}).should.eventually.be.rejectedWith(/blank/);
await d.createSession({
platformName: 'iOS',
deviceName: 'Dumb',
foo: {}
}).should.eventually.be.rejectedWith(/blank/);
await d.createSession({
platformName: 'iOS',
deviceName: 'Dumb',
foo: []
}).should.eventually.be.rejectedWith(/blank/);
await d.createSession({
platformName: 'iOS',
deviceName: 'Dumb',
foo: ' '
}).should.eventually.be.rejectedWith(/blank/);
});
describe('w3c', function () {
it('should accept w3c capabilities', async function () {
const [sessionId, caps] = await d.createSession(null, null, {
alwaysMatch: {
platformName: 'iOS',
deviceName: 'Delorean'
}, firstMatch: [{}],
});
sessionId.should.exist;
caps.should.eql({
platformName: 'iOS',
deviceName: 'Delorean',
});
await d.deleteSession();
});
it('should ignore w3c capabilities if it is not a plain JSON object', async function () {
for (let val of [true, 'string', [], 100]) {
const [sessionId, caps] = await d.createSession({
platformName: 'iOS',
deviceName: 'Delorean'
}, null, val);
sessionId.should.exist;
caps.should.eql({
platformName: 'iOS',
deviceName: 'Delorean',
});
await d.deleteSession();
}
});
});
});
var assert = require("assert");
var sinon = require("sinon");
var path = require("path");
var fs = require("fs");
var connector;
var Connector;
var noop = function () {};
describe("index.js", function () {
var winston = {
clear: sinon.stub(),
add: sinon.stub(),
info: sinon.stub(),
debug: sinon.stub(),
warn: sinon.stub(),
error: sinon.stub(),
verbose: sinon.stub(),
transports: { Console: {} }
};
beforeEach(function () {
winston.clear.reset();
winston.add.reset();
winston.info.reset();
winston.debug.reset();
winston.warn.reset();
winston.error.reset();
winston.verbose.reset();
});
describe("class", function () {
describe("before module was initialied", function () {
it("should fail", function () {
try {
require.uncache("../index.js");
connector = require("../index.js");
Connector = connector.Connector;
new Connector();
assert.fail("should not reach here!");
} catch (e) {
assert.ok(e instanceof Error);
assert.equal(e.message, "'kido-connector' was not initialized.");
}
});
});
describe("after module was initialized.", function () {
var createSession = sinon.stub();
var refreshSession = sinon.stub();
var disposeSession = sinon.stub();
var opts;
before(function () {
require.uncache("../index.js");
connector = require("../index.js");
Connector = connector.Connector;
connector.init("test", winston);
});
beforeEach(function () {
createSession.reset();
refreshSession.reset();
disposeSession.reset();
opts = {
};
});
describe("constructor", function () {
it("should fail on invalid opts", function () {
[
0,
"",
"foo",
true,
[]
].forEach(function (opts) {
try {
new Connector(opts);
assert.fail("should not reach here!");
} catch (e) {
assert.ok(e instanceof Error);
assert.equal(e.message, "'opts' argument must be an object instance.");
}
});
});
it("should fail on invalid opts.config.", function () {
[
10,
"foo",
false,
true,
[]
]
.forEach(function (config) {
try {
opts.config = config;
new Connector(opts);
assert.fail("should not reach here!");
} catch (e) {
assert.ok(e instanceof Error);
assert.equal(e.message, "'opts.config' property is missing or invalid.");
}
});
});
it("should fail on invalid opts.config.timeout.", function () {
[
10,
"1000",
[],
{}
]
.forEach(function (value) {
try {
opts.config = { timeout: value };
new Connector(opts);
assert.fail("should not reach here!");
} catch (e) {
assert.ok(e instanceof Error);
assert.equal(e.message, "'opts.config.timeout' property must be a number equal or greater than 100.");
}
});
});
it("should fail on invalid opts.credentialProps", function () {
[
0,
"",
"foo",
true,
{}
].forEach(function (credentialProps) {
try {
opts.credentialProps = credentialProps;
new Connector(opts);
assert.fail("should not reach here!");
} catch (e) {
assert.ok(e instanceof Error);
assert.equal(e.message, "'opts.credentialProps' property must be an array of strings.");
}
});
});
it("should fail on invalid opts.createSessionCb", function () {
[
0,
"",
"foo",
true,
{},
[]
].forEach(function (createSession) {
try {
opts.createSessionCb = createSession;
new Connector(opts);
assert.fail("should not reach here!");
} catch (e) {
assert.ok(e instanceof Error);
assert.equal(e.message, "'opts.createSessionCb' property is missing or invalid.");
}
});
});
it("should fail on invalid opts.disposeSession", function () {
[
1,
"foo",
true,
{},
[]
].forEach(function (disposeSession) {
try {
opts.disposeSessionCb = disposeSession;
new Connector(opts);
assert.fail("should not reach here!");
} catch (e) {
assert.ok(e instanceof Error);
assert.equal(e.message, "'opts.disposeSessionCb' property must be a function.");
}
});
});
it("should fail on invalid opts.refreshSession", function () {
[
1,
"foo",
true,
{},
[]
].forEach(function (refreshSession) {
try {
opts.refreshSessionCb = refreshSession;
new Connector(opts);
assert.fail("should not reach here!");
} catch (e) {
assert.ok(e instanceof Error);
assert.equal(e.message, "'opts.refreshSessionCb' property must be a function.");
}
});
});
it("should be able to create an instance.", function (done) {
var c = new Connector(opts);
assert.ok(c instanceof Connector);
done();
});
});
describe("getSession", function () {
beforeEach(function () {
opts.credentialProps = ["username", "password"];
opts.createSessionCb = createSession;
opts.refreshSessionCb = refreshSession;
opts.disposeSessionCb = disposeSession;
});
it("should fail if no options argument was passed on.", function (done) {
var values = [
null,
undefined,
true,
1000,
"",
"foo"
];
var c = new Connector(opts);
values.forEach(function (value) {
c.getSession(value, function (err, data) {
assert.ok(err instanceof Error);
assert.equal(err.message, "'options' argument must be an object instance.");
if (value===values[values.length-1]) done();
});
});
});
it("should fail when the options that was passed on is not an object instance.", function (done) {
var values = [
"",
true,
1000,
null,
undefined,
[]
];
var c = new Connector(opts);
values.forEach(function (value) {
c.getSession(value, function (err, data) {
assert.ok(err instanceof Error);
assert.equal(err.message, "'options' argument must be an object instance.");
if (value===values[values.length-1]) done();
});
});
});
it("should return any error returned by createSessionCb callback.", function (done) {
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, "some error");
c.getSession({ username: "mike" }, function (err, data) {
assert.ok(createSession.calledOnce);
assert.ok(!data);
assert.equal(err, "some error");
done();
});
});
it("should return an error if an invalid 'auth' token was passed on.", function (done) {
var c = new Connector(opts);
c.getSession({ auth: "un existing token" }, function (err, data) {
assert.ok(!data);
assert.ok(err instanceof Error);
assert.equal(err.message, "Invalid 'auth' token.");
done();
});
});
it("should work when valid arguments", function (done) {
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, "token");
c.getSession({ username: "mike" }, function (err, data) {
assert.ok(createSession.calledOnce);
assert.ok(data);
assert.equal(data, "token");
done();
});
});
it("should only call createSession once when valid arguments", function (done) {
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, "token");
c.getSession({ username: "mike" }, function (err, data) {
assert.ok(!err);
c.getSession({ username: "mike" }, function (err, data) {
assert.ok(createSession.calledOnce);
done();
});
});
});
it("should ignore cache if 'ignoreCache' option is passed", function (done) {
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, "token");
createSession.onCall(1).callsArgWith(1, null, "new-token");
c.getSession({ username: "mike" }, function (err, data) {
assert.ok(!err);
c.getSession({ username: "mike", ignoreCache: true }, function (err, data) {
assert.ok(!err);
assert.equal(data, "new-token");
done();
});
});
});
describe("When cache are disabled", function () {
it("should invoke createSessionCb always", function (done) {
opts.disableCache = true;
delete opts.refreshSessionCb;
delete opts.disposeSessionCb;
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, { auth: "custom" });
createSession.onCall(1).callsArgWith(1, null, { auth: "custom2" });
c.getSession( {}, function (err, session, auth) {
assert.ok(createSession.calledOnce);
assert.ok(!err);
assert.equal(session.auth, "custom");
assert.equal(auth, "custom");
c.getSession( {}, function (err, session, auth) {
assert.ok(createSession.calledTwice);
assert.ok(!err);
assert.equal(session.auth, "custom2");
assert.equal(auth, "custom2");
done();
});
});
});
it("should fail if session doesn't have 'auth' prop", function (done) {
opts.disableCache = true;
delete opts.refreshSessionCb;
delete opts.disposeSessionCb;
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, {});
c.getSession( {}, function (err, session, auth) {
assert.ok(createSession.calledOnce);
assert.ok(err instanceof Error);
assert.equal(err.message, "session doesn't have 'auth' property.");
done();
});
});
});
describe("When an user hasn't a session.", function () {
describe("and user credentials were not configured", function () {
it("should invoke createSessionCb callback.", function (done) {
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, "custom");
c.getSession({ }, function (err, session, auth) {
assert.ok(createSession.calledOnce);
assert.ok(!err);
assert.equal(session, "custom");
assert.ok(auth);
done();
});
});
});
describe("and user credentials were configured", function () {
beforeEach(function () {
opts.config = { username: "foo", password: "bar" };
});
it("should use configured credentials when no credentials were passed on.", function (done) {
var c = new Connector(opts);
c.getSession({}, function (err, session, auth) {
assert.ok(!err);
assert.ok(session);
assert.ok(auth);
assert.ok(createSession.calledOnce);
var args = createSession.getCall(0).args;
assert.deepEqual(args[0], { username: "foo", password: "bar" });
done();
});
});
it("should use passed on credentials.", function (done) {
var c = new Connector(opts);
c.getSession({ username: "alfa", password: "beta"}, function (err, session, auth) {
assert.ok(!err);
assert.ok(session);
assert.ok(auth);
assert.ok(createSession.calledOnce);
var args = createSession.getCall(0).args;
assert.deepEqual(args[0], { username: "alfa", password: "beta" });
done();
});
});
});
});
describe("When a user has an existing session.", function () {
it("should return data from cache if a valid auth token was passed on.", function (done) {
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, "custom");
c.getSession({ username: "mike", password: "1234" }, function (err, session, auth) {
assert.ok(!err);
assert.ok(session);
assert.ok(auth);
c.getSession({ auth: auth }, function (err, session2, auth2) {
assert.ok(!refreshSession.called);
assert.ok(!err);
assert.equal(session, session2);
assert.equal(auth, auth2);
done();
});
});
});
it("should return data from cache if a valid username and password were passed on.", function (done) {
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, "custom");
c.getSession({ username: "mike", password: "1234" }, function (err, session, auth) {
assert.ok(!err);
assert.ok(session);
assert.ok(auth);
c.getSession({ username: "mike", password: "1234" }, function (err, session2, auth2) {
assert.ok(!err);
assert.equal(session, session2);
assert.equal(auth, auth2);
done();
});
});
});
it("should fail if a valid username but wrong password were passed on.", function (done) {
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, "custom");
createSession.onCall(1).callsArgWith(1, "some error");
c.getSession({ username: "mike", password: "1234" }, function (err, session, auth) {
assert.ok(!err);
assert.ok(session);
assert.ok(auth);
c.getSession({ username: "mike", password: "5678" }, function (err, session2, auth2) {
assert.ok(!session2);
assert.ok(!auth2);
assert.equal(err, "some error");
// valiate createSession calls' arguments
assert.deepEqual(createSession.getCall(0).args[0], { username: "mike", password: "1234" });
assert.deepEqual(createSession.getCall(1).args[0], { username: "mike", password: "5678" });
done();
});
});
});
describe("And module was initialized with a 'refreshSessionCb' callback", function () {
it("should return any error returned by refreshSession callback.", function (done) {
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, "custom", new Date());
c.getSession({ username: "mike" }, function (err, session, auth) {
assert.ok(!err);
assert.ok(session);
assert.ok(auth);
// refresh authentication and returns new metadata and new expiration
refreshSession.onCall(0).callsArgWith(1, "some error");
// wait until token expires
setTimeout(function () {
// token is expired, a new getSession must refresh the token
c.getSession({ auth: auth }, function (err, session2, auth2) {
assert.ok(refreshSession.calledOnce);
assert.ok(!session2);
assert.ok(!auth2);
assert.equal(err, "some error");
done();
});
}, 20);
});
});
it("should invoke 'refreshSession' callback when auth token is expired.", function (done) {
var firstDate = new Date();
var secondDate = new Date(firstDate.getTime() + 5000);
// authenticates user and returns metadata and expiration
// firstDate is an expired time
createSession.onCall(0).callsArgWith(1, null, "custom", firstDate);
// refresh authentication and returns new metadata and new expiration
refreshSession.onCall(0).callsArgWith(1, null, "custom2", secondDate);
var c = new Connector(opts);
// first getSession users
c.getSession({ username: "mike", password: "1234" }, function (err, session, auth) {
assert.ok(!err);
assert.ok(auth);
assert.equal(session, "custom");
// wait until token expires
setTimeout(function () {
// token is expired, a new getSession must refresh the token
c.getSession({ auth: auth }, function (err, session2, auth2) {
assert.ok(!err);
// auth token must remain the same one
assert.equal(auth2, auth);
// the session must be the new one
assert.equal(session2, "custom2");
// Refresh method must be invoked once.
assert.ok(refreshSession.calledOnce);
// The refresh method must recieve:
var args = refreshSession.getCall(0).args;
assert.equal(args[0], session);
done();
});
}, 20);
});
});
it("should return data from cache if auth token is not expired", function (done) {
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, "custom", new Date(new Date().getTime() + 5000));
c.getSession({ username: "mike" }, function (err, session, auth) {
assert.ok(createSession.calledOnce);
assert.ok(!err);
assert.ok(auth);
assert.equal(session, "custom");
c.getSession({ auth: auth }, function (err, session2, auth2) {
assert.ok(!refreshSession.hcalled);
assert.ok(!err);
assert.equal(session, session2);
assert.equal(auth, auth2);
done();
});
});
});
it("should fail when 'createSessionCb' callback returns a token expiration that is not of type Date.", function (done) {
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, "custom", "invalid expiration time");
c.getSession({ username: "mike" }, function (err, session, auth) {
assert.ok(createSession.calledOnce);
assert.ok(!session);
assert.ok(!auth);
assert.ok(err instanceof Error);
assert.equal(err.message, "Create session aborted.");
assert.equal(err.description, "When 'createSessionCb' callback returns an 'expire' argument. It must be of type Date.");
done();
});
});
it("should invoke 'createSessionCb' callback when 'refreshSessionCb' does not return a new session.", function (done) {
var firstDate = new Date();
var secondDate = new Date(firstDate.getTime() + 5000);
// authenticates user and returns session and expiration
// firstDate is an expired time
createSession.onCall(0).callsArgWith(1, null, "custom", firstDate);
// authenticates user and returns sesison and expiration
createSession.onCall(1).callsArgWith(1, null, "custom2", secondDate);
// refresh authentication does NOT return a new session
refreshSession.onCall(0).callsArgWith(1, null, null);
var c = new Connector(opts);
// first getSession users
c.getSession({ username: "mike", password: "1234" }, function (err, session, auth) {
assert.ok(!err);
assert.ok(auth);
assert.equal(session, "custom");
// wait until token expires
setTimeout(function () {
// token is expired, a new getSession must refresh the token
c.getSession({ auth: auth }, function (err, session2, auth2) {
assert.ok(!err);
// auth token must remain the same one
assert.equal(auth2, auth);
// the session must be the new one
assert.equal(session2, "custom2");
// Refresh method must be invoked once.
assert.ok(refreshSession.calledOnce);
// Create method must be invoked twice.
assert.ok(createSession.calledTwice);
// The refresh method must recieve:
var args = refreshSession.getCall(0).args;
assert.equal(args[0], session);
done();
});
}, 20);
});
});
});
describe("And module was initialized with a 'disposeSession' callback", function () {
it("should invoke disposeSession when an item timeouts", function (done) {
var timeout = 100;
opts.config = { timeout: timeout };
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, "custom");
disposeSession.onCall(0).callsArg(1);
c.getSession({ username: "mike" }, function (err, session, auth) {
assert.ok(!err);
assert.ok(auth);
assert.equal(session, "custom");
});
setTimeout(function () {
assert.ok(disposeSession.calledOnce);
var args = disposeSession.getCall(0).args;
assert.equal(args.length, 2);
assert.equal(args[0], "custom");
assert.equal(typeof args[1], "function");
done();
}, timeout + 20);
});
});
});
});
describe("close", function () {
beforeEach(function () {
opts.credentialProps = ["username", "password"];
opts.createSessionCb = createSession;
opts.refreshSessionCb = refreshSession;
opts.disposeSessionCb = disposeSession;
});
it("should no fail if there aren't any session.", function (done) {
var c = new Connector(opts);
c.close(done);
});
it("should no fail if caching is disabled.", function (done) {
opts.disableCache = true;
var c = new Connector(opts);
c.close(done);
});
it("should invoke 'disposeSession' callback for each session", function (done) {
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, "custom");
createSession.onCall(1).callsArgWith(1, null, "custom2");
disposeSession.onCall(0).callsArgWith(1);
disposeSession.onCall(1).callsArgWith(1);
c.getSession({ username: "mike" }, function (err, session, auth) {
assert.ok(createSession.calledOnce);
assert.ok(!err);
assert.equal(session, "custom");
assert.ok(auth);
c.getSession({ username: "john" }, function (err, session2, auth2) {
assert.ok(createSession.calledTwice);
assert.ok(!err);
assert.equal(session2, "custom2");
assert.ok(auth2);
c.close(function () {
assert.ok(disposeSession.calledTwice);
assert.equal(disposeSession.getCall(0).args[0], session);
assert.equal(disposeSession.getCall(1).args[0], session2);
done();
});
});
});
});
});
});
describe("after module was initialized without disposeSession", function () {
var createSession = sinon.stub();
var disposeSession = sinon.stub();
var refreshSession = sinon.stub();
var opts;
before(function () {
require.uncache("../index.js");
connector = require("../index.js");
Connector = connector.Connector;
connector.init("test", winston);
});
beforeEach(function () {
createSession.reset();
disposeSession.reset();
refreshSession.reset();
opts = {
credentialProps: ["username", "password"],
createSessionCb: createSession,
refreshSessionCb: refreshSession
};
});
describe("getSession", function () {
describe("When a user has an existing session.", function () {
it("should not fail when a cache item expires by timeout", function (done) {
var expectedAuth = null;
var timeout = 100;
opts.config = { timeout: timeout };
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, "custom");
createSession.onCall(1).callsArgWith(1, null, "custom");
c.getSession({ username: "mike" }, function (err, session, auth) {
assert.ok(!err);
assert.ok(auth);
assert.equal(session, "custom");
expectedAuth = auth;
});
setTimeout(function () {
// getSession again
c.getSession({ username: "mike" }, function (err, session2, auth2) {
assert.ok(!err);
assert.equal(session2, "custom");
assert.ok(expectedAuth !== auth2);
done();
});
}, timeout + 20);
});
});
});
describe("close", function () {
it("should not fail", function (done) {
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, "custom");
c.getSession({ username: "mike" }, function (err, session, auth) {
assert.ok(createSession.calledOnce);
assert.ok(!err);
assert.equal(session, "custom");
assert.ok(auth);
c.close(function () {
assert.ok(!disposeSession.called);
done();
});
});
});
it("should not fail when caching is disabled", function (done) {
opts.disableCache = true;
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, { auth: "custom" });
c.getSession({ username: "mike" }, function (err, session, auth) {
assert.ok(createSession.calledOnce);
assert.ok(!err);
assert.equal(session.auth, "custom");
assert.equal(auth, "custom");
c.close(function () {
assert.ok(disposeSession.notCalled);
done();
});
});
});
});
});
describe("after module was initialized without refreshSession", function () {
var createSession = sinon.stub();
var disposeSession = sinon.stub();
var refreshSession = sinon.stub();
var opts = {};
before(function () {
require.uncache("../index.js");
connector = require("../index.js");
Connector = connector.Connector;
connector.init("test", winston);
});
beforeEach(function () {
createSession.reset();
disposeSession.reset();
refreshSession.reset();
opts = {
credentialProps: ["username", "password"],
createSessionCb: createSession,
disposeSessionCb: disposeSession
};
});
describe("getSession", function () {
describe("When a user has an existing session.", function () {
it("should fail when 'createSessionCb' callback returns a token's expiration.", function (done) {
var c = new Connector(opts);
createSession.onCall(0).callsArgWith(1, null, "custom", new Date(new Date().getTime() + 5000));
c.getSession({ username: "mike" }, function (err, session, auth) {
assert.ok(createSession.calledOnce);
assert.ok(!auth);
assert.ok(!session);
assert.ok(err instanceof Error);
assert.equal(err.message, "Create session aborted.");
assert.equal(err.description, "When 'createSessionCb' callback returned an 'expire' argument but not 'refreshSessionCb' callback was initialized.");
done();
});
});
});
});
});
});
describe("init", function () {
var dir = path.resolve(process.cwd(), "./logs/");
beforeEach(function () {
if (fs.existsSync(dir)) fs.rmdirSync(dir);
});
it("should fail if no label", function () {
try {
require.uncache("../index.js");
connector = require("../index.js");
Connector = connector.Connector;
connector.init();
assert.fail("should not reach here!");
} catch (e) {
assert.ok(e instanceof Error);
assert.equal(e.message, "'label' argument is missing.");
}
});
it("should fail if no winston", function () {
try {
require.uncache("../index.js");
connector = require("../index.js");
Connector = connector.Connector;
connector.init("foo");
assert.fail("should not reach here!");
} catch (e) {
assert.ok(e instanceof Error);
assert.equal(e.message, "'winston' argument is missing.");
}
});
it("should work", function () {
require.uncache("../index.js");
connector = require("../index.js");
Connector = connector.Connector;
connector.init("test", winston, noop);
});
it("should fail when is initialized by a second time", function () {
require.uncache("../index.js");
connector = require("../index.js");
Connector = connector.Connector;
connector.init("test", winston, noop);
try {
connector.init("test", winston, noop);
assert.fail("Did not fail.");
} catch (e) {
assert.ok(e instanceof Error);
assert.equal(e.message, "Can't be initialized twice.");
}
});
it("should work with --level", function () {
require.uncache("../index.js");
process.argv.push("--level");
process.argv.push("debug");
connector = require("../index.js");
connector.init("test", winston, noop);
});
it("'timestamp' winston option must be a valid function.", function () {
require.uncache("../index.js");
process.argv.push("--level");
process.argv.push("debug");
connector = require("../index.js");
connector.init("test", winston);
assert.ok(winston.add.calledTwice);
var args = winston.add.getCall(0).args;
var timestamp = args[1].timestamp;
assert.equal(typeof timestamp, "function");
assert.ok(timestamp());
});
it("should create log folder.", function () {
require.uncache("../index.js");
process.argv.push("--level");
process.argv.push("debug");
connector = require("../index.js");
connector.init("test", winston);
assert.ok(fs.existsSync(dir));
});
it("should not fail id log folder aready exists.", function () {
fs.mkdirSync(dir);
assert.ok(fs.existsSync(dir));
require.uncache("../index.js");
process.argv.push("--level");
process.argv.push("debug");
connector = require("../index.js");
connector.init("test", winston);
assert.ok(fs.existsSync(dir));
});
});
describe("isHostAllowed", function () {
before(function () {
process.env.RUNNING_ON = "hub";
connector = require("../index.js");
});
it("Should fail with no host", function (done) {
connector.isHostAllowed(function (err, allowed) {
assert.ok(!allowed);
assert.ok(err);
assert.strictEqual(err.message, "host parameter is mandatory");
done();
});
});
it("Should fail with invalid host type", function (done) {
connector.isHostAllowed(123, function (err, allowed) {
assert.ok(!allowed);
assert.ok(err);
assert.strictEqual(err.message, "host must be a string");
done();
});
});
it("Should fail with missing cb host type", function (done) {
connector.isHostAllowed(123, function (err, allowed) {
assert.ok(!allowed);
assert.ok(err);
assert.strictEqual(err.message, "host must be a string");
done();
});
});
it("Should fail with invalid host", function (done) {
connector.isHostAllowed("INVALID", function (err, allowed) {
assert.ok(!allowed);
assert.ok(err);
assert.strictEqual(err.message, "getaddrinfo ENOTFOUND");
done();
});
});
it("Should return invalid with loopback host ipv4", function (done) {
connector.isHostAllowed("localhost", function (err, allowed) {
assert.ok(!err);
assert.ok(!allowed);
done();
});
});
it("Should return invalid with loopback ip ipv4", function (done) {
connector.isHostAllowed("127.0.0.3", function (err, allowed) {
assert.ok(!err);
assert.ok(!allowed);
done();
});
});
it("Should return invalid with loopback host ipv6", function (done) {
connector.isHostAllowed("::1", function (err, allowed) {
assert.ok(!err);
assert.ok(!allowed);
done();
});
});
it("Should return invalid with internal host ipv4", function (done) {
connector.isHostAllowed("10.0.1.10", function (err, allowed) {
assert.ok(!err);
assert.ok(!allowed);
done();
});
});
it("Should return invalid with internal host ipv6", function (done) {
connector.isHostAllowed("fe80::6267:20ff:fe22:4928", function (err, allowed) {
assert.ok(!err);
assert.ok(!allowed);
done();
});
});
it("Should work with valid hostname", function (done) {
connector.isHostAllowed("www.google.com", function (err, allowed) {
assert.ok(!err);
assert.ok(allowed);
done();
});
});
it("Should work with valid IP", function (done) {
connector.isHostAllowed("64.233.186.147", function (err, allowed) {
assert.ok(!err);
assert.ok(allowed);
done();
});
});
it("Should work when not running on hub", function (done) {
process.env.RUNNING_ON = "agent";
connector.isHostAllowed("localhost", function (err, allowed) {
assert.ok(!err);
assert.ok(allowed);
process.env.RUNNING_ON = "hub";
done();
});
});
});
});
/**
* Removes a module from the cache
*/
require.uncache = function (moduleName) {
// Run over the cache looking for the files
// loaded by the specified module name
require.searchCache(moduleName, function (mod) {
delete require.cache[mod.id];
});
};
/**
* Runs over the cache to search for all the cached
* files
*/
require.searchCache = function (moduleName, callback) {
// Resolve the module identified by the specified name
var mod = require.resolve(moduleName);
// Check if the module has been resolved and found within
// the cache
if (mod && ((mod = require.cache[mod]) !== undefined)) {
// Recursively go over the results
(function run(mod) {
// Go over each of the module's children and
// run over it
mod.children.forEach(function (child) {
run(child);
});
// Call the specified callback providing the
// found module
callback(mod);
})(mod);
}
};