rollbar 2.26.4 → 3.0.0-alpha.2

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.
Files changed (140) hide show
  1. package/.claude/settings.local.json +3 -0
  2. package/.cursor/rules/guidelines.mdc +154 -0
  3. package/.github/workflows/ci.yml +4 -6
  4. package/CLAUDE.local.md +297 -0
  5. package/CLAUDE.md +201 -0
  6. package/CLAUDE.testrunner.md +470 -0
  7. package/Gruntfile.js +59 -16
  8. package/Makefile +3 -3
  9. package/SECURITY.md +5 -0
  10. package/babel.config.json +9 -0
  11. package/codex.md +148 -0
  12. package/dist/plugins/jquery.min.js +1 -1
  13. package/dist/rollbar.js +19332 -6596
  14. package/dist/rollbar.js.map +1 -1
  15. package/dist/rollbar.min.js +2 -1
  16. package/dist/rollbar.min.js.LICENSE.txt +1 -0
  17. package/dist/rollbar.min.js.map +1 -1
  18. package/dist/rollbar.named-amd.js +19332 -6596
  19. package/dist/rollbar.named-amd.js.map +1 -1
  20. package/dist/rollbar.named-amd.min.js +2 -1
  21. package/dist/rollbar.named-amd.min.js.LICENSE.txt +1 -0
  22. package/dist/rollbar.named-amd.min.js.map +1 -1
  23. package/dist/rollbar.noconflict.umd.js +19319 -6581
  24. package/dist/rollbar.noconflict.umd.js.map +1 -1
  25. package/dist/rollbar.noconflict.umd.min.js +2 -1
  26. package/dist/rollbar.noconflict.umd.min.js.LICENSE.txt +1 -0
  27. package/dist/rollbar.noconflict.umd.min.js.map +1 -1
  28. package/dist/rollbar.snippet.js +1 -1
  29. package/dist/rollbar.umd.js +19333 -6597
  30. package/dist/rollbar.umd.js.map +1 -1
  31. package/dist/rollbar.umd.min.js +2 -1
  32. package/dist/rollbar.umd.min.js.LICENSE.txt +1 -0
  33. package/dist/rollbar.umd.min.js.map +1 -1
  34. package/eslint.config.mjs +33 -0
  35. package/karma.conf.js +5 -14
  36. package/package.json +19 -20
  37. package/src/api.js +57 -4
  38. package/src/apiUtility.js +2 -3
  39. package/src/browser/core.js +37 -9
  40. package/src/browser/replay/defaults.js +70 -0
  41. package/src/browser/replay/recorder.js +194 -0
  42. package/src/browser/replay/replayMap.js +195 -0
  43. package/src/browser/rollbar.js +11 -7
  44. package/src/browser/telemetry.js +3 -3
  45. package/src/browser/transport/fetch.js +17 -4
  46. package/src/browser/transport/xhr.js +17 -1
  47. package/src/browser/transport.js +11 -8
  48. package/src/defaults.js +1 -1
  49. package/src/queue.js +65 -4
  50. package/src/react-native/rollbar.js +1 -1
  51. package/src/rollbar.js +52 -10
  52. package/src/server/rollbar.js +3 -2
  53. package/src/telemetry.js +76 -11
  54. package/src/tracing/context.js +24 -0
  55. package/src/tracing/contextManager.js +37 -0
  56. package/src/tracing/defaults.js +7 -0
  57. package/src/tracing/exporter.js +188 -0
  58. package/src/tracing/hrtime.js +98 -0
  59. package/src/tracing/id.js +24 -0
  60. package/src/tracing/session.js +55 -0
  61. package/src/tracing/span.js +92 -0
  62. package/src/tracing/spanProcessor.js +15 -0
  63. package/src/tracing/tracer.js +46 -0
  64. package/src/tracing/tracing.js +89 -0
  65. package/src/utility.js +34 -0
  66. package/test/api.test.js +57 -12
  67. package/test/apiUtility.test.js +5 -6
  68. package/test/browser.core.test.js +1 -10
  69. package/test/browser.domUtility.test.js +1 -1
  70. package/test/browser.predicates.test.js +1 -1
  71. package/test/browser.replay.recorder.test.js +430 -0
  72. package/test/browser.rollbar.test.js +58 -12
  73. package/test/browser.telemetry.test.js +1 -1
  74. package/test/browser.transforms.test.js +20 -13
  75. package/test/browser.transport.test.js +5 -4
  76. package/test/browser.url.test.js +1 -1
  77. package/test/fixtures/replay/index.js +20 -0
  78. package/test/fixtures/replay/payloads.fixtures.js +229 -0
  79. package/test/fixtures/replay/rrwebEvents.fixtures.js +251 -0
  80. package/test/fixtures/replay/rrwebSyntheticEvents.fixtures.js +328 -0
  81. package/test/notifier.test.js +1 -1
  82. package/test/predicates.test.js +1 -1
  83. package/test/queue.test.js +1 -1
  84. package/test/rateLimiter.test.js +1 -1
  85. package/test/react-native.rollbar.test.js +1 -1
  86. package/test/react-native.transforms.test.js +2 -2
  87. package/test/react-native.transport.test.js +3 -3
  88. package/test/replay/index.js +2 -0
  89. package/test/replay/integration/api.spans.test.js +136 -0
  90. package/test/replay/integration/e2e.test.js +228 -0
  91. package/test/replay/integration/index.js +9 -0
  92. package/test/replay/integration/queue.replayMap.test.js +332 -0
  93. package/test/replay/integration/replayMap.test.js +163 -0
  94. package/test/replay/integration/sessionRecording.test.js +390 -0
  95. package/test/replay/unit/api.postSpans.test.js +150 -0
  96. package/test/replay/unit/index.js +7 -0
  97. package/test/replay/unit/queue.replayMap.test.js +225 -0
  98. package/test/replay/unit/replayMap.test.js +348 -0
  99. package/test/replay/util/index.js +5 -0
  100. package/test/replay/util/mockRecordFn.js +80 -0
  101. package/test/server.lambda.mocha.test.mjs +172 -0
  102. package/test/server.locals.constructor.mocha.test.mjs +80 -0
  103. package/test/server.locals.error-handling.mocha.test.mjs +387 -0
  104. package/test/server.locals.merge.mocha.test.mjs +267 -0
  105. package/test/server.locals.test-utils.mjs +114 -0
  106. package/test/server.parser.mocha.test.mjs +87 -0
  107. package/test/server.predicates.mocha.test.mjs +63 -0
  108. package/test/server.rollbar.constructor.mocha.test.mjs +199 -0
  109. package/test/server.rollbar.handlers.mocha.test.mjs +253 -0
  110. package/test/server.rollbar.logging.mocha.test.mjs +326 -0
  111. package/test/server.rollbar.misc.mocha.test.mjs +44 -0
  112. package/test/server.rollbar.test-utils.mjs +57 -0
  113. package/test/server.telemetry.mocha.test.mjs +377 -0
  114. package/test/server.transforms.data.mocha.test.mjs +163 -0
  115. package/test/server.transforms.error.mocha.test.mjs +199 -0
  116. package/test/server.transforms.request.mocha.test.mjs +208 -0
  117. package/test/server.transforms.scrub.mocha.test.mjs +140 -0
  118. package/test/server.transforms.sourcemaps.mocha.test.mjs +122 -0
  119. package/test/server.transforms.test-utils.mjs +62 -0
  120. package/test/server.transport.mocha.test.mjs +269 -0
  121. package/test/telemetry.test.js +132 -1
  122. package/test/tracing/contextManager.test.js +28 -0
  123. package/test/tracing/exporter.toPayload.test.js +400 -0
  124. package/test/tracing/id.test.js +24 -0
  125. package/test/tracing/span.test.js +183 -0
  126. package/test/tracing/spanProcessor.test.js +73 -0
  127. package/test/tracing/tracing.test.js +105 -0
  128. package/test/transforms.test.js +2 -2
  129. package/test/truncation.test.js +2 -2
  130. package/test/utility.test.js +44 -6
  131. package/webpack.config.js +6 -44
  132. package/.eslintignore +0 -7
  133. package/test/server.lambda.test.js +0 -194
  134. package/test/server.locals.test.js +0 -1068
  135. package/test/server.parser.test.js +0 -78
  136. package/test/server.predicates.test.js +0 -91
  137. package/test/server.rollbar.test.js +0 -728
  138. package/test/server.telemetry.test.js +0 -443
  139. package/test/server.transforms.test.js +0 -1193
  140. package/test/server.transport.test.js +0 -269
