@prosopo/api-express-router 3.1.50 → 3.1.52

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.
Files changed (52) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +14 -12
  2. package/.turbo/turbo-build$colon$tsc.log +21 -21
  3. package/.turbo/turbo-build.log +16 -13
  4. package/CHANGELOG.md +23 -0
  5. package/dist/_virtual/_rolldown/runtime.js +3 -0
  6. package/dist/apiExpressRouterFactory.js +17 -27
  7. package/dist/cjs/apiExpressRouterFactory.cjs +18 -28
  8. package/dist/cjs/endpointAdapter/apiExpressDefaultEndpointAdapter.cjs +29 -35
  9. package/dist/cjs/errorHandler.cjs +10 -10
  10. package/dist/cjs/index.cjs +14 -13
  11. package/dist/cjs/middlewares/authMiddleware.cjs +54 -56
  12. package/dist/cjs/middlewares/requestLoggerMiddleware.cjs +57 -59
  13. package/dist/endpointAdapter/apiExpressDefaultEndpointAdapter.js +26 -33
  14. package/dist/errorHandler.js +10 -10
  15. package/dist/index.js +8 -12
  16. package/dist/middlewares/authMiddleware.js +54 -57
  17. package/dist/middlewares/requestLoggerMiddleware.js +56 -58
  18. package/dist/tests/apiExpressRouter.test-d.d.ts +2 -0
  19. package/dist/tests/apiExpressRouter.test-d.d.ts.map +1 -0
  20. package/dist/tests/apiExpressRouter.test-d.js +76 -0
  21. package/dist/tests/apiExpressRouter.test-d.js.map +1 -0
  22. package/dist/tests/unit/apiExpressDefaultEndpointAdapter.unit.test.d.ts +2 -0
  23. package/dist/tests/unit/apiExpressDefaultEndpointAdapter.unit.test.d.ts.map +1 -0
  24. package/dist/tests/unit/apiExpressDefaultEndpointAdapter.unit.test.js +197 -0
  25. package/dist/tests/unit/apiExpressDefaultEndpointAdapter.unit.test.js.map +1 -0
  26. package/dist/tests/unit/apiExpressRouterFactory.unit.test.d.ts +2 -0
  27. package/dist/tests/unit/apiExpressRouterFactory.unit.test.d.ts.map +1 -0
  28. package/dist/tests/unit/apiExpressRouterFactory.unit.test.js +108 -0
  29. package/dist/tests/unit/apiExpressRouterFactory.unit.test.js.map +1 -0
  30. package/dist/tests/unit/index.unit.test.d.ts +2 -0
  31. package/dist/tests/unit/index.unit.test.d.ts.map +1 -0
  32. package/dist/tests/unit/index.unit.test.js +44 -0
  33. package/dist/tests/unit/index.unit.test.js.map +1 -0
  34. package/dist/tests/unit/middlewares/authMiddleware.unit.test.js +210 -136
  35. package/dist/tests/unit/middlewares/authMiddleware.unit.test.js.map +1 -1
  36. package/dist/tests/unit/middlewares/requestLoggerMiddleware.unit.test.d.ts +2 -0
  37. package/dist/tests/unit/middlewares/requestLoggerMiddleware.unit.test.d.ts.map +1 -0
  38. package/dist/tests/unit/middlewares/requestLoggerMiddleware.unit.test.js +242 -0
  39. package/dist/tests/unit/middlewares/requestLoggerMiddleware.unit.test.js.map +1 -0
  40. package/dist/tests/unit/testDoubles.d.ts +8 -0
  41. package/dist/tests/unit/testDoubles.d.ts.map +1 -0
  42. package/dist/tests/unit/testDoubles.js +11 -0
  43. package/dist/tests/unit/testDoubles.js.map +1 -0
  44. package/package.json +10 -10
  45. package/src/tests/apiExpressRouter.test-d.ts +190 -0
  46. package/src/tests/unit/apiExpressDefaultEndpointAdapter.unit.test.ts +395 -0
  47. package/src/tests/unit/apiExpressRouterFactory.unit.test.ts +215 -0
  48. package/src/tests/unit/index.unit.test.ts +95 -0
  49. package/src/tests/unit/middlewares/authMiddleware.unit.test.ts +318 -159
  50. package/src/tests/unit/middlewares/requestLoggerMiddleware.unit.test.ts +368 -0
  51. package/src/tests/unit/testDoubles.ts +39 -0
  52. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requestLoggerMiddleware.unit.test.js","sourceRoot":"","sources":["../../../../src/tests/unit/middlewares/requestLoggerMiddleware.unit.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { NextFunction } from "express";
