@vertz/testing 0.2.1 → 0.2.4
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/index.d.ts +40 -49
- package/dist/index.js +14 -119
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type DeepPartial<T> = { [P in keyof T]? : T[P] extends object ? DeepPartial<T[P]> : T[P] };
|
|
1
|
+
import { HandlerCtx, NamedMiddlewareDef } from "@vertz/server";
|
|
3
2
|
/**
|
|
4
3
|
* Route map entry shape that each route in AppRouteMap must follow.
|
|
5
4
|
*/
|
|
@@ -20,11 +19,6 @@ interface TestResponse<TResponse = unknown> {
|
|
|
20
19
|
ok: boolean;
|
|
21
20
|
}
|
|
22
21
|
interface TestRequestBuilder<TResponse = unknown> extends PromiseLike<TestResponse<TResponse>> {
|
|
23
|
-
mock<
|
|
24
|
-
TDeps,
|
|
25
|
-
TState,
|
|
26
|
-
TMethods
|
|
27
|
-
>(service: NamedServiceDef<TDeps, TState, TMethods>, impl: DeepPartial<TMethods>): TestRequestBuilder<TResponse>;
|
|
28
22
|
mockMiddleware<
|
|
29
23
|
TReq extends Record<string, unknown>,
|
|
30
24
|
TProv extends Record<string, unknown>
|
|
@@ -37,16 +31,42 @@ interface RequestOptions<TBody = unknown> {
|
|
|
37
31
|
body?: TBody;
|
|
38
32
|
headers?: Record<string, string>;
|
|
39
33
|
}
|
|
34
|
+
type SchemaLike = {
|
|
35
|
+
parse(value: unknown): {
|
|
36
|
+
ok: boolean;
|
|
37
|
+
data?: unknown;
|
|
38
|
+
error?: unknown;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
40
41
|
/**
|
|
41
|
-
*
|
|
42
|
+
* Route entry for configuring test app routes.
|
|
43
|
+
*/
|
|
44
|
+
interface TestRouteEntry {
|
|
45
|
+
method: string;
|
|
46
|
+
path: string;
|
|
47
|
+
handler: (ctx: HandlerCtx) => unknown;
|
|
48
|
+
bodySchema?: SchemaLike;
|
|
49
|
+
querySchema?: SchemaLike;
|
|
50
|
+
headersSchema?: SchemaLike;
|
|
51
|
+
responseSchema?: {
|
|
52
|
+
safeParse(value: unknown): {
|
|
53
|
+
ok: boolean;
|
|
54
|
+
error?: {
|
|
55
|
+
message: string;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Configuration for creating a test app.
|
|
62
|
+
*/
|
|
63
|
+
interface TestAppConfig {
|
|
64
|
+
routes?: TestRouteEntry[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Test app interface.
|
|
42
68
|
*/
|
|
43
69
|
interface TestApp {
|
|
44
|
-
register(module: NamedModule, options?: Record<string, unknown>): TestApp;
|
|
45
|
-
mock<
|
|
46
|
-
TDeps,
|
|
47
|
-
TState,
|
|
48
|
-
TMethods
|
|
49
|
-
>(service: NamedServiceDef<TDeps, TState, TMethods>, impl: DeepPartial<TMethods>): TestApp;
|
|
50
70
|
mockMiddleware<
|
|
51
71
|
TReq extends Record<string, unknown>,
|
|
52
72
|
TProv extends Record<string, unknown>
|
|
@@ -64,12 +84,6 @@ interface TestApp {
|
|
|
64
84
|
* @template TRouteMap - Route map interface mapping route keys (e.g., 'GET /users') to their types.
|
|
65
85
|
*/
|
|
66
86
|
interface TestAppWithRoutes<TRouteMap extends RouteMapEntry> {
|
|
67
|
-
register(module: NamedModule, options?: Record<string, unknown>): TestAppWithRoutes<TRouteMap>;
|
|
68
|
-
mock<
|
|
69
|
-
TDeps,
|
|
70
|
-
TState,
|
|
71
|
-
TMethods
|
|
72
|
-
>(service: NamedServiceDef<TDeps, TState, TMethods>, impl: DeepPartial<TMethods>): TestAppWithRoutes<TRouteMap>;
|
|
73
87
|
mockMiddleware<
|
|
74
88
|
TReq extends Record<string, unknown>,
|
|
75
89
|
TProv extends Record<string, unknown>
|
|
@@ -107,38 +121,15 @@ interface TestAppWithRoutes<TRouteMap extends RouteMapEntry> {
|
|
|
107
121
|
} ? TResponse : unknown>;
|
|
108
122
|
}
|
|
109
123
|
/**
|
|
110
|
-
* Creates a test app for making HTTP requests against registered
|
|
111
|
-
*
|
|
112
|
-
* @returns A test app instance for registering modules and making requests.
|
|
124
|
+
* Creates a test app for making HTTP requests against registered routes.
|
|
125
|
+
* @returns A test app instance for making requests.
|
|
113
126
|
*/
|
|
114
|
-
declare function createTestApp(): TestApp;
|
|
127
|
+
declare function createTestApp(config?: TestAppConfig): TestApp;
|
|
115
128
|
/**
|
|
116
129
|
* Creates a typed test app with route map for type-safe requests.
|
|
117
130
|
* @template TRouteMap - Route map interface mapping route keys to their types.
|
|
118
131
|
* @returns A typed test app instance.
|
|
119
132
|
*/
|
|
120
|
-
declare function createTestApp<TRouteMap extends RouteMapEntry>(): TestAppWithRoutes<TRouteMap>;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
TDeps,
|
|
124
|
-
TState,
|
|
125
|
-
TMethods,
|
|
126
|
-
TOptions extends Record<string, unknown> = Record<string, unknown>,
|
|
127
|
-
TEnv extends Record<string, unknown> = Record<string, unknown>
|
|
128
|
-
> extends PromiseLike<TMethods> {
|
|
129
|
-
mock<
|
|
130
|
-
TDep,
|
|
131
|
-
TDepState,
|
|
132
|
-
TMock
|
|
133
|
-
>(service: NamedServiceDef2<TDep, TDepState, TMock>, impl: DeepPartial<TMock>): TestServiceBuilder<TDeps, TState, TMethods, TOptions, TEnv>;
|
|
134
|
-
options(opts: Partial<TOptions>): TestServiceBuilder<TDeps, TState, TMethods, TOptions, TEnv>;
|
|
135
|
-
env(env: Partial<TEnv>): TestServiceBuilder<TDeps, TState, TMethods, TOptions, TEnv>;
|
|
136
|
-
}
|
|
137
|
-
declare function createTestService<
|
|
138
|
-
TDeps,
|
|
139
|
-
TState,
|
|
140
|
-
TMethods,
|
|
141
|
-
TOptions extends Record<string, unknown> = Record<string, unknown>,
|
|
142
|
-
TEnv extends Record<string, unknown> = Record<string, unknown>
|
|
143
|
-
>(serviceDef: NamedServiceDef2<TDeps, TState, TMethods, TOptions, TEnv>): TestServiceBuilder<TDeps, TState, TMethods, TOptions, TEnv>;
|
|
144
|
-
export { createTestService, createTestApp, TestServiceBuilder, TestResponse, TestRequestBuilder, TestApp, RouteMapEntry, RequestOptions, DeepPartial };
|
|
133
|
+
declare function createTestApp<TRouteMap extends RouteMapEntry>(config?: TestAppConfig): TestAppWithRoutes<TRouteMap>;
|
|
134
|
+
type DeepPartial<T> = { [P in keyof T]? : T[P] extends object ? DeepPartial<T[P]> : T[P] };
|
|
135
|
+
export { createTestApp, TestRouteEntry, TestResponse, TestRequestBuilder, TestAppConfig, TestApp, RouteMapEntry, RequestOptions, DeepPartial };
|
package/dist/index.js
CHANGED
|
@@ -8,9 +8,7 @@ import {
|
|
|
8
8
|
runMiddlewareChain,
|
|
9
9
|
Trie
|
|
10
10
|
} from "@vertz/core/internals";
|
|
11
|
-
import {
|
|
12
|
-
BadRequestException
|
|
13
|
-
} from "@vertz/server";
|
|
11
|
+
import { BadRequestException } from "@vertz/server";
|
|
14
12
|
function validateSchema(schema, value, label) {
|
|
15
13
|
const result = schema.parse(value);
|
|
16
14
|
if (!result.ok) {
|
|
@@ -20,54 +18,21 @@ function validateSchema(schema, value, label) {
|
|
|
20
18
|
return result.data;
|
|
21
19
|
}
|
|
22
20
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"];
|
|
23
|
-
function createTestApp() {
|
|
24
|
-
const serviceMocks = new Map;
|
|
21
|
+
function createTestApp(config) {
|
|
25
22
|
const middlewareMocks = new Map;
|
|
26
|
-
const
|
|
23
|
+
const routes = config?.routes ?? [];
|
|
27
24
|
let envOverrides = {};
|
|
28
25
|
function buildHandler(perRequest) {
|
|
29
26
|
const trie = new Trie;
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} else {
|
|
40
|
-
throw new Error(`Invalid options for service ${service.moduleName}: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
const env = {};
|
|
44
|
-
realServices.set(service, service.methods({}, undefined, parsedOptions, env));
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
const serviceMap = new Map([...realServices, ...serviceMocks, ...perRequest.services]);
|
|
49
|
-
for (const { module, options } of registrations) {
|
|
50
|
-
for (const router of module.routers) {
|
|
51
|
-
const resolvedServices = {};
|
|
52
|
-
if (router.inject) {
|
|
53
|
-
for (const [name, serviceDef] of Object.entries(router.inject)) {
|
|
54
|
-
resolvedServices[name] = serviceMap.get(serviceDef);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
for (const route of router.routes) {
|
|
58
|
-
const fullPath = router.prefix + route.path;
|
|
59
|
-
const entry = {
|
|
60
|
-
handler: route.config.handler,
|
|
61
|
-
options: options ?? {},
|
|
62
|
-
services: resolvedServices,
|
|
63
|
-
responseSchema: route.config.response,
|
|
64
|
-
bodySchema: route.config.body,
|
|
65
|
-
querySchema: route.config.query,
|
|
66
|
-
headersSchema: route.config.headers
|
|
67
|
-
};
|
|
68
|
-
trie.add(route.method, fullPath, entry);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
27
|
+
for (const route of routes) {
|
|
28
|
+
const entry = {
|
|
29
|
+
handler: route.handler,
|
|
30
|
+
responseSchema: route.responseSchema,
|
|
31
|
+
bodySchema: route.bodySchema,
|
|
32
|
+
querySchema: route.querySchema,
|
|
33
|
+
headersSchema: route.headersSchema
|
|
34
|
+
};
|
|
35
|
+
trie.add(route.method, route.path, entry);
|
|
71
36
|
}
|
|
72
37
|
const effectiveMiddlewareMocks = new Map([...middlewareMocks, ...perRequest.middlewares]);
|
|
73
38
|
return async (request) => {
|
|
@@ -112,8 +77,8 @@ function createTestApp() {
|
|
|
112
77
|
headers: validatedHeaders,
|
|
113
78
|
raw,
|
|
114
79
|
middlewareState,
|
|
115
|
-
services:
|
|
116
|
-
options:
|
|
80
|
+
services: {},
|
|
81
|
+
options: {},
|
|
117
82
|
env: envOverrides
|
|
118
83
|
});
|
|
119
84
|
const result = await entry.handler(ctx);
|
|
@@ -155,14 +120,9 @@ function createTestApp() {
|
|
|
155
120
|
}
|
|
156
121
|
function createRequestBuilder(method, path, options) {
|
|
157
122
|
const perRequest = {
|
|
158
|
-
services: new Map,
|
|
159
123
|
middlewares: new Map
|
|
160
124
|
};
|
|
161
125
|
const builder = {
|
|
162
|
-
mock(service, impl) {
|
|
163
|
-
perRequest.services.set(service, impl);
|
|
164
|
-
return builder;
|
|
165
|
-
},
|
|
166
126
|
mockMiddleware(middleware, result) {
|
|
167
127
|
perRequest.middlewares.set(middleware, result);
|
|
168
128
|
return builder;
|
|
@@ -178,14 +138,6 @@ function createTestApp() {
|
|
|
178
138
|
(path, options) => createRequestBuilder(m, path, options)
|
|
179
139
|
]));
|
|
180
140
|
const app = {
|
|
181
|
-
register(module, options) {
|
|
182
|
-
registrations.push({ module, options });
|
|
183
|
-
return app;
|
|
184
|
-
},
|
|
185
|
-
mock(service, impl) {
|
|
186
|
-
serviceMocks.set(service, impl);
|
|
187
|
-
return app;
|
|
188
|
-
},
|
|
189
141
|
mockMiddleware(middleware, result) {
|
|
190
142
|
middlewareMocks.set(middleware, result);
|
|
191
143
|
return app;
|
|
@@ -205,63 +157,6 @@ class ResponseValidationError extends Error {
|
|
|
205
157
|
this.name = "ResponseValidationError";
|
|
206
158
|
}
|
|
207
159
|
}
|
|
208
|
-
// src/test-service.ts
|
|
209
|
-
function createTestService(serviceDef) {
|
|
210
|
-
const serviceMocks = new Map;
|
|
211
|
-
let providedOptions = {};
|
|
212
|
-
let providedEnv = {};
|
|
213
|
-
async function resolve() {
|
|
214
|
-
const deps = {};
|
|
215
|
-
if (serviceDef.inject) {
|
|
216
|
-
for (const [name, depDef] of Object.entries(serviceDef.inject)) {
|
|
217
|
-
const mock = serviceMocks.get(depDef);
|
|
218
|
-
if (mock === undefined) {
|
|
219
|
-
throw new Error(`Missing mock for injected dependency "${name}". Call .mock(${name}Service, impl) before awaiting.`);
|
|
220
|
-
}
|
|
221
|
-
deps[name] = mock;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
let options = {};
|
|
225
|
-
if (serviceDef.options) {
|
|
226
|
-
const parsed = serviceDef.options.safeParse(providedOptions);
|
|
227
|
-
if (parsed.ok) {
|
|
228
|
-
options = parsed.data;
|
|
229
|
-
} else {
|
|
230
|
-
throw new Error(`Invalid options: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
let env = {};
|
|
234
|
-
if (serviceDef.env) {
|
|
235
|
-
const parsed = serviceDef.env.safeParse(providedEnv);
|
|
236
|
-
if (parsed.ok) {
|
|
237
|
-
env = parsed.data;
|
|
238
|
-
} else {
|
|
239
|
-
throw new Error(`Invalid env: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
const state = serviceDef.onInit ? await serviceDef.onInit(deps, options, env) : undefined;
|
|
243
|
-
return serviceDef.methods(deps, state, options, env);
|
|
244
|
-
}
|
|
245
|
-
const builder = {
|
|
246
|
-
mock(service, impl) {
|
|
247
|
-
serviceMocks.set(service, impl);
|
|
248
|
-
return builder;
|
|
249
|
-
},
|
|
250
|
-
options(opts) {
|
|
251
|
-
providedOptions = opts;
|
|
252
|
-
return builder;
|
|
253
|
-
},
|
|
254
|
-
env(envVars) {
|
|
255
|
-
providedEnv = envVars;
|
|
256
|
-
return builder;
|
|
257
|
-
},
|
|
258
|
-
then(onfulfilled, onrejected) {
|
|
259
|
-
return resolve().then(onfulfilled, onrejected);
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
return builder;
|
|
263
|
-
}
|
|
264
160
|
export {
|
|
265
|
-
createTestService,
|
|
266
161
|
createTestApp
|
|
267
162
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertz/testing",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Testing utilities for Vertz applications",
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
"typecheck": "tsc --noEmit"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@vertz/core": "0.2.
|
|
35
|
-
"@vertz/server": "0.2.
|
|
34
|
+
"@vertz/core": "0.2.2",
|
|
35
|
+
"@vertz/server": "0.2.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^25.3.1",
|
|
39
|
-
"@vertz/schema": "0.2.
|
|
39
|
+
"@vertz/schema": "0.2.2",
|
|
40
40
|
"@vitest/coverage-v8": "^4.0.18",
|
|
41
|
-
"bunup": "
|
|
41
|
+
"bunup": "^0.16.31",
|
|
42
42
|
"typescript": "^5.7.0",
|
|
43
43
|
"vitest": "^4.0.18"
|
|
44
44
|
},
|