@@ -0,0 +1,228 @@
1
+ /**
2
+ * End-to-end integration test for the complete session replay feature
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 Recorder from '../../../src/browser/replay/recorder.js';
15
+ import ReplayMap from '../../../src/browser/replay/replayMap.js';
16
+ import recorderDefaults from '../../../src/browser/replay/defaults.js';
17
+ import Api from '../../../src/api.js';
18
+ import Queue from '../../../src/queue.js';
19
+ import { mockRecordFn } from '../util';
20
+ import * as payloads from '../../fixtures/replay/payloads.fixtures.js';
21
+
22
+ const options = {
23
+ enabled: true,
24
+ resource: {
25
+ 'service.name': 'test_service',
26
+ },
27
+ notifier: {
28
+ name: 'rollbar.js',
29
+ version: '0.1.0',
30
+ },
31
+ recorder: {
32
+ ...recorderDefaults,
33
+ enabled: true,
34
+ autoStart: true,
35
+ emitEveryNms: 50,
36
+ },
37
+ payload: {
38
+ environment: 'testenv',
39
+ },
40
+ };
41
+
42
+ describe('Session Replay E2E', function () {
43
+ let tracing;
44
+ let recorder;
45
+ let api;
46
+ let transport;
47
+ let replayMap;
48
+ let queue;
49
+ let rateLimiter;
50
+
51
+ beforeEach(function () {
52
+ transport = {
53
+ post: sinon
54
+ .stub()
55
+ .callsFake((accessToken, transportOptions, payload, callback) => {
56
+ setTimeout(() => {
57
+ callback(
58
+ null,
59
+ { err: 0, result: { id: '12345' } },
60
+ { 'Rollbar-Replay-Enabled': 'true' }
61
+ );
62
+ }, 10);
63
+ }),
64
+ postJsonPayload: sinon.stub(),
65
+ };
66
+ const urlMock = { parse: sinon.stub().returns({}) };
67
+ const truncationMock = {
68
+ truncate: sinon.stub().returns({ error: null, value: '{}' }),
69
+ };
70
+ const logger = { error: sinon.spy(), log: sinon.spy() };
71
+
72
+ tracing = new Tracing(window, options);
73
+ tracing.initSession();
74
+ api = new Api(
75
+ { accessToken: 'test-token-12345' },
76
+ transport,
77
+ urlMock,
78
+ truncationMock,
79
+ );
80
+
81
+ recorder = new Recorder(options.recorder, mockRecordFn);
82
+
83
+ rateLimiter = { shouldSend: () => ({ shouldSend: true }) };
84
+
85
+ replayMap = new ReplayMap({
86
+ recorder,
87
+ api,
88
+ tracing,
89
+ });
90
+
91
+ queue = new Queue(rateLimiter, api, logger, { transmit: true }, replayMap);
92
+ });
93
+
94
+ afterEach(function () {
95
+ if (recorder && recorder.isRecording) {
96
+ recorder.stop();
97
+ }
98
+ sinon.restore();
99
+ });
100
+
101
+ it('should handle complete session replay flow from error to API call', function (done) {
102
+ recorder.start();
103
+
104
+ setTimeout(() => {
105
+ const recorderDumpSpy = sinon.spy(recorder, 'dump');
106
+ const replayMapAddSpy = sinon.spy(replayMap, 'add');
107
+ const replayMapSendSpy = sinon.spy(replayMap, 'send');
108
+ const apiPostItemSpy = sinon.spy(api, 'postItem');
109
+ const apiPostSpansSpy = sinon.spy(api, 'postSpans');
110
+
111
+ const errorItem = {
112
+ body: {
113
+ trace: {
114
+ exception: {
115
+ message: 'E2E test error',
116
+ },
117
+ },
118
+ },
119
+ attributes: [
120
+ { key: 'replay_id', value: '1234567812345678' },
121
+ ],
122
+ };
123
+
124
+ queue.addItem(errorItem, function (err, resp) {
125
+ expect(errorItem).to.have.property('replayId');
126
+ const expectedReplayId = errorItem.replayId;
127
+ expect(expectedReplayId).to.match(/^[0-9a-fA-F]{16}$/);
128
+
129
+ expect(replayMapAddSpy.calledOnce).to.be.true;
130
+ expect(apiPostItemSpy.calledOnce).to.be.true;
131
+
132
+ setTimeout(() => {
133
+ expect(recorderDumpSpy.called).to.be.true;
134
+ expect(recorderDumpSpy.calledWith(tracing, errorItem.replayId)).to.be
135
+ .true;
136
+ expect(replayMapSendSpy.calledWith(errorItem.replayId)).to.be.true;
137
+ expect(apiPostSpansSpy.calledOnce).to.be.true;
138
+
139
+ const payload = apiPostSpansSpy.firstCall.args[0];
140
+ expect(payload).to.be.an('object');
141
+ expect(payload).to.have.property('resourceSpans');
142
+ expect(payload.resourceSpans).to.be.an('array');
143
+ expect(payload.resourceSpans).to.have.length.greaterThan(0);
144
+
145
+ const resourceSpan = payload.resourceSpans[0];
146
+ expect(resourceSpan).to.have.property('resource');
147
+
148
+ const resource = payload.resourceSpans[0].resource;
149
+ expect(resource).to.deep.equal(
150
+ payloads.standardPayload.resourceSpans[0].resource,
151
+ );
152
+
153
+ const span = resourceSpan.scopeSpans[0].spans[0];
154
+ expect(span).to.have.property('name', 'rrweb-replay-recording');
155
+ expect(span).to.have.property('events');
156
+ expect(span.events).to.be.an('array');
157
+ expect(span).to.have.property('attributes').that.is.an('array');
158
+ expect(span.attributes).to.have.lengthOf(2);
159
+
160
+ expect(span.attributes).to.deep.include({
161
+ key: 'rollbar.replay.id',
162
+ value: { stringValue: expectedReplayId },
163
+ });
164
+
165
+ const sessionIdAttr = span.attributes.find(
166
+ (attr) => attr.key === 'session.id',
167
+ );
168
+ expect(sessionIdAttr).to.exist;
169
+ expect(sessionIdAttr.value.stringValue).to.match(/^[0-9a-fA-F]{32}$/);
170
+
171
+ const transportArgs = transport.post.lastCall.args;
172
+ expect(transportArgs[1].path).to.include('/api/1/session/');
173
+
174
+ done();
175
+ }, 200);
176
+ });
177
+ }, 50);
178
+ });
179
+
180
+ it('should integrate with real components in failure scenario', function (done) {
181
+ transport.post.callsFake(
182
+ (accessToken, transportOptions, payload, callback) => {
183
+ if (transportOptions.path.includes('/api/1/item/')) {
184
+ setTimeout(() => {
185
+ callback(null, { err: 1, message: 'API Error' });
186
+ }, 10);
187
+ } else {
188
+ setTimeout(() => {
189
+ callback(null, { err: 0, result: { id: '12345' } });
190
+ }, 10);
191
+ }
192
+ },
193
+ );
194
+
195
+ const replayMapAddSpy = sinon.spy(replayMap, 'add');
196
+ const replayMapDiscardSpy = sinon.spy(replayMap, 'discard');
197
+ const apiPostSpansSpy = sinon.spy(api, 'postSpans');
198
+
199
+ recorder.start();
200
+
201
+ const errorItem = {
202
+ body: {
203
+ trace: {
204
+ exception: {
205
+ message: 'Error that will fail to send',
206
+ },
207
+ },
208
+ },
209
+ attributes: [
210
+ { key: 'replay_id', value: '1234567812345678' },
211
+ ],
212
+ };
213
+
214
+ queue.addItem(errorItem, function (err, resp) {
215
+ expect(errorItem).to.have.property('replayId');
216
+ expect(replayMapAddSpy.calledOnce).to.be.true;
217
+ expect(resp).to.have.property('err', 1);
218
+
219
+ setTimeout(() => {
220
+ expect(replayMapDiscardSpy.calledWith(errorItem.replayId)).to.be.true;
221
+ expect(apiPostSpansSpy.called).to.be.false;
222
+ expect(replayMap.getSpans(errorItem.replayId)).to.be.null;
223
+
224
+ done();
225
+ }, 100);
226
+ });
227
+ });
228
+ });
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Session Replay Integration Tests
3
+ */
4
+
5
+ export * from './sessionRecording.test.js';
6
+ export * from './api.spans.test.js';
7
+ export * from './replayMap.test.js';
8
+ export * from './queue.replayMap.test.js';
9
+ export * from './e2e.test.js';
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Integration tests for Queue and ReplayMap
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 Queue from '../../../src/queue.js';
14
+ import Api from '../../../src/api.js';
15
+
16
+ describe('Queue ReplayMap Integration', function () {
17
+ let queue;
18
+ let replayMap;
19
+ let api;
20
+ let transport;
21
+
22
+ beforeEach(function () {
23
+ transport = {
24
+ post: sinon
25
+ .stub()
26
+ .callsFake((accessToken, transportOptions, payload, callback) => {
27
+ setTimeout(() => {
28
+ callback(
29
+ null,
30
+ { err: 0, result: { id: '12345' } },
31
+ { 'Rollbar-Replay-Enabled': 'true' }
32
+ );
33
+ }, 10);
34
+ }),
35
+ postJsonPayload: sinon.stub(),
36
+ };
37
+
38
+ const urlMock = { parse: sinon.stub().returns({}) };
39
+ const truncationMock = {
40
+ truncate: sinon.stub().returns({ error: null, value: '{}' }),
41
+ };
42
+
43
+ api = new Api(
44
+ { accessToken: 'test-token' },
45
+ transport,
46
+ urlMock,
47
+ truncationMock,
48
+ );
49
+
50
+ replayMap = {
51
+ add: sinon.stub().returnsArg(0),
52
+ send: sinon.stub().resolves(true),
53
+ discard: sinon.stub().returns(true),
54
+ getSpans: sinon.stub().returns([{ id: 'test-span' }]),
55
+ setSpans: sinon.stub(),
56
+ };
57
+
58
+ queue = new Queue(
59
+ { shouldSend: () => ({ shouldSend: true }) },
60
+ api,
61
+ console,
62
+ { transmit: true },
63
+ replayMap,
64
+ );
65
+ });
66
+
67
+ afterEach(function () {
68
+ sinon.restore();
69
+ });
70
+
71
+ it('should add replayId when processing an item', function (done) {
72
+ const item = {
73
+ body: {
74
+ trace: {
75
+ exception: {
76
+ message: 'Test error',
77
+ },
78
+ },
79
+ },
80
+ attributes: [
81
+ { key: 'replay_id', value: '1234567812345678' },
82
+ ],
83
+ };
84
+
85
+ queue.addItem(item, function () {
86
+ expect(item).to.have.property('replayId', '1234567812345678');
87
+ expect(replayMap.add.calledOnce).to.be.true;
88
+ done();
89
+ });
90
+ });
91
+
92
+ it('should call replayMap.send when API response is successful', function (done) {
93
+ const item = {
94
+ body: {
95
+ trace: {
96
+ exception: {
97
+ message: 'Test error',
98
+ },
99
+ },
100
+ },
101
+ attributes: [
102
+ { key: 'replay_id', value: '1234567812345678' },
103
+ ],
104
+ };
105
+
106
+ queue.addItem(item, function () {
107
+ setTimeout(function () {
108
+ expect(replayMap.send.calledWith('1234567812345678')).to.be.true;
109
+ done();
110
+ }, 50);
111
+ });
112
+ });
113
+
114
+ it('should call replayMap.discard when API response has error', function (done) {
115
+ transport.post.callsFake(
116
+ (accessToken, transportOptions, payload, callback) => {
117
+ setTimeout(() => {
118
+ callback(null, { err: 1, message: 'API Error' });
119
+ }, 10);
120
+ },
121
+ );
122
+
123
+ const item = {
124
+ body: {
125
+ trace: {
126
+ exception: {
127
+ message: 'Test error with API failure',
128
+ },
129
+ },
130
+ },
131
+ attributes: [
132
+ { key: 'replay_id', value: '1234567812345678' },
133
+ ],
134
+ };
135
+
136
+ queue.addItem(item, function () {
137
+ setTimeout(function () {
138
+ expect(replayMap.discard.calledWith('1234567812345678')).to.be.true;
139
+ expect(replayMap.send.called).to.be.false;
140
+ done();
141
+ }, 50);
142
+ });
143
+ });
144
+
145
+ it('should call replayMap.discard when replay is disabled', function (done) {
146
+ transport.post.callsFake(
147
+ (accessToken, transportOptions, payload, callback) => {
148
+ setTimeout(() => {
149
+ callback(
150
+ null,
151
+ { err: 0, result: { id: '12345' } },
152
+ { 'Rollbar-Replay-Enabled': 'false' }
153
+ );
154
+ }, 10);
155
+ },
156
+ );
157
+
158
+ const item = {
159
+ body: {
160
+ trace: {
161
+ exception: {
162
+ message: 'Test error with replay disabled',
163
+ },
164
+ },
165
+ },
166
+ attributes: [
167
+ { key: 'replay_id', value: '1234567812345678' },
168
+ ],
169
+ };
170
+
171
+ queue.addItem(item, function () {
172
+ setTimeout(function () {
173
+ expect(replayMap.discard.calledWith('1234567812345678')).to.be.true;
174
+ expect(replayMap.send.called).to.be.false;
175
+ done();
176
+ }, 50);
177
+ });
178
+ });
179
+
180
+ it('should call replayMap.discard when over quota', function (done) {
181
+ transport.post.callsFake(
182
+ (accessToken, transportOptions, payload, callback) => {
183
+ setTimeout(() => {
184
+ callback(
185
+ null,
186
+ { err: 0, result: { id: '12345' } },
187
+ { 'Rollbar-Replay-Enabled': 'true',
188
+ 'Rollbar-Replay-RateLimit-Remaining': '0'}
189
+ );
190
+ }, 10);
191
+ },
192
+ );
193
+
194
+ const item = {
195
+ body: {
196
+ trace: {
197
+ exception: {
198
+ message: 'Test error with replay over quota',
199
+ },
200
+ },
201
+ },
202
+ attributes: [
203
+ { key: 'replay_id', value: '1234567812345678' },
204
+ ],
205
+ };
206
+
207
+ queue.addItem(item, function () {
208
+ setTimeout(function () {
209
+ expect(replayMap.discard.calledWith('1234567812345678')).to.be.true;
210
+ expect(replayMap.send.called).to.be.false;
211
+ done();
212
+ }, 50);
213
+ });
214
+ });
215
+
216
+ it('should handle retrying items with replayId', function (done) {
217
+ let apiCallCount = 0;
218
+ transport.post.callsFake(
219
+ (accessToken, transportOptions, payload, callback) => {
220
+ apiCallCount++;
221
+ if (apiCallCount === 1) {
222
+ setTimeout(() => {
223
+ callback({ code: 'ECONNRESET' });
224
+ }, 10);
225
+ } else {
226
+ setTimeout(() => {
227
+ callback(
228
+ null,
229
+ { err: 0, result: { id: '12345' } },
230
+ { 'Rollbar-Replay-Enabled': 'true' }
231
+ );
232
+ }, 10);
233
+ }
234
+ },
235
+ );
236
+
237
+ queue.configure({ retryInterval: 50, maxRetries: 3 });
238
+
239
+ const item = {
240
+ body: {
241
+ trace: {
242
+ exception: {
243
+ message: 'Test error with retry',
244
+ },
245
+ },
246
+ },
247
+ attributes: [
248
+ { key: 'replay_id', value: '1234567812345678' },
249
+ ],
250
+ };
251
+
252
+ queue.addItem(item, function (err, resp) {
253
+ if (resp) {
254
+ expect(item).to.have.property('replayId', '1234567812345678');
255
+
256
+ setTimeout(function () {
257
+ expect(replayMap.send.calledWith('1234567812345678')).to.be.true;
258
+ done();
259
+ }, 50);
260
+ }
261
+ });
262
+ });
263
+
264
+ it('should not add replayId to items without a body', function (done) {
265
+ const item = {
266
+ level: 'error',
267
+ message: 'Test error without body',
268
+ attributes: [
269
+ { key: 'replay_id', value: '1234567812345678' },
270
+ ],
271
+ };
272
+
273
+ queue.addItem(item, function () {
274
+ expect(item).to.not.have.property('replayId');
275
+ expect(replayMap.add.called).to.be.false;
276
+ done();
277
+ });
278
+ });
279
+
280
+ it('should not add replayId to items without a replay_id attribute', function (done) {
281
+ const item = {
282
+ level: 'error',
283
+ message: 'Test error without body',
284
+ body: {
285
+ trace: {
286
+ exception: {
287
+ message: 'Test error with retry',
288
+ },
289
+ },
290
+ },
291
+ };
292
+
293
+ queue.addItem(item, function () {
294
+ expect(item).to.not.have.property('replayId');
295
+ expect(replayMap.add.called).to.be.false;
296
+ done();
297
+ });
298
+ });
299
+
300
+ it('should handle null response in _handleReplayResponse', function (done) {
301
+ transport.post.callsFake(
302
+ (accessToken, transportOptions, payload, callback) => {
303
+ setTimeout(() => {
304
+ callback(null, null);
305
+ }, 10);
306
+ },
307
+ );
308
+
309
+ const consoleWarnSpy = sinon.spy(console, 'warn');
310
+
311
+ const item = {
312
+ body: {
313
+ trace: {
314
+ exception: {
315
+ message: 'Test error with null response',
316
+ },
317
+ },
318
+ },
319
+ attributes: [
320
+ { key: 'replay_id', value: '1234567812345678' },
321
+ ],
322
+ };
323
+
324
+ queue.addItem(item, function () {
325
+ setTimeout(function () {
326
+ expect(replayMap.send.called).to.be.false;
327
+ expect(replayMap.discard.calledWith('1234567812345678')).to.be.false;
328
+ done();
329
+ }, 50);
330
+ });
331
+ });
332
+ });