rollbar 2.26.3 → 3.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/guidelines.mdc +154 -0
- package/.github/workflows/ci.yml +32 -12
- package/.lgtm.yml +7 -7
- package/.prettierignore +18 -0
- package/.vscode/settings.json +39 -0
- package/CHANGELOG.md +121 -35
- package/CLAUDE.md +201 -0
- package/Gruntfile.js +101 -48
- package/Makefile +3 -3
- package/README.md +2 -4
- package/SECURITY.md +5 -0
- package/babel.config.json +9 -0
- package/bower.json +1 -3
- package/codex.md +148 -0
- package/defaults.js +17 -5
- package/dist/plugins/jquery.min.js +1 -1
- package/dist/rollbar.js +18748 -5375
- package/dist/rollbar.js.map +1 -1
- package/dist/rollbar.min.js +2 -1
- package/dist/rollbar.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.min.js.map +1 -1
- package/dist/rollbar.named-amd.js +19368 -6000
- package/dist/rollbar.named-amd.js.map +1 -1
- package/dist/rollbar.named-amd.min.js +3 -1
- package/dist/rollbar.named-amd.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.named-amd.min.js.map +1 -1
- package/dist/rollbar.noconflict.umd.js +18749 -5380
- package/dist/rollbar.noconflict.umd.js.map +1 -1
- package/dist/rollbar.noconflict.umd.min.js +3 -1
- package/dist/rollbar.noconflict.umd.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.noconflict.umd.min.js.map +1 -1
- package/dist/rollbar.snippet.js +1 -1
- package/dist/rollbar.umd.js +19367 -6000
- package/dist/rollbar.umd.js.map +1 -1
- package/dist/rollbar.umd.min.js +3 -1
- package/dist/rollbar.umd.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.umd.min.js.map +1 -1
- package/docs/extension-exceptions.md +35 -30
- package/docs/migration_v0_to_v1.md +41 -38
- package/eslint.config.mjs +33 -0
- package/get_versions.js +33 -0
- package/index.d.ts +270 -231
- package/karma.conf.js +18 -27
- package/package.json +21 -21
- package/prettier.config.js +7 -0
- package/src/api.js +78 -14
- package/src/apiUtility.js +14 -11
- package/src/browser/core.js +138 -72
- package/src/browser/defaults/scrubFields.js +3 -3
- package/src/browser/detection.js +7 -8
- package/src/browser/domUtility.js +18 -8
- package/src/browser/globalSetup.js +12 -6
- package/src/browser/logger.js +1 -1
- package/src/browser/plugins/jquery.js +35 -35
- package/src/browser/predicates.js +1 -1
- package/src/browser/replay/defaults.js +71 -0
- package/src/browser/replay/recorder.js +193 -0
- package/src/browser/replay/replayMap.js +195 -0
- package/src/browser/rollbar.js +12 -8
- package/src/browser/rollbarWrapper.js +8 -5
- package/src/browser/shim.js +43 -19
- package/src/browser/snippet_callback.js +6 -4
- package/src/browser/telemetry.js +573 -361
- package/src/browser/transforms.js +46 -27
- package/src/browser/transport/fetch.js +26 -14
- package/src/browser/transport/xhr.js +41 -14
- package/src/browser/transport.js +93 -33
- package/src/browser/url.js +16 -8
- package/src/browser/wrapGlobals.js +27 -8
- package/src/defaults.js +3 -3
- package/src/errorParser.js +14 -11
- package/src/merge.js +32 -23
- package/src/notifier.js +16 -13
- package/src/predicates.js +43 -23
- package/src/queue.js +133 -40
- package/src/rateLimiter.js +59 -18
- package/src/react-native/logger.js +1 -1
- package/src/react-native/rollbar.js +59 -55
- package/src/react-native/transforms.js +13 -9
- package/src/react-native/transport.js +44 -34
- package/src/rollbar.js +72 -21
- package/src/scrub.js +0 -1
- package/src/server/locals.js +69 -39
- package/src/server/logger.js +4 -4
- package/src/server/parser.js +72 -47
- package/src/server/rollbar.js +135 -56
- package/src/server/sourceMap/stackTrace.js +33 -18
- package/src/server/telemetry/urlHelpers.js +9 -11
- package/src/server/telemetry.js +68 -45
- package/src/server/transforms.js +37 -21
- package/src/server/transport.js +62 -32
- package/src/telemetry.js +162 -33
- package/src/tracing/context.js +24 -0
- package/src/tracing/contextManager.js +37 -0
- package/src/tracing/defaults.js +7 -0
- package/src/tracing/exporter.js +188 -0
- package/src/tracing/hrtime.js +98 -0
- package/src/tracing/id.js +24 -0
- package/src/tracing/session.js +55 -0
- package/src/tracing/span.js +92 -0
- package/src/tracing/spanProcessor.js +15 -0
- package/src/tracing/tracer.js +46 -0
- package/src/tracing/tracing.js +89 -0
- package/src/transforms.js +33 -21
- package/src/truncation.js +8 -5
- package/src/utility/headers.js +43 -43
- package/src/utility/replace.js +9 -0
- package/src/utility/traverse.js +1 -1
- package/src/utility.js +123 -52
- package/test/api.test.js +88 -41
- package/test/apiUtility.test.js +48 -50
- package/test/browser.core.test.js +142 -141
- package/test/browser.domUtility.test.js +53 -36
- package/test/browser.predicates.test.js +14 -14
- package/test/browser.replay.recorder.test.js +416 -0
- package/test/browser.rollbar.test.js +655 -515
- package/test/browser.telemetry.test.js +46 -39
- package/test/browser.transforms.test.js +164 -139
- package/test/browser.transport.test.js +59 -50
- package/test/browser.url.test.js +13 -12
- package/test/fixtures/locals.fixtures.js +245 -126
- package/test/fixtures/replay/index.js +20 -0
- package/test/fixtures/replay/payloads.fixtures.js +229 -0
- package/test/fixtures/replay/rrwebEvents.fixtures.js +251 -0
- package/test/fixtures/replay/rrwebSyntheticEvents.fixtures.js +328 -0
- package/test/notifier.test.js +91 -79
- package/test/predicates.test.js +261 -215
- package/test/queue.test.js +231 -215
- package/test/rateLimiter.test.js +51 -43
- package/test/react-native.rollbar.test.js +150 -116
- package/test/react-native.transforms.test.js +23 -25
- package/test/react-native.transport.test.js +26 -14
- package/test/replay/index.js +2 -0
- package/test/replay/integration/api.spans.test.js +136 -0
- package/test/replay/integration/e2e.test.js +228 -0
- package/test/replay/integration/index.js +9 -0
- package/test/replay/integration/queue.replayMap.test.js +332 -0
- package/test/replay/integration/replayMap.test.js +163 -0
- package/test/replay/integration/sessionRecording.test.js +390 -0
- package/test/replay/unit/api.postSpans.test.js +150 -0
- package/test/replay/unit/index.js +7 -0
- package/test/replay/unit/queue.replayMap.test.js +225 -0
- package/test/replay/unit/replayMap.test.js +348 -0
- package/test/replay/util/index.js +5 -0
- package/test/replay/util/mockRecordFn.js +80 -0
- package/test/server.lambda.mocha.test.mjs +172 -0
- package/test/server.locals.constructor.mocha.test.mjs +80 -0
- package/test/server.locals.error-handling.mocha.test.mjs +387 -0
- package/test/server.locals.merge.mocha.test.mjs +267 -0
- package/test/server.locals.test-utils.mjs +114 -0
- package/test/server.parser.mocha.test.mjs +87 -0
- package/test/server.predicates.mocha.test.mjs +63 -0
- package/test/server.rollbar.constructor.mocha.test.mjs +199 -0
- package/test/server.rollbar.handlers.mocha.test.mjs +253 -0
- package/test/server.rollbar.logging.mocha.test.mjs +326 -0
- package/test/server.rollbar.misc.mocha.test.mjs +44 -0
- package/test/server.rollbar.test-utils.mjs +57 -0
- package/test/server.telemetry.mocha.test.mjs +377 -0
- package/test/server.transforms.data.mocha.test.mjs +163 -0
- package/test/server.transforms.error.mocha.test.mjs +199 -0
- package/test/server.transforms.request.mocha.test.mjs +208 -0
- package/test/server.transforms.scrub.mocha.test.mjs +140 -0
- package/test/server.transforms.sourcemaps.mocha.test.mjs +122 -0
- package/test/server.transforms.test-utils.mjs +62 -0
- package/test/server.transport.mocha.test.mjs +269 -0
- package/test/telemetry.test.js +178 -38
- package/test/tracing/contextManager.test.js +28 -0
- package/test/tracing/exporter.toPayload.test.js +400 -0
- package/test/tracing/id.test.js +24 -0
- package/test/tracing/span.test.js +183 -0
- package/test/tracing/spanProcessor.test.js +73 -0
- package/test/tracing/tracing.test.js +105 -0
- package/test/transforms.test.js +70 -68
- package/test/truncation.test.js +57 -55
- package/test/utility.test.js +310 -228
- package/webpack.config.js +36 -70
- package/.eslintignore +0 -7
- package/.gitmodules +0 -3
- package/test/server.lambda.test.js +0 -177
- package/test/server.locals.test.js +0 -841
- package/test/server.parser.test.js +0 -72
- package/test/server.predicates.test.js +0 -89
- package/test/server.rollbar.test.js +0 -676
- package/test/server.telemetry.test.js +0 -318
- package/test/server.transforms.test.js +0 -1099
- package/test/server.transport.test.js +0 -201
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for the Recorder and Tracing interaction
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/* globals describe */
|
|
6
|
+
/* globals it */
|
|
7
|
+
/* globals beforeEach */
|
|
8
|
+
/* globals afterEach */
|
|
9
|
+
|
|
10
|
+
import { expect } from 'chai';
|
|
11
|
+
import sinon from 'sinon';
|
|
12
|
+
|
|
13
|
+
import Tracing from '../../../src/tracing/tracing.js';
|
|
14
|
+
import { Context } from '../../../src/tracing/context.js';
|
|
15
|
+
import Recorder from '../../../src/browser/replay/recorder.js';
|
|
16
|
+
import ReplayMap from '../../../src/browser/replay/replayMap.js';
|
|
17
|
+
import recorderDefaults from '../../../src/browser/replay/defaults.js';
|
|
18
|
+
import { mockRecordFn } from '../util';
|
|
19
|
+
import Api from '../../../src/api.js';
|
|
20
|
+
import Queue from '../../../src/queue.js';
|
|
21
|
+
import { createPayloadWithReplayId } from '../../fixtures/replay';
|
|
22
|
+
|
|
23
|
+
const options = {
|
|
24
|
+
enabled: true,
|
|
25
|
+
resource: {
|
|
26
|
+
attributes: {
|
|
27
|
+
'service.name': 'unknown_service',
|
|
28
|
+
'telemetry.sdk.language': 'webjs',
|
|
29
|
+
'telemetry.sdk.name': 'rollbar',
|
|
30
|
+
'telemetry.sdk.version': '0.1.0',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
notifier: {
|
|
34
|
+
name: 'rollbar.js',
|
|
35
|
+
version: '0.1.0',
|
|
36
|
+
},
|
|
37
|
+
recorder: {
|
|
38
|
+
...recorderDefaults,
|
|
39
|
+
enabled: true,
|
|
40
|
+
autoStart: false,
|
|
41
|
+
emitEveryNms: 100, // non-rrweb, used by mockRecordFn
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const createMockTransport = () => {
|
|
46
|
+
return {
|
|
47
|
+
post: sinon
|
|
48
|
+
.stub()
|
|
49
|
+
.callsFake((accessToken, transportOptions, payload, callback) => {
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
callback(
|
|
52
|
+
null,
|
|
53
|
+
{ err: 0, result: { id: '12345' } },
|
|
54
|
+
{ 'Rollbar-Replay-Enabled': 'true' }
|
|
55
|
+
);
|
|
56
|
+
}, 10);
|
|
57
|
+
}),
|
|
58
|
+
postJsonPayload: sinon.stub(),
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const getAttributeValue = (attributes, key) => {
|
|
63
|
+
const attr = attributes.find((attr) => attr.key === key);
|
|
64
|
+
return attr ? attr.value : null;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
describe('Session Replay Integration', function () {
|
|
68
|
+
let tracing;
|
|
69
|
+
let recorder;
|
|
70
|
+
|
|
71
|
+
beforeEach(function () {
|
|
72
|
+
tracing = new Tracing(window, options);
|
|
73
|
+
tracing.initSession();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
afterEach(function () {
|
|
77
|
+
if (recorder) {
|
|
78
|
+
recorder.stop();
|
|
79
|
+
}
|
|
80
|
+
sinon.restore();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('dumping recording should export tracing', function (done) {
|
|
84
|
+
recorder = new Recorder(options.recorder, mockRecordFn);
|
|
85
|
+
recorder.start();
|
|
86
|
+
|
|
87
|
+
const tracingContext = tracing.contextManager.active();
|
|
88
|
+
expect(tracingContext).to.be.instanceOf(Context);
|
|
89
|
+
|
|
90
|
+
const dumpRecording = () => {
|
|
91
|
+
const replayId = 'test-replay-id';
|
|
92
|
+
const uuid = 'test-uuid';
|
|
93
|
+
const payload = recorder.dump(tracing, replayId, uuid);
|
|
94
|
+
|
|
95
|
+
expect(payload).to.not.be.null;
|
|
96
|
+
expect(payload).to.be.an('object');
|
|
97
|
+
expect(payload).to.have.property('resourceSpans');
|
|
98
|
+
expect(payload.resourceSpans).to.be.an('array');
|
|
99
|
+
expect(payload.resourceSpans[0]).to.have.property('scopeSpans');
|
|
100
|
+
expect(payload.resourceSpans[0].scopeSpans[0]).to.have.property('spans');
|
|
101
|
+
|
|
102
|
+
const uuidValue = getAttributeValue(
|
|
103
|
+
payload.resourceSpans[0].scopeSpans[0].spans[0].attributes,
|
|
104
|
+
'rollbar.occurrence.uuid',
|
|
105
|
+
);
|
|
106
|
+
expect(uuidValue['stringValue']).to.equal(uuid);
|
|
107
|
+
|
|
108
|
+
const events = payload.resourceSpans[0].scopeSpans[0].spans[0].events;
|
|
109
|
+
expect(events.length).to.be.greaterThan(0);
|
|
110
|
+
expect(events.every((e) => e.name === 'rrweb-replay-events')).to.be.true;
|
|
111
|
+
|
|
112
|
+
const eventAttrs = events[0].attributes;
|
|
113
|
+
const eventTypeAttr = eventAttrs.find((attr) => attr.key === 'eventType');
|
|
114
|
+
const jsonAttr = eventAttrs.find((attr) => attr.key === 'json');
|
|
115
|
+
|
|
116
|
+
expect(eventTypeAttr).to.exist;
|
|
117
|
+
expect(jsonAttr).to.exist;
|
|
118
|
+
|
|
119
|
+
done();
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
setTimeout(dumpRecording, 1000);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should handle checkouts correctly', function (done) {
|
|
126
|
+
recorder = new Recorder(
|
|
127
|
+
{
|
|
128
|
+
...options.recorder,
|
|
129
|
+
checkoutEveryNms: 250,
|
|
130
|
+
},
|
|
131
|
+
mockRecordFn,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
recorder.start();
|
|
135
|
+
|
|
136
|
+
const dumpRecording = () => {
|
|
137
|
+
const replayId = 'test-replay-id';
|
|
138
|
+
const payload = recorder.dump(tracing, replayId);
|
|
139
|
+
|
|
140
|
+
expect(payload).to.not.be.null;
|
|
141
|
+
expect(payload).to.be.an('object');
|
|
142
|
+
expect(payload).to.have.property('resourceSpans');
|
|
143
|
+
expect(payload.resourceSpans).to.be.an('array');
|
|
144
|
+
expect(payload.resourceSpans[0]).to.have.property('scopeSpans');
|
|
145
|
+
expect(payload.resourceSpans[0].scopeSpans[0]).to.have.property('spans');
|
|
146
|
+
|
|
147
|
+
const events = payload.resourceSpans[0].scopeSpans[0].spans[0].events;
|
|
148
|
+
expect(events).to.be.an('array');
|
|
149
|
+
expect(events.length).to.be.greaterThan(0);
|
|
150
|
+
|
|
151
|
+
done();
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
setTimeout(dumpRecording, 1000);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should handle no checkouts correctly', function (done) {
|
|
158
|
+
recorder = new Recorder(
|
|
159
|
+
{
|
|
160
|
+
...options.recorder,
|
|
161
|
+
checkoutEveryNms: 500,
|
|
162
|
+
},
|
|
163
|
+
mockRecordFn,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
recorder.start();
|
|
167
|
+
|
|
168
|
+
const dumpRecording = () => {
|
|
169
|
+
const replayId = 'test-replay-id';
|
|
170
|
+
const payload = recorder.dump(tracing, replayId);
|
|
171
|
+
|
|
172
|
+
expect(payload).to.not.be.null;
|
|
173
|
+
expect(payload).to.be.an('object');
|
|
174
|
+
expect(payload).to.have.property('resourceSpans');
|
|
175
|
+
expect(payload.resourceSpans).to.be.an('array');
|
|
176
|
+
expect(payload.resourceSpans[0]).to.have.property('scopeSpans');
|
|
177
|
+
expect(payload.resourceSpans[0].scopeSpans[0]).to.have.property('spans');
|
|
178
|
+
|
|
179
|
+
const events = payload.resourceSpans[0].scopeSpans[0].spans[0].events;
|
|
180
|
+
expect(events).to.be.an('array');
|
|
181
|
+
expect(events.length).to.be.greaterThan(0);
|
|
182
|
+
|
|
183
|
+
done();
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
setTimeout(dumpRecording, 250);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('Session Replay Transport Integration', function () {
|
|
191
|
+
let tracing;
|
|
192
|
+
let recorder;
|
|
193
|
+
let api;
|
|
194
|
+
let transport;
|
|
195
|
+
let replayMap;
|
|
196
|
+
let queue;
|
|
197
|
+
|
|
198
|
+
beforeEach(function () {
|
|
199
|
+
transport = createMockTransport();
|
|
200
|
+
const urlMock = { parse: sinon.stub().returns({}) };
|
|
201
|
+
const truncationMock = {
|
|
202
|
+
truncate: sinon.stub().returns({ error: null, value: '{}' }),
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
tracing = new Tracing(window, options);
|
|
206
|
+
tracing.initSession();
|
|
207
|
+
|
|
208
|
+
api = new Api(
|
|
209
|
+
{ accessToken: 'test-token' },
|
|
210
|
+
transport,
|
|
211
|
+
urlMock,
|
|
212
|
+
truncationMock,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
recorder = new Recorder(options.recorder, mockRecordFn);
|
|
216
|
+
|
|
217
|
+
sinon.stub(recorder, 'dump').callsFake((tracing, replayId) => {
|
|
218
|
+
return createPayloadWithReplayId(replayId);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
replayMap = new ReplayMap({
|
|
222
|
+
recorder,
|
|
223
|
+
api,
|
|
224
|
+
tracing,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
queue = new Queue(
|
|
228
|
+
{ shouldSend: () => ({ shouldSend: true }) },
|
|
229
|
+
api,
|
|
230
|
+
console,
|
|
231
|
+
{ transmit: true, retryInterval: 500 },
|
|
232
|
+
replayMap,
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
recorder.start();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
afterEach(function () {
|
|
239
|
+
if (recorder) {
|
|
240
|
+
recorder.stop();
|
|
241
|
+
}
|
|
242
|
+
sinon.restore();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should add replayId to error item and send replay on success', function (done) {
|
|
246
|
+
const addSpy = sinon.spy(replayMap, 'add');
|
|
247
|
+
const sendSpy = sinon.spy(replayMap, 'send');
|
|
248
|
+
const postSpansSpy = sinon.spy(api, 'postSpans');
|
|
249
|
+
|
|
250
|
+
const errorItem = {
|
|
251
|
+
body: {
|
|
252
|
+
trace: {
|
|
253
|
+
exception: {
|
|
254
|
+
message: 'Test error for session replay',
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
attributes: [
|
|
259
|
+
{ key: 'replay_id', value: '1234567812345678' },
|
|
260
|
+
],
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
queue.addItem(errorItem, function (err, resp) {
|
|
264
|
+
expect(errorItem).to.have.property('replayId');
|
|
265
|
+
expect(addSpy.calledOnce).to.be.true;
|
|
266
|
+
|
|
267
|
+
expect(err).to.be.null;
|
|
268
|
+
expect(resp).to.have.property('err', 0);
|
|
269
|
+
|
|
270
|
+
setTimeout(function () {
|
|
271
|
+
expect(sendSpy.calledWith(errorItem.replayId)).to.be.true;
|
|
272
|
+
expect(postSpansSpy.calledOnce).to.be.true;
|
|
273
|
+
|
|
274
|
+
done();
|
|
275
|
+
}, 50);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should discard replay when API returns an error', function (done) {
|
|
280
|
+
transport.post = sinon
|
|
281
|
+
.stub()
|
|
282
|
+
.callsFake((accessToken, transportOptions, payload, callback) => {
|
|
283
|
+
setTimeout(() => {
|
|
284
|
+
callback(null, { err: 1, message: 'API Error' });
|
|
285
|
+
}, 10);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const addSpy = sinon.spy(replayMap, 'add');
|
|
289
|
+
const sendSpy = sinon.spy(replayMap, 'send');
|
|
290
|
+
const discardSpy = sinon.spy(replayMap, 'discard');
|
|
291
|
+
|
|
292
|
+
const errorItem = {
|
|
293
|
+
body: {
|
|
294
|
+
trace: {
|
|
295
|
+
exception: {
|
|
296
|
+
message: 'Test error with API failure',
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
attributes: [
|
|
301
|
+
{ key: 'replay_id', value: '1234567812345678' },
|
|
302
|
+
],
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
queue.addItem(errorItem, function (err, resp) {
|
|
306
|
+
expect(errorItem).to.have.property('replayId');
|
|
307
|
+
expect(addSpy.calledOnce).to.be.true;
|
|
308
|
+
|
|
309
|
+
setTimeout(function () {
|
|
310
|
+
expect(discardSpy.calledWith(errorItem.replayId)).to.be.true;
|
|
311
|
+
expect(sendSpy.called).to.be.false;
|
|
312
|
+
|
|
313
|
+
done();
|
|
314
|
+
}, 50);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('should handle full end-to-end flow from error to spans', async function () {
|
|
319
|
+
const addSpy = sinon.spy(replayMap, 'add');
|
|
320
|
+
const sendSpy = sinon.spy(replayMap, 'send');
|
|
321
|
+
const postSpansSpy = sinon.spy(api, 'postSpans');
|
|
322
|
+
|
|
323
|
+
const handleReplayResponseSpy = sinon.spy(queue, '_handleReplayResponse');
|
|
324
|
+
|
|
325
|
+
const errorItem = {
|
|
326
|
+
body: {
|
|
327
|
+
trace: {
|
|
328
|
+
exception: {
|
|
329
|
+
message: 'Test E2E flow',
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
attributes: [
|
|
334
|
+
{ key: 'replay_id', value: '1234567812345678' },
|
|
335
|
+
],
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const addItemPromise = new Promise((resolve, reject) => {
|
|
339
|
+
queue.addItem(errorItem, (err, resp) => {
|
|
340
|
+
if (err) reject(err);
|
|
341
|
+
else resolve(resp);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
await addItemPromise;
|
|
346
|
+
expect(errorItem).to.have.property('replayId');
|
|
347
|
+
expect(addSpy.calledOnce).to.be.true;
|
|
348
|
+
|
|
349
|
+
const responsePromise = handleReplayResponseSpy.returnValues[0];
|
|
350
|
+
await responsePromise;
|
|
351
|
+
expect(sendSpy.calledOnce).to.be.true;
|
|
352
|
+
expect(sendSpy.calledWith(errorItem.replayId)).to.be.true;
|
|
353
|
+
expect(postSpansSpy.calledOnce).to.be.true;
|
|
354
|
+
|
|
355
|
+
expect(recorder.dump.called).to.be.true;
|
|
356
|
+
expect(recorder.dump.calledWith(tracing, errorItem.replayId)).to.be.true;
|
|
357
|
+
const callArgs = postSpansSpy.firstCall.args;
|
|
358
|
+
expect(callArgs[0]).to.be.an('object');
|
|
359
|
+
expect(callArgs[0].resourceSpans[0].scopeSpans[0].spans[0].name).to.equal(
|
|
360
|
+
'rrweb-replay-recording',
|
|
361
|
+
);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should not add replayId when replayMap is not provided', function (done) {
|
|
365
|
+
const queueWithoutReplayMap = new Queue(
|
|
366
|
+
{ shouldSend: () => ({ shouldSend: true }) },
|
|
367
|
+
api,
|
|
368
|
+
console,
|
|
369
|
+
{ transmit: true },
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
const errorItem = {
|
|
373
|
+
body: {
|
|
374
|
+
trace: {
|
|
375
|
+
exception: {
|
|
376
|
+
message: 'Test without replayMap',
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
attributes: [
|
|
381
|
+
{ key: 'replay_id', value: '1234567812345678' },
|
|
382
|
+
],
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
queueWithoutReplayMap.addItem(errorItem, function (err, resp) {
|
|
386
|
+
expect(errorItem).to.not.have.property('replayId');
|
|
387
|
+
done();
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the postSpans method in the API module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/* globals describe */
|
|
6
|
+
/* globals it */
|
|
7
|
+
/* globals beforeEach */
|
|
8
|
+
/* globals afterEach */
|
|
9
|
+
/* globals sinon */
|
|
10
|
+
|
|
11
|
+
import { expect } from 'chai';
|
|
12
|
+
import sinon from 'sinon';
|
|
13
|
+
|
|
14
|
+
const Api = require('../../../src/api');
|
|
15
|
+
|
|
16
|
+
describe('Api', function () {
|
|
17
|
+
let api;
|
|
18
|
+
let transport;
|
|
19
|
+
let mockUrl;
|
|
20
|
+
|
|
21
|
+
beforeEach(function () {
|
|
22
|
+
transport = {
|
|
23
|
+
post: sinon
|
|
24
|
+
.stub()
|
|
25
|
+
.callsFake((accessToken, options, payload, callback) => {
|
|
26
|
+
callback(null, { result: 'ok' });
|
|
27
|
+
}),
|
|
28
|
+
postJsonPayload: sinon.stub(),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
mockUrl = {
|
|
32
|
+
parse: sinon.stub().returns({
|
|
33
|
+
hostname: 'api.rollbar.com',
|
|
34
|
+
protocol: 'https:',
|
|
35
|
+
port: 443,
|
|
36
|
+
pathname: '/api/1/item/',
|
|
37
|
+
search: null,
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
api = new Api(
|
|
42
|
+
{ accessToken: 'test_token' },
|
|
43
|
+
transport,
|
|
44
|
+
mockUrl,
|
|
45
|
+
null, // truncation
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterEach(function () {
|
|
50
|
+
sinon.restore();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('postSpans', function () {
|
|
54
|
+
it('should create proper transport options for session endpoint', async function () {
|
|
55
|
+
const spans = [{ id: 'span1' }];
|
|
56
|
+
await api.postSpans(spans);
|
|
57
|
+
|
|
58
|
+
expect(transport.post.called).to.be.true;
|
|
59
|
+
|
|
60
|
+
const options = transport.post.firstCall.args[1];
|
|
61
|
+
expect(options.path).to.include('/session/');
|
|
62
|
+
expect(options.method).to.equal('POST');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should create a payload with resourceSpans', async function () {
|
|
66
|
+
const spans = { resourceSpans: [{ id: 'span1' }] };
|
|
67
|
+
await api.postSpans(spans);
|
|
68
|
+
|
|
69
|
+
expect(transport.post.called).to.be.true;
|
|
70
|
+
|
|
71
|
+
const payload = transport.post.firstCall.args[2];
|
|
72
|
+
expect(payload).to.have.property('resourceSpans');
|
|
73
|
+
expect(payload).to.deep.equal(spans);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should return a promise that resolves with the response', async function () {
|
|
77
|
+
const spans = [{ id: 'span1' }];
|
|
78
|
+
const expectedResponse = { result: 'ok' };
|
|
79
|
+
|
|
80
|
+
const result = await api.postSpans(spans);
|
|
81
|
+
expect(result).to.deep.equal(expectedResponse);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should handle transport errors', async function () {
|
|
85
|
+
const spans = [{ id: 'span1' }];
|
|
86
|
+
const expectedError = new Error('Transport error');
|
|
87
|
+
|
|
88
|
+
transport.post.callsFake((accessToken, options, payload, callback) => {
|
|
89
|
+
callback(expectedError);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
await api.postSpans(spans);
|
|
94
|
+
expect.fail('Should have thrown an error');
|
|
95
|
+
} catch (error) {
|
|
96
|
+
expect(error).to.equal(expectedError);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should use the provided access token', async function () {
|
|
101
|
+
const spans = [{ id: 'span1' }];
|
|
102
|
+
await api.postSpans(spans);
|
|
103
|
+
|
|
104
|
+
expect(transport.post.called).to.be.true;
|
|
105
|
+
const accessToken = transport.post.firstCall.args[0];
|
|
106
|
+
expect(accessToken).to.equal('test_token');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should update transport options when API is reconfigured', async function () {
|
|
110
|
+
await api.postSpans([{ id: 'span1' }]);
|
|
111
|
+
|
|
112
|
+
api.configure({
|
|
113
|
+
endpoint: 'https://custom.rollbar.com/api/1/session/',
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await api.postSpans([{ id: 'span2' }]);
|
|
117
|
+
|
|
118
|
+
expect(transport.post.callCount).to.equal(2);
|
|
119
|
+
|
|
120
|
+
const optionsAfterConfig = transport.post.secondCall.args[1];
|
|
121
|
+
expect(optionsAfterConfig.hostname).to.equal('api.rollbar.com');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('_postPromise', function () {
|
|
126
|
+
it('should resolve with response when transport succeeds', async function () {
|
|
127
|
+
const expectedResponse = { success: true };
|
|
128
|
+
transport.post.callsFake((accessToken, options, payload, callback) => {
|
|
129
|
+
callback(null, expectedResponse);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const result = await api._postPromise('token', {}, {});
|
|
133
|
+
expect(result).to.deep.equal(expectedResponse);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should reject with error when transport fails', async function () {
|
|
137
|
+
const expectedError = new Error('Transport error');
|
|
138
|
+
transport.post.callsFake((accessToken, options, payload, callback) => {
|
|
139
|
+
callback(expectedError);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
await api._postPromise('token', {}, {});
|
|
144
|
+
expect.fail('Should have thrown an error');
|
|
145
|
+
} catch (error) {
|
|
146
|
+
expect(error).to.equal(expectedError);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|