@jskit-ai/kernel 0.1.159 → 0.1.161
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/_testable/index.js +1 -3
- package/client/componentRegistry.js +44 -0
- package/client/componentRegistry.test.js +16 -0
- package/client/moduleBootstrap.js +54 -97
- package/client/moduleBootstrap.test.js +47 -32
- package/client/vite/clientBootstrapPlugin.test.js +2 -2
- package/internal/node/installedPackages.js +2 -7
- package/internal/node/installedPackages.test.js +26 -5
- package/package.json +3 -5
- package/server/actions/actionCatalogue.js +162 -0
- package/server/actions/actionEvents.js +66 -0
- package/server/actions/actionProvider.js +23 -0
- package/server/actions/index.js +3 -6
- package/server/features/defineFeature.js +131 -0
- package/server/features/defineFeature.test.js +238 -0
- package/server/features/index.js +1 -0
- package/server/http/HttpProvider.js +23 -0
- package/server/http/_testable/index.js +0 -1
- package/server/http/capabilityHttpRuntime.js +123 -0
- package/server/http/capabilityHttpRuntime.test.js +161 -0
- package/server/http/directRequestActionExecutor.js +78 -0
- package/server/http/index.js +2 -1
- package/server/http/lib/index.js +1 -13
- package/server/http/lib/routeRegistration.js +9 -25
- package/server/platform/index.js +2 -2
- package/server/platform/installedRuntime.js +122 -0
- package/server/platform/installedRuntime.test.js +103 -0
- package/server/platform/providerRuntime/capabilityProviderLoader.js +100 -0
- package/server/platform/providerRuntime/capabilityProviderLoader.test.js +80 -0
- package/server/platform/providerRuntime/packageCatalog.js +4 -1
- package/server/platform/surfaceRuntime.js +1 -15
- package/server/runtime/BootstrapProvider.js +69 -0
- package/server/runtime/EventProvider.js +74 -0
- package/server/runtime/EventProvider.test.js +32 -0
- package/server/runtime/entityChangeEvents.js +7 -7
- package/server/runtime/entityChangeEvents.test.js +2 -2
- package/server/runtime/fastifyBootstrap.js +0 -40
- package/server/runtime/index.js +2 -3
- package/server/surface/index.js +0 -1
- package/shared/actions/actionDefinitions.js +20 -0
- package/shared/actions/pipeline.js +22 -0
- package/shared/actions/policies.js +1 -1
- package/shared/actions/registry.js +3 -0
- package/shared/capabilities/defineProvider.js +99 -0
- package/shared/capabilities/index.js +2 -0
- package/shared/capabilities/runtime.js +243 -0
- package/shared/capabilities/runtime.test.js +170 -0
- package/test/barrelExposure.test.js +36 -13
- package/test/exportsContract.test.js +8 -3
- package/server/actions/ActionRuntimeServiceProvider.js +0 -304
- package/server/actions/ActionRuntimeServiceProvider.test.js +0 -558
- package/server/container/ContainerCoreServiceProvider.js +0 -27
- package/server/container/index.js +0 -10
- package/server/http/HttpFastifyServiceProvider.js +0 -25
- package/server/http/lib/httpRuntime.js +0 -83
- package/server/http/lib/kernel.js +0 -15
- package/server/http/lib/kernel.test.js +0 -1565
- package/server/http/lib/requestActionExecutor.js +0 -265
- package/server/http/lib/requestScope.js +0 -57
- package/server/kernel/KernelCoreServiceProvider.js +0 -27
- package/server/kernel/index.js +0 -10
- package/server/platform/PlatformServerRuntimeServiceProvider.js +0 -30
- package/server/platform/providerRuntime/providerLoader.js +0 -283
- package/server/platform/providerRuntime.js +0 -146
- package/server/platform/providerRuntime.test.js +0 -214
- package/server/platform/runtime.js +0 -40
- package/server/registries/actionSurfaceSourceRegistry.js +0 -148
- package/server/registries/bootstrapPayloadContributorRegistry.js +0 -51
- package/server/registries/domainEventListenerRegistry.js +0 -58
- package/server/registries/index.js +0 -32
- package/server/registries/primitives.js +0 -63
- package/server/registries/routeVisibilityResolverRegistry.js +0 -84
- package/server/registries/serviceRegistrationRegistry.js +0 -488
- package/server/runtime/ServerRuntimeCoreServiceProvider.js +0 -64
- package/server/runtime/ServerRuntimeCoreServiceProvider.test.js +0 -53
- package/server/runtime/apiRoutePolicyParity.test.js +0 -109
- package/server/runtime/apiRouteRegistration.js +0 -65
- package/server/runtime/bootBootstrapRoutes.js +0 -29
- package/server/runtime/bootBootstrapRoutes.test.js +0 -69
- package/server/runtime/bootstrapContributors.test.js +0 -166
- package/server/runtime/composition.js +0 -142
- package/server/runtime/domainEvents.test.js +0 -114
- package/server/runtime/domainRules.js +0 -50
- package/server/runtime/domainRules.test.js +0 -87
- package/server/runtime/runtimeAssembly.js +0 -113
- package/server/runtime/runtimeKernel.js +0 -55
- package/server/runtime/serviceRegistration.test.js +0 -251
- package/server/support/SupportCoreServiceProvider.js +0 -27
- package/server/surface/SurfaceRoutingServiceProvider.js +0 -27
- package/shared/actions/index.js +0 -2
- package/shared/runtime/application.js +0 -324
- package/shared/runtime/container.js +0 -261
- package/shared/runtime/containerErrors.js +0 -22
- package/shared/runtime/index.js +0 -18
- package/shared/runtime/kernelErrors.js +0 -25
- package/shared/runtime/kernelErrors.test.js +0 -120
- package/shared/runtime/serviceProvider.js +0 -13
- package/shared/support/containerToken.js +0 -8
- package/shared/support/generatedUiContract.js +0 -542
- package/shared/support/generatedUiContract.test.js +0 -218
- package/shared/support/tokens.js +0 -1
- package/shared/support/tokens.test.js +0 -17
|
@@ -1,1565 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { createSchema } from "json-rest-schema";
|
|
4
|
-
import { registerActionContextContributor } from "../../actions/ActionRuntimeServiceProvider.js";
|
|
5
|
-
import { createApplication } from "../../kernel/index.js";
|
|
6
|
-
import { createRouter } from "./router.js";
|
|
7
|
-
import { createHttpRuntime, registerHttpRuntime, registerRoutes } from "./kernel.js";
|
|
8
|
-
import { registerRouteVisibilityResolver } from "../../registries/routeVisibilityResolverRegistry.js";
|
|
9
|
-
|
|
10
|
-
function createFastifyStub() {
|
|
11
|
-
const routes = [];
|
|
12
|
-
return {
|
|
13
|
-
routes,
|
|
14
|
-
setErrorHandlerCalls: 0,
|
|
15
|
-
errorHandler: null,
|
|
16
|
-
contentTypeParsers: new Map(),
|
|
17
|
-
hooks: {},
|
|
18
|
-
route(definition) {
|
|
19
|
-
routes.push(definition);
|
|
20
|
-
},
|
|
21
|
-
addHook(name, handler) {
|
|
22
|
-
this.hooks[name] = handler;
|
|
23
|
-
},
|
|
24
|
-
setErrorHandler(handler) {
|
|
25
|
-
this.errorHandler = handler;
|
|
26
|
-
this.setErrorHandlerCalls += 1;
|
|
27
|
-
},
|
|
28
|
-
hasContentTypeParser(contentType) {
|
|
29
|
-
return this.contentTypeParsers.has(String(contentType || "").trim().toLowerCase());
|
|
30
|
-
},
|
|
31
|
-
addContentTypeParser(contentType, options, parser) {
|
|
32
|
-
this.contentTypeParsers.set(String(contentType || "").trim().toLowerCase(), {
|
|
33
|
-
options,
|
|
34
|
-
parser
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function createReplyStub() {
|
|
41
|
-
return {
|
|
42
|
-
sent: false,
|
|
43
|
-
statusCode: 200,
|
|
44
|
-
payload: undefined,
|
|
45
|
-
headers: {},
|
|
46
|
-
code(value) {
|
|
47
|
-
this.statusCode = Number(value);
|
|
48
|
-
return this;
|
|
49
|
-
},
|
|
50
|
-
header(name, value) {
|
|
51
|
-
this.headers[name] = value;
|
|
52
|
-
return this;
|
|
53
|
-
},
|
|
54
|
-
send(payload) {
|
|
55
|
-
this.payload = payload;
|
|
56
|
-
this.sent = true;
|
|
57
|
-
return this;
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
test("registerRoutes attaches request scope and request context tokens", async () => {
|
|
63
|
-
const fastify = createFastifyStub();
|
|
64
|
-
const app = createApplication();
|
|
65
|
-
const observed = {};
|
|
66
|
-
|
|
67
|
-
registerRoutes(fastify, {
|
|
68
|
-
app,
|
|
69
|
-
routes: [
|
|
70
|
-
{
|
|
71
|
-
method: "GET",
|
|
72
|
-
path: "/scope-check",
|
|
73
|
-
middleware: [
|
|
74
|
-
(request) => {
|
|
75
|
-
observed.middlewareRequest = request.scope.make("jskit.http.request");
|
|
76
|
-
observed.middlewareReply = request.scope.make("jskit.http.reply");
|
|
77
|
-
observed.middlewareRequestId = request.scope.make("jskit.http.requestId");
|
|
78
|
-
observed.middlewareScope = request.scope.make("jskit.http.requestScope");
|
|
79
|
-
}
|
|
80
|
-
],
|
|
81
|
-
handler: async (request, reply) => {
|
|
82
|
-
observed.handlerScope = request.scope;
|
|
83
|
-
observed.handlerRequest = request.scope.make("jskit.http.request");
|
|
84
|
-
observed.handlerRequestId = request.scope.make("jskit.http.requestId");
|
|
85
|
-
reply.code(200).send({ ok: true });
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
]
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const request = { id: "req-123" };
|
|
92
|
-
const reply = createReplyStub();
|
|
93
|
-
|
|
94
|
-
await fastify.routes[0].handler(request, reply);
|
|
95
|
-
|
|
96
|
-
assert.equal(reply.statusCode, 200);
|
|
97
|
-
assert.equal(request.scope.scopeId, "http:req-123");
|
|
98
|
-
assert.equal(observed.middlewareRequest, request);
|
|
99
|
-
assert.equal(observed.middlewareReply, reply);
|
|
100
|
-
assert.equal(observed.middlewareRequestId, "req-123");
|
|
101
|
-
assert.equal(observed.middlewareScope, request.scope);
|
|
102
|
-
assert.equal(observed.handlerScope, request.scope);
|
|
103
|
-
assert.equal(observed.handlerRequest, request);
|
|
104
|
-
assert.equal(observed.handlerRequestId, "req-123");
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("registerRoutes attaches request.executeAction and applies action context contributors", async () => {
|
|
108
|
-
const fastify = createFastifyStub();
|
|
109
|
-
const app = createApplication();
|
|
110
|
-
const observed = [];
|
|
111
|
-
|
|
112
|
-
registerActionContextContributor(app, "test.auth.actionContextContributor", () => ({
|
|
113
|
-
contributorId: "test.auth",
|
|
114
|
-
contribute({ request, definition }) {
|
|
115
|
-
return {
|
|
116
|
-
actor: request?.user || null,
|
|
117
|
-
permissions: Array.isArray(request?.permissions) ? request.permissions.slice() : [],
|
|
118
|
-
workspace: request?.workspace || null,
|
|
119
|
-
membership: request?.membership || null,
|
|
120
|
-
requestMeta: {
|
|
121
|
-
definitionId: definition?.id || ""
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
}));
|
|
126
|
-
|
|
127
|
-
app.instance("actionExecutor", {
|
|
128
|
-
getDefinition(actionId) {
|
|
129
|
-
return {
|
|
130
|
-
id: actionId,
|
|
131
|
-
surfaces: ["coffie"]
|
|
132
|
-
};
|
|
133
|
-
},
|
|
134
|
-
async execute(payload) {
|
|
135
|
-
observed.push(payload);
|
|
136
|
-
return {
|
|
137
|
-
ok: true
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
registerRoutes(fastify, {
|
|
143
|
-
app,
|
|
144
|
-
routes: [
|
|
145
|
-
{
|
|
146
|
-
method: "GET",
|
|
147
|
-
path: "/action-helper",
|
|
148
|
-
surface: "coffie",
|
|
149
|
-
middleware: [
|
|
150
|
-
(request) => {
|
|
151
|
-
request.user = {
|
|
152
|
-
id: 7,
|
|
153
|
-
email: "user@example.com"
|
|
154
|
-
};
|
|
155
|
-
request.permissions = ["settings.read"];
|
|
156
|
-
request.workspace = {
|
|
157
|
-
id: 10,
|
|
158
|
-
slug: "main"
|
|
159
|
-
};
|
|
160
|
-
request.membership = {
|
|
161
|
-
roleSid: "owner"
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
],
|
|
165
|
-
handler: async (request, reply) => {
|
|
166
|
-
await request.executeAction({
|
|
167
|
-
actionId: "settings.read",
|
|
168
|
-
input: {
|
|
169
|
-
locale: "en-US"
|
|
170
|
-
},
|
|
171
|
-
context: {
|
|
172
|
-
requestMeta: {
|
|
173
|
-
commandId: "cmd-1"
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
await request.executeAction({
|
|
179
|
-
actionId: "settings.override",
|
|
180
|
-
context: {
|
|
181
|
-
actor: {
|
|
182
|
-
id: 99
|
|
183
|
-
},
|
|
184
|
-
permissions: ["*"],
|
|
185
|
-
surface: "console",
|
|
186
|
-
channel: "internal"
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
reply.code(200).send({
|
|
191
|
-
ok: true
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
]
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const request = {
|
|
199
|
-
id: "req-55",
|
|
200
|
-
raw: {
|
|
201
|
-
url: "/api/admin/workspace/settings?mode=full"
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
const reply = createReplyStub();
|
|
205
|
-
|
|
206
|
-
await fastify.routes[0].handler(request, reply);
|
|
207
|
-
|
|
208
|
-
assert.equal(reply.statusCode, 200);
|
|
209
|
-
assert.equal(typeof request.executeAction, "function");
|
|
210
|
-
assert.equal(Object.prototype.propertyIsEnumerable.call(request, "executeAction"), false);
|
|
211
|
-
assert.equal(observed.length, 2);
|
|
212
|
-
|
|
213
|
-
assert.equal(observed[0].actionId, "settings.read");
|
|
214
|
-
assert.deepEqual(observed[0].input, { locale: "en-US" });
|
|
215
|
-
assert.equal(observed[0].context.actor?.id, 7);
|
|
216
|
-
assert.deepEqual(observed[0].context.permissions, ["settings.read"]);
|
|
217
|
-
assert.equal(observed[0].context.workspace?.id, 10);
|
|
218
|
-
assert.equal(observed[0].context.membership?.roleSid, "owner");
|
|
219
|
-
assert.equal(observed[0].context.surface, "coffie");
|
|
220
|
-
assert.equal(observed[0].context.channel, "api");
|
|
221
|
-
assert.equal(observed[0].context.requestMeta.commandId, "cmd-1");
|
|
222
|
-
assert.equal(observed[0].context.requestMeta.definitionId, "settings.read");
|
|
223
|
-
assert.equal(observed[0].context.requestMeta.request, request);
|
|
224
|
-
|
|
225
|
-
assert.equal(observed[1].actionId, "settings.override");
|
|
226
|
-
assert.equal(observed[1].context.actor?.id, 99);
|
|
227
|
-
assert.deepEqual(observed[1].context.permissions, ["*"]);
|
|
228
|
-
assert.equal(observed[1].context.surface, "console");
|
|
229
|
-
assert.equal(observed[1].context.channel, "internal");
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test("registerRoutes attaches visibilityContext from route visibility resolvers", async () => {
|
|
233
|
-
const fastify = createFastifyStub();
|
|
234
|
-
const app = createApplication();
|
|
235
|
-
const observed = [];
|
|
236
|
-
|
|
237
|
-
registerActionContextContributor(app, "test.auth.actionContextContributor", () => ({
|
|
238
|
-
contributorId: "test.auth",
|
|
239
|
-
contribute({ request }) {
|
|
240
|
-
return {
|
|
241
|
-
actor: request?.user || null
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
}));
|
|
245
|
-
|
|
246
|
-
registerRouteVisibilityResolver(app, "test.http.visibilityResolver", () => ({
|
|
247
|
-
resolverId: "test.visibility",
|
|
248
|
-
resolve({ visibility, context }) {
|
|
249
|
-
if (visibility !== "user") {
|
|
250
|
-
return {};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return {
|
|
254
|
-
userId: context?.actor?.id,
|
|
255
|
-
requiresActorScope: true
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
}));
|
|
259
|
-
|
|
260
|
-
app.instance("actionExecutor", {
|
|
261
|
-
async execute(payload) {
|
|
262
|
-
observed.push(payload);
|
|
263
|
-
return { ok: true };
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
registerRoutes(fastify, {
|
|
268
|
-
app,
|
|
269
|
-
routes: [
|
|
270
|
-
{
|
|
271
|
-
method: "GET",
|
|
272
|
-
path: "/visible",
|
|
273
|
-
visibility: "user",
|
|
274
|
-
handler: async (request, reply) => {
|
|
275
|
-
request.user = {
|
|
276
|
-
id: 23
|
|
277
|
-
};
|
|
278
|
-
await request.executeAction({
|
|
279
|
-
actionId: "contacts.list"
|
|
280
|
-
});
|
|
281
|
-
reply.code(200).send({ ok: true });
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
]
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
const request = { id: "req-visible" };
|
|
288
|
-
const reply = createReplyStub();
|
|
289
|
-
|
|
290
|
-
await fastify.routes[0].handler(request, reply);
|
|
291
|
-
|
|
292
|
-
assert.equal(reply.statusCode, 200);
|
|
293
|
-
assert.equal(observed.length, 1);
|
|
294
|
-
assert.deepEqual(observed[0].context.visibilityContext, {
|
|
295
|
-
visibility: "user",
|
|
296
|
-
scopeKind: null,
|
|
297
|
-
requiresActorScope: true,
|
|
298
|
-
scopeOwnerId: null,
|
|
299
|
-
userId: "23"
|
|
300
|
-
});
|
|
301
|
-
assert.deepEqual(observed[0].context.requestMeta.visibilityContext, observed[0].context.visibilityContext);
|
|
302
|
-
assert.equal(observed[0].context.requestMeta.routeVisibility, "user");
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
test("registerRoutes keeps actor scope requirement for core user visibility without resolver opt-in", async () => {
|
|
306
|
-
const fastify = createFastifyStub();
|
|
307
|
-
const app = createApplication();
|
|
308
|
-
const observed = [];
|
|
309
|
-
|
|
310
|
-
registerActionContextContributor(app, "test.auth.actionContextContributor", () => ({
|
|
311
|
-
contributorId: "test.auth",
|
|
312
|
-
contribute({ request }) {
|
|
313
|
-
return {
|
|
314
|
-
actor: request?.user || null
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
}));
|
|
318
|
-
|
|
319
|
-
registerRouteVisibilityResolver(app, "test.http.visibilityResolver", () => ({
|
|
320
|
-
resolverId: "test.visibility",
|
|
321
|
-
resolve({ visibility, context }) {
|
|
322
|
-
if (visibility !== "user") {
|
|
323
|
-
return {};
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return {
|
|
327
|
-
userId: context?.actor?.id
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
}));
|
|
331
|
-
|
|
332
|
-
app.instance("actionExecutor", {
|
|
333
|
-
async execute(payload) {
|
|
334
|
-
observed.push(payload);
|
|
335
|
-
return { ok: true };
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
registerRoutes(fastify, {
|
|
340
|
-
app,
|
|
341
|
-
routes: [
|
|
342
|
-
{
|
|
343
|
-
method: "GET",
|
|
344
|
-
path: "/visible-no-actor-scope",
|
|
345
|
-
visibility: "user",
|
|
346
|
-
handler: async (request, reply) => {
|
|
347
|
-
request.user = {
|
|
348
|
-
id: 23
|
|
349
|
-
};
|
|
350
|
-
await request.executeAction({
|
|
351
|
-
actionId: "contacts.list"
|
|
352
|
-
});
|
|
353
|
-
reply.code(200).send({ ok: true });
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
]
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
const request = { id: "req-visible-no-actor-scope" };
|
|
360
|
-
const reply = createReplyStub();
|
|
361
|
-
|
|
362
|
-
await fastify.routes[0].handler(request, reply);
|
|
363
|
-
|
|
364
|
-
assert.equal(reply.statusCode, 200);
|
|
365
|
-
assert.equal(observed.length, 1);
|
|
366
|
-
assert.deepEqual(observed[0].context.visibilityContext, {
|
|
367
|
-
visibility: "user",
|
|
368
|
-
scopeKind: null,
|
|
369
|
-
requiresActorScope: true,
|
|
370
|
-
scopeOwnerId: null,
|
|
371
|
-
userId: "23"
|
|
372
|
-
});
|
|
373
|
-
assert.equal(observed[0].context.requestMeta.routeVisibility, "user");
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
test("registerRoutes does not infer actor scope from non-core route visibility tokens", async () => {
|
|
377
|
-
const fastify = createFastifyStub();
|
|
378
|
-
const app = createApplication();
|
|
379
|
-
const observed = [];
|
|
380
|
-
|
|
381
|
-
app.instance("actionExecutor", {
|
|
382
|
-
async execute(payload) {
|
|
383
|
-
observed.push(payload);
|
|
384
|
-
return { ok: true };
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
registerRoutes(fastify, {
|
|
389
|
-
app,
|
|
390
|
-
routes: [
|
|
391
|
-
{
|
|
392
|
-
method: "GET",
|
|
393
|
-
path: "/scoped-visible",
|
|
394
|
-
visibility: "workspace_user",
|
|
395
|
-
handler: async (request, reply) => {
|
|
396
|
-
await request.executeAction({
|
|
397
|
-
actionId: "contacts.list"
|
|
398
|
-
});
|
|
399
|
-
reply.code(200).send({ ok: true });
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
]
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
const request = { id: "req-scoped-visible" };
|
|
406
|
-
const reply = createReplyStub();
|
|
407
|
-
|
|
408
|
-
await fastify.routes[0].handler(request, reply);
|
|
409
|
-
|
|
410
|
-
assert.equal(reply.statusCode, 200);
|
|
411
|
-
assert.equal(observed.length, 1);
|
|
412
|
-
assert.deepEqual(observed[0].context.visibilityContext, {
|
|
413
|
-
visibility: "workspace_user",
|
|
414
|
-
scopeKind: null,
|
|
415
|
-
requiresActorScope: false,
|
|
416
|
-
scopeOwnerId: null,
|
|
417
|
-
userId: null
|
|
418
|
-
});
|
|
419
|
-
assert.equal(observed[0].context.requestMeta.routeVisibility, "workspace_user");
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
test("registerRoutes can disable request scope attachment", async () => {
|
|
423
|
-
const fastify = createFastifyStub();
|
|
424
|
-
const app = createApplication();
|
|
425
|
-
|
|
426
|
-
registerRoutes(fastify, {
|
|
427
|
-
app,
|
|
428
|
-
enableRequestScope: false,
|
|
429
|
-
routes: [
|
|
430
|
-
{
|
|
431
|
-
method: "GET",
|
|
432
|
-
path: "/scope-disabled",
|
|
433
|
-
handler: async (request, reply) => {
|
|
434
|
-
assert.equal(request.scope, undefined);
|
|
435
|
-
reply.code(200).send({ ok: true });
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
]
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
const request = { id: "req-999" };
|
|
442
|
-
const reply = createReplyStub();
|
|
443
|
-
|
|
444
|
-
await fastify.routes[0].handler(request, reply);
|
|
445
|
-
|
|
446
|
-
assert.equal(reply.statusCode, 200);
|
|
447
|
-
assert.equal(request.scope, undefined);
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
test("registerRoutes supports custom request scope property and requestId resolver", async () => {
|
|
451
|
-
const fastify = createFastifyStub();
|
|
452
|
-
const app = createApplication();
|
|
453
|
-
|
|
454
|
-
registerRoutes(fastify, {
|
|
455
|
-
app,
|
|
456
|
-
requestScopeProperty: "requestScope",
|
|
457
|
-
requestScopeIdPrefix: "request",
|
|
458
|
-
requestIdResolver: (request) => request.meta?.requestKey,
|
|
459
|
-
routes: [
|
|
460
|
-
{
|
|
461
|
-
method: "GET",
|
|
462
|
-
path: "/scope-custom",
|
|
463
|
-
handler: async (request, reply) => {
|
|
464
|
-
assert.equal(Boolean(request.scope), false);
|
|
465
|
-
assert.equal(Boolean(request.requestScope), true);
|
|
466
|
-
assert.equal(request.requestScope.scopeId, "request:r-42");
|
|
467
|
-
assert.equal(request.requestScope.make("jskit.http.requestId"), "r-42");
|
|
468
|
-
reply.code(200).send({ ok: true });
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
]
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
const request = {
|
|
475
|
-
id: "ignored",
|
|
476
|
-
meta: {
|
|
477
|
-
requestKey: "r-42"
|
|
478
|
-
}
|
|
479
|
-
};
|
|
480
|
-
const reply = createReplyStub();
|
|
481
|
-
|
|
482
|
-
await fastify.routes[0].handler(request, reply);
|
|
483
|
-
|
|
484
|
-
assert.equal(reply.statusCode, 200);
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
test("registerHttpRuntime passes app context so request scope is available", async () => {
|
|
488
|
-
const app = createApplication();
|
|
489
|
-
const fastify = createFastifyStub();
|
|
490
|
-
const router = createRouter();
|
|
491
|
-
|
|
492
|
-
router.get("/runtime-scope", async (request, reply) => {
|
|
493
|
-
const requestId = request.scope.make("jskit.http.requestId");
|
|
494
|
-
reply.code(200).send({ requestId });
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
app.instance("jskit.fastify", fastify);
|
|
498
|
-
app.instance("jskit.http.router", router);
|
|
499
|
-
|
|
500
|
-
const registration = registerHttpRuntime(app);
|
|
501
|
-
assert.equal(registration.routeCount, 1);
|
|
502
|
-
|
|
503
|
-
const request = { id: "runtime-1" };
|
|
504
|
-
const reply = createReplyStub();
|
|
505
|
-
|
|
506
|
-
await fastify.routes[0].handler(request, reply);
|
|
507
|
-
|
|
508
|
-
assert.equal(reply.statusCode, 200);
|
|
509
|
-
assert.deepEqual(reply.payload, { requestId: "runtime-1" });
|
|
510
|
-
assert.equal(fastify.setErrorHandlerCalls, 1);
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
test("createRouter preserves explicit internal route flags", () => {
|
|
514
|
-
const router = createRouter();
|
|
515
|
-
|
|
516
|
-
router.get(
|
|
517
|
-
"/internal-only",
|
|
518
|
-
{
|
|
519
|
-
internal: true
|
|
520
|
-
},
|
|
521
|
-
async (_request, reply) => {
|
|
522
|
-
reply.code(204).send();
|
|
523
|
-
}
|
|
524
|
-
);
|
|
525
|
-
|
|
526
|
-
const [route] = router.list();
|
|
527
|
-
assert.equal(route?.internal, true);
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
test("registerHttpRuntime skips internal routes from public HTTP registration", () => {
|
|
531
|
-
const app = createApplication();
|
|
532
|
-
const fastify = createFastifyStub();
|
|
533
|
-
const router = createRouter();
|
|
534
|
-
|
|
535
|
-
router.get(
|
|
536
|
-
"/internal-only",
|
|
537
|
-
{
|
|
538
|
-
internal: true
|
|
539
|
-
},
|
|
540
|
-
async (_request, reply) => {
|
|
541
|
-
reply.code(200).send({ hidden: true });
|
|
542
|
-
}
|
|
543
|
-
);
|
|
544
|
-
router.get("/public-route", async (_request, reply) => {
|
|
545
|
-
reply.code(200).send({ ok: true });
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
app.instance("jskit.fastify", fastify);
|
|
549
|
-
app.instance("jskit.http.router", router);
|
|
550
|
-
|
|
551
|
-
const registration = registerHttpRuntime(app);
|
|
552
|
-
assert.equal(registration.routeCount, 1);
|
|
553
|
-
assert.equal(fastify.routes.length, 1);
|
|
554
|
-
assert.equal(fastify.routes[0].url, "/public-route");
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
test("registerHttpRuntime skips internal routes generated through router apiResource helpers", () => {
|
|
558
|
-
const app = createApplication();
|
|
559
|
-
const fastify = createFastifyStub();
|
|
560
|
-
const router = createRouter();
|
|
561
|
-
|
|
562
|
-
router.apiResource(
|
|
563
|
-
"widgets",
|
|
564
|
-
{
|
|
565
|
-
index: async (_request, reply) => reply.code(200).send({ ok: true }),
|
|
566
|
-
store: async (_request, reply) => reply.code(201).send({ ok: true }),
|
|
567
|
-
show: async (_request, reply) => reply.code(200).send({ ok: true }),
|
|
568
|
-
update: async (_request, reply) => reply.code(200).send({ ok: true }),
|
|
569
|
-
destroy: async (_request, reply) => reply.code(204).send()
|
|
570
|
-
},
|
|
571
|
-
{
|
|
572
|
-
internal: true
|
|
573
|
-
}
|
|
574
|
-
);
|
|
575
|
-
|
|
576
|
-
app.instance("jskit.fastify", fastify);
|
|
577
|
-
app.instance("jskit.http.router", router);
|
|
578
|
-
|
|
579
|
-
const registration = registerHttpRuntime(app);
|
|
580
|
-
assert.equal(registration.routeCount, 0);
|
|
581
|
-
assert.equal(fastify.routes.length, 0);
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
test("registerHttpRuntime installs API error handling once by default", () => {
|
|
585
|
-
const app = createApplication();
|
|
586
|
-
const fastify = createFastifyStub();
|
|
587
|
-
const router = createRouter();
|
|
588
|
-
|
|
589
|
-
app.instance("jskit.fastify", fastify);
|
|
590
|
-
app.instance("jskit.http.router", router);
|
|
591
|
-
|
|
592
|
-
registerHttpRuntime(app);
|
|
593
|
-
registerHttpRuntime(app);
|
|
594
|
-
|
|
595
|
-
assert.equal(fastify.setErrorHandlerCalls, 1);
|
|
596
|
-
assert.equal(typeof fastify.errorHandler, "function");
|
|
597
|
-
assert.equal(fastify.contentTypeParsers.size, 0);
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
test("registerHttpRuntime installs the JSON:API parser only when a registered route accepts JSON:API request bodies", () => {
|
|
601
|
-
const app = createApplication();
|
|
602
|
-
const fastify = createFastifyStub();
|
|
603
|
-
const router = createRouter();
|
|
604
|
-
|
|
605
|
-
router.post(
|
|
606
|
-
"/jsonapi-body",
|
|
607
|
-
{
|
|
608
|
-
body: {
|
|
609
|
-
schema: createSchema({})
|
|
610
|
-
},
|
|
611
|
-
transport: {
|
|
612
|
-
kind: "jsonapi-resource",
|
|
613
|
-
contentType: "application/vnd.api+json"
|
|
614
|
-
}
|
|
615
|
-
},
|
|
616
|
-
async (_request, reply) => {
|
|
617
|
-
reply.code(204).send();
|
|
618
|
-
}
|
|
619
|
-
);
|
|
620
|
-
|
|
621
|
-
app.instance("jskit.fastify", fastify);
|
|
622
|
-
app.instance("jskit.http.router", router);
|
|
623
|
-
|
|
624
|
-
registerHttpRuntime(app);
|
|
625
|
-
registerHttpRuntime(app);
|
|
626
|
-
|
|
627
|
-
assert.equal(fastify.contentTypeParsers.has("application/vnd.api+json"), true);
|
|
628
|
-
assert.equal(fastify.contentTypeParsers.size, 1);
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
test("registerHttpRuntime can disable automatic API error handling", () => {
|
|
632
|
-
const app = createApplication();
|
|
633
|
-
const fastify = createFastifyStub();
|
|
634
|
-
const router = createRouter();
|
|
635
|
-
|
|
636
|
-
app.instance("jskit.fastify", fastify);
|
|
637
|
-
app.instance("jskit.http.router", router);
|
|
638
|
-
|
|
639
|
-
registerHttpRuntime(app, {
|
|
640
|
-
autoRegisterApiErrorHandling: false
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
assert.equal(fastify.setErrorHandlerCalls, 0);
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
test("registerHttpRuntime resolves request action default surface from appConfig", async () => {
|
|
647
|
-
const app = createApplication();
|
|
648
|
-
const fastify = createFastifyStub();
|
|
649
|
-
const router = createRouter();
|
|
650
|
-
const observed = [];
|
|
651
|
-
|
|
652
|
-
router.get("/runtime-default-surface", async (request, reply) => {
|
|
653
|
-
await request.executeAction({
|
|
654
|
-
actionId: "surface.default"
|
|
655
|
-
});
|
|
656
|
-
reply.code(200).send({ ok: true });
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
app.instance("jskit.fastify", fastify);
|
|
660
|
-
app.instance("jskit.http.router", router);
|
|
661
|
-
app.instance("appConfig", {
|
|
662
|
-
surfaceDefaultId: "home"
|
|
663
|
-
});
|
|
664
|
-
app.instance("actionExecutor", {
|
|
665
|
-
async execute(payload) {
|
|
666
|
-
observed.push(payload);
|
|
667
|
-
return { ok: true };
|
|
668
|
-
}
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
registerHttpRuntime(app);
|
|
672
|
-
|
|
673
|
-
const request = { id: "runtime-default-surface" };
|
|
674
|
-
const reply = createReplyStub();
|
|
675
|
-
await fastify.routes[0].handler(request, reply);
|
|
676
|
-
|
|
677
|
-
assert.equal(reply.statusCode, 200);
|
|
678
|
-
assert.equal(observed.length, 1);
|
|
679
|
-
assert.equal(observed[0].context.surface, "home");
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
test("registerRoutes leaves request action surface empty when no default is configured", async () => {
|
|
683
|
-
const fastify = createFastifyStub();
|
|
684
|
-
const app = createApplication();
|
|
685
|
-
const observed = [];
|
|
686
|
-
|
|
687
|
-
app.instance("actionExecutor", {
|
|
688
|
-
async execute(payload) {
|
|
689
|
-
observed.push(payload);
|
|
690
|
-
return { ok: true };
|
|
691
|
-
}
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
registerRoutes(fastify, {
|
|
695
|
-
app,
|
|
696
|
-
routes: [
|
|
697
|
-
{
|
|
698
|
-
method: "GET",
|
|
699
|
-
path: "/public-default-surface",
|
|
700
|
-
handler: async (request, reply) => {
|
|
701
|
-
await request.executeAction({
|
|
702
|
-
actionId: "surface.default.public"
|
|
703
|
-
});
|
|
704
|
-
reply.code(200).send({ ok: true });
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
]
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
const request = { id: "public-default-surface" };
|
|
711
|
-
const reply = createReplyStub();
|
|
712
|
-
await fastify.routes[0].handler(request, reply);
|
|
713
|
-
|
|
714
|
-
assert.equal(reply.statusCode, 200);
|
|
715
|
-
assert.equal(observed.length, 1);
|
|
716
|
-
assert.equal(observed[0].context.surface, "");
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
test("createHttpRuntime installs API error handling when Fastify is provided", () => {
|
|
720
|
-
const app = createApplication();
|
|
721
|
-
const fastify = createFastifyStub();
|
|
722
|
-
|
|
723
|
-
createHttpRuntime({
|
|
724
|
-
app,
|
|
725
|
-
fastify
|
|
726
|
-
});
|
|
727
|
-
|
|
728
|
-
assert.equal(fastify.setErrorHandlerCalls, 1);
|
|
729
|
-
});
|
|
730
|
-
|
|
731
|
-
test("registerHttpRuntime forwards middleware alias/group config to route execution", async () => {
|
|
732
|
-
const app = createApplication();
|
|
733
|
-
const fastify = createFastifyStub();
|
|
734
|
-
const router = createRouter();
|
|
735
|
-
const observed = [];
|
|
736
|
-
|
|
737
|
-
router.get(
|
|
738
|
-
"/runtime-middleware",
|
|
739
|
-
{
|
|
740
|
-
middleware: ["api"]
|
|
741
|
-
},
|
|
742
|
-
async (_request, reply) => {
|
|
743
|
-
observed.push("handler");
|
|
744
|
-
reply.code(200).send({
|
|
745
|
-
ok: true
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
);
|
|
749
|
-
|
|
750
|
-
app.instance("jskit.fastify", fastify);
|
|
751
|
-
app.instance("jskit.http.router", router);
|
|
752
|
-
|
|
753
|
-
registerHttpRuntime(app, {
|
|
754
|
-
middleware: {
|
|
755
|
-
aliases: {
|
|
756
|
-
auth: async () => {
|
|
757
|
-
observed.push("auth");
|
|
758
|
-
},
|
|
759
|
-
"throttle:60,1": async () => {
|
|
760
|
-
observed.push("throttle");
|
|
761
|
-
}
|
|
762
|
-
},
|
|
763
|
-
groups: {
|
|
764
|
-
api: ["auth", "throttle:60,1"]
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
});
|
|
768
|
-
|
|
769
|
-
const request = {};
|
|
770
|
-
const reply = createReplyStub();
|
|
771
|
-
await fastify.routes[0].handler(request, reply);
|
|
772
|
-
|
|
773
|
-
assert.equal(reply.statusCode, 200);
|
|
774
|
-
assert.deepEqual(observed, ["auth", "throttle", "handler"]);
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
test("registerRoutes attaches request.input when route input transforms are configured", async () => {
|
|
778
|
-
const fastify = createFastifyStub();
|
|
779
|
-
|
|
780
|
-
registerRoutes(fastify, {
|
|
781
|
-
routes: [
|
|
782
|
-
{
|
|
783
|
-
method: "POST",
|
|
784
|
-
path: "/input-transform",
|
|
785
|
-
input: {
|
|
786
|
-
body: (body) => ({
|
|
787
|
-
name: String(body?.name || "").trim(),
|
|
788
|
-
email: String(body?.email || "")
|
|
789
|
-
.trim()
|
|
790
|
-
.toLowerCase()
|
|
791
|
-
}),
|
|
792
|
-
query: (query) => ({
|
|
793
|
-
dryRun: query?.dryRun === true
|
|
794
|
-
})
|
|
795
|
-
},
|
|
796
|
-
middleware: [
|
|
797
|
-
(request) => {
|
|
798
|
-
assert.deepEqual(request.input, {
|
|
799
|
-
body: {
|
|
800
|
-
name: "Alice",
|
|
801
|
-
email: "alice@example.com"
|
|
802
|
-
},
|
|
803
|
-
query: {
|
|
804
|
-
dryRun: true
|
|
805
|
-
},
|
|
806
|
-
params: undefined
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
],
|
|
810
|
-
handler: async (request, reply) => {
|
|
811
|
-
assert.deepEqual(request.input.body, {
|
|
812
|
-
name: "Alice",
|
|
813
|
-
email: "alice@example.com"
|
|
814
|
-
});
|
|
815
|
-
assert.deepEqual(request.input.query, { dryRun: true });
|
|
816
|
-
reply.code(200).send({ ok: true });
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
]
|
|
820
|
-
});
|
|
821
|
-
|
|
822
|
-
const request = {
|
|
823
|
-
body: {
|
|
824
|
-
name: " Alice ",
|
|
825
|
-
email: " ALICE@EXAMPLE.COM "
|
|
826
|
-
},
|
|
827
|
-
query: {
|
|
828
|
-
dryRun: true
|
|
829
|
-
}
|
|
830
|
-
};
|
|
831
|
-
const reply = createReplyStub();
|
|
832
|
-
|
|
833
|
-
await fastify.routes[0].handler(request, reply);
|
|
834
|
-
|
|
835
|
-
assert.equal(reply.statusCode, 200);
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
test("registerRoutes strips Fastify body schemas when JSKIT runtime body validation is present", () => {
|
|
839
|
-
const fastify = createFastifyStub();
|
|
840
|
-
|
|
841
|
-
registerRoutes(fastify, {
|
|
842
|
-
routes: [
|
|
843
|
-
{
|
|
844
|
-
method: "POST",
|
|
845
|
-
path: "/body-schema-runtime-validation",
|
|
846
|
-
schema: {
|
|
847
|
-
body: {
|
|
848
|
-
type: "object",
|
|
849
|
-
properties: {
|
|
850
|
-
flag: {
|
|
851
|
-
anyOf: [{ type: "boolean" }, { type: "null" }]
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
},
|
|
855
|
-
querystring: {
|
|
856
|
-
type: "object",
|
|
857
|
-
properties: {
|
|
858
|
-
limit: { type: "integer" }
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
},
|
|
862
|
-
input: {
|
|
863
|
-
body: (body) => ({
|
|
864
|
-
flag: body?.flag ?? null
|
|
865
|
-
})
|
|
866
|
-
},
|
|
867
|
-
handler: async (_request, reply) => {
|
|
868
|
-
reply.code(200).send({ ok: true });
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
]
|
|
872
|
-
});
|
|
873
|
-
|
|
874
|
-
assert.equal(Object.prototype.hasOwnProperty.call(fastify.routes[0].schema, "body"), false);
|
|
875
|
-
assert.deepEqual(fastify.routes[0].schema.querystring, {
|
|
876
|
-
type: "object",
|
|
877
|
-
properties: {
|
|
878
|
-
limit: { type: "integer" }
|
|
879
|
-
}
|
|
880
|
-
});
|
|
881
|
-
});
|
|
882
|
-
|
|
883
|
-
test("registerRoutes applies transport request transforms before route input normalization", async () => {
|
|
884
|
-
const fastify = createFastifyStub();
|
|
885
|
-
|
|
886
|
-
registerRoutes(fastify, {
|
|
887
|
-
routes: [
|
|
888
|
-
{
|
|
889
|
-
method: "POST",
|
|
890
|
-
path: "/transport-input",
|
|
891
|
-
transport: {
|
|
892
|
-
kind: "jsonapi-resource",
|
|
893
|
-
request: {
|
|
894
|
-
body(body) {
|
|
895
|
-
return body?.data?.attributes || {};
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
},
|
|
899
|
-
input: {
|
|
900
|
-
body: (body) => ({
|
|
901
|
-
name: String(body?.name || "").trim()
|
|
902
|
-
})
|
|
903
|
-
},
|
|
904
|
-
handler: async (request, reply) => {
|
|
905
|
-
assert.equal(request.routeOptions.config.transport.kind, "jsonapi-resource");
|
|
906
|
-
assert.deepEqual(request.input, {
|
|
907
|
-
body: {
|
|
908
|
-
name: "Alice"
|
|
909
|
-
},
|
|
910
|
-
query: undefined,
|
|
911
|
-
params: undefined
|
|
912
|
-
});
|
|
913
|
-
reply.code(200).send({ ok: true });
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
]
|
|
917
|
-
});
|
|
918
|
-
|
|
919
|
-
const request = {
|
|
920
|
-
body: {
|
|
921
|
-
data: {
|
|
922
|
-
type: "contacts",
|
|
923
|
-
attributes: {
|
|
924
|
-
name: " Alice "
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
};
|
|
929
|
-
const reply = createReplyStub();
|
|
930
|
-
|
|
931
|
-
await fastify.routes[0].handler(request, reply);
|
|
932
|
-
|
|
933
|
-
assert.equal(reply.statusCode, 200);
|
|
934
|
-
});
|
|
935
|
-
|
|
936
|
-
test("registerRoutes does not invoke transport body transforms for bodyless requests", async () => {
|
|
937
|
-
const fastify = createFastifyStub();
|
|
938
|
-
let bodyTransformCalls = 0;
|
|
939
|
-
|
|
940
|
-
registerRoutes(fastify, {
|
|
941
|
-
routes: [
|
|
942
|
-
{
|
|
943
|
-
method: "GET",
|
|
944
|
-
path: "/transport-input-no-body",
|
|
945
|
-
transport: {
|
|
946
|
-
kind: "jsonapi-resource",
|
|
947
|
-
request: {
|
|
948
|
-
body() {
|
|
949
|
-
bodyTransformCalls += 1;
|
|
950
|
-
throw new Error("transport body transform should not run");
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
},
|
|
954
|
-
input: {
|
|
955
|
-
query: (query) => ({
|
|
956
|
-
page: Number(query?.page || 1)
|
|
957
|
-
})
|
|
958
|
-
},
|
|
959
|
-
handler: async (request, reply) => {
|
|
960
|
-
assert.deepEqual(request.input, {
|
|
961
|
-
body: undefined,
|
|
962
|
-
query: {
|
|
963
|
-
page: 2
|
|
964
|
-
},
|
|
965
|
-
params: undefined
|
|
966
|
-
});
|
|
967
|
-
reply.code(200).send({ ok: true });
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
]
|
|
971
|
-
});
|
|
972
|
-
|
|
973
|
-
const request = {
|
|
974
|
-
query: {
|
|
975
|
-
page: "2"
|
|
976
|
-
}
|
|
977
|
-
};
|
|
978
|
-
const reply = createReplyStub();
|
|
979
|
-
|
|
980
|
-
await fastify.routes[0].handler(request, reply);
|
|
981
|
-
|
|
982
|
-
assert.equal(reply.statusCode, 200);
|
|
983
|
-
assert.equal(bodyTransformCalls, 0);
|
|
984
|
-
});
|
|
985
|
-
|
|
986
|
-
test("registerRoutes leaves request.input undefined when route input is not configured", async () => {
|
|
987
|
-
const fastify = createFastifyStub();
|
|
988
|
-
|
|
989
|
-
registerRoutes(fastify, {
|
|
990
|
-
routes: [
|
|
991
|
-
{
|
|
992
|
-
method: "GET",
|
|
993
|
-
path: "/no-input-transform",
|
|
994
|
-
handler: async (request, reply) => {
|
|
995
|
-
assert.equal(request.input, undefined);
|
|
996
|
-
reply.code(200).send({ ok: true });
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
]
|
|
1000
|
-
});
|
|
1001
|
-
|
|
1002
|
-
const request = {};
|
|
1003
|
-
const reply = createReplyStub();
|
|
1004
|
-
|
|
1005
|
-
await fastify.routes[0].handler(request, reply);
|
|
1006
|
-
assert.equal(reply.statusCode, 200);
|
|
1007
|
-
});
|
|
1008
|
-
|
|
1009
|
-
test("registerRoutes applies output transform on reply.send without changing handler shape", async () => {
|
|
1010
|
-
const fastify = createFastifyStub();
|
|
1011
|
-
|
|
1012
|
-
registerRoutes(fastify, {
|
|
1013
|
-
routes: [
|
|
1014
|
-
{
|
|
1015
|
-
method: "GET",
|
|
1016
|
-
path: "/transport-output",
|
|
1017
|
-
transport: {
|
|
1018
|
-
kind: "jsonapi-resource"
|
|
1019
|
-
},
|
|
1020
|
-
output(payload) {
|
|
1021
|
-
return {
|
|
1022
|
-
data: payload
|
|
1023
|
-
};
|
|
1024
|
-
},
|
|
1025
|
-
handler: async (request, reply) => {
|
|
1026
|
-
assert.equal(request.routeOptions.config.transport.kind, "jsonapi-resource");
|
|
1027
|
-
reply.code(200).send({
|
|
1028
|
-
id: "7",
|
|
1029
|
-
name: "Alice"
|
|
1030
|
-
});
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
]
|
|
1034
|
-
});
|
|
1035
|
-
|
|
1036
|
-
const request = {};
|
|
1037
|
-
const reply = createReplyStub();
|
|
1038
|
-
|
|
1039
|
-
await fastify.routes[0].handler(request, reply);
|
|
1040
|
-
|
|
1041
|
-
assert.equal(reply.statusCode, 200);
|
|
1042
|
-
assert.equal(reply.headers["Content-Type"], undefined);
|
|
1043
|
-
assert.deepEqual(reply.payload, {
|
|
1044
|
-
data: {
|
|
1045
|
-
id: "7",
|
|
1046
|
-
name: "Alice"
|
|
1047
|
-
}
|
|
1048
|
-
});
|
|
1049
|
-
});
|
|
1050
|
-
|
|
1051
|
-
test("registerRoutes applies transport response transforms before reply serialization", async () => {
|
|
1052
|
-
const fastify = createFastifyStub();
|
|
1053
|
-
|
|
1054
|
-
registerRoutes(fastify, {
|
|
1055
|
-
routes: [
|
|
1056
|
-
{
|
|
1057
|
-
method: "GET",
|
|
1058
|
-
path: "/transport-response",
|
|
1059
|
-
transport: {
|
|
1060
|
-
kind: "jsonapi-resource",
|
|
1061
|
-
contentType: "application/vnd.api+json",
|
|
1062
|
-
response(payload) {
|
|
1063
|
-
return {
|
|
1064
|
-
data: {
|
|
1065
|
-
type: "contacts",
|
|
1066
|
-
id: String(payload?.id || ""),
|
|
1067
|
-
attributes: {
|
|
1068
|
-
name: String(payload?.name || "")
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
};
|
|
1072
|
-
}
|
|
1073
|
-
},
|
|
1074
|
-
handler: async (_request, reply) => {
|
|
1075
|
-
reply.code(200).send({
|
|
1076
|
-
id: 7,
|
|
1077
|
-
name: "Alice"
|
|
1078
|
-
});
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
]
|
|
1082
|
-
});
|
|
1083
|
-
|
|
1084
|
-
const request = {};
|
|
1085
|
-
const reply = createReplyStub();
|
|
1086
|
-
|
|
1087
|
-
await fastify.routes[0].handler(request, reply);
|
|
1088
|
-
|
|
1089
|
-
assert.equal(reply.statusCode, 200);
|
|
1090
|
-
assert.equal(reply.headers["Content-Type"], "application/vnd.api+json");
|
|
1091
|
-
assert.deepEqual(reply.payload, {
|
|
1092
|
-
data: {
|
|
1093
|
-
type: "contacts",
|
|
1094
|
-
id: "7",
|
|
1095
|
-
attributes: {
|
|
1096
|
-
name: "Alice"
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
});
|
|
1100
|
-
});
|
|
1101
|
-
|
|
1102
|
-
test("registerRoutes bypasses success transport and output transforms for error replies", async () => {
|
|
1103
|
-
const fastify = createFastifyStub();
|
|
1104
|
-
let successTransportCalls = 0;
|
|
1105
|
-
let outputTransformCalls = 0;
|
|
1106
|
-
|
|
1107
|
-
registerRoutes(fastify, {
|
|
1108
|
-
routes: [
|
|
1109
|
-
{
|
|
1110
|
-
method: "GET",
|
|
1111
|
-
path: "/transport-error-bypass",
|
|
1112
|
-
transport: {
|
|
1113
|
-
kind: "jsonapi-resource",
|
|
1114
|
-
contentType: "application/vnd.api+json",
|
|
1115
|
-
response() {
|
|
1116
|
-
successTransportCalls += 1;
|
|
1117
|
-
return {
|
|
1118
|
-
data: {
|
|
1119
|
-
type: "contacts",
|
|
1120
|
-
id: "7",
|
|
1121
|
-
attributes: {
|
|
1122
|
-
name: "Alice"
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
};
|
|
1126
|
-
}
|
|
1127
|
-
},
|
|
1128
|
-
output() {
|
|
1129
|
-
outputTransformCalls += 1;
|
|
1130
|
-
return {
|
|
1131
|
-
mutated: true
|
|
1132
|
-
};
|
|
1133
|
-
},
|
|
1134
|
-
handler: async (_request, reply) => {
|
|
1135
|
-
reply.code(500).send({
|
|
1136
|
-
errors: [
|
|
1137
|
-
{
|
|
1138
|
-
status: "500",
|
|
1139
|
-
code: "internal_server_error",
|
|
1140
|
-
title: "Internal server error."
|
|
1141
|
-
}
|
|
1142
|
-
]
|
|
1143
|
-
});
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
]
|
|
1147
|
-
});
|
|
1148
|
-
|
|
1149
|
-
const request = {};
|
|
1150
|
-
const reply = createReplyStub();
|
|
1151
|
-
|
|
1152
|
-
await fastify.routes[0].handler(request, reply);
|
|
1153
|
-
|
|
1154
|
-
assert.equal(reply.statusCode, 500);
|
|
1155
|
-
assert.equal(reply.headers["Content-Type"], "application/vnd.api+json");
|
|
1156
|
-
assert.equal(successTransportCalls, 0);
|
|
1157
|
-
assert.equal(outputTransformCalls, 0);
|
|
1158
|
-
assert.deepEqual(reply.payload, {
|
|
1159
|
-
errors: [
|
|
1160
|
-
{
|
|
1161
|
-
status: "500",
|
|
1162
|
-
code: "internal_server_error",
|
|
1163
|
-
title: "Internal server error."
|
|
1164
|
-
}
|
|
1165
|
-
]
|
|
1166
|
-
});
|
|
1167
|
-
});
|
|
1168
|
-
|
|
1169
|
-
test("registerRoutes bypasses success transport for Error payloads before status normalization", async () => {
|
|
1170
|
-
const fastify = createFastifyStub();
|
|
1171
|
-
let successTransportCalls = 0;
|
|
1172
|
-
|
|
1173
|
-
registerRoutes(fastify, {
|
|
1174
|
-
routes: [
|
|
1175
|
-
{
|
|
1176
|
-
method: "GET",
|
|
1177
|
-
path: "/transport-error-instance",
|
|
1178
|
-
transport: {
|
|
1179
|
-
kind: "jsonapi-resource",
|
|
1180
|
-
contentType: "application/vnd.api+json",
|
|
1181
|
-
response() {
|
|
1182
|
-
successTransportCalls += 1;
|
|
1183
|
-
return {
|
|
1184
|
-
data: {
|
|
1185
|
-
type: "contacts",
|
|
1186
|
-
id: "7",
|
|
1187
|
-
attributes: {
|
|
1188
|
-
name: "Alice"
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
};
|
|
1192
|
-
}
|
|
1193
|
-
},
|
|
1194
|
-
handler: async (_request, reply) => {
|
|
1195
|
-
reply.send(new Error("Boom"));
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
]
|
|
1199
|
-
});
|
|
1200
|
-
|
|
1201
|
-
const request = {};
|
|
1202
|
-
const reply = createReplyStub();
|
|
1203
|
-
|
|
1204
|
-
await fastify.routes[0].handler(request, reply);
|
|
1205
|
-
|
|
1206
|
-
assert.equal(successTransportCalls, 0);
|
|
1207
|
-
assert.equal(reply.headers["Content-Type"], "application/vnd.api+json");
|
|
1208
|
-
assert.equal(reply.payload instanceof Error, true);
|
|
1209
|
-
assert.equal(reply.payload.message, "Boom");
|
|
1210
|
-
});
|
|
1211
|
-
|
|
1212
|
-
test("registerRoutes exposes full transport runtime on Fastify route config for pre-handler error serialization", () => {
|
|
1213
|
-
const fastify = createFastifyStub();
|
|
1214
|
-
const transport = {
|
|
1215
|
-
kind: "jsonapi-resource",
|
|
1216
|
-
contentType: "application/vnd.api+json",
|
|
1217
|
-
error() {
|
|
1218
|
-
return {
|
|
1219
|
-
errors: []
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
|
-
};
|
|
1223
|
-
|
|
1224
|
-
registerRoutes(fastify, {
|
|
1225
|
-
routes: [
|
|
1226
|
-
{
|
|
1227
|
-
method: "GET",
|
|
1228
|
-
path: "/transport-runtime",
|
|
1229
|
-
transport,
|
|
1230
|
-
handler: async (_request, reply) => {
|
|
1231
|
-
reply.code(200).send({ ok: true });
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
]
|
|
1235
|
-
});
|
|
1236
|
-
|
|
1237
|
-
assert.equal(fastify.routes[0].config.transport.kind, "jsonapi-resource");
|
|
1238
|
-
assert.equal(fastify.routes[0].config.transport.contentType, "application/vnd.api+json");
|
|
1239
|
-
assert.deepEqual(fastify.routes[0].config.transport.runtime, transport);
|
|
1240
|
-
});
|
|
1241
|
-
|
|
1242
|
-
test("registerRoutes keeps transport response payload when output transform returns undefined", async () => {
|
|
1243
|
-
const fastify = createFastifyStub();
|
|
1244
|
-
|
|
1245
|
-
registerRoutes(fastify, {
|
|
1246
|
-
routes: [
|
|
1247
|
-
{
|
|
1248
|
-
method: "GET",
|
|
1249
|
-
path: "/transport-response-output-undefined",
|
|
1250
|
-
transport: {
|
|
1251
|
-
kind: "jsonapi-resource",
|
|
1252
|
-
contentType: "application/vnd.api+json",
|
|
1253
|
-
response(payload) {
|
|
1254
|
-
return {
|
|
1255
|
-
data: {
|
|
1256
|
-
type: "contacts",
|
|
1257
|
-
id: String(payload?.id || ""),
|
|
1258
|
-
attributes: {
|
|
1259
|
-
name: String(payload?.name || "")
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
};
|
|
1263
|
-
}
|
|
1264
|
-
},
|
|
1265
|
-
output() {
|
|
1266
|
-
return undefined;
|
|
1267
|
-
},
|
|
1268
|
-
handler: async (_request, reply) => {
|
|
1269
|
-
reply.code(200).send({
|
|
1270
|
-
id: 7,
|
|
1271
|
-
name: "Alice"
|
|
1272
|
-
});
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
]
|
|
1276
|
-
});
|
|
1277
|
-
|
|
1278
|
-
const request = {};
|
|
1279
|
-
const reply = createReplyStub();
|
|
1280
|
-
|
|
1281
|
-
await fastify.routes[0].handler(request, reply);
|
|
1282
|
-
|
|
1283
|
-
assert.equal(reply.statusCode, 200);
|
|
1284
|
-
assert.equal(reply.headers["Content-Type"], "application/vnd.api+json");
|
|
1285
|
-
assert.deepEqual(reply.payload, {
|
|
1286
|
-
data: {
|
|
1287
|
-
type: "contacts",
|
|
1288
|
-
id: "7",
|
|
1289
|
-
attributes: {
|
|
1290
|
-
name: "Alice"
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
});
|
|
1294
|
-
});
|
|
1295
|
-
|
|
1296
|
-
test("registerRoutes sets transport content type on successful replies", async () => {
|
|
1297
|
-
const fastify = createFastifyStub();
|
|
1298
|
-
|
|
1299
|
-
registerRoutes(fastify, {
|
|
1300
|
-
routes: [
|
|
1301
|
-
{
|
|
1302
|
-
method: "GET",
|
|
1303
|
-
path: "/transport-content-type",
|
|
1304
|
-
transport: {
|
|
1305
|
-
kind: "jsonapi-resource",
|
|
1306
|
-
contentType: "application/vnd.api+json"
|
|
1307
|
-
},
|
|
1308
|
-
handler: async (_request, reply) => {
|
|
1309
|
-
reply.code(200).send({
|
|
1310
|
-
data: {
|
|
1311
|
-
type: "contacts",
|
|
1312
|
-
id: "7"
|
|
1313
|
-
}
|
|
1314
|
-
});
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
]
|
|
1318
|
-
});
|
|
1319
|
-
|
|
1320
|
-
const reply = createReplyStub();
|
|
1321
|
-
await fastify.routes[0].handler({}, reply);
|
|
1322
|
-
|
|
1323
|
-
assert.equal(reply.statusCode, 200);
|
|
1324
|
-
assert.equal(reply.headers["Content-Type"], "application/vnd.api+json");
|
|
1325
|
-
});
|
|
1326
|
-
|
|
1327
|
-
test("registerRoutes rejects invalid route input transform definitions", () => {
|
|
1328
|
-
const fastify = createFastifyStub();
|
|
1329
|
-
|
|
1330
|
-
assert.throws(
|
|
1331
|
-
() =>
|
|
1332
|
-
registerRoutes(fastify, {
|
|
1333
|
-
routes: [
|
|
1334
|
-
{
|
|
1335
|
-
method: "POST",
|
|
1336
|
-
path: "/invalid-input",
|
|
1337
|
-
input: {
|
|
1338
|
-
body: "not-a-function"
|
|
1339
|
-
},
|
|
1340
|
-
handler: async () => {}
|
|
1341
|
-
}
|
|
1342
|
-
]
|
|
1343
|
-
}),
|
|
1344
|
-
/input\.body must be a function/
|
|
1345
|
-
);
|
|
1346
|
-
});
|
|
1347
|
-
|
|
1348
|
-
test("registerRoutes rejects invalid transport definitions", () => {
|
|
1349
|
-
const fastify = createFastifyStub();
|
|
1350
|
-
|
|
1351
|
-
assert.throws(
|
|
1352
|
-
() =>
|
|
1353
|
-
registerRoutes(fastify, {
|
|
1354
|
-
routes: [
|
|
1355
|
-
{
|
|
1356
|
-
method: "GET",
|
|
1357
|
-
path: "/invalid-transport",
|
|
1358
|
-
transport: {
|
|
1359
|
-
kind: "weird"
|
|
1360
|
-
},
|
|
1361
|
-
handler: async () => {}
|
|
1362
|
-
}
|
|
1363
|
-
]
|
|
1364
|
-
}),
|
|
1365
|
-
/transport\.kind must be one of: command, jsonapi-resource/
|
|
1366
|
-
);
|
|
1367
|
-
});
|
|
1368
|
-
|
|
1369
|
-
test("registerRoutes enforces request content type for unsafe transport routes", async () => {
|
|
1370
|
-
const fastify = createFastifyStub();
|
|
1371
|
-
|
|
1372
|
-
registerRoutes(fastify, {
|
|
1373
|
-
routes: [
|
|
1374
|
-
{
|
|
1375
|
-
method: "POST",
|
|
1376
|
-
path: "/transport-content-type-enforced",
|
|
1377
|
-
body: {
|
|
1378
|
-
type: "object",
|
|
1379
|
-
additionalProperties: true
|
|
1380
|
-
},
|
|
1381
|
-
transport: {
|
|
1382
|
-
kind: "jsonapi-resource",
|
|
1383
|
-
contentType: "application/vnd.api+json"
|
|
1384
|
-
},
|
|
1385
|
-
handler: async (_request, reply) => {
|
|
1386
|
-
reply.code(200).send({ ok: true });
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
]
|
|
1390
|
-
});
|
|
1391
|
-
|
|
1392
|
-
await assert.rejects(
|
|
1393
|
-
() =>
|
|
1394
|
-
fastify.routes[0].handler(
|
|
1395
|
-
{
|
|
1396
|
-
headers: {
|
|
1397
|
-
"content-type": "application/json"
|
|
1398
|
-
}
|
|
1399
|
-
},
|
|
1400
|
-
createReplyStub()
|
|
1401
|
-
),
|
|
1402
|
-
(error) => {
|
|
1403
|
-
assert.equal(error?.status, 415);
|
|
1404
|
-
assert.equal(error?.code, "unsupported_media_type");
|
|
1405
|
-
return true;
|
|
1406
|
-
}
|
|
1407
|
-
);
|
|
1408
|
-
});
|
|
1409
|
-
|
|
1410
|
-
test("registerRoutes does not enforce request content type for bodyless unsafe transport routes", async () => {
|
|
1411
|
-
const fastify = createFastifyStub();
|
|
1412
|
-
|
|
1413
|
-
registerRoutes(fastify, {
|
|
1414
|
-
routes: [
|
|
1415
|
-
{
|
|
1416
|
-
method: "POST",
|
|
1417
|
-
path: "/transport-content-type-bodyless",
|
|
1418
|
-
transport: {
|
|
1419
|
-
kind: "jsonapi-resource",
|
|
1420
|
-
contentType: "application/vnd.api+json"
|
|
1421
|
-
},
|
|
1422
|
-
handler: async (_request, reply) => {
|
|
1423
|
-
reply.code(204).send();
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
]
|
|
1427
|
-
});
|
|
1428
|
-
|
|
1429
|
-
const reply = createReplyStub();
|
|
1430
|
-
await fastify.routes[0].handler(
|
|
1431
|
-
{
|
|
1432
|
-
headers: {}
|
|
1433
|
-
},
|
|
1434
|
-
reply
|
|
1435
|
-
);
|
|
1436
|
-
|
|
1437
|
-
assert.equal(reply.statusCode, 204);
|
|
1438
|
-
assert.equal(reply.payload, undefined);
|
|
1439
|
-
});
|
|
1440
|
-
|
|
1441
|
-
test("registerRoutes resolves middleware aliases and groups", async () => {
|
|
1442
|
-
const fastify = createFastifyStub();
|
|
1443
|
-
const observed = {
|
|
1444
|
-
traces: []
|
|
1445
|
-
};
|
|
1446
|
-
|
|
1447
|
-
const requireAuth = async (request) => {
|
|
1448
|
-
observed.traces.push("auth");
|
|
1449
|
-
request.user = {
|
|
1450
|
-
id: 7
|
|
1451
|
-
};
|
|
1452
|
-
};
|
|
1453
|
-
const throttle = async () => {
|
|
1454
|
-
observed.traces.push("throttle");
|
|
1455
|
-
};
|
|
1456
|
-
const attachAuditContext = async (request) => {
|
|
1457
|
-
observed.traces.push("audit");
|
|
1458
|
-
request.audit = {
|
|
1459
|
-
requestScoped: true
|
|
1460
|
-
};
|
|
1461
|
-
};
|
|
1462
|
-
|
|
1463
|
-
registerRoutes(fastify, {
|
|
1464
|
-
middleware: {
|
|
1465
|
-
aliases: {
|
|
1466
|
-
auth: requireAuth,
|
|
1467
|
-
"throttle:60,1": throttle,
|
|
1468
|
-
audit: attachAuditContext
|
|
1469
|
-
},
|
|
1470
|
-
groups: {
|
|
1471
|
-
api: ["auth", "throttle:60,1", "audit"],
|
|
1472
|
-
publicApi: ["throttle:60,1"]
|
|
1473
|
-
}
|
|
1474
|
-
},
|
|
1475
|
-
routes: [
|
|
1476
|
-
{
|
|
1477
|
-
method: "GET",
|
|
1478
|
-
path: "/contacts",
|
|
1479
|
-
middleware: ["api"],
|
|
1480
|
-
handler: async (request, reply) => {
|
|
1481
|
-
assert.equal(request.user?.id, 7);
|
|
1482
|
-
assert.equal(request.audit?.requestScoped, true);
|
|
1483
|
-
reply.code(200).send({
|
|
1484
|
-
ok: true
|
|
1485
|
-
});
|
|
1486
|
-
}
|
|
1487
|
-
},
|
|
1488
|
-
{
|
|
1489
|
-
method: "GET",
|
|
1490
|
-
path: "/public/ping",
|
|
1491
|
-
middleware: ["publicApi"],
|
|
1492
|
-
handler: async (request, reply) => {
|
|
1493
|
-
assert.equal(request.user, undefined);
|
|
1494
|
-
assert.equal(request.audit, undefined);
|
|
1495
|
-
reply.code(200).send({
|
|
1496
|
-
ok: true
|
|
1497
|
-
});
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
]
|
|
1501
|
-
});
|
|
1502
|
-
|
|
1503
|
-
const contactsReply = createReplyStub();
|
|
1504
|
-
await fastify.routes[0].handler({}, contactsReply);
|
|
1505
|
-
assert.equal(contactsReply.statusCode, 200);
|
|
1506
|
-
assert.deepEqual(observed.traces, ["auth", "throttle", "audit"]);
|
|
1507
|
-
|
|
1508
|
-
observed.traces.length = 0;
|
|
1509
|
-
const publicReply = createReplyStub();
|
|
1510
|
-
await fastify.routes[1].handler({}, publicReply);
|
|
1511
|
-
assert.equal(publicReply.statusCode, 200);
|
|
1512
|
-
assert.deepEqual(observed.traces, ["throttle"]);
|
|
1513
|
-
});
|
|
1514
|
-
|
|
1515
|
-
test("registerRoutes rejects unknown named middleware references", () => {
|
|
1516
|
-
const fastify = createFastifyStub();
|
|
1517
|
-
|
|
1518
|
-
assert.throws(
|
|
1519
|
-
() =>
|
|
1520
|
-
registerRoutes(fastify, {
|
|
1521
|
-
middleware: {
|
|
1522
|
-
aliases: {
|
|
1523
|
-
auth: async () => {}
|
|
1524
|
-
},
|
|
1525
|
-
groups: {
|
|
1526
|
-
api: ["auth"]
|
|
1527
|
-
}
|
|
1528
|
-
},
|
|
1529
|
-
routes: [
|
|
1530
|
-
{
|
|
1531
|
-
method: "GET",
|
|
1532
|
-
path: "/contacts",
|
|
1533
|
-
middleware: ["missing-group"],
|
|
1534
|
-
handler: async () => {}
|
|
1535
|
-
}
|
|
1536
|
-
]
|
|
1537
|
-
}),
|
|
1538
|
-
/unknown middleware "missing-group"/
|
|
1539
|
-
);
|
|
1540
|
-
});
|
|
1541
|
-
|
|
1542
|
-
test("registerRoutes rejects middleware group cycles", () => {
|
|
1543
|
-
const fastify = createFastifyStub();
|
|
1544
|
-
|
|
1545
|
-
assert.throws(
|
|
1546
|
-
() =>
|
|
1547
|
-
registerRoutes(fastify, {
|
|
1548
|
-
middleware: {
|
|
1549
|
-
groups: {
|
|
1550
|
-
api: ["audited"],
|
|
1551
|
-
audited: ["api"]
|
|
1552
|
-
}
|
|
1553
|
-
},
|
|
1554
|
-
routes: [
|
|
1555
|
-
{
|
|
1556
|
-
method: "GET",
|
|
1557
|
-
path: "/contacts",
|
|
1558
|
-
middleware: ["api"],
|
|
1559
|
-
handler: async () => {}
|
|
1560
|
-
}
|
|
1561
|
-
]
|
|
1562
|
-
}),
|
|
1563
|
-
/middleware group cycle detected/
|
|
1564
|
-
);
|
|
1565
|
-
});
|