tellform/public/lib/raven-js/test/raven.test.js
2015-07-14 13:45:54 -07:00

1774 lines
63 KiB
JavaScript

function flushRavenState() {
authQueryString = undefined;
hasJSON = !isUndefined(window.JSON);
lastCapturedException = undefined;
lastEventId = undefined;
globalServer = undefined;
globalUser = undefined;
globalProject = undefined;
globalOptions = {
logger: 'javascript',
release: undefined,
ignoreErrors: [],
ignoreUrls: [],
whitelistUrls: [],
includePaths: [],
collectWindowErrors: true,
maxMessageLength: 100,
tags: {},
extra: {}
},
startTime = 0
;
Raven.uninstall();
}
// window.console must be stubbed in for browsers that don't have it
if (typeof window.console === 'undefined') {
console = {error: function(){}};
}
var SENTRY_DSN = 'http://abc@example.com:80/2';
function setupRaven() {
Raven.config(SENTRY_DSN);
}
// patched to return a predictable result
function uuid4() {
return 'abc123';
}
// patched to be predictable
function now() {
return 100;
}
describe('TraceKit', function(){
describe('stacktrace info', function() {
it('should not remove anonymous functions from the stack', function() {
// mock up an error object with a stack trace that includes both
// named functions and anonymous functions
var stack_str = "" +
" Error: \n" +
" at new <anonymous> (http://example.com/js/test.js:63)\n" + // stack[0]
" at namedFunc0 (http://example.com/js/script.js:10)\n" + // stack[1]
" at http://example.com/js/test.js:65\n" + // stack[2]
" at namedFunc2 (http://example.com/js/script.js:20)\n" + // stack[3]
" at http://example.com/js/test.js:67\n" + // stack[4]
" at namedFunc4 (http://example.com/js/script.js:100001)"; // stack[5]
var mock_err = { stack: stack_str };
var trace = TraceKit.computeStackTrace.computeStackTraceFromStackProp(mock_err);
// Make sure TraceKit didn't remove the anonymous functions
// from the stack like it used to :)
assert.equal(trace.stack[0].func, 'new <anonymous>');
assert.equal(trace.stack[1].func, 'namedFunc0');
assert.equal(trace.stack[2].func, '?');
assert.equal(trace.stack[3].func, 'namedFunc2');
assert.equal(trace.stack[4].func, '?');
assert.equal(trace.stack[5].func, 'namedFunc4');
});
});
describe('error notifications', function(){
var testMessage = "__mocha_ignore__";
var subscriptionHandler;
// TraceKit waits 2000ms for window.onerror to fire, so give the tests
// some extra time.
this.timeout(3000);
before(function() {
// Prevent the onerror call that's part of our tests from getting to
// mocha's handler, which would treat it as a test failure.
//
// We set this up here and don't ever restore the old handler, because
// we can't do that without clobbering TraceKit's handler, which can only
// be installed once.
var oldOnError = window.onerror;
window.onerror = function(message) {
if (message == testMessage) {
return true;
}
return oldOnError.apply(this, arguments);
};
});
afterEach(function() {
if (subscriptionHandler) {
TraceKit.report.unsubscribe(subscriptionHandler);
subscriptionHandler = null;
}
});
function testErrorNotification(collectWindowErrors, callOnError, numReports, done) {
var extraVal = "foo";
var numDone = 0;
// TraceKit's collectWindowErrors flag shouldn't affect direct calls
// to TraceKit.report, so we parameterize it for the tests.
TraceKit.collectWindowErrors = collectWindowErrors;
subscriptionHandler = function(stackInfo, extra) {
assert.equal(extra, extraVal);
numDone++;
if (numDone == numReports) {
done();
}
};
TraceKit.report.subscribe(subscriptionHandler);
// TraceKit.report always throws an exception in order to trigger
// window.onerror so it can gather more stack data. Mocha treats
// uncaught exceptions as errors, so we catch it via assert.throws
// here (and manually call window.onerror later if appropriate).
//
// We test multiple reports because TraceKit has special logic for when
// report() is called a second time before either a timeout elapses or
// window.onerror is called (which is why we always call window.onerror
// only once below, after all calls to report()).
for (var i=0; i < numReports; i++) {
var e = new Error('testing');
assert.throws(function() {
TraceKit.report(e, extraVal);
}, e);
}
// The call to report should work whether or not window.onerror is
// triggered, so we parameterize it for the tests. We only call it
// once, regardless of numReports, because the case we want to test for
// multiple reports is when window.onerror is *not* called between them.
if (callOnError) {
window.onerror(testMessage);
}
}
Mocha.utils.forEach([false, true], function(collectWindowErrors) {
Mocha.utils.forEach([false, true], function(callOnError) {
Mocha.utils.forEach([1, 2], function(numReports) {
it('it should receive arguments from report() when' +
' collectWindowErrors is ' + collectWindowErrors +
' and callOnError is ' + callOnError +
' and numReports is ' + numReports, function(done) {
testErrorNotification(collectWindowErrors, callOnError, numReports, done);
});
});
});
});
});
});
describe('globals', function() {
beforeEach(function() {
setupRaven();
globalOptions.fetchContext = true;
});
afterEach(function() {
flushRavenState();
});
describe('getHttpData', function() {
var data = getHttpData();
it('should have a url', function() {
assert.equal(data.url, window.location.href);
});
it('should have the user-agent header', function() {
assert.equal(data.headers['User-Agent'], navigator.userAgent);
});
it('should have referer header when available', function() {
// lol this test is awful
if (window.document.referrer) {
assert.equal(data.headers.Referer, window.document.referrer);
} else {
assert.isUndefined(data.headers.Referer);
}
});
});
describe('isUndefined', function() {
it('should do as advertised', function() {
assert.isTrue(isUndefined());
assert.isFalse(isUndefined({}));
assert.isFalse(isUndefined(''));
assert.isTrue(isUndefined(undefined));
});
});
describe('isFunction', function() {
it('should do as advertised', function() {
assert.isTrue(isFunction(function(){}));
assert.isFalse(isFunction({}));
assert.isFalse(isFunction(''));
assert.isFalse(isFunction(undefined));
});
});
describe('isString', function() {
it('should do as advertised', function() {
assert.isTrue(isString(''));
assert.isTrue(isString(String('')));
assert.isTrue(isString(new String('')));
assert.isFalse(isString({}));
assert.isFalse(isString(undefined));
assert.isFalse(isString(function(){}));
});
});
describe('isObject', function() {
it('should do as advertised', function() {
assert.isTrue(isObject({}));
assert.isTrue(isObject(new Error()))
assert.isFalse(isObject(''));
});
});
describe('isEmptyObject', function() {
it('should work as advertised', function() {
assert.isTrue(isEmptyObject({}));
assert.isFalse(isEmptyObject({foo: 1}));
});
});
describe('isError', function() {
it('should work as advertised', function() {
assert.isTrue(isError(new Error()));
assert.isTrue(isError(new ReferenceError()));
assert.isTrue(isError(new RavenConfigError()));
assert.isFalse(isError({}));
assert.isFalse(isError(''));
assert.isFalse(isError(true));
});
});
describe('objectMerge', function() {
it('should work as advertised', function() {
assert.deepEqual(objectMerge({}, {}), {});
assert.deepEqual(objectMerge({a:1}, {b:2}), {a:1, b:2});
assert.deepEqual(objectMerge({a:1}), {a:1});
});
});
describe('truncate', function() {
it('should work as advertised', function() {
assert.equal(truncate('lolol', 3), 'lol\u2026');
assert.equal(truncate('lolol', 10), 'lolol');
assert.equal(truncate('lol', 3), 'lol');
});
});
describe('isSetup', function() {
it('should return false with no JSON support', function() {
globalServer = 'http://localhost/';
hasJSON = false;
assert.isFalse(isSetup());
});
it('should return false when Raven is not configured', function() {
hasJSON = true; // be explicit
globalServer = undefined;
this.sinon.stub(window, 'logDebug');
assert.isFalse(isSetup());
});
it('should return true when everything is all gravy', function() {
hasJSON = true;
assert.isTrue(isSetup());
});
});
describe('logDebug', function() {
var level = 'error',
message = 'foobar';
it('should not write to console when Raven.debug is false', function() {
Raven.debug = false;
this.sinon.stub(console, level);
logDebug(level, message);
assert.isFalse(console[level].called);
});
it('should write to console when Raven.debug is true', function() {
Raven.debug = true;
this.sinon.stub(console, level);
logDebug(level, message);
assert.isTrue(console[level].calledOnce);
});
});
describe('setAuthQueryString', function() {
it('should return a properly formatted string and cache it', function() {
var expected = '?sentry_version=4&sentry_client=raven-js/<%= pkg.version %>&sentry_key=abc';
setAuthQueryString();
assert.strictEqual(authQueryString, expected);
});
});
describe('parseDSN', function() {
it('should do what it advertises', function() {
var pieces = parseDSN('http://abc@example.com:80/2');
assert.strictEqual(pieces.protocol, 'http');
assert.strictEqual(pieces.user, 'abc');
assert.strictEqual(pieces.port, '80');
assert.strictEqual(pieces.path, '/2');
assert.strictEqual(pieces.host, 'example.com');
});
it('should parse protocol relative', function() {
var pieces = parseDSN('//user@mattrobenolt.com/');
assert.strictEqual(pieces.protocol, '');
assert.strictEqual(pieces.user, 'user');
assert.strictEqual(pieces.port, '');
assert.strictEqual(pieces.path, '/');
assert.strictEqual(pieces.host, 'mattrobenolt.com');
});
it('should parse domain with hyphen', function() {
var pieces = parseDSN('http://user@matt-robenolt.com/1');
assert.strictEqual(pieces.protocol, 'http');
assert.strictEqual(pieces.user, 'user');
assert.strictEqual(pieces.port, '');
assert.strictEqual(pieces.path, '/1');
assert.strictEqual(pieces.host, 'matt-robenolt.com');
});
it('should raise a RavenConfigError when setting a password', function() {
try {
parseDSN('http://user:pass@example.com/2');
} catch(e) {
return assert.equal(e.name, 'RavenConfigError');
}
// shouldn't hit this
assert.isTrue(false);
});
it('should raise a RavenConfigError with an invalid DSN', function() {
try {
parseDSN('lol');
} catch(e) {
return assert.equal(e.name, 'RavenConfigError');
}
// shouldn't hit this
assert.isTrue(false);
});
});
describe('normalizeFrame', function() {
it('should handle a normal frame', function() {
var context = [
['line1'], // pre
'line2', // culprit
['line3'] // post
];
this.sinon.stub(window, 'extractContextFromFrame').returns(context);
var frame = {
url: 'http://example.com/path/file.js',
line: 10,
column: 11,
func: 'lol'
// context: [] context is stubbed
};
globalOptions.fetchContext = true;
assert.deepEqual(normalizeFrame(frame), {
filename: 'http://example.com/path/file.js',
lineno: 10,
colno: 11,
'function': 'lol',
pre_context: ['line1'],
context_line: 'line2',
post_context: ['line3'],
in_app: true
});
});
it('should handle a frame without context', function() {
this.sinon.stub(window, 'extractContextFromFrame').returns(undefined);
var frame = {
url: 'http://example.com/path/file.js',
line: 10,
column: 11,
func: 'lol'
// context: [] context is stubbed
};
globalOptions.fetchContext = true;
assert.deepEqual(normalizeFrame(frame), {
filename: 'http://example.com/path/file.js',
lineno: 10,
colno: 11,
'function': 'lol',
in_app: true
});
});
it('should not mark `in_app` if rules match', function() {
this.sinon.stub(window, 'extractContextFromFrame').returns(undefined);
var frame = {
url: 'http://example.com/path/file.js',
line: 10,
column: 11,
func: 'lol'
// context: [] context is stubbed
};
globalOptions.fetchContext = true;
globalOptions.includePaths = /^http:\/\/example\.com/;
assert.deepEqual(normalizeFrame(frame), {
filename: 'http://example.com/path/file.js',
lineno: 10,
colno: 11,
'function': 'lol',
in_app: true
});
});
it('should mark `in_app` if rules do not match', function() {
this.sinon.stub(window, 'extractContextFromFrame').returns(undefined);
var frame = {
url: 'http://lol.com/path/file.js',
line: 10,
column: 11,
func: 'lol'
// context: [] context is stubbed
};
globalOptions.fetchContext = true;
globalOptions.includePaths = /^http:\/\/example\.com/;
assert.deepEqual(normalizeFrame(frame), {
filename: 'http://lol.com/path/file.js',
lineno: 10,
colno: 11,
'function': 'lol',
in_app: false
});
});
it('should mark `in_app` for raven.js', function() {
this.sinon.stub(window, 'extractContextFromFrame').returns(undefined);
var frame = {
url: 'http://lol.com/path/raven.js',
line: 10,
column: 11,
func: 'lol'
// context: [] context is stubbed
};
assert.deepEqual(normalizeFrame(frame), {
filename: 'http://lol.com/path/raven.js',
lineno: 10,
colno: 11,
'function': 'lol',
in_app: false
});
});
it('should mark `in_app` for raven.min.js', function() {
this.sinon.stub(window, 'extractContextFromFrame').returns(undefined);
var frame = {
url: 'http://lol.com/path/raven.min.js',
line: 10,
column: 11,
func: 'lol'
// context: [] context is stubbed
};
assert.deepEqual(normalizeFrame(frame), {
filename: 'http://lol.com/path/raven.min.js',
lineno: 10,
colno: 11,
'function': 'lol',
in_app: false
});
});
it('should mark `in_app` for Raven', function() {
this.sinon.stub(window, 'extractContextFromFrame').returns(undefined);
var frame = {
url: 'http://lol.com/path/file.js',
line: 10,
column: 11,
func: 'Raven.wrap'
// context: [] context is stubbed
};
assert.deepEqual(normalizeFrame(frame), {
filename: 'http://lol.com/path/file.js',
lineno: 10,
colno: 11,
'function': 'Raven.wrap',
in_app: false
});
});
it('should mark `in_app` for TraceKit', function() {
this.sinon.stub(window, 'extractContextFromFrame').returns(undefined);
var frame = {
url: 'http://lol.com/path/file.js',
line: 10,
column: 11,
func: 'TraceKit.lol'
// context: [] context is stubbed
};
assert.deepEqual(normalizeFrame(frame), {
filename: 'http://lol.com/path/file.js',
lineno: 10,
colno: 11,
'function': 'TraceKit.lol',
in_app: false
});
});
});
describe('extractContextFromFrame', function() {
it('should handle a normal frame', function() {
var frame = {
column: 2,
context: [
'line1',
'line2',
'line3',
'line4',
'line5',
'culprit',
'line7',
'line8',
'line9',
'line10',
'line11'
]
};
var context = extractContextFromFrame(frame);
assert.deepEqual(context, [
['line1', 'line2', 'line3', 'line4', 'line5'],
'culprit',
['line7', 'line8', 'line9', 'line10', 'line11']
]);
});
it('should return nothing if there is no context', function() {
var frame = {
column: 2
};
assert.isUndefined(extractContextFromFrame(frame));
});
it('should reject a context if a line is too long without a column', function() {
var frame = {
context: [
new Array(1000).join('f') // generate a line that is 1000 chars long
]
};
assert.isUndefined(extractContextFromFrame(frame));
});
it('should reject a minified context with fetchContext disabled', function() {
var frame = {
column: 2,
context: [
'line1',
'line2',
'line3',
'line4',
'line5',
'culprit',
'line7',
'line8',
'line9',
'line10',
'line11'
]
};
globalOptions.fetchContext = false;
assert.isUndefined(extractContextFromFrame(frame));
});
it('should truncate the minified line if there is a column number without sourcemaps enabled', function() {
// Note to future self:
// Array(51).join('f').length === 50
var frame = {
column: 2,
context: [
'aa' + (new Array(51).join('f')) + (new Array(500).join('z'))
]
};
assert.deepEqual(extractContextFromFrame(frame), [[], new Array(51).join('f'), []]);
});
});
describe('processException', function() {
it('should respect `ignoreErrors`', function() {
this.sinon.stub(window, 'send');
globalOptions.ignoreErrors = joinRegExp(['e1', 'e2']);
processException('Error', 'e1', 'http://example.com', []);
assert.isFalse(window.send.called);
processException('Error', 'e2', 'http://example.com', []);
assert.isFalse(window.send.called);
processException('Error', 'error', 'http://example.com', []);
assert.isTrue(window.send.calledOnce);
});
it('should respect `ignoreUrls`', function() {
this.sinon.stub(window, 'send');
globalOptions.ignoreUrls = joinRegExp([/.+?host1.+/, /.+?host2.+/]);
processException('Error', 'error', 'http://host1/', []);
assert.isFalse(window.send.called);
processException('Error', 'error', 'http://host2/', []);
assert.isFalse(window.send.called);
processException('Error', 'error', 'http://host3/', []);
assert.isTrue(window.send.calledOnce);
});
it('should respect `whitelistUrls`', function() {
this.sinon.stub(window, 'send');
globalOptions.whitelistUrls = joinRegExp([/.+?host1.+/, /.+?host2.+/]);
processException('Error', 'error', 'http://host1/', []);
assert.equal(window.send.callCount, 1);
processException('Error', 'error', 'http://host2/', []);
assert.equal(window.send.callCount, 2);
processException('Error', 'error', 'http://host3/', []);
assert.equal(window.send.callCount, 2);
});
it('should send a proper payload with frames', function() {
this.sinon.stub(window, 'send');
var frames = [
{
filename: 'http://example.com/file1.js'
},
{
filename: 'http://example.com/file2.js'
}
], framesFlipped = frames.slice(0);
framesFlipped.reverse();
processException('Error', 'lol', 'http://example.com/override.js', 10, frames.slice(0), {});
assert.deepEqual(window.send.lastCall.args, [{
exception: {
type: 'Error',
value: 'lol'
},
stacktrace: {
frames: framesFlipped
},
culprit: 'http://example.com/file1.js',
message: 'lol at 10'
}]);
processException('Error', 'lol', '', 10, frames.slice(0), {});
assert.deepEqual(window.send.lastCall.args, [{
exception: {
type: 'Error',
value: 'lol'
},
stacktrace: {
frames: framesFlipped
},
culprit: 'http://example.com/file1.js',
message: 'lol at 10'
}]);
processException('Error', 'lol', '', 10, frames.slice(0), {extra: 'awesome'});
assert.deepEqual(window.send.lastCall.args, [{
exception: {
type: 'Error',
value: 'lol'
},
stacktrace: {
frames: framesFlipped
},
culprit: 'http://example.com/file1.js',
message: 'lol at 10',
extra: 'awesome'
}]);
});
it('should send a proper payload without frames', function() {
this.sinon.stub(window, 'send');
processException('Error', 'lol', 'http://example.com/override.js', 10, [], {});
assert.deepEqual(window.send.lastCall.args, [{
exception: {
type: 'Error',
value: 'lol'
},
stacktrace: {
frames: [{
filename: 'http://example.com/override.js',
lineno: 10,
in_app: true
}]
},
culprit: 'http://example.com/override.js',
message: 'lol at 10'
}]);
processException('Error', 'lol', 'http://example.com/override.js', 10, [], {});
assert.deepEqual(window.send.lastCall.args, [{
exception: {
type: 'Error',
value: 'lol'
},
stacktrace: {
frames: [{
filename: 'http://example.com/override.js',
lineno: 10,
in_app: true
}]
},
culprit: 'http://example.com/override.js',
message: 'lol at 10'
}]);
processException('Error', 'lol', 'http://example.com/override.js', 10, [], {extra: 'awesome'});
assert.deepEqual(window.send.lastCall.args, [{
exception: {
type: 'Error',
value: 'lol'
},
stacktrace: {
frames: [{
filename: 'http://example.com/override.js',
lineno: 10,
in_app: true
}]
},
culprit: 'http://example.com/override.js',
message: 'lol at 10',
extra: 'awesome'
}]);
});
it('should ignored falsey messages', function() {
this.sinon.stub(window, 'send');
processException('Error', '', 'http://example.com', []);
assert.isFalse(window.send.called);
processException('TypeError', '', 'http://example.com', []);
assert.isTrue(window.send.called);
});
it('should not blow up with `undefined` message', function() {
this.sinon.stub(window, 'send');
processException('TypeError', undefined, 'http://example.com', []);
assert.isTrue(window.send.called);
});
it('should truncate messages to the specified length', function() {
this.sinon.stub(window, 'send');
processException('TypeError', new Array(500).join('a'), 'http://example.com', []);
assert.deepEqual(window.send.lastCall.args, [{
message: new Array(101).join('a')+'\u2026 at ',
exception: {
type: 'TypeError',
value: new Array(101).join('a')+'\u2026'
},
stacktrace: {
frames: [{
filename: 'http://example.com',
lineno: [],
in_app: true
}]
},
culprit: 'http://example.com',
}]);
globalOptions.maxMessageLength = 150;
processException('TypeError', new Array(500).join('a'), 'http://example.com', []);
assert.deepEqual(window.send.lastCall.args, [{
message: new Array(151).join('a')+'\u2026 at ',
exception: {
type: 'TypeError',
value: new Array(151).join('a')+'\u2026'
},
stacktrace: {
frames: [{
filename: 'http://example.com',
lineno: [],
in_app: true
}]
},
culprit: 'http://example.com',
}]);
});
});
describe('send', function() {
it('should check `isSetup`', function() {
this.sinon.stub(window, 'isSetup').returns(false);
this.sinon.stub(window, 'makeRequest');
send();
assert.isTrue(window.isSetup.calledOnce);
assert.isFalse(window.makeRequest.calledOnce);
});
it('should build a good data payload', function() {
this.sinon.stub(window, 'isSetup').returns(true);
this.sinon.stub(window, 'makeRequest');
this.sinon.stub(window, 'getHttpData').returns({
url: 'http://localhost/?a=b',
headers: {'User-Agent': 'lolbrowser'}
});
globalProject = '2';
globalOptions = {
logger: 'javascript'
};
send({foo: 'bar'});
assert.deepEqual(window.makeRequest.lastCall.args[0], {
project: '2',
logger: 'javascript',
platform: 'javascript',
request: {
url: 'http://localhost/?a=b',
headers: {
'User-Agent': 'lolbrowser'
}
},
event_id: 'abc123',
foo: 'bar',
extra: {'session:duration': 100}
});
});
it('should build a good data payload with a User', function() {
this.sinon.stub(window, 'isSetup').returns(true);
this.sinon.stub(window, 'makeRequest');
this.sinon.stub(window, 'getHttpData').returns({
url: 'http://localhost/?a=b',
headers: {'User-Agent': 'lolbrowser'}
});
globalProject = '2';
globalOptions = {
logger: 'javascript'
};
globalUser = {name: 'Matt'};
send({foo: 'bar'});
assert.deepEqual(window.makeRequest.lastCall.args, [{
project: '2',
logger: 'javascript',
platform: 'javascript',
request: {
url: 'http://localhost/?a=b',
headers: {
'User-Agent': 'lolbrowser'
}
},
event_id: 'abc123',
user: {
name: 'Matt'
},
foo: 'bar',
extra: {'session:duration': 100}
}]);
});
it('should merge in global tags', function() {
this.sinon.stub(window, 'isSetup').returns(true);
this.sinon.stub(window, 'makeRequest');
this.sinon.stub(window, 'getHttpData').returns({
url: 'http://localhost/?a=b',
headers: {'User-Agent': 'lolbrowser'}
});
globalProject = '2';
globalOptions = {
logger: 'javascript',
tags: {tag1: 'value1'}
};
send({tags: {tag2: 'value2'}});
assert.deepEqual(window.makeRequest.lastCall.args, [{
project: '2',
logger: 'javascript',
platform: 'javascript',
request: {
url: 'http://localhost/?a=b',
headers: {
'User-Agent': 'lolbrowser'
}
},
event_id: 'abc123',
tags: {tag1: 'value1', tag2: 'value2'},
extra: {'session:duration': 100}
}]);
assert.deepEqual(globalOptions, {
logger: 'javascript',
tags: {tag1: 'value1'}
});
});
it('should merge in global extra', function() {
this.sinon.stub(window, 'isSetup').returns(true);
this.sinon.stub(window, 'makeRequest');
this.sinon.stub(window, 'getHttpData').returns({
url: 'http://localhost/?a=b',
headers: {'User-Agent': 'lolbrowser'}
});
globalProject = '2';
globalOptions = {
logger: 'javascript',
extra: {key1: 'value1'}
};
send({extra: {key2: 'value2'}});
assert.deepEqual(window.makeRequest.lastCall.args, [{
project: '2',
logger: 'javascript',
platform: 'javascript',
request: {
url: 'http://localhost/?a=b',
headers: {
'User-Agent': 'lolbrowser'
}
},
event_id: 'abc123',
extra: {key1: 'value1', key2: 'value2', 'session:duration': 100}
}]);
assert.deepEqual(globalOptions, {
logger: 'javascript',
extra: {key1: 'value1'}
});
});
it('should let dataCallback override everything', function() {
this.sinon.stub(window, 'isSetup').returns(true);
this.sinon.stub(window, 'makeRequest');
globalOptions = {
projectId: 2,
logger: 'javascript',
dataCallback: function() {
return {lol: 'ibrokeit'};
}
};
globalUser = {name: 'Matt'};
send({foo: 'bar'});
assert.deepEqual(window.makeRequest.lastCall.args, [{
lol: 'ibrokeit',
event_id: 'abc123',
}]);
});
it('should ignore dataCallback if it does not return anything', function() {
this.sinon.stub(window, 'isSetup').returns(true);
this.sinon.stub(window, 'makeRequest');
this.sinon.stub(window, 'getHttpData').returns({
url: 'http://localhost/?a=b',
headers: {'User-Agent': 'lolbrowser'}
});
globalProject = '2';
globalOptions = {
logger: 'javascript',
dataCallback: function() {
return;
}
};
send({foo: 'bar'});
assert.deepEqual(window.makeRequest.lastCall.args[0], {
project: '2',
logger: 'javascript',
platform: 'javascript',
request: {
url: 'http://localhost/?a=b',
headers: {
'User-Agent': 'lolbrowser'
}
},
event_id: 'abc123',
foo: 'bar',
extra: {'session:duration': 100}
});
});
it('should strip empty tags', function() {
this.sinon.stub(window, 'isSetup').returns(true);
this.sinon.stub(window, 'makeRequest');
this.sinon.stub(window, 'getHttpData').returns({
url: 'http://localhost/?a=b',
headers: {'User-Agent': 'lolbrowser'}
});
globalOptions = {
projectId: 2,
logger: 'javascript',
tags: {}
};
send({foo: 'bar', tags: {}, extra: {}});
assert.deepEqual(window.makeRequest.lastCall.args[0], {
project: '2',
logger: 'javascript',
platform: 'javascript',
request: {
url: 'http://localhost/?a=b',
headers: {
'User-Agent': 'lolbrowser'
}
},
event_id: 'abc123',
foo: 'bar',
extra: {'session:duration': 100}
});
});
it('should attach release if available', function() {
this.sinon.stub(window, 'isSetup').returns(true);
this.sinon.stub(window, 'makeRequest');
this.sinon.stub(window, 'getHttpData').returns({
url: 'http://localhost/?a=b',
headers: {'User-Agent': 'lolbrowser'}
});
globalOptions = {
projectId: 2,
logger: 'javascript',
release: 'abc123',
};
send({foo: 'bar'});
assert.deepEqual(window.makeRequest.lastCall.args[0], {
project: '2',
release: 'abc123',
logger: 'javascript',
platform: 'javascript',
request: {
url: 'http://localhost/?a=b',
headers: {
'User-Agent': 'lolbrowser'
}
},
event_id: 'abc123',
foo: 'bar',
extra: {'session:duration': 100}
});
});
});
describe('makeRequest', function() {
it('should load an Image', function() {
authQueryString = '?lol';
globalServer = 'http://localhost/';
var imageCache = [];
this.sinon.stub(window, 'newImage', function(){ var img = {}; imageCache.push(img); return img; });
makeRequest({foo: 'bar'});
assert.equal(imageCache.length, 1);
assert.equal(imageCache[0].src, 'http://localhost/?lol&sentry_data=%7B%22foo%22%3A%22bar%22%7D');
});
});
describe('handleStackInfo', function() {
it('should work as advertised', function() {
var frame = {url: 'http://example.com'};
this.sinon.stub(window, 'normalizeFrame').returns(frame);
this.sinon.stub(window, 'processException');
var stackInfo = {
name: 'Matt',
message: 'hey',
url: 'http://example.com',
lineno: 10,
stack: [
frame, frame
]
};
handleStackInfo(stackInfo, {foo: 'bar'});
assert.deepEqual(window.processException.lastCall.args, [
'Matt', 'hey', 'http://example.com', 10, [frame, frame], {foo: 'bar'}
]);
});
it('should work as advertised #integration', function() {
this.sinon.stub(window, 'makeRequest');
var stackInfo = {
name: 'Error',
message: 'crap',
url: 'http://example.com',
lineno: 10,
stack: [
{
url: 'http://example.com/file1.js',
line: 10,
column: 11,
func: 'broken',
context: [
'line1',
'line2',
'line3'
]
},
{
url: 'http://example.com/file2.js',
line: 12,
column: 13,
func: 'lol',
context: [
'line4',
'line5',
'line6'
]
}
]
};
handleStackInfo(stackInfo, {foo: 'bar'});
assert.isTrue(window.makeRequest.calledOnce);
/* This is commented out because chai is broken.
assert.deepEqual(window.makeRequest.lastCall.args, [{
project: '2',
logger: 'javascript',
platform: 'javascript',
request: {
url: window.location.protocol + '//' + window.location.host + window.location.pathname,
querystring: window.location.search.slice(1)
},
exception: {
type: 'Error',
value: 'crap'
},
stacktrace: {
frames: [{
filename: 'http://example.com/file1.js',
filename: 'file1.js',
lineno: 10,
colno: 11,
'function': 'broken',
post_context: ['line3'],
context_line: 'line2',
pre_context: ['line1']
}, {
filename: 'http://example.com/file2.js',
filename: 'file2.js',
lineno: 12,
colno: 13,
'function': 'lol',
post_context: ['line6'],
context_line: 'line5',
pre_context: ['line4']
}]
},
culprit: 'http://example.com',
message: 'crap at 10',
foo: 'bar'
}]);
*/
});
it('should ignore frames that dont have a url', function() {
this.sinon.stub(window, 'normalizeFrame').returns(undefined);
this.sinon.stub(window, 'processException');
var stackInfo = {
name: 'Matt',
message: 'hey',
url: 'http://example.com',
lineno: 10,
stack: new Array(2)
};
handleStackInfo(stackInfo, {foo: 'bar'});
assert.deepEqual(window.processException.lastCall.args, [
'Matt', 'hey', 'http://example.com', 10, [], {foo: 'bar'}
]);
});
it('should not shit when there is no stack object from TK', function() {
this.sinon.stub(window, 'normalizeFrame').returns(undefined);
this.sinon.stub(window, 'processException');
var stackInfo = {
name: 'Matt',
message: 'hey',
url: 'http://example.com',
lineno: 10
// stack: new Array(2)
};
handleStackInfo(stackInfo);
assert.isFalse(window.normalizeFrame.called);
assert.deepEqual(window.processException.lastCall.args, [
'Matt', 'hey', 'http://example.com', 10, [], undefined
]);
});
it('should detect 2-words patterns (angularjs frequent case)', function() {
this.sinon.stub(window, 'normalizeFrame').returns(undefined);
this.sinon.stub(window, 'processException');
var stackInfo = {
name: 'new <anonymous>',
message: 'hey',
url: 'http://example.com',
lineno: 10
// stack: new Array(2)
};
handleStackInfo(stackInfo);
assert.isFalse(window.normalizeFrame.called);
assert.deepEqual(window.processException.lastCall.args, [
'new <anonymous>', 'hey', 'http://example.com', 10, [], undefined
]);
});
});
describe('joinRegExp', function() {
it('should work as advertised', function() {
assert.equal(joinRegExp([
'a', 'b', 'a.b', /d/, /[0-9]/
]).source, 'a|b|a\\.b|d|[0-9]');
});
it('should not process empty or undefined variables', function() {
assert.equal(joinRegExp([
'a', 'b', null, undefined
]).source, 'a|b');
});
it('should skip entries that are not strings or regular expressions in the passed array of patterns', function() {
assert.equal(joinRegExp([
'a', 'b', null, 'a.b', undefined, true, /d/, 123, {}, /[0-9]/, []
]).source, 'a|b|a\\.b|d|[0-9]');
});
});
});
describe('Raven (public API)', function() {
afterEach(function() {
flushRavenState();
});
describe('.VERSION', function() {
it('should have a version', function() {
assert.isString(Raven.VERSION);
});
});
describe('ignore errors', function() {
it('should install default ignore errors', function() {
Raven.config('//abc@example.com/2');
assert.isTrue(globalOptions.ignoreErrors.test('Script error'), 'it should install "Script error" by default');
assert.isTrue(globalOptions.ignoreErrors.test('Script error.'), 'it should install "Script error." by default');
assert.isTrue(globalOptions.ignoreErrors.test('Javascript error: Script error on line 0'), 'it should install "Javascript error: Script error on line 0" by default');
assert.isTrue(globalOptions.ignoreErrors.test('Javascript error: Script error. on line 0'), 'it should install "Javascript error: Script error. on line 0" by default');
});
});
describe('callback function', function() {
it('should callback a function if it is global', function() {
window.RavenConfig = {
dsn: "http://random@some.other.server:80/2",
config: {some: 'config'}
};
this.sinon.stub(window, 'isSetup').returns(false);
this.sinon.stub(TraceKit.report, 'subscribe');
afterLoad();
assert.equal(globalKey, 'random');
assert.equal(globalServer, 'http://some.other.server:80/api/2/store/');
assert.equal(globalOptions.some, 'config');
assert.equal(globalProject, '2');
assert.isTrue(window.isSetup.calledOnce);
assert.isFalse(TraceKit.report.subscribe.calledOnce);
delete window.RavenConfig;
});
});
describe('.config', function() {
it('should work with a DSN', function() {
assert.equal(Raven, Raven.config(SENTRY_DSN, {foo: 'bar'}), 'it should return Raven');
assert.equal(globalKey, 'abc');
assert.equal(globalServer, 'http://example.com:80/api/2/store/');
assert.equal(globalOptions.foo, 'bar');
assert.equal(globalProject, '2');
assert.isTrue(isSetup());
});
it('should work with a protocol relative DSN', function() {
Raven.config('//abc@example.com/2');
assert.equal(globalKey, 'abc');
assert.equal(globalServer, '//example.com/api/2/store/');
assert.equal(globalProject, '2');
assert.isTrue(isSetup());
});
it('should work should work at a non root path', function() {
Raven.config('//abc@example.com/sentry/2');
assert.equal(globalKey, 'abc');
assert.equal(globalServer, '//example.com/sentry/api/2/store/');
assert.equal(globalProject, '2');
assert.isTrue(isSetup());
});
it('should noop a falsey dsn', function() {
Raven.config('');
assert.isFalse(isSetup());
});
it('should return Raven for a falsey dsn', function() {
assert.equal(Raven.config(''), Raven);
});
it('should not set global options more than once', function() {
this.sinon.spy(window, 'parseDSN');
this.sinon.stub(window, 'logDebug');
setupRaven();
setupRaven();
assert.isTrue(parseDSN.calledOnce);
assert.isTrue(logDebug.called);
});
describe('whitelistUrls', function() {
it('should be false if none are passed', function() {
Raven.config('//abc@example.com/2');
assert.equal(globalOptions.whitelistUrls, false);
});
it('should join into a single RegExp', function() {
Raven.config('//abc@example.com/2', {
whitelistUrls: [
/my.app/i,
/other.app/i
]
});
assert.match(globalOptions.whitelistUrls, /my.app|other.app/i);
});
it('should handle strings as well', function() {
Raven.config('//abc@example.com/2', {
whitelistUrls: [
/my.app/i,
"stringy.app"
]
});
assert.match(globalOptions.whitelistUrls, /my.app|stringy.app/i);
});
});
describe('collectWindowErrors', function() {
it('should be true by default', function() {
Raven.config(SENTRY_DSN);
assert.isTrue(TraceKit.collectWindowErrors);
});
it('should be true if set to true', function() {
Raven.config(SENTRY_DSN, {
collectWindowErrors: true
});
assert.isTrue(TraceKit.collectWindowErrors);
});
it('should be false if set to false', function() {
Raven.config(SENTRY_DSN, {
collectWindowErrors: false
});
assert.isFalse(TraceKit.collectWindowErrors);
});
});
});
describe('.install', function() {
it('should check `isSetup`', function() {
this.sinon.stub(window, 'isSetup').returns(false);
this.sinon.stub(TraceKit.report, 'subscribe');
Raven.install();
assert.isTrue(window.isSetup.calledOnce);
assert.isFalse(TraceKit.report.subscribe.calledOnce);
});
it('should register itself with TraceKit', function() {
this.sinon.stub(window, 'isSetup').returns(true);
this.sinon.stub(TraceKit.report, 'subscribe');
assert.equal(Raven, Raven.install());
assert.isTrue(TraceKit.report.subscribe.calledOnce);
assert.equal(TraceKit.report.subscribe.lastCall.args[0], handleStackInfo);
});
it('should not register itself more than once', function() {
this.sinon.stub(window, 'isSetup').returns(true);
this.sinon.stub(TraceKit.report, 'subscribe');
Raven.install();
Raven.install();
assert.isTrue(TraceKit.report.subscribe.calledOnce);
});
});
describe('.wrap', function() {
it('should return a wrapped callback', function() {
var spy = this.sinon.spy();
var wrapped = Raven.wrap(spy);
assert.isFunction(wrapped);
assert.isTrue(wrapped.__raven__);
wrapped();
assert.isTrue(spy.calledOnce);
});
it('should copy property when wrapping function', function() {
var func = function() {};
func.test = true;
var wrapped = Raven.wrap(func);
assert.isTrue(wrapped.test);
});
it('should not copy prototype property when wrapping function', function() {
var func = function() {};
func.prototype.test = true;
var wrapped = Raven.wrap(func);
assert.isUndefined(new wrapped().test);
});
it('should return the result of a wrapped function', function() {
var func = function() { return 'foo'; };
var wrapped = Raven.wrap(func);
assert.equal(wrapped(), 'foo');
});
it('should not wrap a non-function', function() {
assert.equal(Raven.wrap('lol'), 'lol');
assert.equal(Raven.wrap({}, 'lol'), 'lol');
assert.equal(Raven.wrap(undefined, 'lol'), 'lol');
var a = [1, 2];
assert.equal(Raven.wrap(a), a);
});
it('should wrap function arguments', function() {
var spy = this.sinon.spy();
var wrapped = Raven.wrap(function(f) {
assert.isTrue(f.__raven__);
f();
});
wrapped(spy);
assert.isTrue(spy.calledOnce);
});
it('should not wrap function arguments', function() {
var spy = this.sinon.spy();
var wrapped = Raven.wrap({ deep: false }, function(f) {
assert.isUndefined(f.__raven__);
f();
});
wrapped(spy);
assert.isTrue(spy.calledOnce);
});
it('should maintain the correct scope', function() {
var foo = {};
var bar = function() {
assert.equal(this, foo);
};
bar.apply(foo, []);
Raven.wrap(bar).apply(foo, []);
});
it('should re-raise a thrown exception', function() {
var error = new Error('lol');
assert.throws(function() {
Raven.wrap(function() { throw error; })();
}, error);
});
});
describe('.context', function() {
it('should execute the callback with options', function() {
var spy = this.sinon.spy();
this.sinon.stub(Raven, 'captureException');
Raven.context({'foo': 'bar'}, spy);
assert.isTrue(spy.calledOnce);
assert.isFalse(Raven.captureException.called);
});
it('should execute the callback with arguments', function() {
var spy = this.sinon.spy();
var args = [1, 2];
Raven.context(spy, args);
assert.deepEqual(spy.lastCall.args, args);
});
it('should execute the callback without options', function() {
var spy = this.sinon.spy();
this.sinon.stub(Raven, 'captureException');
Raven.context(spy);
assert.isTrue(spy.calledOnce);
assert.isFalse(Raven.captureException.called);
});
it('should capture the exception with options', function() {
var error = new Error('crap');
var broken = function() { throw error; };
this.sinon.stub(Raven, 'captureException');
assert.throws(function() {
Raven.context({foo: 'bar'}, broken);
}, error);
assert.isTrue(Raven.captureException.called);
assert.deepEqual(Raven.captureException.lastCall.args, [error, {'foo': 'bar'}]);
});
it('should capture the exception without options', function() {
var error = new Error('crap');
var broken = function() { throw error; };
this.sinon.stub(Raven, 'captureException');
assert.throws(function() {
Raven.context(broken);
}, error);
assert.isTrue(Raven.captureException.called);
assert.deepEqual(Raven.captureException.lastCall.args, [error, undefined]);
});
it('should execute the callback without arguments', function() {
// This is only reproducable in a browser that complains about passing
// undefined to Function.apply
var spy = this.sinon.spy();
Raven.context(spy);
assert.deepEqual(spy.lastCall.args, []);
});
it('should return the result of the wrapped function', function() {
var val = {};
var func = function() { return val; };
assert.equal(Raven.context(func), val);
});
});
describe('.uninstall', function() {
it('should uninstall from TraceKit', function() {
this.sinon.stub(TraceKit.report, 'uninstall');
Raven.uninstall();
assert.isTrue(TraceKit.report.uninstall.calledOnce);
});
it('should set isRavenInstalled flag to false', function() {
isRavenInstalled = true;
this.sinon.stub(TraceKit.report, 'uninstall');
Raven.uninstall();
assert.isFalse(isRavenInstalled);
});
});
describe('.setUserContext', function() {
it('should set the globalUser object', function() {
Raven.setUserContext({name: 'Matt'});
assert.deepEqual(globalUser, {name: 'Matt'});
});
it('should clear the globalUser with no arguments', function() {
globalUser = {name: 'Matt'};
Raven.setUserContext();
assert.isUndefined(globalUser);
});
});
describe('.setExtraContext', function() {
it('should set the globalOptions.extra object', function() {
Raven.setExtraContext({name: 'Matt'});
assert.deepEqual(globalOptions.extra, {name: 'Matt'});
});
it('should clear globalOptions.extra with no arguments', function() {
globalOptions = {name: 'Matt'};
Raven.setExtraContext();
assert.deepEqual(globalOptions.extra, {});
});
});
describe('.setTagsContext', function() {
it('should set the globalOptions.tags object', function() {
Raven.setTagsContext({name: 'Matt'});
assert.deepEqual(globalOptions.tags, {name: 'Matt'});
});
it('should clear globalOptions.tags with no arguments', function() {
globalOptions = {name: 'Matt'};
Raven.setTagsContext();
assert.deepEqual(globalOptions.tags, {});
});
});
describe('.setReleaseContext', function() {
it('should set the globalOptions.release attribute', function() {
Raven.setReleaseContext('abc123');
assert.equal(globalOptions.release, 'abc123');
});
it('should clear globalOptions.release with no arguments', function() {
globalOptions.release = 'abc123';
Raven.setReleaseContext();
assert.isUndefined(globalOptions.release);
});
});
describe('.setDataCallback', function() {
it('should set the globalOptions.dataCallback attribute', function() {
var foo = function(){};
Raven.setDataCallback(foo);
assert.equal(globalOptions.dataCallback, foo);
});
it('should clear globalOptions.dataCallback with no arguments', function() {
var foo = function(){};
globalOptions.dataCallback = foo;
Raven.setDataCallback();
assert.isUndefined(globalOptions.dataCallback);
});
});
describe('.setShouldSendCallback', function() {
it('should set the globalOptions.shouldSendCallback attribute', function() {
var foo = function(){};
Raven.setShouldSendCallback(foo);
assert.equal(globalOptions.shouldSendCallback, foo);
});
it('should clear globalOptions.shouldSendCallback with no arguments', function() {
var foo = function(){};
globalOptions.shouldSendCallback = foo;
Raven.setShouldSendCallback();
assert.isUndefined(globalOptions.shouldSendCallback);
});
});
describe('.captureMessage', function() {
it('should work as advertised', function() {
this.sinon.stub(window, 'send');
Raven.captureMessage('lol', {foo: 'bar'});
assert.deepEqual(window.send.lastCall.args, [{
message: 'lol',
foo: 'bar'
}]);
});
it('should coerce message to a string', function() {
this.sinon.stub(window, 'send');
Raven.captureMessage({});
assert.deepEqual(window.send.lastCall.args, [{
message: '[object Object]'
}]);
});
it('should work as advertised #integration', function() {
var imageCache = [];
this.sinon.stub(window, 'newImage', function(){ var img = {}; imageCache.push(img); return img; });
setupRaven();
Raven.captureMessage('lol', {foo: 'bar'});
assert.equal(imageCache.length, 1);
// It'd be hard to assert the actual payload being sent
// since it includes the generated url, which is going to
// vary between users running the tests
// Unit tests should cover that the payload was constructed properly
});
it('should tag lastEventId #integration', function() {
setupRaven();
Raven.captureMessage('lol');
assert.equal(Raven.lastEventId(), 'abc123');
});
it('should respect `ignoreErrors`', function() {
this.sinon.stub(window, 'send');
globalOptions.ignoreErrors = joinRegExp(['e1', 'e2']);
Raven.captureMessage('e1');
assert.isFalse(window.send.called);
Raven.captureMessage('e2');
assert.isFalse(window.send.called);
Raven.captureMessage('Non-ignored error');
assert.isTrue(window.send.calledOnce);
});
});
describe('.captureException', function() {
it('should call TraceKit.report', function() {
var error = new Error('crap');
this.sinon.stub(TraceKit, 'report');
Raven.captureException(error, {foo: 'bar'});
assert.isTrue(TraceKit.report.calledOnce);
assert.deepEqual(TraceKit.report.lastCall.args, [error, {foo: 'bar'}]);
});
it('should store the last exception', function() {
var error = new Error('crap');
this.sinon.stub(TraceKit, 'report');
Raven.captureException(error);
assert.equal(Raven.lastException(), error);
});
it('shouldn\'t reraise the if the error is the same error', function() {
var error = new Error('crap');
this.sinon.stub(TraceKit, 'report').throws(error);
// this would raise if the errors didn't match
Raven.captureException(error, {foo: 'bar'});
assert.isTrue(TraceKit.report.calledOnce);
});
it('should reraise a different error', function() {
var error = new Error('crap1');
this.sinon.stub(TraceKit, 'report').throws(error);
assert.throws(function() {
Raven.captureException(new Error('crap2'));
}, error);
});
it('should capture as a normal message if a non-Error is passed', function() {
this.sinon.stub(Raven, 'captureMessage');
this.sinon.stub(TraceKit, 'report');
Raven.captureException('derp');
assert.equal(Raven.captureMessage.lastCall.args[0], 'derp');
assert.isFalse(TraceKit.report.called);
Raven.captureException(true);
assert.equal(Raven.captureMessage.lastCall.args[0], true);
assert.isFalse(TraceKit.report.called);
});
});
describe('.isSetup', function() {
it('should work as advertised', function() {
var isSetup = this.sinon.stub(window, 'isSetup');
isSetup.returns(true);
assert.isTrue(Raven.isSetup());
isSetup.returns(false);
assert.isFalse(Raven.isSetup());
});
});
});