fortress
Version:
Fortress is designed for the defense of client side applications.
473 lines (372 loc) • 13.6 kB
JavaScript
/*globals suite, test*/
;
var EventEmitter = require('eventemitter3')
, Fortress = require('./index.js')
, Container = Fortress.Container
, assert = require('assert')
, Images = Fortress.Image;
window.mocha.checkLeaks = false;
//
// Fixtures.
//
var fixture = {
console: 'console.log("foo");'
, blocking: 'alert("foo"); confirm("bar"); prompt("baz");'
, throws: 'throw new Error("error thrown")'
, freeze: 'while(true) { if (window.bar) break; }'
, recursive: 'setTimeout(function () { function x(a) { foo(a++) } function foo(a) { x(a++) } x(10);}, 500)'
};
describe('Fortress', function () {
var fort;
beforeEach(function () {
fort = new Fortress();
});
afterEach(function () {
fort.destroy();
});
it('is a function', function () {
assert.equal(typeof Fortress, 'function');
});
it('inherits from EventEmitter3', function () {
assert.ok(fort instanceof EventEmitter);
});
it('should not throw an error when a new instance is created', function () {
assert.ok(fort instanceof Fortress);
});
it('should create a new fortress instance if its not created using new', function () {
var fortress = Fortress // This is just here to please my JSHint preferences
, fort = fortress();
assert.ok(fort instanceof Fortress, 'should be an instance of Fortress');
fort.destroy();
});
describe('#id', function () {
var iterations = 1000;
it('should generate a js variable compatible id', function () {
assert.ok(!!~fort.id().indexOf('fortress'), 'id should be prefixed');
for (var i = 0; i < iterations; i++) {
var x = new Function('var '+ fort.id() + ' = 10; return 0;');
assert.equal(x(), 0, 'should return the compiled functions result');
}
});
it('should generate unique ids', function () {
var ids = {};
for (var i = 0; i < iterations; i++) {
var id = fort.id();
assert.ok(!(id in ids), 'id should be unique');
ids[id] = 1;
}
});
});
describe('#get', function () {
it('returns undefined when the container is not found', function () {
assert.equal(fort.get('foo'), undefined, 'This does not exist');
});
it('returns the container when given the correct id', function () {
var container = fort.create();
assert.ok(container instanceof Fortress.Container);
assert.ok(container === fort.get(container.id));
});
});
describe('#globals', function () {
it('detects the introduced `foo` global', function () {
var current = fort.globals();
assert.ok(current.length > 0, 'We should have introduced atleast one global');
window.foo = 'bar';
var introduced = fort.globals(current);
assert.equal(introduced.length, 1, 'we introduced one global');
assert.equal(introduced[0], 'foo', 'the global has the name foo');
// Remove global.
try { delete window.foo; }
catch (e) {}
});
});
describe('#all', function () {
it('returns an empty array', function () {
assert.equal(fort.all().length, 0, 'no length as we have no containers');
});
it('returns array with container instances', function () {
var container = fort.create()
, all = fort.all();
assert.ok(container instanceof Fortress.Container);
assert.equal(all.length, 1, 'we only created 1 container');
assert.equal(all[0], container, 'fort.create returns container');
});
});
describe('#create', function () {
describe('with code', function () {
it('runs our console code', function () {
var container = fort.create(fixture.console);
});
});
});
});
describe('Container', function () {
var container
, id = 0;
beforeEach(function () {
container = new Container(document.body, 'test_'+ id++);
container.on('error', function (err) {
if (typeof console !== 'undefined') {
console.log('('+ container.id +') error:', err.message, err.scope, err);
} else {
alert('error: '+ err.message +', scope:'+ err.scope);
}
});
});
afterEach(function () {
container.destroy();
});
it('inherits from EventEmitter3', function () {
assert.ok(container instanceof EventEmitter);
});
it('emits an `start` event when the container is started', function (done) {
container.on('start', done);
container.load(fixture.console).start();
});
it('emits an `stop` event when the container is stopped', function (done) {
container.on('start', function () {
container.stop();
});
container.on('stop', done);
container.load(fixture.console).start();
});
it('sets the correct readyStates', function (done) {
assert.equal(container.readyState, Container.CLOSED, 'not started, readyState is CLOSED');
container.on('start', function () {
assert.equal(container.readyState, Container.OPEN, 'readyState is OPEN after a start');
container.on('stop', function () {
assert.equal(container.readyState, Container.CLOSED, 'readyState is CLOSED');
done();
}).stop();
assert.equal(container.readyState, Container.CLOSING, 'readystate is CLOSING after stopping');
});
//
// This is a sync call.
//
container.load(fixture.console).start();
assert.equal(container.readyState, Container.OPENING, 'readyState OPENING, weve started');
});
describe('#ping', function () {
it('sets a new ping timeout for the given timeout', function (done) {
container.timeout = 100;
container.on('error', function () {
throw new Error('I shouldnt not error');
}).ping();
setTimeout(function () {
container.ping();
}, 90);
setTimeout(function () {
container.stop();
done();
}, 150);
});
it('produces an error when the iframe times out', function (done) {
container.timeout = 40;
var start = new Date();
container.on('error', function (e) {
var end = new Date() - start;
assert.ok(end >= 40, 'should have taken longer than 40 ms');
assert.ok(end < 100, 'and less then 100');
assert.ok(e instanceof Error, 'instance of');
assert.ok(e.message.indexOf('iframe') > -1, 'correct message');
done();
});
container.ping();
});
});
describe('#retry', function () {
it('honors the retries limit', function (done) {
var retries = 0;
container.retries = 4;
container.on('retry', function () {
retries++;
});
container.once('end', function () {
assert.equal(retries, 4);
done();
});
container.load(fixture.throws).start();
});
it('emits `retry` after each attempt');
it('recreates the iframe on the last retry');
it('emits `end` after the last retry');
});
describe('#inspect', function () {
it('can inspect a running container', function (done) {
container.on('start', function () {
container.inspect();
done();
}).load(fixture.console).start();
});
it('returns the stats', function (done) {
var start = +new Date();
container.on('start', function () {
var stats = container.inspect()
, end = (+new Date()) - start;
assert.ok(stats.uptime <= end, 'took longer than expected');
assert.ok(stats.uptime > 0, 'no uptime');
assert.equal(stats.readyState, Container.OPEN, 'incorrect readystate');
assert.ok(stats.date instanceof Date, 'not a valid date');
assert.equal(stats.retries, container.retries, 'a retry happend');
done();
}).load(fixture.console).start();
});
it('returns an empty object when the container is down', function () {
var stats = container.stop().inspect();
assert.equal(typeof stats, 'object');
for (var key in stats) {
if (stats.hasOwnProperty(key)) throw new Error('I should be empty');
}
});
if (
typeof performance === 'undefined'
|| !performance.memory
|| performance.memory.usedJSHeapSize === 0
) return;
it('returns the memory of the VM when the browser supports it', function (done) {
container.on('start', function () {
var stats = container.inspect();
assert.equal(typeof stats, 'object');
assert.equal(typeof stats.memory, 'object');
assert.ok(stats.memory.limit > 0, 'limit should be greater than 0');
assert.ok(stats.memory.total > 0, 'total should be greater than 0');
assert.ok(stats.memory.used > 0, 'used should be greater than 0');
done();
}).load(fixture.console).start();
});
});
describe('#onmessage', function () {
it('returns false for non-objects', function () {
assert.ok(!container.onmessage('foo'), 'dont accept strings');
assert.ok(!container.onmessage([]), 'dont accept strings');
assert.ok(!container.onmessage(new Date), 'dont accept date');
});
it('returns false if there isnt a packet type', function () {
assert.ok(!container.onmessage({ foo: 'bar '}), 'no type prop');
assert.ok(container.onmessage({ type: 'load' }), 'has type');
});
it('stores console messages', function () {
assert.equal(container.console.length, 0, 'no active console messages');
container.onmessage({ type: 'console', scope: 'log', args: ['foo'] });
assert.equal(container.console.length, 1, '1 active console message');
assert.equal(container.console[0].args[0], 'foo');
});
it('emits `attach` events for packet.attach & log types', function (done) {
container.on('attach', function (scope, data) {
assert.equal(scope, 'log');
assert.equal(data, 'foo');
done();
}).onmessage({
type: 'console',
scope: 'log',
args: ['foo'],
attach: true
});
});
it('emits `attach::scope` for packet.attach & log types', function (done) {
container.on('attach::log', function (data) {
assert.equal(data, 'foo');
done();
}).onmessage({
type: 'console',
scope: 'log',
args: ['foo'],
attach: true
});
});
it('emits `error` for error packets', function (done) {
container.on('error', function (err) {
assert.ok(err instanceof Error);
assert.equal(err.message, 'foo');
done();
}).onmessage({
type: 'error',
args: ['foo']
});
});
it('emits `message` for all other responses', function (done) {
container.on('message', function (data) {
assert.equal('foo', data);
done();
}).onmessage({
type: 'banana',
args: ['foo']
});
});
it('restarts the ping sequence with a ping packet', function (done) {
assert.ok(!container.setTimeout.pong);
container.on('ping', function () {
assert.ok(!!container.setTimeout.pong);
container.stop();
done();
}).onmessage({
type: 'ping'
});
});
});
describe('#eval', function () {
it('returns the result of the evaluated cmd', function (done) {
container.eval('({ foo: "bar" })', function (err, data) {
if (err) return done(err);
assert.equal(data.foo, 'bar');
done();
});
});
it('captures errors in the evaluated cmd', function (done) {
container.on('start', function () {
container.eval('throw new Error("mess-up all the things")', function (err, data) {
if (!err) return done(new Error('I should have received an error'));
assert.equal(data, undefined);
assert.equal(err.message, 'mess-up all the things');
assert.equal(Object.prototype.toString.call(err), '[object Error]');
done();
});
});
container.on('error', function (e) {
throw new Error('I should never be called if an eval fails');
});
container.load(fixture.console).start();
});
});
describe('#load', function () {
it('loads the new code in an image', function () {
assert.ok(!container.image);
container.load(fixture.console);
assert.ok(!!container.image);
assert.equal(container.image.source, fixture.console);
});
});
describe('#destroy', function () {
it('removes all listeners', function () {
container.on('foo', function () {
throw new Error();
});
container.destroy();
container.emit('foo');
});
it('cleans up all references', function (done) {
container.on('start', function () {
assert.ok(!!document.getElementById(container.id));
container.destroy();
assert.ok(!document.getElementById(container.id));
assert.ok(!container.i && !container.image && !container.mount);
assert.ok(container.console.length === 0);
done();
}).load(fixture.console).start();
});
});
});
describe('Image', function () {
var source = 'var foo = "bar";';
it('returns the tranformed string using toString', function () {
var image = new Images('dingdong', source);
assert.ok(image instanceof Images);
assert.ok(!!~image.toString().indexOf(source));
// Test again to see if it's cached internally correctly
assert.ok(!!~image.toString().indexOf(source));
});
it('stores the source', function () {
var image = new Images('dingdong', source);
assert.equal(image.source, source);
});
});