@vertz/testing 0.2.0 → 0.2.3
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 +19 -126
- package/package.json +6 -6
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,68 +8,31 @@ 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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (error instanceof BadRequestException)
|
|
19
|
-
throw error;
|
|
20
|
-
const message = error instanceof Error ? error.message : `Invalid ${label}`;
|
|
13
|
+
const result = schema.parse(value);
|
|
14
|
+
if (!result.ok) {
|
|
15
|
+
const message = result.error instanceof Error ? result.error.message : `Invalid ${label}`;
|
|
21
16
|
throw new BadRequestException(message);
|
|
22
17
|
}
|
|
18
|
+
return result.data;
|
|
23
19
|
}
|
|
24
20
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"];
|
|
25
|
-
function createTestApp() {
|
|
26
|
-
const serviceMocks = new Map;
|
|
21
|
+
function createTestApp(config) {
|
|
27
22
|
const middlewareMocks = new Map;
|
|
28
|
-
const
|
|
23
|
+
const routes = config?.routes ?? [];
|
|
29
24
|
let envOverrides = {};
|
|
30
25
|
function buildHandler(perRequest) {
|
|
31
26
|
const trie = new Trie;
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
} else {
|
|
42
|
-
throw new Error(`Invalid options for service ${service.moduleName}: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
const env = {};
|
|
46
|
-
realServices.set(service, service.methods({}, undefined, parsedOptions, env));
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const serviceMap = new Map([...realServices, ...serviceMocks, ...perRequest.services]);
|
|
51
|
-
for (const { module, options } of registrations) {
|
|
52
|
-
for (const router of module.routers) {
|
|
53
|
-
const resolvedServices = {};
|
|
54
|
-
if (router.inject) {
|
|
55
|
-
for (const [name, serviceDef] of Object.entries(router.inject)) {
|
|
56
|
-
resolvedServices[name] = serviceMap.get(serviceDef);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
for (const route of router.routes) {
|
|
60
|
-
const fullPath = router.prefix + route.path;
|
|
61
|
-
const entry = {
|
|
62
|
-
handler: route.config.handler,
|
|
63
|
-
options: options ?? {},
|
|
64
|
-
services: resolvedServices,
|
|
65
|
-
responseSchema: route.config.response,
|
|
66
|
-
bodySchema: route.config.body,
|
|
67
|
-
querySchema: route.config.query,
|
|
68
|
-
headersSchema: route.config.headers
|
|
69
|
-
};
|
|
70
|
-
trie.add(route.method, fullPath, entry);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
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);
|
|
73
36
|
}
|
|
74
37
|
const effectiveMiddlewareMocks = new Map([...middlewareMocks, ...perRequest.middlewares]);
|
|
75
38
|
return async (request) => {
|
|
@@ -114,14 +77,14 @@ function createTestApp() {
|
|
|
114
77
|
headers: validatedHeaders,
|
|
115
78
|
raw,
|
|
116
79
|
middlewareState,
|
|
117
|
-
services:
|
|
118
|
-
options:
|
|
80
|
+
services: {},
|
|
81
|
+
options: {},
|
|
119
82
|
env: envOverrides
|
|
120
83
|
});
|
|
121
84
|
const result = await entry.handler(ctx);
|
|
122
85
|
if (entry.responseSchema) {
|
|
123
86
|
const validation = entry.responseSchema.safeParse(result);
|
|
124
|
-
if (!validation.
|
|
87
|
+
if (!validation.ok) {
|
|
125
88
|
throw new ResponseValidationError(validation.error?.message ?? "Unknown validation error");
|
|
126
89
|
}
|
|
127
90
|
}
|
|
@@ -157,14 +120,9 @@ function createTestApp() {
|
|
|
157
120
|
}
|
|
158
121
|
function createRequestBuilder(method, path, options) {
|
|
159
122
|
const perRequest = {
|
|
160
|
-
services: new Map,
|
|
161
123
|
middlewares: new Map
|
|
162
124
|
};
|
|
163
125
|
const builder = {
|
|
164
|
-
mock(service, impl) {
|
|
165
|
-
perRequest.services.set(service, impl);
|
|
166
|
-
return builder;
|
|
167
|
-
},
|
|
168
126
|
mockMiddleware(middleware, result) {
|
|
169
127
|
perRequest.middlewares.set(middleware, result);
|
|
170
128
|
return builder;
|
|
@@ -180,14 +138,6 @@ function createTestApp() {
|
|
|
180
138
|
(path, options) => createRequestBuilder(m, path, options)
|
|
181
139
|
]));
|
|
182
140
|
const app = {
|
|
183
|
-
register(module, options) {
|
|
184
|
-
registrations.push({ module, options });
|
|
185
|
-
return app;
|
|
186
|
-
},
|
|
187
|
-
mock(service, impl) {
|
|
188
|
-
serviceMocks.set(service, impl);
|
|
189
|
-
return app;
|
|
190
|
-
},
|
|
191
141
|
mockMiddleware(middleware, result) {
|
|
192
142
|
middlewareMocks.set(middleware, result);
|
|
193
143
|
return app;
|
|
@@ -207,63 +157,6 @@ class ResponseValidationError extends Error {
|
|
|
207
157
|
this.name = "ResponseValidationError";
|
|
208
158
|
}
|
|
209
159
|
}
|
|
210
|
-
// src/test-service.ts
|
|
211
|
-
function createTestService(serviceDef) {
|
|
212
|
-
const serviceMocks = new Map;
|
|
213
|
-
let providedOptions = {};
|
|
214
|
-
let providedEnv = {};
|
|
215
|
-
async function resolve() {
|
|
216
|
-
const deps = {};
|
|
217
|
-
if (serviceDef.inject) {
|
|
218
|
-
for (const [name, depDef] of Object.entries(serviceDef.inject)) {
|
|
219
|
-
const mock = serviceMocks.get(depDef);
|
|
220
|
-
if (mock === undefined) {
|
|
221
|
-
throw new Error(`Missing mock for injected dependency "${name}". Call .mock(${name}Service, impl) before awaiting.`);
|
|
222
|
-
}
|
|
223
|
-
deps[name] = mock;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
let options = {};
|
|
227
|
-
if (serviceDef.options) {
|
|
228
|
-
const parsed = serviceDef.options.safeParse(providedOptions);
|
|
229
|
-
if (parsed.success) {
|
|
230
|
-
options = parsed.data;
|
|
231
|
-
} else {
|
|
232
|
-
throw new Error(`Invalid options: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
let env = {};
|
|
236
|
-
if (serviceDef.env) {
|
|
237
|
-
const parsed = serviceDef.env.safeParse(providedEnv);
|
|
238
|
-
if (parsed.success) {
|
|
239
|
-
env = parsed.data;
|
|
240
|
-
} else {
|
|
241
|
-
throw new Error(`Invalid env: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
const state = serviceDef.onInit ? await serviceDef.onInit(deps, options, env) : undefined;
|
|
245
|
-
return serviceDef.methods(deps, state, options, env);
|
|
246
|
-
}
|
|
247
|
-
const builder = {
|
|
248
|
-
mock(service, impl) {
|
|
249
|
-
serviceMocks.set(service, impl);
|
|
250
|
-
return builder;
|
|
251
|
-
},
|
|
252
|
-
options(opts) {
|
|
253
|
-
providedOptions = opts;
|
|
254
|
-
return builder;
|
|
255
|
-
},
|
|
256
|
-
env(envVars) {
|
|
257
|
-
providedEnv = envVars;
|
|
258
|
-
return builder;
|
|
259
|
-
},
|
|
260
|
-
then(onfulfilled, onrejected) {
|
|
261
|
-
return resolve().then(onfulfilled, onrejected);
|
|
262
|
-
}
|
|
263
|
-
};
|
|
264
|
-
return builder;
|
|
265
|
-
}
|
|
266
160
|
export {
|
|
267
|
-
createTestService,
|
|
268
161
|
createTestApp
|
|
269
162
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertz/testing",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
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": "
|
|
35
|
-
"@vertz/server": "
|
|
34
|
+
"@vertz/core": "0.2.2",
|
|
35
|
+
"@vertz/server": "0.2.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@types/node": "^25.
|
|
39
|
-
"@vertz/schema": "
|
|
38
|
+
"@types/node": "^25.3.1",
|
|
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
|
},
|