@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
|
@@ -16,11 +16,12 @@ jest.mock('../integrations/jquery', () => ({
|
|
|
16
16
|
exposeAppJourney: jest.fn(),
|
|
17
17
|
}));
|
|
18
18
|
|
|
19
|
-
import { configureJourney,
|
|
19
|
+
import { configureJourney, getSlowRequests } from '../config';
|
|
20
20
|
import { resetJourney } from '../core';
|
|
21
21
|
import { moduleRuntime } from '../core/runtime';
|
|
22
22
|
import type { JourneyEvent } from '../core/types';
|
|
23
23
|
import { longestEndpointMatch, sortEndpointsByLongestMatch } from '../core/endpoint-policy';
|
|
24
|
+
import { resetSharedPolicies, setJourneyDefaultEnabled } from '../global';
|
|
24
25
|
import { instrumentAxios } from '../integrations/axios';
|
|
25
26
|
import { instrumentFetch } from '../integrations/fetch';
|
|
26
27
|
import {
|
|
@@ -36,11 +37,18 @@ const instrumentFetchMock = jest.mocked(instrumentFetch);
|
|
|
36
37
|
const instrumentJqueryMock = jest.mocked(instrumentJquery);
|
|
37
38
|
const exposeAppJourneyMock = jest.mocked(exposeAppJourney);
|
|
38
39
|
|
|
39
|
-
describe('[journey] configureJourney /
|
|
40
|
+
describe('[journey] configureJourney / getSlowRequests', () => {
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
setJourneyDefaultEnabled(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
40
45
|
afterEach(() => {
|
|
46
|
+
setJourneyDefaultEnabled(false);
|
|
47
|
+
localStorage.clear();
|
|
48
|
+
resetSharedPolicies();
|
|
41
49
|
resetJourney({
|
|
42
50
|
sinks: [sendToDatadog, sendToConsole],
|
|
43
|
-
|
|
51
|
+
slowRequests: { defaultMs: 4_000, endpoints: [] },
|
|
44
52
|
});
|
|
45
53
|
instrumentAxiosMock.mockClear();
|
|
46
54
|
instrumentFetchMock.mockClear();
|
|
@@ -49,27 +57,36 @@ describe('[journey] configureJourney / getRequestTimeouts', () => {
|
|
|
49
57
|
});
|
|
50
58
|
|
|
51
59
|
test('defaults to 4s with empty endpoints', () => {
|
|
52
|
-
expect(
|
|
60
|
+
expect(getSlowRequests()).toEqual({ defaultMs: 4_000, endpoints: [] });
|
|
53
61
|
});
|
|
54
62
|
|
|
55
63
|
test('overrides the app-wide request timeout tier', () => {
|
|
56
64
|
configureJourney({
|
|
57
|
-
|
|
65
|
+
slowRequests: {
|
|
58
66
|
defaultMs: 2_000,
|
|
59
|
-
endpoints: [{ match: '/slow',
|
|
67
|
+
endpoints: [{ match: '/slow', slowRequestMs: 8_000 }],
|
|
60
68
|
},
|
|
61
69
|
});
|
|
62
70
|
|
|
63
|
-
expect(
|
|
71
|
+
expect(getSlowRequests()).toEqual({
|
|
64
72
|
defaultMs: 2_000,
|
|
65
|
-
endpoints: [{ match: '/slow',
|
|
73
|
+
endpoints: [{ match: '/slow', slowRequestMs: 8_000 }],
|
|
66
74
|
});
|
|
67
75
|
});
|
|
68
76
|
|
|
69
|
-
test('ignores configureJourney when
|
|
70
|
-
configureJourney({
|
|
77
|
+
test('ignores configureJourney when slowRequests is omitted', () => {
|
|
78
|
+
configureJourney({ slowRequests: { defaultMs: 1_000, endpoints: [] } });
|
|
71
79
|
configureJourney({});
|
|
72
|
-
expect(
|
|
80
|
+
expect(getSlowRequests().defaultMs).toBe(1_000);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('clamps maxSteps to 1..200', () => {
|
|
84
|
+
configureJourney({ maxSteps: Infinity });
|
|
85
|
+
expect(moduleRuntime.getMaxSteps()).toBe(50);
|
|
86
|
+
configureJourney({ maxSteps: 0 });
|
|
87
|
+
expect(moduleRuntime.getMaxSteps()).toBe(50);
|
|
88
|
+
configureJourney({ maxSteps: 500 });
|
|
89
|
+
expect(moduleRuntime.getMaxSteps()).toBe(200);
|
|
73
90
|
});
|
|
74
91
|
|
|
75
92
|
describe('with sinks', () => {
|
|
@@ -102,11 +119,25 @@ describe('[journey] configureJourney / getRequestTimeouts', () => {
|
|
|
102
119
|
|
|
103
120
|
test('leaves sinks unchanged when sinks is omitted', () => {
|
|
104
121
|
configureJourney({ sinks: [custom] });
|
|
105
|
-
configureJourney({
|
|
122
|
+
configureJourney({ slowRequests: { defaultMs: 2_000, endpoints: [] } });
|
|
123
|
+
moduleRuntime.emit(payload);
|
|
124
|
+
|
|
125
|
+
expect(custom).toHaveBeenCalledWith(payload);
|
|
126
|
+
expect(sendToDatadog).not.toHaveBeenCalled();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('ignores a later sinks replacement on this runtime', () => {
|
|
130
|
+
const warn = jest.spyOn(console, 'warn').mockImplementation(() => undefined);
|
|
131
|
+
configureJourney({ sinks: [custom] });
|
|
132
|
+
configureJourney({ sinks: [sendToDatadog] });
|
|
106
133
|
moduleRuntime.emit(payload);
|
|
107
134
|
|
|
108
135
|
expect(custom).toHaveBeenCalledWith(payload);
|
|
109
136
|
expect(sendToDatadog).not.toHaveBeenCalled();
|
|
137
|
+
expect(warn).toHaveBeenCalledWith(
|
|
138
|
+
expect.stringContaining('sinks already set on this runtime')
|
|
139
|
+
);
|
|
140
|
+
warn.mockRestore();
|
|
110
141
|
});
|
|
111
142
|
|
|
112
143
|
test('disables emission when sinks is empty', () => {
|
|
@@ -125,6 +156,15 @@ describe('[journey] configureJourney / getRequestTimeouts', () => {
|
|
|
125
156
|
};
|
|
126
157
|
const jquery = { ajaxPrefilter: jest.fn(), bind: jest.fn() } as never;
|
|
127
158
|
const fetchTarget = { fetch: jest.fn() };
|
|
159
|
+
let warnSpy: jest.SpyInstance;
|
|
160
|
+
|
|
161
|
+
beforeEach(() => {
|
|
162
|
+
warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
afterEach(() => {
|
|
166
|
+
warnSpy.mockRestore();
|
|
167
|
+
});
|
|
128
168
|
|
|
129
169
|
test('instruments axios when provided', () => {
|
|
130
170
|
configureJourney({ axios: axios as never });
|
|
@@ -155,6 +195,38 @@ describe('[journey] configureJourney / getRequestTimeouts', () => {
|
|
|
155
195
|
expect(instrumentJqueryMock).toHaveBeenCalledWith(jquery);
|
|
156
196
|
});
|
|
157
197
|
|
|
198
|
+
test('instruments global $ when jquery is true', () => {
|
|
199
|
+
const $ = Object.assign(jest.fn(), { ajaxPrefilter: jest.fn() });
|
|
200
|
+
(globalThis as { $?: unknown }).$ = $;
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
configureJourney({ jquery: true });
|
|
204
|
+
|
|
205
|
+
expect(instrumentJqueryMock).toHaveBeenCalledWith($);
|
|
206
|
+
} finally {
|
|
207
|
+
delete (globalThis as { $?: unknown }).$;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('falls back to global jQuery when jquery is true and $ is absent', () => {
|
|
212
|
+
const jQuery = Object.assign(jest.fn(), { ajaxPrefilter: jest.fn() });
|
|
213
|
+
(globalThis as { jQuery?: unknown }).jQuery = jQuery;
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
configureJourney({ jquery: true });
|
|
217
|
+
|
|
218
|
+
expect(instrumentJqueryMock).toHaveBeenCalledWith(jQuery);
|
|
219
|
+
} finally {
|
|
220
|
+
delete (globalThis as { jQuery?: unknown }).jQuery;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('skips jquery wiring when jquery is false', () => {
|
|
225
|
+
configureJourney({ jquery: false });
|
|
226
|
+
|
|
227
|
+
expect(instrumentJqueryMock).not.toHaveBeenCalled();
|
|
228
|
+
});
|
|
229
|
+
|
|
158
230
|
test('exposes App.Journey when exposeAppJourney is true', () => {
|
|
159
231
|
configureJourney({ exposeAppJourney: true });
|
|
160
232
|
|
|
@@ -169,21 +241,64 @@ describe('[journey] configureJourney / getRequestTimeouts', () => {
|
|
|
169
241
|
});
|
|
170
242
|
|
|
171
243
|
test('skips transport helpers when omitted', () => {
|
|
172
|
-
configureJourney({
|
|
244
|
+
configureJourney({ slowRequests: { defaultMs: 1_000, endpoints: [] } });
|
|
173
245
|
|
|
174
246
|
expect(instrumentAxiosMock).not.toHaveBeenCalled();
|
|
175
247
|
expect(instrumentFetchMock).not.toHaveBeenCalled();
|
|
176
248
|
expect(instrumentJqueryMock).not.toHaveBeenCalled();
|
|
177
249
|
expect(exposeAppJourneyMock).not.toHaveBeenCalled();
|
|
178
250
|
});
|
|
251
|
+
|
|
252
|
+
test('never throws on missing config', () => {
|
|
253
|
+
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
254
|
+
|
|
255
|
+
expect(() => configureJourney(null as never)).not.toThrow();
|
|
256
|
+
expect(() => configureJourney(undefined as never)).not.toThrow();
|
|
257
|
+
expect(warn).toHaveBeenCalledWith(
|
|
258
|
+
expect.stringContaining('[journey] configureJourney: skipped — missing')
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('skips when journey is disabled', () => {
|
|
263
|
+
setJourneyDefaultEnabled(false);
|
|
264
|
+
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
265
|
+
|
|
266
|
+
configureJourney({ axios: axios as never, fetch: true });
|
|
267
|
+
|
|
268
|
+
expect(warn).toHaveBeenCalledWith(
|
|
269
|
+
expect.stringContaining('[journey] configureJourney: skipped — disabled')
|
|
270
|
+
);
|
|
271
|
+
expect(instrumentAxiosMock).not.toHaveBeenCalled();
|
|
272
|
+
expect(instrumentFetchMock).not.toHaveBeenCalled();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('never throws when a transport step fails', () => {
|
|
276
|
+
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
277
|
+
instrumentAxiosMock.mockImplementationOnce(() => {
|
|
278
|
+
throw new Error('axios boom');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
expect(() =>
|
|
282
|
+
configureJourney({
|
|
283
|
+
axios: axios as never,
|
|
284
|
+
jquery,
|
|
285
|
+
exposeAppJourney: true,
|
|
286
|
+
})
|
|
287
|
+
).not.toThrow();
|
|
288
|
+
|
|
289
|
+
expect(error).toHaveBeenCalledWith(
|
|
290
|
+
'[journey] configureJourney failed',
|
|
291
|
+
expect.any(Error)
|
|
292
|
+
);
|
|
293
|
+
});
|
|
179
294
|
});
|
|
180
295
|
});
|
|
181
296
|
|
|
182
297
|
describe('longestEndpointMatch', () => {
|
|
183
298
|
// Lookup expects longest-match-first (configureJourney / startJourney sort for you).
|
|
184
299
|
const endpoints = [
|
|
185
|
-
{ match: '/api/invoices',
|
|
186
|
-
{ match: '/api',
|
|
300
|
+
{ match: '/api/invoices', slowRequestMs: 2_000 },
|
|
301
|
+
{ match: '/api', slowRequestMs: 1_000 },
|
|
187
302
|
{ match: '/other', ignore: true },
|
|
188
303
|
];
|
|
189
304
|
|
|
@@ -193,8 +308,8 @@ describe('longestEndpointMatch', () => {
|
|
|
193
308
|
});
|
|
194
309
|
|
|
195
310
|
test('picks the longest matching prefix (first hit when sorted)', () => {
|
|
196
|
-
expect(longestEndpointMatch(endpoints, '/api/invoices/42')?.
|
|
197
|
-
expect(longestEndpointMatch(endpoints, '/api/jobs')?.
|
|
311
|
+
expect(longestEndpointMatch(endpoints, '/api/invoices/42')?.slowRequestMs).toBe(2_000);
|
|
312
|
+
expect(longestEndpointMatch(endpoints, '/api/jobs')?.slowRequestMs).toBe(1_000);
|
|
198
313
|
});
|
|
199
314
|
|
|
200
315
|
test('returns undefined when nothing matches', () => {
|
|
@@ -202,40 +317,44 @@ describe('longestEndpointMatch', () => {
|
|
|
202
317
|
});
|
|
203
318
|
|
|
204
319
|
test('requires a path boundary after the prefix', () => {
|
|
205
|
-
const user = [{ match: '/api/user',
|
|
206
|
-
expect(longestEndpointMatch(user, '/api/user')?.
|
|
207
|
-
expect(longestEndpointMatch(user, '/api/user/42')?.
|
|
320
|
+
const user = [{ match: '/api/user', slowRequestMs: 1_000 }];
|
|
321
|
+
expect(longestEndpointMatch(user, '/api/user')?.slowRequestMs).toBe(1_000);
|
|
322
|
+
expect(longestEndpointMatch(user, '/api/user/42')?.slowRequestMs).toBe(1_000);
|
|
208
323
|
expect(longestEndpointMatch(user, '/api/user-preferences')).toBeUndefined();
|
|
209
324
|
expect(longestEndpointMatch(user, '/api/users')).toBeUndefined();
|
|
210
325
|
|
|
211
|
-
const trailed = [{ match: '/api/user/',
|
|
212
|
-
expect(longestEndpointMatch(trailed, '/api/user/42')?.
|
|
326
|
+
const trailed = [{ match: '/api/user/', slowRequestMs: 2_000 }];
|
|
327
|
+
expect(longestEndpointMatch(trailed, '/api/user/42')?.slowRequestMs).toBe(2_000);
|
|
213
328
|
});
|
|
214
329
|
});
|
|
215
330
|
|
|
216
331
|
describe('configureJourney endpoint ordering', () => {
|
|
332
|
+
beforeEach(() => {
|
|
333
|
+
setJourneyDefaultEnabled(true);
|
|
334
|
+
});
|
|
335
|
+
|
|
217
336
|
afterEach(() => {
|
|
337
|
+
setJourneyDefaultEnabled(false);
|
|
338
|
+
localStorage.clear();
|
|
339
|
+
resetSharedPolicies();
|
|
218
340
|
resetJourney({
|
|
219
341
|
sinks: [sendToDatadog, sendToConsole],
|
|
220
|
-
|
|
342
|
+
slowRequests: { defaultMs: 4_000, endpoints: [] },
|
|
221
343
|
});
|
|
222
344
|
});
|
|
223
345
|
|
|
224
346
|
test('stores endpoints longest-match-first', () => {
|
|
225
347
|
configureJourney({
|
|
226
|
-
|
|
348
|
+
slowRequests: {
|
|
227
349
|
defaultMs: 4_000,
|
|
228
350
|
endpoints: [
|
|
229
|
-
{ match: '/api',
|
|
230
|
-
{ match: '/api/invoices',
|
|
351
|
+
{ match: '/api', slowRequestMs: 1_000 },
|
|
352
|
+
{ match: '/api/invoices', slowRequestMs: 2_000 },
|
|
231
353
|
],
|
|
232
354
|
},
|
|
233
355
|
});
|
|
234
356
|
|
|
235
|
-
expect(
|
|
236
|
-
'/api/invoices',
|
|
237
|
-
'/api',
|
|
238
|
-
]);
|
|
357
|
+
expect(getSlowRequests().endpoints?.map(e => e.match)).toEqual(['/api/invoices', '/api']);
|
|
239
358
|
});
|
|
240
359
|
});
|
|
241
360
|
|
|
@@ -245,7 +364,7 @@ describe('[journey] sortEndpointsByLongestMatch', () => {
|
|
|
245
364
|
});
|
|
246
365
|
|
|
247
366
|
test('returns a copy for a single endpoint', () => {
|
|
248
|
-
const only = [{ match: '/api',
|
|
367
|
+
const only = [{ match: '/api', slowRequestMs: 1_000 }];
|
|
249
368
|
const sorted = sortEndpointsByLongestMatch(only);
|
|
250
369
|
|
|
251
370
|
expect(sorted).toEqual(only);
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/** @jest-environment jsdom */
|
|
2
2
|
import { baseJourneyDef, useJourneyTestRuntime } from '../../__test-utils__/test-runtime';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
singleInFlightJourneyStep,
|
|
5
|
+
completeJourney,
|
|
6
|
+
journeyStep,
|
|
7
|
+
JourneyStepStamp,
|
|
8
|
+
} from '../../core';
|
|
4
9
|
import { moduleRuntime } from '../../core/runtime';
|
|
5
10
|
|
|
6
11
|
const emitMock = jest.fn();
|
|
7
12
|
const baseDef = { ...baseJourneyDef, name: 'ambient-a' };
|
|
8
13
|
const otherDef = { ...baseJourneyDef, name: 'ambient-b' };
|
|
9
14
|
|
|
10
|
-
describe('[journey]
|
|
15
|
+
describe('[journey] singleInFlightJourneyStep', () => {
|
|
11
16
|
useJourneyTestRuntime(emitMock);
|
|
12
17
|
|
|
13
18
|
describe('around a single step', () => {
|
|
@@ -16,13 +21,13 @@ describe('[journey] activeJourneyStep', () => {
|
|
|
16
21
|
});
|
|
17
22
|
|
|
18
23
|
test('is set only while the step is running', async () => {
|
|
19
|
-
expect(
|
|
24
|
+
expect(singleInFlightJourneyStep()).toBeNull();
|
|
20
25
|
|
|
21
26
|
await journeyStep(baseDef.name, 'do-work', () => {
|
|
22
|
-
expect(
|
|
27
|
+
expect(singleInFlightJourneyStep()).toBeInstanceOf(JourneyStepStamp);
|
|
23
28
|
});
|
|
24
29
|
|
|
25
|
-
expect(
|
|
30
|
+
expect(singleInFlightJourneyStep()).toBeNull();
|
|
26
31
|
completeJourney(baseDef.name);
|
|
27
32
|
});
|
|
28
33
|
});
|
|
@@ -50,7 +55,7 @@ describe('[journey] activeJourneyStep', () => {
|
|
|
50
55
|
completeJourney(otherDef.name);
|
|
51
56
|
});
|
|
52
57
|
|
|
53
|
-
const subject = () => journeyStep(otherDef.name, 'book', () =>
|
|
58
|
+
const subject = () => journeyStep(otherDef.name, 'book', () => singleInFlightJourneyStep());
|
|
54
59
|
|
|
55
60
|
test('returns null', async () => {
|
|
56
61
|
await expect(subject()).resolves.toBeNull();
|
|
@@ -60,7 +65,7 @@ describe('[journey] activeJourneyStep', () => {
|
|
|
60
65
|
describe('when the first-finishing sibling ends first', () => {
|
|
61
66
|
let releaseB!: () => void;
|
|
62
67
|
let stepB: Promise<unknown>;
|
|
63
|
-
let ambientWhileBAlone: ReturnType<typeof
|
|
68
|
+
let ambientWhileBAlone: ReturnType<typeof singleInFlightJourneyStep> | 'unset';
|
|
64
69
|
|
|
65
70
|
beforeEach(() => {
|
|
66
71
|
moduleRuntime.startJourney(baseDef);
|
|
@@ -72,7 +77,7 @@ describe('[journey] activeJourneyStep', () => {
|
|
|
72
77
|
});
|
|
73
78
|
stepB = journeyStep(otherDef.name, 'book', async () => {
|
|
74
79
|
await gateB;
|
|
75
|
-
ambientWhileBAlone =
|
|
80
|
+
ambientWhileBAlone = singleInFlightJourneyStep();
|
|
76
81
|
});
|
|
77
82
|
});
|
|
78
83
|
|
|
@@ -85,7 +90,7 @@ describe('[journey] activeJourneyStep', () => {
|
|
|
85
90
|
|
|
86
91
|
test('keeps ambient on the remaining step', async () => {
|
|
87
92
|
await journeyStep(baseDef.name, 'load', () => {
|
|
88
|
-
expect(
|
|
93
|
+
expect(singleInFlightJourneyStep()).toBeNull();
|
|
89
94
|
});
|
|
90
95
|
|
|
91
96
|
releaseB();
|
|
@@ -96,7 +101,8 @@ describe('[journey] activeJourneyStep', () => {
|
|
|
96
101
|
if (ambientWhileBAlone === 'unset' || ambientWhileBAlone == null) {
|
|
97
102
|
throw new Error('expected ambient step');
|
|
98
103
|
}
|
|
99
|
-
expect(ambientWhileBAlone
|
|
104
|
+
expect(ambientWhileBAlone).toBeInstanceOf(JourneyStepStamp);
|
|
105
|
+
expect(ambientWhileBAlone.step.name).toBe('book');
|
|
100
106
|
});
|
|
101
107
|
});
|
|
102
108
|
});
|
|
@@ -20,10 +20,10 @@ const otherDef = { ...baseJourneyDef, name: 'api-other' };
|
|
|
20
20
|
describe('[journey] api', () => {
|
|
21
21
|
useJourneyTestRuntime(emitMock);
|
|
22
22
|
|
|
23
|
-
describe('
|
|
23
|
+
describe('firstOpenJourneyName', () => {
|
|
24
24
|
const names = [baseDef.name, otherDef.name];
|
|
25
25
|
|
|
26
|
-
const subject = () => moduleRuntime.
|
|
26
|
+
const subject = () => moduleRuntime.firstOpenJourneyName(names);
|
|
27
27
|
|
|
28
28
|
describe('when none are open', () => {
|
|
29
29
|
test('returns null', () => {
|
|
@@ -49,7 +49,7 @@ describe('[journey] api', () => {
|
|
|
49
49
|
|
|
50
50
|
test('returns the first match in order', () => {
|
|
51
51
|
expect(subject()).toBe(baseDef.name);
|
|
52
|
-
expect(moduleRuntime.
|
|
52
|
+
expect(moduleRuntime.firstOpenJourneyName([otherDef.name, baseDef.name])).toBe(
|
|
53
53
|
otherDef.name
|
|
54
54
|
);
|
|
55
55
|
});
|
|
@@ -85,7 +85,7 @@ describe('[journey] api', () => {
|
|
|
85
85
|
beforeEach(async () => {
|
|
86
86
|
moduleRuntime.startJourney(baseDef);
|
|
87
87
|
await journeyStep(baseDef.name, 'do-work', step => {
|
|
88
|
-
step.
|
|
88
|
+
step.completeJourney();
|
|
89
89
|
});
|
|
90
90
|
});
|
|
91
91
|
|
|
@@ -102,7 +102,7 @@ describe('[journey] api', () => {
|
|
|
102
102
|
beforeEach(async () => {
|
|
103
103
|
moduleRuntime.startJourney(baseDef);
|
|
104
104
|
await journeyStep(baseDef.name, 'do-work', step => {
|
|
105
|
-
step.
|
|
105
|
+
step.failJourney('validation');
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
108
|
|
|
@@ -136,7 +136,7 @@ describe('[journey] api', () => {
|
|
|
136
136
|
beforeEach(async () => {
|
|
137
137
|
moduleRuntime.startJourney(baseDef);
|
|
138
138
|
await journeyStep(baseDef.name, 'do-work', step => {
|
|
139
|
-
step.
|
|
139
|
+
step.failJourney('validation');
|
|
140
140
|
});
|
|
141
141
|
});
|
|
142
142
|
|
|
@@ -209,7 +209,7 @@ describe('[journey] api', () => {
|
|
|
209
209
|
beforeEach(async () => {
|
|
210
210
|
moduleRuntime.startJourney(baseDef);
|
|
211
211
|
await journeyStep(baseDef.name, 'do-work', step => {
|
|
212
|
-
step.
|
|
212
|
+
step.failJourney('validation');
|
|
213
213
|
});
|
|
214
214
|
emitMock.mockClear();
|
|
215
215
|
});
|
|
@@ -26,7 +26,7 @@ describe('[journey] defineJourney', () => {
|
|
|
26
26
|
const subject = () =>
|
|
27
27
|
journey.step('do-work', step => {
|
|
28
28
|
step.setAttribute('x', 1);
|
|
29
|
-
step.
|
|
29
|
+
step.completeJourney({ 'payment.id': 42 });
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
test('opens, records the step, and completes good', async () => {
|
|
@@ -41,7 +41,7 @@ describe('[journey] defineJourney', () => {
|
|
|
41
41
|
|
|
42
42
|
test('mountStep also opens the journey', async () => {
|
|
43
43
|
await journey.mountStep('boot', step => {
|
|
44
|
-
step.
|
|
44
|
+
step.completeJourney();
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
expect(emitMock).toHaveBeenCalledTimes(1);
|
|
@@ -57,7 +57,7 @@ describe('[journey] defineJourney', () => {
|
|
|
57
57
|
|
|
58
58
|
const subject = () =>
|
|
59
59
|
journey.step('do-work', step => {
|
|
60
|
-
step.
|
|
60
|
+
step.completeJourney();
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
test('does not restart the open journey', async () => {
|
|
@@ -76,7 +76,7 @@ describe('[journey] defineJourney', () => {
|
|
|
76
76
|
|
|
77
77
|
const subject = () =>
|
|
78
78
|
journeyStep(journey, 'via-free', step => {
|
|
79
|
-
step.
|
|
79
|
+
step.completeJourney();
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
test('resolves the handle name', async () => {
|
|
@@ -153,7 +153,7 @@ describe('[journey] defineJourney', () => {
|
|
|
153
153
|
|
|
154
154
|
const subject = () =>
|
|
155
155
|
journey.mountStep('boot', step => {
|
|
156
|
-
step.
|
|
156
|
+
step.completeJourney();
|
|
157
157
|
});
|
|
158
158
|
|
|
159
159
|
test('does not restart the open journey', async () => {
|
|
@@ -166,12 +166,29 @@ describe('[journey] defineJourney', () => {
|
|
|
166
166
|
});
|
|
167
167
|
});
|
|
168
168
|
|
|
169
|
-
describe('when
|
|
170
|
-
test('exposes the
|
|
171
|
-
expect(journey.
|
|
172
|
-
expect(journey.
|
|
173
|
-
expect(journey.
|
|
174
|
-
expect(journey.
|
|
169
|
+
describe('when keeping the original config on the handle', () => {
|
|
170
|
+
test('exposes the same config object', () => {
|
|
171
|
+
expect(journey.config).toBe(base);
|
|
172
|
+
expect(journey.config.name).toBe(base.name);
|
|
173
|
+
expect(journey.config.name).toBe(base.name);
|
|
174
|
+
expect(journey.config.team).toBe(base.team);
|
|
175
|
+
expect(journey.config.group).toBe(base.group);
|
|
176
|
+
expect(journey.config.service).toBe(base.service);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('when stepping via free journeyStep with a handle and nothing open', () => {
|
|
181
|
+
const subject = () =>
|
|
182
|
+
journeyStep(journey, 'via-handle', step => {
|
|
183
|
+
step.completeJourney();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('auto-starts from the handle config', async () => {
|
|
187
|
+
await subject();
|
|
188
|
+
|
|
189
|
+
expect(emitMock).toHaveBeenCalledTimes(1);
|
|
190
|
+
expect(lastJourney(emitMock).outcome).toBe('good');
|
|
191
|
+
expect(lastJourney(emitMock).steps[0].name).toBe('via-handle');
|
|
175
192
|
});
|
|
176
193
|
});
|
|
177
194
|
});
|