@bleedingdev/modern-js-runtime-extensions 3.9.0-ultramodern.9 → 3.9.3-ultramodern.10
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/dist/cjs/{reactContext.js → localisedUrls/config.js} +14 -10
- package/dist/cjs/localisedUrls/index.js +95 -0
- package/dist/cjs/localisedUrls/normalise.js +87 -0
- package/dist/cjs/localisedUrls/pathname.js +68 -0
- package/dist/cjs/localisedUrls/patterns.js +214 -0
- package/dist/cjs/localisedUrls/redirect.js +104 -0
- package/dist/cjs/localisedUrls/resolve.js +78 -0
- package/dist/cjs/localisedUrls/routeIdentity.js +92 -0
- package/dist/cjs/localisedUrls/routes.js +217 -0
- package/dist/cjs/localisedUrls/types.js +18 -0
- package/dist/cjs/rendererHead.js +18 -5
- package/dist/cjs/request-policy/policyCore.js +1 -1
- package/dist/cjs/request-policy/transport.js +2 -2
- package/dist/cjs/routerState.js +85 -77
- package/dist/cjs/routerStatePlugin.js +63 -9
- package/dist/cjs/server-config/serverSource.js +18 -0
- package/dist/esm/localisedUrls/config.mjs +11 -0
- package/dist/esm/localisedUrls/index.mjs +8 -0
- package/dist/esm/localisedUrls/normalise.mjs +37 -0
- package/dist/esm/localisedUrls/pathname.mjs +24 -0
- package/dist/esm/localisedUrls/patterns.mjs +152 -0
- package/dist/esm/localisedUrls/redirect.mjs +57 -0
- package/dist/esm/localisedUrls/resolve.mjs +37 -0
- package/dist/esm/localisedUrls/routeIdentity.mjs +54 -0
- package/dist/esm/localisedUrls/routes.mjs +176 -0
- package/dist/esm/localisedUrls/types.mjs +0 -0
- package/dist/esm/rendererHead.mjs +14 -5
- package/dist/esm/request-policy/policyCore.mjs +1 -1
- package/dist/esm/request-policy/transport.mjs +2 -2
- package/dist/esm/routerState.mjs +81 -73
- package/dist/esm/routerStatePlugin.mjs +62 -11
- package/dist/esm/server-config/serverSource.mjs +0 -0
- package/dist/esm-node/localisedUrls/config.mjs +12 -0
- package/dist/esm-node/localisedUrls/index.mjs +9 -0
- package/dist/esm-node/localisedUrls/normalise.mjs +38 -0
- package/dist/esm-node/localisedUrls/pathname.mjs +25 -0
- package/dist/esm-node/localisedUrls/patterns.mjs +153 -0
- package/dist/esm-node/localisedUrls/redirect.mjs +58 -0
- package/dist/esm-node/localisedUrls/resolve.mjs +38 -0
- package/dist/esm-node/localisedUrls/routeIdentity.mjs +55 -0
- package/dist/esm-node/localisedUrls/routes.mjs +177 -0
- package/dist/esm-node/localisedUrls/types.mjs +1 -0
- package/dist/esm-node/rendererHead.mjs +14 -5
- package/dist/esm-node/request-policy/policyCore.mjs +1 -1
- package/dist/esm-node/request-policy/transport.mjs +2 -2
- package/dist/esm-node/routerState.mjs +81 -73
- package/dist/esm-node/routerStatePlugin.mjs +62 -11
- package/dist/esm-node/server-config/serverSource.mjs +1 -0
- package/dist/types/helmetCompat.d.ts +1 -1
- package/dist/types/localisedUrls/config.d.ts +9 -0
- package/dist/types/localisedUrls/index.d.ts +10 -0
- package/dist/types/localisedUrls/normalise.d.ts +10 -0
- package/dist/types/localisedUrls/pathname.d.ts +4 -0
- package/dist/types/localisedUrls/patterns.d.ts +11 -0
- package/dist/types/localisedUrls/redirect.d.ts +19 -0
- package/dist/types/localisedUrls/resolve.d.ts +7 -0
- package/dist/types/localisedUrls/routeIdentity.d.ts +22 -0
- package/dist/types/localisedUrls/routes.d.ts +3 -0
- package/dist/types/localisedUrls/types.d.ts +14 -0
- package/dist/types/rendererHead.d.ts +5 -3
- package/dist/types/request-policy/browser.d.ts +5 -1
- package/dist/types/request-policy/node.d.ts +5 -1
- package/dist/types/request-policy/types.d.ts +1 -1
- package/dist/types/routeHydration.d.ts +2 -2
- package/dist/types/routerProvider.d.ts +1 -3
- package/dist/types/routerState.d.ts +11 -6
- package/dist/types/routerStatePlugin.d.ts +12 -0
- package/dist/types/routerStateTypes.d.ts +47 -17
- package/dist/types/server-config/index.d.ts +1 -0
- package/dist/types/server-config/serverSource.d.ts +16 -0
- package/package.json +43 -45
- package/src/helmetCompat.ts +1 -1
- package/src/localisedUrls/config.ts +18 -0
- package/src/localisedUrls/index.ts +25 -0
- package/src/localisedUrls/normalise.ts +62 -0
- package/src/localisedUrls/pathname.ts +60 -0
- package/src/localisedUrls/patterns.ts +283 -0
- package/src/localisedUrls/redirect.ts +110 -0
- package/src/localisedUrls/resolve.ts +106 -0
- package/src/localisedUrls/routeIdentity.ts +83 -0
- package/src/localisedUrls/routes.ts +451 -0
- package/src/localisedUrls/types.ts +18 -0
- package/src/rendererHead.ts +25 -8
- package/src/request-policy/policyCore.ts +1 -1
- package/src/request-policy/transport.ts +2 -2
- package/src/request-policy/types.ts +1 -1
- package/src/routeHydration.ts +2 -2
- package/src/routerProvider.ts +1 -1
- package/src/routerState.ts +127 -115
- package/src/routerStatePlugin.ts +106 -10
- package/src/routerStateTypes.ts +47 -17
- package/src/server-config/index.ts +1 -0
- package/src/server-config/serverSource.ts +23 -0
- package/dist/cjs/buildIdentity.js +0 -73
- package/dist/esm/buildIdentity.mjs +0 -35
- package/dist/esm/reactContext.mjs +0 -7
- package/dist/esm-node/buildIdentity.mjs +0 -36
- package/dist/esm-node/reactContext.mjs +0 -8
- package/dist/types/buildIdentity.d.ts +0 -37
- package/dist/types/reactContext.d.ts +0 -6
- package/rslib.config.mts +0 -19
- package/rstest.config.mts +0 -38
- package/src/buildIdentity.ts +0 -61
- package/src/reactContext.ts +0 -16
- package/tests/buildIdentity.test.ts +0 -51
- package/tests/contextExtensions.test.ts +0 -27
- package/tests/headRuntime.test.tsx +0 -229
- package/tests/rendererHead.test.ts +0 -187
- package/tests/request-policy/browser-policy.test.ts +0 -108
- package/tests/request-policy/client-isolation.test.ts +0 -109
- package/tests/request-policy/node-policy.test.ts +0 -447
- package/tests/request-policy/policyCore.test.ts +0 -129
- package/tests/request-policy/requestContext.test.ts +0 -66
- package/tests/request-policy/requestFactory.outbound-contract.test.ts +0 -290
- package/tests/request-policy/traceparent.test.ts +0 -52
- package/tests/request-policy/transport-retry.test.ts +0 -367
- package/tests/routeHydration.test.ts +0 -180
- package/tests/routerPrefetchPolicy.test.ts +0 -102
- package/tests/routerProvider.test.ts +0 -29
- package/tests/routerState.test.ts +0 -78
- package/tests/routerStatePlugin.test.ts +0 -63
- package/tests/runtimeContextProjection.test.ts +0 -83
- package/tests/safeFailure.test.ts +0 -136
- package/tests/shellStream.test.ts +0 -122
- package/tests/workspaceEvents.test.ts +0 -221
- package/tsconfig.json +0 -10
|
@@ -1,447 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CrossOriginEnvelopePolicyError,
|
|
3
|
-
configure,
|
|
4
|
-
createRequest,
|
|
5
|
-
IdentityBindingViolationError,
|
|
6
|
-
OperationContractViolationError,
|
|
7
|
-
ProducerClientNotInitializedError,
|
|
8
|
-
ProducerDomainNotConfiguredError,
|
|
9
|
-
} from '@modern-js/runtime-extensions/request-policy';
|
|
10
|
-
import { storage } from '@modern-js/runtime-utils/node';
|
|
11
|
-
import nock from 'nock';
|
|
12
|
-
|
|
13
|
-
describe('configure', () => {
|
|
14
|
-
const path = '/api';
|
|
15
|
-
const method = 'GET';
|
|
16
|
-
const response = {
|
|
17
|
-
code: 200,
|
|
18
|
-
data: {
|
|
19
|
-
message: 'hello Modernjs',
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const run = (
|
|
24
|
-
headers: Record<string, string>,
|
|
25
|
-
callback: () => Promise<void> | void,
|
|
26
|
-
) =>
|
|
27
|
-
storage.run(
|
|
28
|
-
{
|
|
29
|
-
headers,
|
|
30
|
-
monitors: {} as any,
|
|
31
|
-
},
|
|
32
|
-
callback,
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
// beforeEach(() => {
|
|
36
|
-
// nock.disableNetConnect();
|
|
37
|
-
// });
|
|
38
|
-
|
|
39
|
-
// afterEach(() => {
|
|
40
|
-
// nock.cleanAll();
|
|
41
|
-
// });
|
|
42
|
-
|
|
43
|
-
test('should support inputParams for non-default requestId with configured domain', async () => {
|
|
44
|
-
const producer = 'producer-input-params';
|
|
45
|
-
const producerUrl = 'http://127.0.0.1:9091';
|
|
46
|
-
|
|
47
|
-
await run({}, async () => {
|
|
48
|
-
nock(producerUrl).post(path, 'modernjs').reply(200, response);
|
|
49
|
-
|
|
50
|
-
configure({
|
|
51
|
-
requestId: producer,
|
|
52
|
-
operationContract: {
|
|
53
|
-
enabled: false,
|
|
54
|
-
},
|
|
55
|
-
setDomain: () => producerUrl,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const request = createRequest(
|
|
59
|
-
path,
|
|
60
|
-
'POST',
|
|
61
|
-
8080,
|
|
62
|
-
'inputParams',
|
|
63
|
-
undefined,
|
|
64
|
-
producer,
|
|
65
|
-
);
|
|
66
|
-
const data = await request('modernjs');
|
|
67
|
-
expect(data).toStrictEqual(response);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('should throw for non-default requestId when producer client is not initialized', async () => {
|
|
72
|
-
const request = createRequest({
|
|
73
|
-
path,
|
|
74
|
-
method,
|
|
75
|
-
port: 8080,
|
|
76
|
-
requestId: 'producer-app',
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
expect(() => request()).toThrow(ProducerClientNotInitializedError);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('should require setDomain when configuring non-default requestId', () => {
|
|
83
|
-
expect(() =>
|
|
84
|
-
configure({
|
|
85
|
-
requestId: 'producer-a',
|
|
86
|
-
}),
|
|
87
|
-
).toThrow(ProducerDomainNotConfiguredError);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('should support secure resolveHeaders callback for non-default requestId', async () => {
|
|
91
|
-
const producer = 'producer-with-resolver';
|
|
92
|
-
const authKey = 'token-def';
|
|
93
|
-
const tenant = 'tenant-a';
|
|
94
|
-
const producerUrl = 'http://127.0.0.1:9084';
|
|
95
|
-
|
|
96
|
-
await run(
|
|
97
|
-
{
|
|
98
|
-
authorization: authKey,
|
|
99
|
-
'x-tenant-id': tenant,
|
|
100
|
-
},
|
|
101
|
-
async () => {
|
|
102
|
-
nock(producerUrl, {
|
|
103
|
-
reqheaders: {
|
|
104
|
-
authorization: authKey,
|
|
105
|
-
'x-tenant-id': 'tenant-masked',
|
|
106
|
-
},
|
|
107
|
-
})
|
|
108
|
-
.get(path)
|
|
109
|
-
.reply(200, response);
|
|
110
|
-
|
|
111
|
-
configure({
|
|
112
|
-
requestId: producer,
|
|
113
|
-
allowedHeaders: ['authorization', 'x-tenant-id'],
|
|
114
|
-
operationContract: {
|
|
115
|
-
enabled: false,
|
|
116
|
-
},
|
|
117
|
-
resolveHeaders: ({ incomingHeaders }) => ({
|
|
118
|
-
...incomingHeaders,
|
|
119
|
-
'x-tenant-id': 'tenant-masked',
|
|
120
|
-
'x-injected': 'blocked',
|
|
121
|
-
}),
|
|
122
|
-
setDomain: () => producerUrl,
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const request = createRequest(
|
|
126
|
-
path,
|
|
127
|
-
method,
|
|
128
|
-
8080,
|
|
129
|
-
undefined,
|
|
130
|
-
undefined,
|
|
131
|
-
producer,
|
|
132
|
-
);
|
|
133
|
-
const data = await request();
|
|
134
|
-
|
|
135
|
-
expect(data).toStrictEqual(response);
|
|
136
|
-
},
|
|
137
|
-
);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test('should reject client-supplied tenant headers by default for non-default producer clients', async () => {
|
|
141
|
-
const producer = 'producer-identity-strip';
|
|
142
|
-
const producerUrl = 'http://127.0.0.1:9085';
|
|
143
|
-
|
|
144
|
-
await run({}, async () => {
|
|
145
|
-
nock(producerUrl, {
|
|
146
|
-
badheaders: ['x-tenant-id'],
|
|
147
|
-
})
|
|
148
|
-
.get(path)
|
|
149
|
-
.reply(200, response);
|
|
150
|
-
|
|
151
|
-
configure({
|
|
152
|
-
requestId: producer,
|
|
153
|
-
operationContract: {
|
|
154
|
-
enabled: false,
|
|
155
|
-
},
|
|
156
|
-
setDomain: () => producerUrl,
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
const request = createRequest(
|
|
160
|
-
path,
|
|
161
|
-
method,
|
|
162
|
-
8080,
|
|
163
|
-
undefined,
|
|
164
|
-
undefined,
|
|
165
|
-
producer,
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
expect(() =>
|
|
169
|
-
request({
|
|
170
|
-
headers: {
|
|
171
|
-
'x-tenant-id': 'tenant-client',
|
|
172
|
-
},
|
|
173
|
-
}),
|
|
174
|
-
).toThrow(IdentityBindingViolationError);
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test('should enforce server-derived tenant and subject context over client overrides', async () => {
|
|
179
|
-
const producer = 'producer-identity-derived';
|
|
180
|
-
const producerUrl = 'http://127.0.0.1:9086';
|
|
181
|
-
|
|
182
|
-
await run(
|
|
183
|
-
{
|
|
184
|
-
'x-tenant-id': 'tenant-server',
|
|
185
|
-
'x-subject-id': 'subject-server',
|
|
186
|
-
},
|
|
187
|
-
async () => {
|
|
188
|
-
nock(producerUrl, {
|
|
189
|
-
reqheaders: {
|
|
190
|
-
'x-tenant-id': 'tenant-server',
|
|
191
|
-
'x-subject-id': 'subject-server',
|
|
192
|
-
},
|
|
193
|
-
})
|
|
194
|
-
.get(path)
|
|
195
|
-
.reply(200, response);
|
|
196
|
-
|
|
197
|
-
configure({
|
|
198
|
-
requestId: producer,
|
|
199
|
-
operationContract: {
|
|
200
|
-
enabled: false,
|
|
201
|
-
},
|
|
202
|
-
identityBinding: {
|
|
203
|
-
strict: false,
|
|
204
|
-
},
|
|
205
|
-
setDomain: () => producerUrl,
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
const request = createRequest(
|
|
209
|
-
path,
|
|
210
|
-
method,
|
|
211
|
-
8080,
|
|
212
|
-
undefined,
|
|
213
|
-
undefined,
|
|
214
|
-
producer,
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
const data = await request({
|
|
218
|
-
headers: {
|
|
219
|
-
'x-tenant-id': 'tenant-client',
|
|
220
|
-
'x-subject-id': 'subject-client',
|
|
221
|
-
},
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
expect(data).toStrictEqual(response);
|
|
225
|
-
},
|
|
226
|
-
);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
test('should reject client identity override in strict identity binding mode', () => {
|
|
230
|
-
const producer = 'producer-identity-strict';
|
|
231
|
-
|
|
232
|
-
configure({
|
|
233
|
-
requestId: producer,
|
|
234
|
-
setDomain: () => 'http://127.0.0.1:9087',
|
|
235
|
-
operationContract: {
|
|
236
|
-
enabled: false,
|
|
237
|
-
},
|
|
238
|
-
identityBinding: {
|
|
239
|
-
strict: true,
|
|
240
|
-
},
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
const request = createRequest(
|
|
244
|
-
path,
|
|
245
|
-
method,
|
|
246
|
-
8080,
|
|
247
|
-
undefined,
|
|
248
|
-
undefined,
|
|
249
|
-
producer,
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
expect(() =>
|
|
253
|
-
request({
|
|
254
|
-
headers: {
|
|
255
|
-
'x-tenant-id': 'tenant-client',
|
|
256
|
-
},
|
|
257
|
-
}),
|
|
258
|
-
).toThrow(IdentityBindingViolationError);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test('should require envelope and block cross-origin producer calls in production by default', async () => {
|
|
262
|
-
const producer = 'producer-envelope-default';
|
|
263
|
-
const previousEnv = process.env.NODE_ENV;
|
|
264
|
-
process.env.NODE_ENV = 'production';
|
|
265
|
-
|
|
266
|
-
await run(
|
|
267
|
-
{
|
|
268
|
-
origin: 'https://consumer.internal',
|
|
269
|
-
},
|
|
270
|
-
async () => {
|
|
271
|
-
try {
|
|
272
|
-
configure({
|
|
273
|
-
requestId: producer,
|
|
274
|
-
operationContract: {
|
|
275
|
-
enabled: false,
|
|
276
|
-
},
|
|
277
|
-
setDomain: () => 'https://producer.internal',
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
const request = createRequest(
|
|
281
|
-
path,
|
|
282
|
-
method,
|
|
283
|
-
8080,
|
|
284
|
-
undefined,
|
|
285
|
-
undefined,
|
|
286
|
-
producer,
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
expect(() => request()).toThrow(CrossOriginEnvelopePolicyError);
|
|
290
|
-
} finally {
|
|
291
|
-
process.env.NODE_ENV = previousEnv;
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
|
-
);
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
test('should allow explicit cross-origin envelope policy and attach envelope header', async () => {
|
|
298
|
-
const producer = 'producer-envelope-policy';
|
|
299
|
-
const producerUrl = 'https://producer.internal';
|
|
300
|
-
const previousEnv = process.env.NODE_ENV;
|
|
301
|
-
process.env.NODE_ENV = 'production';
|
|
302
|
-
|
|
303
|
-
await run(
|
|
304
|
-
{
|
|
305
|
-
origin: 'https://consumer.internal',
|
|
306
|
-
},
|
|
307
|
-
async () => {
|
|
308
|
-
try {
|
|
309
|
-
nock(producerUrl).get(path).reply(200, response);
|
|
310
|
-
const customRequest = rs.fn((requestPath: any, init: any) =>
|
|
311
|
-
fetch(requestPath, init),
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
configure({
|
|
315
|
-
request: customRequest as unknown as typeof fetch,
|
|
316
|
-
requestId: producer,
|
|
317
|
-
operationContract: {
|
|
318
|
-
enabled: false,
|
|
319
|
-
},
|
|
320
|
-
setDomain: () => producerUrl,
|
|
321
|
-
allowCrossOriginEnvelope: ({
|
|
322
|
-
requestId,
|
|
323
|
-
sourceOrigin,
|
|
324
|
-
targetOrigin,
|
|
325
|
-
}) =>
|
|
326
|
-
requestId === producer &&
|
|
327
|
-
sourceOrigin === 'https://consumer.internal' &&
|
|
328
|
-
targetOrigin === producerUrl,
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
const request = createRequest(
|
|
332
|
-
path,
|
|
333
|
-
method,
|
|
334
|
-
8080,
|
|
335
|
-
undefined,
|
|
336
|
-
undefined,
|
|
337
|
-
producer,
|
|
338
|
-
);
|
|
339
|
-
const res = await request();
|
|
340
|
-
const data = await res.json();
|
|
341
|
-
|
|
342
|
-
const headers = customRequest.mock.calls[0]?.[1]?.headers;
|
|
343
|
-
const envelope = JSON.parse(headers['x-modernjs-bff-envelope']);
|
|
344
|
-
expect(envelope.requestId).toBe(producer);
|
|
345
|
-
expect(envelope.target).toBe('server');
|
|
346
|
-
expect(data).toStrictEqual(response);
|
|
347
|
-
} finally {
|
|
348
|
-
process.env.NODE_ENV = previousEnv;
|
|
349
|
-
}
|
|
350
|
-
},
|
|
351
|
-
);
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
test('should attach operation context headers for non-default producer client', async () => {
|
|
355
|
-
const producer = 'crm.producer-a';
|
|
356
|
-
const producerUrl = 'http://127.0.0.1:18080';
|
|
357
|
-
const traceparent =
|
|
358
|
-
'00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01';
|
|
359
|
-
|
|
360
|
-
await run({ traceparent }, async () => {
|
|
361
|
-
nock(producerUrl).get(path).reply(200, response);
|
|
362
|
-
const customRequest = rs.fn((requestPath: any, init: any) =>
|
|
363
|
-
fetch(requestPath, init),
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
configure({
|
|
367
|
-
request: customRequest as unknown as typeof fetch,
|
|
368
|
-
requestId: producer,
|
|
369
|
-
setDomain: () => producerUrl,
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
const request = createRequest(
|
|
373
|
-
path,
|
|
374
|
-
method,
|
|
375
|
-
8080,
|
|
376
|
-
undefined,
|
|
377
|
-
undefined,
|
|
378
|
-
producer,
|
|
379
|
-
{
|
|
380
|
-
operationId: `GET:${path}`,
|
|
381
|
-
routePath: path,
|
|
382
|
-
method,
|
|
383
|
-
schemaHash: 'schema-test',
|
|
384
|
-
operationVersion: 1,
|
|
385
|
-
traceparent,
|
|
386
|
-
},
|
|
387
|
-
);
|
|
388
|
-
await request();
|
|
389
|
-
|
|
390
|
-
const headers = customRequest.mock.calls[0]?.[1]?.headers;
|
|
391
|
-
expect(headers['x-operation-id']).toBe(`${producer}:GET:${path}`);
|
|
392
|
-
expect(headers.traceparent).toBe(traceparent);
|
|
393
|
-
const operationContext = JSON.parse(
|
|
394
|
-
headers['x-modernjs-bff-operation-context'],
|
|
395
|
-
);
|
|
396
|
-
expect(operationContext.requestId).toBe(producer);
|
|
397
|
-
expect(operationContext.operationId).toBe(`${producer}:GET:${path}`);
|
|
398
|
-
expect(operationContext.traceparent).toBe(traceparent);
|
|
399
|
-
expect(operationContext.traceId).toBe('4bf92f3577b34da6a3ce929d0e0e4736');
|
|
400
|
-
expect(operationContext.spanId).toBe('00f067aa0ba902b7');
|
|
401
|
-
});
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
test('should reject requests missing schema/version operation contract metadata by default', () => {
|
|
405
|
-
const producer = 'producer-operation-contract-default';
|
|
406
|
-
|
|
407
|
-
configure({
|
|
408
|
-
requestId: producer,
|
|
409
|
-
setDomain: () => 'http://127.0.0.1:19080',
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
const request = createRequest(
|
|
413
|
-
path,
|
|
414
|
-
method,
|
|
415
|
-
8080,
|
|
416
|
-
undefined,
|
|
417
|
-
undefined,
|
|
418
|
-
producer,
|
|
419
|
-
);
|
|
420
|
-
|
|
421
|
-
expect(() => request()).toThrow(OperationContractViolationError);
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
test('should enforce operation contract metadata for default requestId when strict-default mode is enabled', () => {
|
|
425
|
-
process.env.MODERN_BFF_STRICT_DEFAULT_REQUEST_ID = 'true';
|
|
426
|
-
try {
|
|
427
|
-
configure({
|
|
428
|
-
operationContract: {
|
|
429
|
-
enabled: true,
|
|
430
|
-
strict: true,
|
|
431
|
-
requireSchemaHash: true,
|
|
432
|
-
requireOperationVersion: true,
|
|
433
|
-
},
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
const request = createRequest(path, method, 8080);
|
|
437
|
-
expect(() => request()).toThrow(OperationContractViolationError);
|
|
438
|
-
} finally {
|
|
439
|
-
configure({
|
|
440
|
-
operationContract: {
|
|
441
|
-
enabled: false,
|
|
442
|
-
},
|
|
443
|
-
});
|
|
444
|
-
delete process.env.MODERN_BFF_STRICT_DEFAULT_REQUEST_ID;
|
|
445
|
-
}
|
|
446
|
-
});
|
|
447
|
-
});
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
attachOperationContextHeaders,
|
|
3
|
-
buildEnvelopeHeaderValue,
|
|
4
|
-
buildOperationContext,
|
|
5
|
-
CrossOriginEnvelopePolicyError,
|
|
6
|
-
deleteHeader,
|
|
7
|
-
findHeaderKey,
|
|
8
|
-
isSecuredRequestId,
|
|
9
|
-
OperationContractViolationError,
|
|
10
|
-
readHeader,
|
|
11
|
-
validateOperationContract,
|
|
12
|
-
writeHeader,
|
|
13
|
-
} from '../../src/request-policy/policyCore';
|
|
14
|
-
|
|
15
|
-
describe('policyCore (shared browser/node policy module)', () => {
|
|
16
|
-
test('header helpers are case-insensitive and normalize duplicate keys', () => {
|
|
17
|
-
const headers: Record<string, any> = { 'X-Foo': 'a' };
|
|
18
|
-
|
|
19
|
-
expect(findHeaderKey(headers, 'x-foo')).toBe('X-Foo');
|
|
20
|
-
expect(readHeader(headers, 'x-FOO')).toBe('a');
|
|
21
|
-
|
|
22
|
-
writeHeader(headers, 'x-foo', 'b');
|
|
23
|
-
expect(headers).toEqual({ 'x-foo': 'b' });
|
|
24
|
-
|
|
25
|
-
deleteHeader(headers, 'X-FOO');
|
|
26
|
-
expect(headers).toEqual({});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('isSecuredRequestId treats only "default" as unsecured by default', () => {
|
|
30
|
-
expect(isSecuredRequestId('default')).toBe(false);
|
|
31
|
-
expect(isSecuredRequestId('crm-producer')).toBe(true);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('validateOperationContract throws on missing schema hash for secured ids', () => {
|
|
35
|
-
const contextPayload = buildOperationContext({
|
|
36
|
-
requestId: 'crm',
|
|
37
|
-
method: 'GET',
|
|
38
|
-
path: '/hello',
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
expect(() =>
|
|
42
|
-
validateOperationContract({
|
|
43
|
-
requestId: 'crm',
|
|
44
|
-
target: 'browser',
|
|
45
|
-
contextPayload,
|
|
46
|
-
operationContract: undefined,
|
|
47
|
-
}),
|
|
48
|
-
).toThrow(OperationContractViolationError);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('buildEnvelopeHeaderValue denies cross-origin flows unless allowed', () => {
|
|
52
|
-
expect(() =>
|
|
53
|
-
buildEnvelopeHeaderValue({
|
|
54
|
-
requestId: 'crm',
|
|
55
|
-
target: 'server',
|
|
56
|
-
sourceOrigin: 'https://a.example',
|
|
57
|
-
targetOrigin: 'https://b.example',
|
|
58
|
-
traceContext: undefined,
|
|
59
|
-
allowCrossOriginEnvelope: undefined,
|
|
60
|
-
}),
|
|
61
|
-
).toThrow(CrossOriginEnvelopePolicyError);
|
|
62
|
-
|
|
63
|
-
const envelope = JSON.parse(
|
|
64
|
-
buildEnvelopeHeaderValue({
|
|
65
|
-
requestId: 'crm',
|
|
66
|
-
target: 'server',
|
|
67
|
-
sourceOrigin: 'https://a.example',
|
|
68
|
-
targetOrigin: 'https://b.example',
|
|
69
|
-
traceContext: { traceId: 't', spanId: 's' },
|
|
70
|
-
allowCrossOriginEnvelope: true,
|
|
71
|
-
}),
|
|
72
|
-
);
|
|
73
|
-
expect(envelope).toMatchObject({
|
|
74
|
-
requestId: 'crm',
|
|
75
|
-
target: 'server',
|
|
76
|
-
sourceOrigin: 'https://a.example',
|
|
77
|
-
targetOrigin: 'https://b.example',
|
|
78
|
-
traceId: 't',
|
|
79
|
-
spanId: 's',
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('buildEnvelopeHeaderValue requires cross-origin predicate to return true', () => {
|
|
84
|
-
const allowCrossOriginEnvelope = () => 'yes';
|
|
85
|
-
|
|
86
|
-
expect(() =>
|
|
87
|
-
buildEnvelopeHeaderValue({
|
|
88
|
-
requestId: 'crm',
|
|
89
|
-
target: 'server',
|
|
90
|
-
sourceOrigin: 'https://a.example',
|
|
91
|
-
targetOrigin: 'https://b.example',
|
|
92
|
-
traceContext: undefined,
|
|
93
|
-
allowCrossOriginEnvelope:
|
|
94
|
-
allowCrossOriginEnvelope as unknown as () => boolean,
|
|
95
|
-
}),
|
|
96
|
-
).toThrow(CrossOriginEnvelopePolicyError);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('attachOperationContextHeaders writes id and detail headers without clobbering caller id', () => {
|
|
100
|
-
const headers: Record<string, any> = {
|
|
101
|
-
'x-operation-id': 'crm:custom-op',
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
attachOperationContextHeaders({
|
|
105
|
-
headers,
|
|
106
|
-
requestId: 'crm',
|
|
107
|
-
target: 'server',
|
|
108
|
-
method: 'GET',
|
|
109
|
-
path: '/hello',
|
|
110
|
-
operationContext: {
|
|
111
|
-
operationId: 'getHello',
|
|
112
|
-
schemaHash: 'abc',
|
|
113
|
-
operationVersion: 1,
|
|
114
|
-
},
|
|
115
|
-
operationContract: undefined,
|
|
116
|
-
operationContextHeader: 'x-operation-id',
|
|
117
|
-
operationContextDetailHeader: 'x-modernjs-bff-operation-context',
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
expect(headers['x-operation-id']).toBe('crm:custom-op');
|
|
121
|
-
const details = JSON.parse(headers['x-modernjs-bff-operation-context']);
|
|
122
|
-
expect(details).toMatchObject({
|
|
123
|
-
requestId: 'crm',
|
|
124
|
-
operationId: 'crm:getHello',
|
|
125
|
-
schemaHash: 'abc',
|
|
126
|
-
operationVersion: 1,
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BFF_LOCALE_HEADER,
|
|
3
|
-
BFF_TRACEPARENT_HEADER,
|
|
4
|
-
createRequestContextHeaders,
|
|
5
|
-
createRequestContextSnapshot,
|
|
6
|
-
} from '@modern-js/runtime-extensions/request-policy/client';
|
|
7
|
-
|
|
8
|
-
describe('request context helpers', () => {
|
|
9
|
-
test('should create propagation headers from explicit locale and traceparent', () => {
|
|
10
|
-
expect(
|
|
11
|
-
createRequestContextHeaders({
|
|
12
|
-
locale: 'cs-CZ',
|
|
13
|
-
traceparent: '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01',
|
|
14
|
-
}),
|
|
15
|
-
).toEqual({
|
|
16
|
-
[BFF_LOCALE_HEADER]: 'cs-CZ',
|
|
17
|
-
[BFF_TRACEPARENT_HEADER]:
|
|
18
|
-
'00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01',
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('should derive locale and trace metadata from incoming headers', () => {
|
|
23
|
-
expect(
|
|
24
|
-
createRequestContextSnapshot({
|
|
25
|
-
headers: {
|
|
26
|
-
'accept-language': 'en-US,en;q=0.8',
|
|
27
|
-
traceparent:
|
|
28
|
-
'00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01',
|
|
29
|
-
},
|
|
30
|
-
}),
|
|
31
|
-
).toEqual({
|
|
32
|
-
headers: {
|
|
33
|
-
'accept-language': 'en-US,en;q=0.8',
|
|
34
|
-
traceparent: '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01',
|
|
35
|
-
},
|
|
36
|
-
locale: 'en-US,en;q=0.8',
|
|
37
|
-
traceparent: '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01',
|
|
38
|
-
traceId: '4bf92f3577b34da6a3ce929d0e0e4736',
|
|
39
|
-
spanId: '00f067aa0ba902b7',
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('should keep operation context in snapshots without widening propagation headers', () => {
|
|
44
|
-
const snapshot = createRequestContextSnapshot({
|
|
45
|
-
locale: 'cs-CZ',
|
|
46
|
-
operationContext: {
|
|
47
|
-
operationId: 'shell:list',
|
|
48
|
-
routePath: '/effect/recommendations',
|
|
49
|
-
method: 'GET',
|
|
50
|
-
source: 'generated-client',
|
|
51
|
-
scope: { workspace: 'demo' },
|
|
52
|
-
sessionClaims: { role: 'viewer' },
|
|
53
|
-
traceparent: '00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-01',
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
expect(Object.keys(snapshot.headers)).toEqual([
|
|
58
|
-
BFF_LOCALE_HEADER,
|
|
59
|
-
BFF_TRACEPARENT_HEADER,
|
|
60
|
-
]);
|
|
61
|
-
expect(snapshot.operationContext?.traceId).toBe(
|
|
62
|
-
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
63
|
-
);
|
|
64
|
-
expect(snapshot.operationContext?.spanId).toBe('bbbbbbbbbbbbbbbb');
|
|
65
|
-
});
|
|
66
|
-
});
|