2
+ interface NextCapture {
3
+ fn: NextFunction;
4
+ calls: unknown[][];
5
+ }
6
+ declare const captureNext: () => NextCapture;
7
+ export { captureNext, type NextCapture };
8
+ //# sourceMappingURL=testDoubles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testDoubles.d.ts","sourceRoot":"","sources":["../../../src/tests/unit/testDoubles.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAS5C,UAAU,WAAW;IACpB,EAAE,EAAE,YAAY,CAAC;IACjB,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;CACnB;AAED,QAAA,MAAM,WAAW,QAAO,WAQvB,CAAC;AAEF,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,CAAC"}
@@ -0,0 +1,11 @@
1
+ const captureNext = () => {
2
+ const calls = [];
3
+ return {
4
+ fn: (...args) => {
5
+ calls.push(args);
6
+ },
7
+ calls,
8
+ };
9
+ };
10
+ export { captureNext };
11
+ //# sourceMappingURL=testDoubles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testDoubles.js","sourceRoot":"","sources":["../../../src/tests/unit/testDoubles.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,GAAG,GAAgB,EAAE;IACrC,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,OAAO;QACN,EAAE,EAAE,CAAC,GAAG,IAAe,EAAQ,EAAE;YAChC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;QACD,KAAK;KACL,CAAC;AACH,CAAC,CAAC;AAEF,OAAO,EAAE,WAAW,EAAoB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prosopo/api-express-router",
3
- "version": "3.1.50",
3
+ "version": "3.1.52",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -26,13 +26,13 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@polkadot/util": "13.5.7",
29
- "@prosopo/api-route": "2.6.52",
30
- "@prosopo/common": "3.1.47",
31
- "@prosopo/logger": "2.0.3",
32
- "@prosopo/env": "3.6.19",
33
- "@prosopo/locale": "3.2.7",
34
- "@prosopo/types": "4.9.12",
35
- "@prosopo/util": "3.3.4",
29
+ "@prosopo/api-route": "2.6.53",
30
+ "@prosopo/common": "3.1.48",
31
+ "@prosopo/logger": "2.0.4",
32
+ "@prosopo/env": "3.6.21",
33
+ "@prosopo/locale": "3.2.8",
34
+ "@prosopo/types": "4.10.0",
35
+ "@prosopo/util": "3.3.5",
36
36
  "@prosopo/util-crypto": "13.5.30",
37
37
  "dotenv": "16.4.5",
38
38
  "express": "4.21.2",
@@ -41,9 +41,9 @@
41
41
  "zod": "3.23.8"
42
42
  },
43
43
  "devDependencies": {
44
- "@prosopo/config": "3.3.4",
44
+ "@prosopo/config": "3.3.5",
45
45
  "@types/node": "22.10.2",
46
- "vitest": "3.2.4"
46
+ "vitest": "4.1.10"
47
47
  },
48
48
  "author": "PROSOPO LIMITED <info@prosopo.io>",
49
49
  "license": "Apache-2.0",
