@servicetitan/journey 2.1.0 → 3.0.0
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/README.md +1 -1
- package/dist/__test-utils__/test-runtime.d.ts.map +1 -1
- package/dist/__test-utils__/test-runtime.js +9 -2
- package/dist/__test-utils__/test-runtime.js.map +1 -1
- package/dist/__tests__/core/sanitize.test.d.ts +2 -0
- package/dist/__tests__/core/sanitize.test.d.ts.map +1 -0
- package/dist/__tests__/hardening.test.d.ts +2 -0
- package/dist/__tests__/hardening.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/host-mfe.test.d.ts +2 -0
- package/dist/__tests__/integrations/host-mfe.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/use-journey-step.test.d.ts +2 -0
- package/dist/__tests__/integrations/use-journey-step.test.d.ts.map +1 -0
- package/dist/config.d.ts +57 -13
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +87 -24
- package/dist/config.js.map +1 -1
- package/dist/core/abort-error.d.ts +4 -0
- package/dist/core/abort-error.d.ts.map +1 -0
- package/dist/core/abort-error.js +22 -0
- package/dist/core/abort-error.js.map +1 -0
- package/dist/core/api.d.ts +22 -9
- package/dist/core/api.d.ts.map +1 -1
- package/dist/core/api.js +77 -30
- package/dist/core/api.js.map +1 -1
- package/dist/core/endpoint-policy.d.ts +14 -4
- package/dist/core/endpoint-policy.d.ts.map +1 -1
- package/dist/core/endpoint-policy.js +32 -1
- package/dist/core/endpoint-policy.js.map +1 -1
- package/dist/core/index.d.ts +4 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/limits.d.ts +29 -0
- package/dist/core/limits.d.ts.map +1 -0
- package/dist/core/limits.js +63 -0
- package/dist/core/limits.js.map +1 -0
- package/dist/core/request-policy.d.ts +39 -0
- package/dist/core/request-policy.d.ts.map +1 -0
- package/dist/core/request-policy.js +18 -0
- package/dist/core/request-policy.js.map +1 -0
- package/dist/core/runtime.d.ts +65 -15
- package/dist/core/runtime.d.ts.map +1 -1
- package/dist/core/runtime.js +403 -126
- package/dist/core/runtime.js.map +1 -1
- package/dist/core/sanitize.d.ts +23 -0
- package/dist/core/sanitize.d.ts.map +1 -0
- package/dist/core/sanitize.js +143 -0
- package/dist/core/sanitize.js.map +1 -0
- package/dist/core/step-tag.d.ts +25 -6
- package/dist/core/step-tag.d.ts.map +1 -1
- package/dist/core/step-tag.js +56 -10
- package/dist/core/step-tag.js.map +1 -1
- package/dist/core/types.d.ts +73 -25
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +4 -1
- package/dist/core/types.js.map +1 -1
- package/dist/global.d.ts +91 -13
- package/dist/global.d.ts.map +1 -1
- package/dist/global.js +235 -25
- package/dist/global.js.map +1 -1
- package/dist/index.d.ts +7 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -6
- package/dist/index.js.map +1 -1
- package/dist/integrations/axios.d.ts +5 -1
- package/dist/integrations/axios.d.ts.map +1 -1
- package/dist/integrations/axios.js +146 -34
- package/dist/integrations/axios.js.map +1 -1
- package/dist/integrations/fetch.d.ts +3 -2
- package/dist/integrations/fetch.d.ts.map +1 -1
- package/dist/integrations/fetch.js +105 -26
- package/dist/integrations/fetch.js.map +1 -1
- package/dist/integrations/http-report.d.ts +27 -0
- package/dist/integrations/http-report.d.ts.map +1 -0
- package/dist/integrations/http-report.js +123 -0
- package/dist/integrations/http-report.js.map +1 -0
- package/dist/integrations/jquery/index.d.ts +8 -4
- package/dist/integrations/jquery/index.d.ts.map +1 -1
- package/dist/integrations/jquery/index.js +26 -8
- package/dist/integrations/jquery/index.js.map +1 -1
- package/dist/integrations/jquery/instrument.d.ts +14 -5
- package/dist/integrations/jquery/instrument.d.ts.map +1 -1
- package/dist/integrations/jquery/instrument.js +111 -29
- package/dist/integrations/jquery/instrument.js.map +1 -1
- package/dist/integrations/react/context.d.ts +4 -1
- package/dist/integrations/react/context.d.ts.map +1 -1
- package/dist/integrations/react/context.js +4 -1
- package/dist/integrations/react/context.js.map +1 -1
- package/dist/integrations/react/scope.d.ts +14 -3
- package/dist/integrations/react/scope.d.ts.map +1 -1
- package/dist/integrations/react/scope.js +46 -8
- package/dist/integrations/react/scope.js.map +1 -1
- package/dist/integrations/react/stable-config-keys.d.ts +6 -0
- package/dist/integrations/react/stable-config-keys.d.ts.map +1 -0
- package/dist/integrations/react/stable-config-keys.js +21 -0
- package/dist/integrations/react/stable-config-keys.js.map +1 -0
- package/dist/integrations/react/use-journey-step.d.ts +11 -0
- package/dist/integrations/react/use-journey-step.d.ts.map +1 -0
- package/dist/integrations/react/use-journey-step.js +19 -0
- package/dist/integrations/react/use-journey-step.js.map +1 -0
- package/dist/integrations/request-key.d.ts +8 -3
- package/dist/integrations/request-key.d.ts.map +1 -1
- package/dist/integrations/request-key.js +10 -9
- package/dist/integrations/request-key.js.map +1 -1
- package/dist/integrations/soft-skip.d.ts +7 -0
- package/dist/integrations/soft-skip.d.ts.map +1 -0
- package/dist/integrations/soft-skip.js +30 -0
- package/dist/integrations/soft-skip.js.map +1 -0
- package/dist/sinks/console.d.ts.map +1 -1
- package/dist/sinks/console.js +2 -1
- package/dist/sinks/console.js.map +1 -1
- package/dist/sinks/datadog.d.ts +1 -1
- package/dist/sinks/datadog.d.ts.map +1 -1
- package/dist/sinks/datadog.js +16 -20
- package/dist/sinks/datadog.js.map +1 -1
- package/package.json +2 -2
- package/src/__test-utils__/test-runtime.ts +9 -2
- package/src/__tests__/config.test.ts +150 -31
- package/src/__tests__/core/ambient.test.ts +16 -10
- package/src/__tests__/core/api.test.ts +7 -7
- package/src/__tests__/core/define-journey.test.ts +28 -11
- package/src/__tests__/core/requests.test.ts +49 -31
- package/src/__tests__/core/runtime.test.ts +67 -20
- package/src/__tests__/core/sanitize.test.ts +69 -0
- package/src/__tests__/core/step.test.ts +132 -36
- package/src/__tests__/core/timeouts.test.ts +69 -4
- package/src/__tests__/global.test.ts +44 -3
- package/src/__tests__/hardening.test.ts +613 -0
- package/src/__tests__/integrations/axios.test.ts +247 -27
- package/src/__tests__/integrations/expose-app-journey.test.ts +9 -3
- package/src/__tests__/integrations/fetch.test.ts +153 -19
- package/src/__tests__/integrations/host-mfe.test.ts +548 -0
- package/src/__tests__/integrations/jquery.test.ts +209 -34
- package/src/__tests__/integrations/scope.test.tsx +160 -2
- package/src/__tests__/integrations/use-journey-step.test.tsx +43 -0
- package/src/__tests__/sinks/console.test.ts +9 -4
- package/src/__tests__/sinks/datadog.test.ts +47 -0
- package/src/config.ts +162 -38
- package/src/core/abort-error.ts +24 -0
- package/src/core/api.ts +83 -33
- package/src/core/endpoint-policy.ts +46 -4
- package/src/core/index.ts +12 -4
- package/src/core/limits.ts +73 -0
- package/src/core/request-policy.ts +66 -0
- package/src/core/runtime.ts +507 -133
- package/src/core/sanitize.ts +177 -0
- package/src/core/step-tag.ts +82 -12
- package/src/core/types.ts +79 -25
- package/src/global.ts +312 -42
- package/src/index.ts +17 -17
- package/src/integrations/axios.ts +183 -45
- package/src/integrations/fetch.ts +125 -39
- package/src/integrations/http-report.ts +184 -0
- package/src/integrations/jquery/index.ts +34 -10
- package/src/integrations/jquery/instrument.ts +126 -42
- package/src/integrations/react/context.ts +4 -1
- package/src/integrations/react/scope.tsx +59 -9
- package/src/integrations/react/stable-config-keys.ts +28 -0
- package/src/integrations/react/use-journey-step.ts +31 -0
- package/src/integrations/request-key.ts +18 -10
- package/src/integrations/soft-skip.ts +35 -0
- package/src/sinks/console.ts +2 -1
- package/src/sinks/datadog.ts +23 -29
|
@@ -9,17 +9,17 @@ import {
|
|
|
9
9
|
completeJourney,
|
|
10
10
|
journeyStep,
|
|
11
11
|
reportBackendRequest,
|
|
12
|
-
resolveJourneyStep,
|
|
13
12
|
type JourneyDef,
|
|
14
|
-
type
|
|
13
|
+
type JourneyStepStamp,
|
|
15
14
|
} from '../../core';
|
|
15
|
+
import type { StepRecord } from '../../core/types';
|
|
16
16
|
import { moduleRuntime } from '../../core/runtime';
|
|
17
17
|
|
|
18
18
|
const emitMock = jest.fn();
|
|
19
19
|
const baseDef = { ...baseJourneyDef, name: 'requests-journey' };
|
|
20
20
|
|
|
21
21
|
describe('[journey] reportBackendRequest', () => {
|
|
22
|
-
let stepRef:
|
|
22
|
+
let stepRef: StepRecord | undefined;
|
|
23
23
|
let status: number | undefined;
|
|
24
24
|
let durationMs: number;
|
|
25
25
|
let key: string;
|
|
@@ -36,12 +36,12 @@ describe('[journey] reportBackendRequest', () => {
|
|
|
36
36
|
const subject = () => reportBackendRequest(stepRef, status, durationMs, key);
|
|
37
37
|
|
|
38
38
|
async function withOpenStep(
|
|
39
|
-
run: (ref:
|
|
40
|
-
def: JourneyDef = { ...baseDef,
|
|
39
|
+
run: (ref: StepRecord) => void | Promise<void>,
|
|
40
|
+
def: JourneyDef = { ...baseDef, slowRequestMs: 5_000 }
|
|
41
41
|
) {
|
|
42
42
|
moduleRuntime.startJourney(def);
|
|
43
43
|
await journeyStep(baseDef.name, 'do-work', async step => {
|
|
44
|
-
stepRef =
|
|
44
|
+
stepRef = (step.stamp() as JourneyStepStamp).step;
|
|
45
45
|
await run(stepRef!);
|
|
46
46
|
});
|
|
47
47
|
}
|
|
@@ -100,7 +100,7 @@ describe('[journey] reportBackendRequest', () => {
|
|
|
100
100
|
() => {
|
|
101
101
|
subject();
|
|
102
102
|
},
|
|
103
|
-
{ ...baseDef,
|
|
103
|
+
{ ...baseDef, slowRequestMs: 50 }
|
|
104
104
|
);
|
|
105
105
|
|
|
106
106
|
expect(lastJourney(emitMock).reason).toBe('request-latency');
|
|
@@ -160,7 +160,7 @@ describe('[journey] reportBackendRequest', () => {
|
|
|
160
160
|
},
|
|
161
161
|
{
|
|
162
162
|
...baseDef,
|
|
163
|
-
|
|
163
|
+
slowRequestMs: 5_000,
|
|
164
164
|
endpoints: [{ match: '/flaky', ignore: true }],
|
|
165
165
|
}
|
|
166
166
|
);
|
|
@@ -170,28 +170,29 @@ describe('[journey] reportBackendRequest', () => {
|
|
|
170
170
|
});
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
-
describe('when a journey endpoint
|
|
174
|
-
test('
|
|
173
|
+
describe('when a journey endpoint slowRequestMs is 0', () => {
|
|
174
|
+
test('never scores latency for that prefix', async () => {
|
|
175
175
|
moduleRuntime.startJourney({
|
|
176
176
|
...baseDef,
|
|
177
|
-
|
|
178
|
-
endpoints: [{ match: '/special',
|
|
177
|
+
slowRequestMs: 30,
|
|
178
|
+
endpoints: [{ match: '/special', slowRequestMs: 0 }],
|
|
179
179
|
});
|
|
180
180
|
|
|
181
181
|
await journeyStep(baseDef.name, 'do-work', step => {
|
|
182
|
-
stepRef =
|
|
182
|
+
stepRef = (step.stamp() as JourneyStepStamp).step;
|
|
183
183
|
status = 200;
|
|
184
184
|
durationMs = 100;
|
|
185
185
|
key = '/special/path';
|
|
186
186
|
subject();
|
|
187
187
|
});
|
|
188
|
+
completeJourney(baseDef.name);
|
|
188
189
|
|
|
189
|
-
expect(lastJourney(emitMock).
|
|
190
|
+
expect(lastJourney(emitMock).outcome).toBe('good');
|
|
190
191
|
});
|
|
191
192
|
});
|
|
192
193
|
|
|
193
194
|
describe('when journey endpoint timeoutMs is set', () => {
|
|
194
|
-
test('wins over the journey
|
|
195
|
+
test('wins over the journey slowRequestMs', async () => {
|
|
195
196
|
await withOpenStep(
|
|
196
197
|
() => {
|
|
197
198
|
status = 200;
|
|
@@ -201,8 +202,8 @@ describe('[journey] reportBackendRequest', () => {
|
|
|
201
202
|
},
|
|
202
203
|
{
|
|
203
204
|
...baseDef,
|
|
204
|
-
|
|
205
|
-
endpoints: [{ match: '/special',
|
|
205
|
+
slowRequestMs: 5_000,
|
|
206
|
+
endpoints: [{ match: '/special', slowRequestMs: 20 }],
|
|
206
207
|
}
|
|
207
208
|
);
|
|
208
209
|
|
|
@@ -213,7 +214,7 @@ describe('[journey] reportBackendRequest', () => {
|
|
|
213
214
|
describe('when the journey endpoint ignore overrides the app-wide policy', () => {
|
|
214
215
|
beforeEach(() => {
|
|
215
216
|
configureJourney({
|
|
216
|
-
|
|
217
|
+
slowRequests: {
|
|
217
218
|
defaultMs: 4_000,
|
|
218
219
|
endpoints: [{ match: '/flaky', ignore: true }],
|
|
219
220
|
},
|
|
@@ -229,7 +230,7 @@ describe('[journey] reportBackendRequest', () => {
|
|
|
229
230
|
},
|
|
230
231
|
{
|
|
231
232
|
...baseDef,
|
|
232
|
-
|
|
233
|
+
slowRequestMs: 5_000,
|
|
233
234
|
endpoints: [{ match: '/flaky', ignore: false }],
|
|
234
235
|
}
|
|
235
236
|
);
|
|
@@ -241,11 +242,11 @@ describe('[journey] reportBackendRequest', () => {
|
|
|
241
242
|
describe('when only app-wide endpoint policies apply', () => {
|
|
242
243
|
beforeEach(() => {
|
|
243
244
|
configureJourney({
|
|
244
|
-
|
|
245
|
+
slowRequests: {
|
|
245
246
|
defaultMs: 4_000,
|
|
246
247
|
endpoints: [
|
|
247
248
|
{ match: '/ignored', ignore: true },
|
|
248
|
-
{ match: '/tight',
|
|
249
|
+
{ match: '/tight', slowRequestMs: 30 },
|
|
249
250
|
],
|
|
250
251
|
},
|
|
251
252
|
});
|
|
@@ -274,41 +275,42 @@ describe('[journey] reportBackendRequest', () => {
|
|
|
274
275
|
});
|
|
275
276
|
});
|
|
276
277
|
|
|
277
|
-
describe('when app endpoint
|
|
278
|
+
describe('when app endpoint slowRequestMs is 0', () => {
|
|
278
279
|
beforeEach(() => {
|
|
279
280
|
configureJourney({
|
|
280
|
-
|
|
281
|
+
slowRequests: {
|
|
281
282
|
defaultMs: 40,
|
|
282
|
-
endpoints: [{ match: '/loose',
|
|
283
|
+
endpoints: [{ match: '/loose', slowRequestMs: 0 }],
|
|
283
284
|
},
|
|
284
285
|
});
|
|
285
286
|
});
|
|
286
287
|
|
|
287
|
-
test('
|
|
288
|
+
test('never scores latency for that prefix', async () => {
|
|
288
289
|
moduleRuntime.startJourney(baseDef);
|
|
289
290
|
|
|
290
291
|
await journeyStep(baseDef.name, 'do-work', step => {
|
|
291
|
-
stepRef =
|
|
292
|
+
stepRef = (step.stamp() as JourneyStepStamp).step;
|
|
292
293
|
status = 200;
|
|
293
294
|
durationMs = 100;
|
|
294
295
|
key = '/loose/x';
|
|
295
296
|
subject();
|
|
296
297
|
});
|
|
298
|
+
completeJourney(baseDef.name);
|
|
297
299
|
|
|
298
|
-
expect(lastJourney(emitMock).
|
|
300
|
+
expect(lastJourney(emitMock).outcome).toBe('good');
|
|
299
301
|
});
|
|
300
302
|
});
|
|
301
303
|
|
|
302
|
-
describe('when the journey has no
|
|
304
|
+
describe('when the journey has no slowRequestMs', () => {
|
|
303
305
|
beforeEach(() => {
|
|
304
|
-
configureJourney({
|
|
306
|
+
configureJourney({ slowRequests: { defaultMs: 40, endpoints: [] } });
|
|
305
307
|
});
|
|
306
308
|
|
|
307
309
|
test('uses the app defaultMs', async () => {
|
|
308
310
|
moduleRuntime.startJourney(baseDef);
|
|
309
311
|
|
|
310
312
|
await journeyStep(baseDef.name, 'do-work', step => {
|
|
311
|
-
stepRef =
|
|
313
|
+
stepRef = (step.stamp() as JourneyStepStamp).step;
|
|
312
314
|
status = 200;
|
|
313
315
|
durationMs = 100;
|
|
314
316
|
key = '/any';
|
|
@@ -320,7 +322,7 @@ describe('[journey] reportBackendRequest', () => {
|
|
|
320
322
|
});
|
|
321
323
|
|
|
322
324
|
describe('against a closed journey step ref', () => {
|
|
323
|
-
let closedRef:
|
|
325
|
+
let closedRef: StepRecord | undefined;
|
|
324
326
|
|
|
325
327
|
beforeEach(async () => {
|
|
326
328
|
await withOpenStep(ref => {
|
|
@@ -338,4 +340,20 @@ describe('[journey] reportBackendRequest', () => {
|
|
|
338
340
|
expect(emitMock).not.toHaveBeenCalled();
|
|
339
341
|
});
|
|
340
342
|
});
|
|
343
|
+
|
|
344
|
+
describe('when many distinct keys are attributed', () => {
|
|
345
|
+
test('stops recording after 50 keys', async () => {
|
|
346
|
+
await withOpenStep(ref => {
|
|
347
|
+
for (let i = 0; i < 55; i += 1) {
|
|
348
|
+
reportBackendRequest(ref, 200, 1, `/api/${i}`);
|
|
349
|
+
}
|
|
350
|
+
expect(ref.attributedRequestKeys).toHaveLength(50);
|
|
351
|
+
expect(
|
|
352
|
+
moduleRuntime.getDebugSnapshot().openJourneys[0].steps[0].attributedRequestKeys
|
|
353
|
+
).toHaveLength(50);
|
|
354
|
+
});
|
|
355
|
+
completeJourney(baseDef.name);
|
|
356
|
+
expect(lastJourney(emitMock).outcome).toBe('good');
|
|
357
|
+
});
|
|
358
|
+
});
|
|
341
359
|
});
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/** @jest-environment jsdom */
|
|
2
2
|
import { lastJourney, useJourneyTestRuntime } from '../../__test-utils__/test-runtime';
|
|
3
|
-
import { configureJourney,
|
|
3
|
+
import { configureJourney, getSlowRequests } from '../../config';
|
|
4
4
|
import {
|
|
5
|
-
activeJourneyStep,
|
|
6
5
|
completeJourney,
|
|
7
6
|
defineJourney,
|
|
8
7
|
journeyStep,
|
|
9
8
|
resetJourney,
|
|
9
|
+
singleInFlightJourneyStep,
|
|
10
10
|
} from '../../core';
|
|
11
11
|
import { createJourneyRuntime, moduleRuntime } from '../../core/runtime';
|
|
12
|
+
import { registerJourneyRuntime, setJourneyDefaultEnabled } from '../../global';
|
|
12
13
|
import { sendToConsole } from '../../sinks/console';
|
|
13
14
|
import { sendToDatadog } from '../../sinks/datadog';
|
|
14
15
|
|
|
@@ -52,29 +53,31 @@ describe('[journey] resetJourney', () => {
|
|
|
52
53
|
|
|
53
54
|
describe('when request timeouts were customized', () => {
|
|
54
55
|
beforeEach(() => {
|
|
55
|
-
configureJourney({
|
|
56
|
+
configureJourney({ slowRequests: { defaultMs: 100, endpoints: [] } });
|
|
56
57
|
});
|
|
57
58
|
|
|
58
59
|
const subject = () => resetJourney({ sinks: [emitMock] });
|
|
59
60
|
|
|
60
|
-
test('
|
|
61
|
+
test('clears this runtime overlay and inherits shared policies', () => {
|
|
61
62
|
subject();
|
|
62
63
|
|
|
63
|
-
expect(
|
|
64
|
+
expect(moduleRuntime.getDebugSnapshot().policyOverlay.slowRequests).toBe(false);
|
|
65
|
+
expect(moduleRuntime.getSlowRequests().defaultMs).toBe(100);
|
|
66
|
+
expect(getSlowRequests().defaultMs).toBe(100);
|
|
64
67
|
});
|
|
65
68
|
});
|
|
66
69
|
|
|
67
|
-
describe('when reset passes custom
|
|
70
|
+
describe('when reset passes custom slowRequests', () => {
|
|
68
71
|
const subject = () =>
|
|
69
72
|
resetJourney({
|
|
70
73
|
sinks: [emitMock],
|
|
71
|
-
|
|
74
|
+
slowRequests: { defaultMs: 250, endpoints: [] },
|
|
72
75
|
});
|
|
73
76
|
|
|
74
|
-
test('applies those timeouts', () => {
|
|
77
|
+
test('applies those timeouts on the module engine', () => {
|
|
75
78
|
subject();
|
|
76
79
|
|
|
77
|
-
expect(
|
|
80
|
+
expect(moduleRuntime.getSlowRequests().defaultMs).toBe(250);
|
|
78
81
|
});
|
|
79
82
|
});
|
|
80
83
|
|
|
@@ -120,11 +123,24 @@ describe('[journey] resetJourney', () => {
|
|
|
120
123
|
const subject = () => resetJourney({ sinks: [emitMock] });
|
|
121
124
|
|
|
122
125
|
test('clears the ambient step', () => {
|
|
123
|
-
expect(
|
|
126
|
+
expect(singleInFlightJourneyStep()).not.toBeNull();
|
|
124
127
|
|
|
125
128
|
subject();
|
|
126
129
|
|
|
127
|
-
expect(
|
|
130
|
+
expect(singleInFlightJourneyStep()).toBeNull();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('reuses the same ambient stamp while the active step is unchanged', async () => {
|
|
134
|
+
const first = singleInFlightJourneyStep();
|
|
135
|
+
const second = singleInFlightJourneyStep();
|
|
136
|
+
|
|
137
|
+
expect(first).not.toBeNull();
|
|
138
|
+
expect(second).toBe(first);
|
|
139
|
+
|
|
140
|
+
resolveWork();
|
|
141
|
+
await stepPromise;
|
|
142
|
+
|
|
143
|
+
expect(singleInFlightJourneyStep()).toBeNull();
|
|
128
144
|
});
|
|
129
145
|
});
|
|
130
146
|
|
|
@@ -157,19 +173,35 @@ describe('[journey] createJourneyRuntime', () => {
|
|
|
157
173
|
let runtime: ReturnType<typeof createJourneyRuntime>;
|
|
158
174
|
|
|
159
175
|
beforeEach(() => {
|
|
176
|
+
setJourneyDefaultEnabled(true);
|
|
160
177
|
resetJourney({
|
|
161
178
|
sinks: [defaultEmit],
|
|
162
|
-
|
|
179
|
+
slowRequests: { defaultMs: 4_000, endpoints: [] },
|
|
163
180
|
});
|
|
164
181
|
defaultEmit.mockClear();
|
|
165
182
|
isolatedEmit.mockClear();
|
|
166
183
|
runtime = createJourneyRuntime({ sinks: [isolatedEmit] });
|
|
167
184
|
});
|
|
168
185
|
|
|
186
|
+
test('auto-assigns a unique runtime-N id when registered without an id', () => {
|
|
187
|
+
registerJourneyRuntime(runtime);
|
|
188
|
+
|
|
189
|
+
expect(moduleRuntime.id).toMatch(/^runtime-\d+$/);
|
|
190
|
+
expect(runtime.id).toMatch(/^runtime-\d+$/);
|
|
191
|
+
expect(runtime.id).not.toBe(moduleRuntime.id);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('uses the provided id', () => {
|
|
195
|
+
const named = createJourneyRuntime({ id: 'job-booking', sinks: [isolatedEmit] });
|
|
196
|
+
expect(named.id).toBe('job-booking');
|
|
197
|
+
});
|
|
198
|
+
|
|
169
199
|
afterEach(() => {
|
|
200
|
+
setJourneyDefaultEnabled(false);
|
|
201
|
+
localStorage.clear();
|
|
170
202
|
resetJourney({
|
|
171
203
|
sinks: [sendToDatadog, sendToConsole],
|
|
172
|
-
|
|
204
|
+
slowRequests: { defaultMs: 4_000, endpoints: [] },
|
|
173
205
|
});
|
|
174
206
|
});
|
|
175
207
|
|
|
@@ -192,18 +224,20 @@ describe('[journey] createJourneyRuntime', () => {
|
|
|
192
224
|
});
|
|
193
225
|
});
|
|
194
226
|
|
|
195
|
-
describe('when the isolated runtime has its own
|
|
227
|
+
describe('when the isolated runtime has its own slowRequests', () => {
|
|
196
228
|
beforeEach(() => {
|
|
197
229
|
runtime = createJourneyRuntime({
|
|
198
230
|
sinks: [isolatedEmit],
|
|
199
|
-
|
|
231
|
+
slowRequests: { defaultMs: 10, endpoints: [] },
|
|
200
232
|
});
|
|
201
|
-
configureJourney({
|
|
233
|
+
configureJourney({ slowRequests: { defaultMs: 9_000, endpoints: [] } });
|
|
202
234
|
});
|
|
203
235
|
|
|
204
|
-
test('
|
|
205
|
-
expect(runtime.
|
|
206
|
-
|
|
236
|
+
test('inherits shared host thresholds unless this runtime overlays', () => {
|
|
237
|
+
expect(runtime.getSlowRequests().defaultMs).toBe(9_000);
|
|
238
|
+
runtime.setPolicyOverlay({ slowRequests: { defaultMs: 10, endpoints: [] } });
|
|
239
|
+
expect(runtime.getSlowRequests().defaultMs).toBe(10);
|
|
240
|
+
expect(getSlowRequests().defaultMs).toBe(9_000);
|
|
207
241
|
});
|
|
208
242
|
});
|
|
209
243
|
|
|
@@ -227,7 +261,7 @@ describe('[journey] createJourneyRuntime', () => {
|
|
|
227
261
|
describe('when defineJourney is used', () => {
|
|
228
262
|
const subject = () =>
|
|
229
263
|
defineJourney(def).step('load', step => {
|
|
230
|
-
step.
|
|
264
|
+
step.completeJourney();
|
|
231
265
|
});
|
|
232
266
|
|
|
233
267
|
test('binds to the module runtime', async () => {
|
|
@@ -270,4 +304,17 @@ describe('[journey] createJourneyRuntime', () => {
|
|
|
270
304
|
expect(isolatedEmit).toHaveBeenCalledTimes(1);
|
|
271
305
|
});
|
|
272
306
|
});
|
|
307
|
+
|
|
308
|
+
describe('maxSteps bounds', () => {
|
|
309
|
+
test('clamps invalid and oversized values', () => {
|
|
310
|
+
moduleRuntime.setMaxSteps(Infinity);
|
|
311
|
+
expect(moduleRuntime.getMaxSteps()).toBe(50);
|
|
312
|
+
moduleRuntime.setMaxSteps(0);
|
|
313
|
+
expect(moduleRuntime.getMaxSteps()).toBe(50);
|
|
314
|
+
moduleRuntime.setMaxSteps(500);
|
|
315
|
+
expect(moduleRuntime.getMaxSteps()).toBe(200);
|
|
316
|
+
moduleRuntime.setMaxSteps(Number.NaN);
|
|
317
|
+
expect(moduleRuntime.getMaxSteps()).toBe(50);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
273
320
|
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** @jest-environment jsdom */
|
|
2
|
+
import { MAX_ATTRIBUTE_KEYS, MAX_TAG_KEYS, isUnsafeKey } from '../../core/limits';
|
|
3
|
+
import { assignRecord, copyRecord, emptyRecord, sanitizeJourneyEvent } from '../../core/sanitize';
|
|
4
|
+
import type { JourneyEvent } from '../../core/types';
|
|
5
|
+
|
|
6
|
+
describe('[journey] sanitize / copyRecord', () => {
|
|
7
|
+
test('isUnsafeKey blocks proto and secret names', () => {
|
|
8
|
+
expect(isUnsafeKey('__proto__')).toBe(true);
|
|
9
|
+
expect(isUnsafeKey('constructor')).toBe(true);
|
|
10
|
+
expect(isUnsafeKey('Authorization')).toBe(true);
|
|
11
|
+
expect(isUnsafeKey('token')).toBe(true);
|
|
12
|
+
expect(isUnsafeKey('customer.id')).toBe(false);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('JSON __proto__ assign does not pollute Object.prototype', () => {
|
|
16
|
+
const target = emptyRecord();
|
|
17
|
+
const polluted = JSON.parse('{"__proto__":{"polluted":true},"ok":1}') as Record<
|
|
18
|
+
string,
|
|
19
|
+
unknown
|
|
20
|
+
>;
|
|
21
|
+
assignRecord(target, polluted, MAX_TAG_KEYS);
|
|
22
|
+
expect(target.ok).toBe(1);
|
|
23
|
+
expect(({} as { polluted?: boolean }).polluted).toBeUndefined();
|
|
24
|
+
expect(Object.prototype.hasOwnProperty.call(target, '__proto__')).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('copyRecord caps keys and drops secrets', () => {
|
|
28
|
+
const src: Record<string, string | number> = { token: 'secret', keep: 1 };
|
|
29
|
+
for (let i = 0; i < MAX_ATTRIBUTE_KEYS + 5; i += 1) {
|
|
30
|
+
src[`k${i}`] = i;
|
|
31
|
+
}
|
|
32
|
+
const out = copyRecord(src, { maxKeys: MAX_ATTRIBUTE_KEYS, normalizeKeys: true });
|
|
33
|
+
expect(out.token).toBeUndefined();
|
|
34
|
+
expect(Object.keys(out).length).toBe(MAX_ATTRIBUTE_KEYS);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('sanitizeJourneyEvent normalizes step names and tag keys', () => {
|
|
38
|
+
const event: JourneyEvent = {
|
|
39
|
+
journey: {
|
|
40
|
+
name: 'Make A Sale!!',
|
|
41
|
+
team: 'Sales',
|
|
42
|
+
group: 'retail',
|
|
43
|
+
service: 'checkout',
|
|
44
|
+
outcome: 'good',
|
|
45
|
+
durationMs: 10,
|
|
46
|
+
expected: ['Create Sale'],
|
|
47
|
+
tags: { 'User Email': 'a@b.com', 'token': 'nope', 'plan': 'Pro' },
|
|
48
|
+
steps: [
|
|
49
|
+
{
|
|
50
|
+
name: 'Load Customer 1',
|
|
51
|
+
startMs: 0,
|
|
52
|
+
durationMs: 4,
|
|
53
|
+
outcome: 'good',
|
|
54
|
+
attributes: { 'token': 'x', 'Draft.id': 'abc' },
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
const j = sanitizeJourneyEvent(event).journey;
|
|
60
|
+
expect(j.name).toBe('make_a_sale');
|
|
61
|
+
expect(j.expected).toEqual(['create_sale']);
|
|
62
|
+
expect(j.steps[0].name).toBe('load_customer_1');
|
|
63
|
+
expect(j.tags?.user_email).toBe('a_b.com');
|
|
64
|
+
expect(j.tags?.token).toBeUndefined();
|
|
65
|
+
expect(j.tags?.plan).toBe('pro');
|
|
66
|
+
expect(j.steps[0].attributes?.token).toBeUndefined();
|
|
67
|
+
expect(j.steps[0].attributes?.['draft.id']).toBe('abc');
|
|
68
|
+
});
|
|
69
|
+
});
|