@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
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
lastJourney,
|
|
5
5
|
useJourneyTestRuntime,
|
|
6
6
|
} from '../../__test-utils__/test-runtime';
|
|
7
|
-
import { completeJourney, failJourney,
|
|
7
|
+
import { completeJourney, failJourney, JourneyStepStamp, journeyStep } from '../../core';
|
|
8
8
|
import { moduleRuntime } from '../../core/runtime';
|
|
9
9
|
|
|
10
10
|
const emitMock = jest.fn();
|
|
@@ -19,7 +19,8 @@ describe('[journey] journeyStep', () => {
|
|
|
19
19
|
moduleRuntime.startJourney(baseDef);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
const subject = () =>
|
|
22
|
+
const subject = () =>
|
|
23
|
+
journeyStep(['missing', baseDef.name], 'do-work', step => step.journeyName);
|
|
23
24
|
|
|
24
25
|
test('resolves the first open name', async () => {
|
|
25
26
|
await expect(subject()).resolves.toBe(baseDef.name);
|
|
@@ -33,7 +34,7 @@ describe('[journey] journeyStep', () => {
|
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
const subject = () =>
|
|
36
|
-
journeyStep([baseDef.name, otherDef.name], 'do-work', step => step.
|
|
37
|
+
journeyStep([baseDef.name, otherDef.name], 'do-work', step => step.journeyName);
|
|
37
38
|
|
|
38
39
|
test('prefers the first name in order', async () => {
|
|
39
40
|
await expect(subject()).resolves.toBe(baseDef.name);
|
|
@@ -43,9 +44,9 @@ describe('[journey] journeyStep', () => {
|
|
|
43
44
|
describe('with no matching open journey', () => {
|
|
44
45
|
const subject = () =>
|
|
45
46
|
journeyStep(['missing-a', 'missing-b'], 'do-work', step => {
|
|
46
|
-
step.
|
|
47
|
-
step.
|
|
48
|
-
return step.
|
|
47
|
+
step.completeJourney({ a: 1 });
|
|
48
|
+
step.failJourney('noop');
|
|
49
|
+
return step.journeyName;
|
|
49
50
|
});
|
|
50
51
|
|
|
51
52
|
test('runs untraced', async () => {
|
|
@@ -54,12 +55,21 @@ describe('[journey] journeyStep', () => {
|
|
|
54
55
|
});
|
|
55
56
|
});
|
|
56
57
|
|
|
58
|
+
describe('with a bare name and nothing open', () => {
|
|
59
|
+
test('does not auto-start', async () => {
|
|
60
|
+
await journeyStep(baseDef.name, 'do-work', step => {
|
|
61
|
+
expect(step.journeyName).toBeNull();
|
|
62
|
+
expect(step.stamp()).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
expect(emitMock).not.toHaveBeenCalled();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
57
68
|
describe('with a null target', () => {
|
|
58
|
-
const subject = () =>
|
|
59
|
-
journeyStep(null, 'untraced', step => step.tag() as Record<string, unknown>);
|
|
69
|
+
const subject = () => journeyStep(null, 'untraced', step => step.stamp());
|
|
60
70
|
|
|
61
|
-
test('returns
|
|
62
|
-
await expect(subject()).resolves.
|
|
71
|
+
test('returns undefined', async () => {
|
|
72
|
+
await expect(subject()).resolves.toBeUndefined();
|
|
63
73
|
});
|
|
64
74
|
});
|
|
65
75
|
|
|
@@ -72,7 +82,7 @@ describe('[journey] journeyStep', () => {
|
|
|
72
82
|
const subject = () =>
|
|
73
83
|
journeyStep(baseDef.name, 'do-work', step => {
|
|
74
84
|
step.setAttribute('x', 1);
|
|
75
|
-
step.
|
|
85
|
+
step.completeJourney({ 'payment.id': 42 });
|
|
76
86
|
});
|
|
77
87
|
|
|
78
88
|
test('emits good with tags and attributes', async () => {
|
|
@@ -103,29 +113,63 @@ describe('[journey] journeyStep', () => {
|
|
|
103
113
|
});
|
|
104
114
|
});
|
|
105
115
|
|
|
106
|
-
describe('when
|
|
107
|
-
|
|
116
|
+
describe('when setAttribute uses unsafe or overflowing keys', () => {
|
|
117
|
+
test('skips prototype keys and caps the map', async () => {
|
|
118
|
+
await journeyStep(baseDef.name, 'attrs', step => {
|
|
119
|
+
step.setAttribute('__proto__', 'nope');
|
|
120
|
+
step.setAttribute('token', 'secret');
|
|
121
|
+
step.setAttribute('ok', 1);
|
|
122
|
+
for (let i = 0; i < 70; i += 1) {
|
|
123
|
+
step.setAttribute(`k${i}`, i);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
completeJourney(baseDef.name);
|
|
127
|
+
|
|
128
|
+
const attributes = lastJourney(emitMock).steps[0].attributes as Record<
|
|
129
|
+
string,
|
|
130
|
+
unknown
|
|
131
|
+
>;
|
|
132
|
+
expect(Object.prototype.hasOwnProperty.call(attributes, '__proto__')).toBe(false);
|
|
133
|
+
expect(attributes.token).toBeUndefined();
|
|
134
|
+
expect(attributes.ok).toBe(1);
|
|
135
|
+
expect(Object.keys(attributes).length).toBe(64);
|
|
136
|
+
expect(({} as { polluted?: boolean }).polluted).toBeUndefined();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('when step.stamp is called', () => {
|
|
141
|
+
let tagged: JourneyStepStamp | undefined;
|
|
108
142
|
|
|
109
143
|
beforeEach(() => {
|
|
110
|
-
tagged =
|
|
144
|
+
tagged = undefined;
|
|
111
145
|
});
|
|
112
146
|
|
|
113
147
|
const subject = () =>
|
|
114
148
|
journeyStep(baseDef.name, 'do-work', step => {
|
|
115
|
-
tagged = step.
|
|
149
|
+
tagged = step.stamp();
|
|
116
150
|
});
|
|
117
151
|
|
|
118
|
-
test('binds
|
|
152
|
+
test('binds stamp', async () => {
|
|
119
153
|
await subject();
|
|
120
154
|
|
|
121
|
-
expect(tagged).
|
|
155
|
+
expect(tagged).toBeInstanceOf(JourneyStepStamp);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('when step.stamp is called with a score function', () => {
|
|
160
|
+
test('stores score on the stamp', async () => {
|
|
161
|
+
await journeyStep(baseDef.name, 'do-work', step => {
|
|
162
|
+
const score = () => 'exclude' as const;
|
|
163
|
+
const stamp = step.stamp(score);
|
|
164
|
+
expect(stamp?.score).toBe(score);
|
|
165
|
+
});
|
|
122
166
|
});
|
|
123
167
|
});
|
|
124
168
|
|
|
125
|
-
describe('when step.
|
|
169
|
+
describe('when step.failJourney is called', () => {
|
|
126
170
|
const subject = () =>
|
|
127
171
|
journeyStep(baseDef.name, 'do-work', step => {
|
|
128
|
-
step.
|
|
172
|
+
step.failJourney('empty-catalog');
|
|
129
173
|
});
|
|
130
174
|
|
|
131
175
|
test('marks bad without throwing', async () => {
|
|
@@ -145,7 +189,7 @@ describe('[journey] journeyStep', () => {
|
|
|
145
189
|
test('marks step-error and rethrows', async () => {
|
|
146
190
|
await expect(subject()).rejects.toThrow('boom');
|
|
147
191
|
expect(lastJourney(emitMock).reason).toBe('step-error');
|
|
148
|
-
expect(lastJourney(emitMock).steps[0].attributes).
|
|
192
|
+
expect(lastJourney(emitMock).steps[0].attributes).toBeUndefined();
|
|
149
193
|
});
|
|
150
194
|
});
|
|
151
195
|
|
|
@@ -156,27 +200,22 @@ describe('[journey] journeyStep', () => {
|
|
|
156
200
|
throw 'string-boom';
|
|
157
201
|
});
|
|
158
202
|
|
|
159
|
-
test('
|
|
203
|
+
test('does not stringify the throw onto attributes', async () => {
|
|
160
204
|
await expect(subject()).rejects.toBe('string-boom');
|
|
161
|
-
expect(lastJourney(emitMock).steps[0].attributes).
|
|
162
|
-
error: 'string-boom',
|
|
163
|
-
});
|
|
205
|
+
expect(lastJourney(emitMock).steps[0].attributes).toBeUndefined();
|
|
164
206
|
});
|
|
165
207
|
});
|
|
166
208
|
|
|
167
209
|
describe('when error is pre-set on the step', () => {
|
|
168
210
|
const subject = () =>
|
|
169
211
|
journeyStep(baseDef.name, 'do-work', step => {
|
|
170
|
-
|
|
171
|
-
(step.tag() as { journeyStep: unknown }).journeyStep
|
|
172
|
-
)!;
|
|
173
|
-
ref.attributes.error = 'already';
|
|
212
|
+
step.setAttribute('phase', 'load');
|
|
174
213
|
throw new Error('boom');
|
|
175
214
|
});
|
|
176
215
|
|
|
177
|
-
test('
|
|
216
|
+
test('keeps developer attributes', async () => {
|
|
178
217
|
await expect(subject()).rejects.toThrow('boom');
|
|
179
|
-
expect(lastJourney(emitMock).steps[0].attributes).toEqual({
|
|
218
|
+
expect(lastJourney(emitMock).steps[0].attributes).toEqual({ phase: 'load' });
|
|
180
219
|
});
|
|
181
220
|
});
|
|
182
221
|
|
|
@@ -184,7 +223,7 @@ describe('[journey] journeyStep', () => {
|
|
|
184
223
|
beforeEach(() => {
|
|
185
224
|
// Parent already opened baseDef; restart with expected steps.
|
|
186
225
|
emitMock.mockClear();
|
|
187
|
-
moduleRuntime.startJourney({ ...baseDef,
|
|
226
|
+
moduleRuntime.startJourney({ ...baseDef, expected: ['load', 'save'] });
|
|
188
227
|
emitMock.mockClear();
|
|
189
228
|
});
|
|
190
229
|
|
|
@@ -195,7 +234,7 @@ describe('[journey] journeyStep', () => {
|
|
|
195
234
|
expect(emitMock).not.toHaveBeenCalled();
|
|
196
235
|
|
|
197
236
|
await journeyStep(baseDef.name, 'save', step => {
|
|
198
|
-
step.
|
|
237
|
+
step.completeJourney({ done: true });
|
|
199
238
|
});
|
|
200
239
|
|
|
201
240
|
expect(emitMock).toHaveBeenCalledTimes(1);
|
|
@@ -210,8 +249,8 @@ describe('[journey] journeyStep', () => {
|
|
|
210
249
|
describe('with a JourneyDef-like object target', () => {
|
|
211
250
|
const subject = () =>
|
|
212
251
|
journeyStep(baseDef, 'via-def', step => {
|
|
213
|
-
step.
|
|
214
|
-
return step.
|
|
252
|
+
step.completeJourney();
|
|
253
|
+
return step.journeyName;
|
|
215
254
|
});
|
|
216
255
|
|
|
217
256
|
test('resolves the def name', async () => {
|
|
@@ -241,8 +280,8 @@ describe('[journey] journeyStep', () => {
|
|
|
241
280
|
|
|
242
281
|
const subject = () =>
|
|
243
282
|
moduleRuntime.journeyMountStep(['missing', baseDef.name], 'mount-load', step => {
|
|
244
|
-
step.
|
|
245
|
-
return step.
|
|
283
|
+
step.completeJourney();
|
|
284
|
+
return step.journeyName;
|
|
246
285
|
});
|
|
247
286
|
|
|
248
287
|
test('resolves the open candidate', async () => {
|
|
@@ -300,4 +339,61 @@ describe('[journey] journeyStep', () => {
|
|
|
300
339
|
await stepPromise;
|
|
301
340
|
});
|
|
302
341
|
});
|
|
342
|
+
|
|
343
|
+
describe('when the same name is restarted while a step is in flight', () => {
|
|
344
|
+
test('completeJourney on the old step does not close the new instance', async () => {
|
|
345
|
+
moduleRuntime.startJourney(baseDef);
|
|
346
|
+
let resolveWork!: () => void;
|
|
347
|
+
const hang = new Promise<void>(resolve => {
|
|
348
|
+
resolveWork = resolve;
|
|
349
|
+
});
|
|
350
|
+
const stepPromise = journeyStep(baseDef.name, 'slow', async step => {
|
|
351
|
+
await hang;
|
|
352
|
+
step.completeJourney();
|
|
353
|
+
});
|
|
354
|
+
await Promise.resolve();
|
|
355
|
+
moduleRuntime.startJourney(baseDef);
|
|
356
|
+
expect(lastJourney(emitMock).outcome).toBe('excluded');
|
|
357
|
+
emitMock.mockClear();
|
|
358
|
+
|
|
359
|
+
resolveWork();
|
|
360
|
+
await stepPromise;
|
|
361
|
+
expect(emitMock).not.toHaveBeenCalled();
|
|
362
|
+
|
|
363
|
+
completeJourney(baseDef.name);
|
|
364
|
+
expect(lastJourney(emitMock).outcome).toBe('good');
|
|
365
|
+
expect(lastJourney(emitMock).steps).toHaveLength(0);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe('when maxSteps is exceeded', () => {
|
|
370
|
+
test('stops recording steps and sets steps_truncated', async () => {
|
|
371
|
+
moduleRuntime.startJourney({ ...baseDef, maxSteps: 2 });
|
|
372
|
+
await journeyStep(baseDef.name, 'a', () => {});
|
|
373
|
+
await journeyStep(baseDef.name, 'b', () => {});
|
|
374
|
+
await journeyStep(baseDef.name, 'c', () => {});
|
|
375
|
+
completeJourney(baseDef.name);
|
|
376
|
+
|
|
377
|
+
expect(lastJourney(emitMock).steps.map((s: { name: string }) => s.name)).toEqual([
|
|
378
|
+
'a',
|
|
379
|
+
'b',
|
|
380
|
+
]);
|
|
381
|
+
expect(lastJourney(emitMock).tags?.steps_truncated).toBe(true);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
describe('when the started config is frozen', () => {
|
|
386
|
+
test('rejects mutation of the snapshot', () => {
|
|
387
|
+
const config = { ...baseDef, name: 'frozen-def' };
|
|
388
|
+
moduleRuntime.startJourney(config);
|
|
389
|
+
const snapshot = moduleRuntime.getOpenJourney('frozen-def')!.config;
|
|
390
|
+
expect(Object.isFrozen(snapshot)).toBe(true);
|
|
391
|
+
expect(snapshot).not.toBe(config);
|
|
392
|
+
expect(() => {
|
|
393
|
+
(snapshot as { name: string }).name = 'hacked';
|
|
394
|
+
}).toThrow();
|
|
395
|
+
expect(snapshot.name).toBe('frozen-def');
|
|
396
|
+
completeJourney('frozen-def');
|
|
397
|
+
});
|
|
398
|
+
});
|
|
303
399
|
});
|
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
lastJourney,
|
|
5
5
|
useJourneyTestRuntime,
|
|
6
6
|
} from '../../__test-utils__/test-runtime';
|
|
7
|
-
import {
|
|
7
|
+
import type { JourneyStepStamp } from '../../core';
|
|
8
|
+
import { completeJourney, journeyStep } from '../../core';
|
|
8
9
|
import { moduleRuntime } from '../../core/runtime';
|
|
9
10
|
|
|
10
11
|
const emitMock = jest.fn();
|
|
@@ -52,6 +53,24 @@ describe('[journey] timeouts', () => {
|
|
|
52
53
|
});
|
|
53
54
|
});
|
|
54
55
|
|
|
56
|
+
describe('without timeoutMs', () => {
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
moduleRuntime.startJourney({
|
|
59
|
+
name: 'timeout-idle',
|
|
60
|
+
team: baseDef.team,
|
|
61
|
+
group: baseDef.group,
|
|
62
|
+
service: baseDef.service,
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('closes excluded with journey-idle-timeout after default idle window', async () => {
|
|
67
|
+
await jest.advanceTimersByTimeAsync(15 * 60 * 1_000);
|
|
68
|
+
|
|
69
|
+
expect(lastJourney(emitMock).outcome).toBe('excluded');
|
|
70
|
+
expect(lastJourney(emitMock).reason).toBe('journey-idle-timeout');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
55
74
|
describe('with a step timeoutMs', () => {
|
|
56
75
|
let resolveWork!: () => void;
|
|
57
76
|
let stepPromise: Promise<unknown>;
|
|
@@ -154,9 +173,7 @@ describe('[journey] timeouts', () => {
|
|
|
154
173
|
resolveWork = resolve;
|
|
155
174
|
});
|
|
156
175
|
stepPromise = journeyStep(baseDef.name, 'slow', async step => {
|
|
157
|
-
const ref =
|
|
158
|
-
(step.tag() as { journeyStep: unknown }).journeyStep
|
|
159
|
-
)!;
|
|
176
|
+
const ref = (step.stamp() as JourneyStepStamp).step;
|
|
160
177
|
ref.reason = 'pre-existing';
|
|
161
178
|
ref.attributes.timeout_ms = 999;
|
|
162
179
|
await hang;
|
|
@@ -199,6 +216,34 @@ describe('[journey] timeouts', () => {
|
|
|
199
216
|
});
|
|
200
217
|
});
|
|
201
218
|
|
|
219
|
+
describe('when a journey timeout closes a hung step', () => {
|
|
220
|
+
let releaseWork!: () => void;
|
|
221
|
+
let stepPromise: Promise<unknown>;
|
|
222
|
+
|
|
223
|
+
beforeEach(() => {
|
|
224
|
+
moduleRuntime.startJourney({ ...baseDef, timeoutMs: 50 });
|
|
225
|
+
const hang = new Promise<void>(resolve => {
|
|
226
|
+
releaseWork = resolve;
|
|
227
|
+
});
|
|
228
|
+
stepPromise = journeyStep(baseDef.name, 'slow', async () => {
|
|
229
|
+
await hang;
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
afterEach(async () => {
|
|
234
|
+
releaseWork();
|
|
235
|
+
await stepPromise;
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('releases inFlight state before the step settles', async () => {
|
|
239
|
+
await jest.advanceTimersByTimeAsync(50);
|
|
240
|
+
|
|
241
|
+
expect(moduleRuntime.activeJourneyStep()).toBeNull();
|
|
242
|
+
expect(lastJourney(emitMock).outcome).toBe('bad');
|
|
243
|
+
expect(lastJourney(emitMock).reason).toBe('journey-timeout');
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
202
247
|
describe('with stepTimeoutMs 0', () => {
|
|
203
248
|
let resolveWork!: () => void;
|
|
204
249
|
let stepPromise: Promise<unknown>;
|
|
@@ -228,4 +273,24 @@ describe('[journey] timeouts', () => {
|
|
|
228
273
|
expect(lastJourney(emitMock).outcome).toBe('good');
|
|
229
274
|
});
|
|
230
275
|
});
|
|
276
|
+
|
|
277
|
+
describe('with non-finite timeoutMs', () => {
|
|
278
|
+
test('Infinity disables the countdown', async () => {
|
|
279
|
+
moduleRuntime.startJourney({ ...baseDef, timeoutMs: Infinity });
|
|
280
|
+
await jest.advanceTimersByTimeAsync(60_000);
|
|
281
|
+
|
|
282
|
+
expect(emitMock).not.toHaveBeenCalled();
|
|
283
|
+
completeJourney(baseDef.name);
|
|
284
|
+
expect(lastJourney(emitMock).outcome).toBe('good');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test('NaN disables the countdown', async () => {
|
|
288
|
+
moduleRuntime.startJourney({ ...baseDef, timeoutMs: Number.NaN });
|
|
289
|
+
await jest.advanceTimersByTimeAsync(60_000);
|
|
290
|
+
|
|
291
|
+
expect(emitMock).not.toHaveBeenCalled();
|
|
292
|
+
completeJourney(baseDef.name);
|
|
293
|
+
expect(lastJourney(emitMock).outcome).toBe('good');
|
|
294
|
+
});
|
|
295
|
+
});
|
|
231
296
|
});
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
/** @jest-environment jsdom */
|
|
2
2
|
import '../config';
|
|
3
3
|
import { defineJourney, resetJourney } from '../core';
|
|
4
|
+
import { moduleRuntime } from '../core/runtime';
|
|
4
5
|
import {
|
|
5
6
|
JOURNEY_DEBUG_STORAGE_KEY,
|
|
6
7
|
JOURNEY_ENABLED_STORAGE_KEY,
|
|
7
8
|
ST_JOURNEY_GLOBAL_KEY,
|
|
9
|
+
ST_JOURNEY_SCHEMA_VERSION,
|
|
8
10
|
disableJourney,
|
|
9
11
|
enableJourney,
|
|
10
12
|
isJourneyEnabled,
|
|
13
|
+
setJourneyDefaultEnabled,
|
|
11
14
|
type StJourneyGlobal,
|
|
12
15
|
} from '../global';
|
|
13
16
|
import { setJourneyRum } from '../sinks/datadog';
|
|
@@ -19,6 +22,8 @@ function getStJourney(): StJourneyGlobal {
|
|
|
19
22
|
describe('[journey] __stJourney global', () => {
|
|
20
23
|
beforeEach(() => {
|
|
21
24
|
localStorage.clear();
|
|
25
|
+
sessionStorage.clear();
|
|
26
|
+
setJourneyDefaultEnabled(false);
|
|
22
27
|
delete getStJourney().rum;
|
|
23
28
|
delete (globalThis as Record<string, unknown>).DD_RUM;
|
|
24
29
|
resetJourney({ sinks: [] });
|
|
@@ -26,6 +31,8 @@ describe('[journey] __stJourney global', () => {
|
|
|
26
31
|
|
|
27
32
|
afterEach(() => {
|
|
28
33
|
localStorage.clear();
|
|
34
|
+
sessionStorage.clear();
|
|
35
|
+
setJourneyDefaultEnabled(false);
|
|
29
36
|
delete getStJourney().rum;
|
|
30
37
|
delete (globalThis as Record<string, unknown>).DD_RUM;
|
|
31
38
|
resetJourney({ sinks: [] });
|
|
@@ -34,6 +41,7 @@ describe('[journey] __stJourney global', () => {
|
|
|
34
41
|
test('installs __stJourney on package load', () => {
|
|
35
42
|
expect(getStJourney()).toEqual(
|
|
36
43
|
expect.objectContaining({
|
|
44
|
+
schemaVersion: ST_JOURNEY_SCHEMA_VERSION,
|
|
37
45
|
enable: expect.any(Function),
|
|
38
46
|
disable: expect.any(Function),
|
|
39
47
|
isEnabled: expect.any(Function),
|
|
@@ -58,15 +66,47 @@ describe('[journey] __stJourney global', () => {
|
|
|
58
66
|
expect(isJourneyEnabled()).toBe(false);
|
|
59
67
|
});
|
|
60
68
|
|
|
61
|
-
test('
|
|
69
|
+
test('isJourneyEnabled is true when host default is on', () => {
|
|
70
|
+
expect(isJourneyEnabled()).toBe(false);
|
|
71
|
+
|
|
72
|
+
setJourneyDefaultEnabled(true);
|
|
73
|
+
expect(isJourneyEnabled()).toBe(true);
|
|
74
|
+
expect(getStJourney().isEnabled()).toBe(true);
|
|
75
|
+
|
|
76
|
+
setJourneyDefaultEnabled(false);
|
|
77
|
+
expect(isJourneyEnabled()).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('isJourneyEnabled is true when either host default or force flag is on', () => {
|
|
81
|
+
setJourneyDefaultEnabled(true);
|
|
82
|
+
enableJourney();
|
|
83
|
+
expect(isJourneyEnabled()).toBe(true);
|
|
84
|
+
|
|
85
|
+
setJourneyDefaultEnabled(false);
|
|
86
|
+
expect(isJourneyEnabled()).toBe(true);
|
|
87
|
+
|
|
88
|
+
disableJourney();
|
|
89
|
+
expect(isJourneyEnabled()).toBe(false);
|
|
90
|
+
|
|
91
|
+
setJourneyDefaultEnabled(true);
|
|
92
|
+
expect(isJourneyEnabled()).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('host default is shared on __stJourney for cross-bundle reads', () => {
|
|
96
|
+
setJourneyDefaultEnabled(true);
|
|
97
|
+
expect(getStJourney().defaultEnabled).toBe(true);
|
|
98
|
+
expect(isJourneyEnabled()).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('enableDebugging / disableDebugging toggle console-debug sessionStorage', () => {
|
|
62
102
|
const st = getStJourney();
|
|
63
103
|
|
|
64
104
|
st.enableDebugging();
|
|
65
|
-
expect(
|
|
105
|
+
expect(sessionStorage.getItem(JOURNEY_DEBUG_STORAGE_KEY)).toBe('true');
|
|
66
106
|
expect(st.isDebugEnabled()).toBe(true);
|
|
67
107
|
|
|
68
108
|
st.disableDebugging();
|
|
69
|
-
expect(
|
|
109
|
+
expect(sessionStorage.getItem(JOURNEY_DEBUG_STORAGE_KEY)).toBeNull();
|
|
70
110
|
expect(st.isDebugEnabled()).toBe(false);
|
|
71
111
|
});
|
|
72
112
|
|
|
@@ -91,6 +131,7 @@ describe('[journey] __stJourney global', () => {
|
|
|
91
131
|
getStJourney().enableDebugging();
|
|
92
132
|
const on = getStJourney().getDebugState();
|
|
93
133
|
expect(on.debug).toBe(true);
|
|
134
|
+
expect(on.runtimes.some(r => r.id === moduleRuntime.id)).toBe(true);
|
|
94
135
|
expect(on.runtimes.some(r => r.openJourneys.some(j => j.name === 'Book Job'))).toBe(true);
|
|
95
136
|
|
|
96
137
|
getStJourney().disableDebugging();
|