@@ -0,0 +1,190 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import type { ApiEndpoint, ApiRoutesProvider } from "@prosopo/api-route";
16
+ import type { LogLevel } from "@prosopo/logger";
17
+ import type { KeyringPair } from "@prosopo/types";
18
+ import type {
19
+ NextFunction,
20
+ Request,
21
+ RequestHandler,
22
+ Response,
23
+ Router,
24
+ } from "express";
25
+ import { assertType, describe, expectTypeOf, test } from "vitest";
26
+ import type { ZodType } from "zod";
27
+ import type * as barrel from "../index.js";
28
+ import {
29
+ type ApiExpressEndpointAdapter,
30
+ apiExpressRouterFactory,
31
+ authMiddleware,
32
+ createApiExpressDefaultEndpointAdapter,
33
+ handleErrors,
34
+ requestLoggerMiddleware,
35
+ verifySignature,
36
+ } from "../index.js";
37
+
38
+ /**
39
+ * These pin the package's public type surface. Consumers wire routers and
40
+ * middleware from this barrel alone, so a widened parameter or a dropped export
41
+ * is a break they would only find at their own call sites.
42
+ */
43
+
44
+ describe("the barrel's shape", () => {
45
+ test("exports exactly the intended names", () => {
46
+ expectTypeOf<keyof typeof barrel>().toEqualTypeOf<
47
+ | "apiExpressRouterFactory"
48
+ | "createApiExpressDefaultEndpointAdapter"
49
+ | "handleErrors"
50
+ | "authMiddleware"
51
+ | "verifySignature"
52
+ | "requestLoggerMiddleware"
53
+ >();
54
+ });
55
+ });
56
+
57
+ describe("createApiExpressDefaultEndpointAdapter", () => {
58
+ test("takes a log level and an optional status code", () => {
59
+ expectTypeOf(
60
+ createApiExpressDefaultEndpointAdapter,
61
+ ).parameters.toEqualTypeOf<
62
+ [logLevel: LogLevel, errorStatusCode?: number]
63
+ >();
64
+ });
65
+
66
+ test("returns the interface, not the concrete class", () => {
67
+ // Callers depend on the interface so an alternative adapter can be
68
+ // substituted without a cast.
69
+ expectTypeOf(
70
+ createApiExpressDefaultEndpointAdapter,
71
+ ).returns.toEqualTypeOf<ApiExpressEndpointAdapter>();
72
+ });
73
+
74
+ test("rejects a log level that is not one of the known levels", () => {
75
+ // @ts-expect-error - an arbitrary string is not a LogLevel
76
+ createApiExpressDefaultEndpointAdapter("chatty");
77
+ });
78
+
79
+ test("rejects a non-numeric status code", () => {
80
+ // @ts-expect-error - the status code is a number
81
+ createApiExpressDefaultEndpointAdapter("info", "500");
82
+ });
83
+ });
84
+
85
+ describe("the router factory", () => {
86
+ test("takes a routes provider and an adapter, and returns a Router", () => {
87
+ expectTypeOf(apiExpressRouterFactory.createRouter).toEqualTypeOf<
88
+ (
89
+ apiRoutesProvider: ApiRoutesProvider,
90
+ apiExpressEndpointAdapter: ApiExpressEndpointAdapter,
91
+ ) => Router
92
+ >();
93
+ });
94
+
95
+ test("rejects a bare routes object in place of a provider", () => {
96
+ const adapter: ApiExpressEndpointAdapter = {
97
+ handleRequest: async () => undefined,
98
+ };
99
+ // @ts-expect-error - the factory wants a provider, not the routes it returns
100
+ apiExpressRouterFactory.createRouter({}, adapter);
101
+ });
102
+ });
103
+
104
+ describe("the endpoint adapter interface", () => {
105
+ test("takes the endpoint and the express trio and resolves to void", () => {
106
+ expectTypeOf<ApiExpressEndpointAdapter["handleRequest"]>().toEqualTypeOf<
107
+ (
108
+ endpoint: ApiEndpoint<ZodType | undefined>,
109
+ request: Request,
110
+ response: Response,
111
+ next: NextFunction,
112
+ ) => Promise<void>
113
+ >();
114
+ });
115
+
116
+ test("is satisfied by any object with a matching handleRequest", () => {
117
+ // Structural, deliberately: a consumer can supply its own adapter
118
+ // without importing or extending the default class.
119
+ assertType<ApiExpressEndpointAdapter>({
120
+ handleRequest: async (): Promise<void> => undefined,
121
+ });
122
+ });
123
+ });
124
+
125
+ describe("the middleware", () => {
126
+ test("authMiddleware takes an optional pair and an optional auth account", () => {
127
+ expectTypeOf(authMiddleware).parameters.toEqualTypeOf<
128
+ [pair: KeyringPair | undefined, authAccount?: KeyringPair | undefined]
129
+ >();
130
+ });
131
+
132
+ test("authMiddleware returns an async express handler", () => {
133
+ expectTypeOf(authMiddleware).returns.toEqualTypeOf<
134
+ (req: Request, res: Response, next: NextFunction) => Promise<void>
135
+ >();
136
+ });
137
+
138
+ test("authMiddleware still requires the first argument to be passed", () => {
139
+ // It is `KeyringPair | undefined` rather than optional, so a provider
140
+ // with no key has to say so explicitly instead of forgetting to wire it.
141
+ // @ts-expect-error - the first parameter is required
142
+ authMiddleware();
143
+ });
144
+
145
+ test("requestLoggerMiddleware returns a synchronous express handler", () => {
146
+ expectTypeOf(requestLoggerMiddleware).returns.toEqualTypeOf<
147
+ (req: Request, res: Response, next: NextFunction) => void
148
+ >();
149
+ });
150
+
151
+ test("the handler each factory builds is usable as express middleware", () => {
152
+ // The exports are factories, so it is what they return — not the
153
+ // factories themselves — that gets mounted with app.use().
154
+ assertType<RequestHandler>(
155
+ requestLoggerMiddleware(
156
+ {} as Parameters<typeof requestLoggerMiddleware>[0],
157
+ ),
158
+ );
159
+ assertType<RequestHandler>(authMiddleware(undefined));
160
+ });
161
+
162
+ test("verifySignature takes a signature, a message and a pair, and returns void", () => {
163
+ expectTypeOf(verifySignature).parameters.toEqualTypeOf<
164
+ [signature: string, message: string, pair: KeyringPair]
165
+ >();
166
+ expectTypeOf(verifySignature).returns.toEqualTypeOf<void>();
167
+ });
168
+
169
+ test("verifySignature rejects a byte-array signature", () => {
170
+ // The hex string is decoded internally; passing bytes would be decoded
171
+ // a second time and silently verify the wrong value.
172
+ // @ts-expect-error - the signature is a hex string
173
+ verifySignature(new Uint8Array([1]), "msg", {} as KeyringPair);
174
+ });
175
+ });
176
+
177
+ describe("the error handler", () => {
178
+ test("takes an error first, which is what makes express treat it as one", () => {
179
+ // A three-parameter handler would be mounted as ordinary middleware and
180
+ // never see an error at all.
181
+ expectTypeOf(handleErrors).parameters.toMatchTypeOf<
182
+ [unknown, Request, Response, NextFunction]
183
+ >();
184
+ });
185
+
186
+ test("rejects being mounted as an ordinary handler", () => {
187
+ // @ts-expect-error - the first parameter is an error, not a request
188
+ assertType<RequestHandler>(handleErrors);
189
+ });
190
+ });
@@ -0,0 +1,395 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import {
16
+ type ApiEndpoint,
17
+ type ApiEndpointResponse,
18
+ ApiEndpointResponseStatus,
19
+ } from "@prosopo/api-route";
20
+ import { ProsopoApiError } from "@prosopo/common";
21
+ import { LogLevel, type Logger } from "@prosopo/logger";
22
+ import type { Request, Response } from "express";
23
+ import { beforeEach, describe, expect, test, vi } from "vitest";
24
+ import { type ZodType, z } from "zod";
25
+ import { ApiExpressDefaultEndpointAdapter } from "../../endpointAdapter/apiExpressDefaultEndpointAdapter.js";
26
+ import { type NextCapture, captureNext } from "./testDoubles.js";
27
+
28
+ /**
29
+ * The adapter is the only place request bodies are validated and the only place
30
+ * an endpoint's throw is turned into a response, so the split matters: a bad
31
+ * body must reach the error handler through next(), while a failure inside the
32
+ * endpoint must be answered directly with a 500.
33
+ */
34
+
35
+ interface Harness {
36
+ adapter: ApiExpressDefaultEndpointAdapter;
37
+ request: Request;
38
+ response: Response;
39
+ next: NextCapture;
40
+ logger: Logger;
41
+ json: ReturnType<typeof vi.fn<(body: unknown) => Response>>;
42
+ status: ReturnType<typeof vi.fn<(code: number) => Response>>;
43
+ send: ReturnType<typeof vi.fn<(body: unknown) => Response>>;
44
+ logged: ReturnType<typeof vi.fn<(entry: () => unknown) => void>>;
45
+ }
46
+
47
+ const build = (body: unknown = {}, errorStatusCode = 500): Harness => {
48
+ const logged = vi.fn<(entry: () => unknown) => void>();
49
+ const logger = {
50
+ error: logged,
51
+ info: vi.fn<(entry: () => unknown) => void>(),
52
+ debug: vi.fn<(entry: () => unknown) => void>(),
53
+ warn: vi.fn<(entry: () => unknown) => void>(),
54
+ } as unknown as Logger;
55
+
56
+ const json = vi.fn<(payload: unknown) => Response>();
57
+ const send = vi.fn<(payload: unknown) => Response>();
58
+ const status = vi.fn<(code: number) => Response>();
59
+ const response = { json, send, status } as unknown as Response;
60
+ status.mockReturnValue(response);
61
+
62
+ return {
63
+ adapter: new ApiExpressDefaultEndpointAdapter(
64
+ LogLevel.enum.info,
65
+ errorStatusCode,
66
+ ),
67
+ request: { body, logger } as unknown as Request,
68
+ response,
69
+ next: captureNext(),
70
+ logger,
71
+ json,
72
+ status,
73
+ send,
74
+ logged,
75
+ };
76
+ };
77
+
78
+ const endpoint = (
79
+ schema: ZodType | undefined,
80
+ process: ApiEndpoint<ZodType | undefined>["processRequest"] = async () => ({
81
+ status: ApiEndpointResponseStatus.SUCCESS,
82
+ }),
83
+ ): ApiEndpoint<ZodType | undefined> => ({
84
+ getRequestArgsSchema: () => schema,
85
+ processRequest:
86
+ vi.fn<ApiEndpoint<ZodType | undefined>["processRequest"]>(process),
87
+ });
88
+
89
+ let harness: Harness;
90
+
91
+ beforeEach(() => {
92
+ harness = build();
93
+ });
94
+
95
+ describe("a request the endpoint accepts", () => {
96
+ test("parses the body and passes the parsed value on", async () => {
97
+ harness = build({ count: "5" });
98
+ const schema = z.object({ count: z.coerce.number() });
99
+ const target = endpoint(schema);
100
+
101
+ await harness.adapter.handleRequest(
102
+ target,
103
+ harness.request,
104
+ harness.response,
105
+ harness.next.fn,
106
+ );
107
+
108
+ // The parsed value, not the raw body: coercion has to survive the trip.
109
+ expect(target.processRequest).toHaveBeenCalledWith(
110
+ { count: 5 },
111
+ harness.logger,
112
+ );
113
+ });
114
+
115
+ test("passes undefined args when the endpoint declares no schema", async () => {
116
+ const target = endpoint(undefined);
117
+ await harness.adapter.handleRequest(
118
+ target,
119
+ harness.request,
120
+ harness.response,
121
+ harness.next.fn,
122
+ );
123
+ expect(target.processRequest).toHaveBeenCalledWith(
124
+ undefined,
125
+ harness.logger,
126
+ );
127
+ });
128
+
129
+ test("hands the endpoint the request's own logger", async () => {
130
+ const target = endpoint(undefined);
131
+ await harness.adapter.handleRequest(
132
+ target,
133
+ harness.request,
134
+ harness.response,
135
+ harness.next.fn,
136
+ );
137
+ expect(vi.mocked(target.processRequest).mock.calls[0]?.[1]).toBe(
138
+ harness.logger,
139
+ );
140
+ });
141
+
142
+ test("answers with the endpoint's response as JSON", async () => {
143
+ await harness.adapter.handleRequest(
144
+ endpoint(undefined, async () => ({
145
+ status: ApiEndpointResponseStatus.SUCCESS,
146
+ data: { value: 1 },
147
+ })),
148
+ harness.request,
149
+ harness.response,
150
+ harness.next.fn,
151
+ );
152
+ expect(harness.json).toHaveBeenCalledWith({
153
+ status: ApiEndpointResponseStatus.SUCCESS,
154
+ data: { value: 1 },
155
+ });
156
+ expect(harness.next.calls).toHaveLength(0);
157
+ });
158
+
159
+ test("stringifies bigints, which res.json() cannot serialise", async () => {
160
+ await harness.adapter.handleRequest(
161
+ endpoint(undefined, async () => ({
162
+ status: ApiEndpointResponseStatus.SUCCESS,
163
+ data: { big: 10n },
164
+ })),
165
+ harness.request,
166
+ harness.response,
167
+ harness.next.fn,
168
+ );
169
+ expect(harness.json).toHaveBeenCalledWith({
170
+ status: ApiEndpointResponseStatus.SUCCESS,
171
+ data: { big: "10" },
172
+ });
173
+ });
174
+
175
+ test("passes an empty data payload straight through", async () => {
176
+ // Nothing coerces an empty result into a body, so the client sees
177
+ // exactly what the endpoint returned.
178
+ await harness.adapter.handleRequest(
179
+ endpoint(undefined, async () => ({
180
+ status: ApiEndpointResponseStatus.SUCCESS,
181
+ data: {},
182
+ })),
183
+ harness.request,
184
+ harness.response,
185
+ harness.next.fn,
186
+ );
187
+ expect(harness.json).toHaveBeenCalledWith({
188
+ status: ApiEndpointResponseStatus.SUCCESS,
189
+ data: {},
190
+ });
191
+ });
192
+
193
+ test("an absent request body is still handed to the schema", async () => {
194
+ // Express only populates req.body when a body parser is mounted, so an
195
+ // endpoint whose schema tolerates undefined has to keep working.
196
+ harness.request.body = undefined;
197
+ const schema = z.object({ a: z.string() }).optional();
198
+ const target = endpoint(schema);
199
+ await harness.adapter.handleRequest(
200
+ target,
201
+ harness.request,
202
+ harness.response,
203
+ harness.next.fn,
204
+ );
205
+ expect(target.processRequest).toHaveBeenCalledWith(
206
+ undefined,
207
+ harness.logger,
208
+ );
209
+ expect(harness.json).toHaveBeenCalledWith({
210
+ status: ApiEndpointResponseStatus.SUCCESS,
211
+ });
212
+ });
213
+ });
214
+
215
+ describe("a body the schema rejects", () => {
216
+ test("goes to the error handler rather than being answered here", async () => {
217
+ harness = build({ count: "not a number" });
218
+ const target = endpoint(z.object({ count: z.number() }));
219
+
220
+ await harness.adapter.handleRequest(
221
+ target,
222
+ harness.request,
223
+ harness.response,
224
+ harness.next.fn,
225
+ );
226
+
227
+ expect(harness.next.calls).toHaveLength(1);
228
+ expect(harness.json).not.toHaveBeenCalled();
229
+ expect(target.processRequest).not.toHaveBeenCalled();
230
+ });
231
+
232
+ test("is reported as a 400 parse error carrying the cause", async () => {
233
+ harness = build({ count: "not a number" });
234
+ await harness.adapter.handleRequest(
235
+ endpoint(z.object({ count: z.number() })),
236
+ harness.request,
237
+ harness.response,
238
+ harness.next.fn,
239
+ );
240
+
241
+ const [forwarded] = harness.next.calls[0] ?? [];
242
+ expect(forwarded).toBeInstanceOf(ProsopoApiError);
243
+ const error = forwarded as ProsopoApiError;
244
+ expect(error.context?.code).toBe(400);
245
+ expect(error.context?.error).toBeDefined();
246
+ });
247
+
248
+ test("a schema that throws something other than a ZodError is treated the same", async () => {
249
+ // getRequestArgsSchema().parse is only known to throw; nothing depends
250
+ // on it being a ZodError, so a custom schema cannot escape the 400.
251
+ const schema = {
252
+ parse: () => {
253
+ throw new Error("custom validator exploded");
254
+ },
255
+ } as unknown as ZodType;
256
+
257
+ await harness.adapter.handleRequest(
258
+ endpoint(schema),
259
+ harness.request,
260
+ harness.response,
261
+ harness.next.fn,
262
+ );
263
+
264
+ expect(harness.next.calls).toHaveLength(1);
265
+ expect(harness.next.calls[0]?.[0]).toBeInstanceOf(ProsopoApiError);
266
+ });
267
+
268
+ test("an endpoint that fails while building its schema is caught too", async () => {
269
+ // getRequestArgsSchema() is called inside the same try as parse(), so a
270
+ // broken schema factory is reported as a bad request rather than
271
+ // escaping handleRequest as an unhandled rejection.
272
+ const target: ApiEndpoint<ZodType | undefined> = {
273
+ getRequestArgsSchema: () => {
274
+ throw new Error("schema construction failed");
275
+ },
276
+ processRequest:
277
+ vi.fn<ApiEndpoint<ZodType | undefined>["processRequest"]>(),
278
+ };
279
+
280
+ await harness.adapter.handleRequest(
281
+ target,
282
+ harness.request,
283
+ harness.response,
284
+ harness.next.fn,
285
+ );
286
+
287
+ expect(harness.next.calls[0]?.[0]).toBeInstanceOf(ProsopoApiError);
288
+ expect(target.processRequest).not.toHaveBeenCalled();
289
+ });
290
+ });
291
+
292
+ describe("an endpoint that fails", () => {
293
+ test("is answered with a 500 and a generic message", async () => {
294
+ await harness.adapter.handleRequest(
295
+ endpoint(undefined, async () => {
296
+ throw new Error("database down");
297
+ }),
298
+ harness.request,
299
+ harness.response,
300
+ harness.next.fn,
301
+ );
302
+
303
+ expect(harness.status).toHaveBeenCalledWith(500);
304
+ expect(harness.send).toHaveBeenCalledWith(
305
+ "An internal server error occurred.",
306
+ );
307
+ });
308
+
309
+ test("does not leak the failure's message to the client", async () => {
310
+ await harness.adapter.handleRequest(
311
+ endpoint(undefined, async () => {
312
+ throw new Error("connection string: postgres://user:secret@host");
313
+ }),
314
+ harness.request,
315
+ harness.response,
316
+ harness.next.fn,
317
+ );
318
+ expect(String(harness.send.mock.calls[0]?.[0])).not.toContain("secret");
319
+ });
320
+
321
+ test("logs the failure against the request's logger", async () => {
322
+ const failure = new Error("database down");
323
+ await harness.adapter.handleRequest(
324
+ endpoint(undefined, async () => {
325
+ throw failure;
326
+ }),
327
+ harness.request,
328
+ harness.response,
329
+ harness.next.fn,
330
+ );
331
+
332
+ expect(harness.logged).toHaveBeenCalledTimes(1);
333
+ const entry = harness.logged.mock.calls[0]?.[0];
334
+ expect(entry?.()).toEqual({ err: failure });
335
+ });
336
+
337
+ test("does not also call next(), so the error handler is not double-run", async () => {
338
+ await harness.adapter.handleRequest(
339
+ endpoint(undefined, async () => {
340
+ throw new Error("database down");
341
+ }),
342
+ harness.request,
343
+ harness.response,
344
+ harness.next.fn,
345
+ );
346
+ expect(harness.next.calls).toHaveLength(0);
347
+ });
348
+
349
+ test("ignores the configured errorStatusCode and always answers 500", async () => {
350
+ // The constructor takes an errorStatusCode that the handler never reads.
351
+ // Pinned here so the behaviour is visible rather than assumed; the fix
352
+ // is in flight separately.
353
+ harness = build({}, 503);
354
+ await harness.adapter.handleRequest(
355
+ endpoint(undefined, async () => {
356
+ throw new Error("unavailable");
357
+ }),
358
+ harness.request,
359
+ harness.response,
360
+ harness.next.fn,
361
+ );
362
+ expect(harness.status).toHaveBeenCalledWith(500);
363
+ expect(harness.status).not.toHaveBeenCalledWith(503);
364
+ });
365
+
366
+ test("a rejection with a non-Error value is handled just the same", async () => {
367
+ await harness.adapter.handleRequest(
368
+ endpoint(undefined, async () => {
369
+ throw "a bare string";
370
+ }),
371
+ harness.request,
372
+ harness.response,
373
+ harness.next.fn,
374
+ );
375
+ expect(harness.status).toHaveBeenCalledWith(500);
376
+ expect(harness.logged).toHaveBeenCalledTimes(1);
377
+ });
378
+
379
+ test("a failure while serialising the response is caught too", async () => {
380
+ // stringifyBigInts walks the response, so a getter that throws lands in
381
+ // the same catch as an endpoint failure rather than escaping.
382
+ const hostile: ApiEndpointResponse = {
383
+ get status(): ApiEndpointResponseStatus {
384
+ throw new Error("getter exploded");
385
+ },
386
+ };
387
+ await harness.adapter.handleRequest(
388
+ endpoint(undefined, async () => hostile),
389
+ harness.request,
390
+ harness.response,
391
+ harness.next.fn,
392
+ );
393
+ expect(harness.status).toHaveBeenCalledWith(500);
394
+ });
395
+ });