@engjts/nexus 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advanced/playground/playground.js.map +1 -1
- package/dist/advanced/static/generateDirectoryListing.d.ts +1 -1
- package/dist/advanced/static/generateDirectoryListing.d.ts.map +1 -1
- package/dist/advanced/static/generateDirectoryListing.js +12 -6
- package/dist/advanced/static/generateDirectoryListing.js.map +1 -1
- package/dist/advanced/static/index.d.ts +2 -0
- package/dist/advanced/static/index.d.ts.map +1 -1
- package/dist/advanced/static/index.js +4 -1
- package/dist/advanced/static/index.js.map +1 -1
- package/dist/advanced/static/serveStatic.d.ts.map +1 -1
- package/dist/advanced/static/serveStatic.js +7 -1
- package/dist/advanced/static/serveStatic.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/BENCHMARK_REPORT.md +0 -343
- package/documentation/01-getting-started.md +0 -240
- package/documentation/02-context.md +0 -335
- package/documentation/03-routing.md +0 -397
- package/documentation/04-middleware.md +0 -483
- package/documentation/05-validation.md +0 -514
- package/documentation/06-error-handling.md +0 -465
- package/documentation/07-performance.md +0 -364
- package/documentation/08-adapters.md +0 -470
- package/documentation/09-api-reference.md +0 -548
- package/documentation/10-examples.md +0 -582
- package/documentation/11-deployment.md +0 -477
- package/documentation/12-sentry.md +0 -620
- package/documentation/13-sentry-data-storage.md +0 -996
- package/documentation/14-sentry-data-reference.md +0 -457
- package/documentation/15-sentry-summary.md +0 -409
- package/documentation/16-alerts-system.md +0 -745
- package/documentation/17-alert-adapters.md +0 -696
- package/documentation/18-alerts-implementation-summary.md +0 -385
- package/documentation/19-class-based-routing.md +0 -840
- package/documentation/20-websocket-realtime.md +0 -813
- package/documentation/21-cache-system.md +0 -510
- package/documentation/22-job-queue.md +0 -772
- package/documentation/23-sentry-plugin.md +0 -551
- package/documentation/24-testing-utilities.md +0 -1287
- package/documentation/25-api-versioning.md +0 -533
- package/documentation/26-context-store.md +0 -607
- package/documentation/27-dependency-injection.md +0 -329
- package/documentation/28-lifecycle-hooks.md +0 -521
- package/documentation/29-package-structure.md +0 -196
- package/documentation/30-plugin-system.md +0 -414
- package/documentation/31-jwt-authentication.md +0 -597
- package/documentation/32-cli.md +0 -268
- package/documentation/ALERTS-COMPLETE-SUMMARY.md +0 -429
- package/documentation/ALERTS-INDEX.md +0 -330
- package/documentation/ALERTS-QUICK-REFERENCE.md +0 -286
- package/documentation/README.md +0 -178
- package/documentation/index.html +0 -34
- package/modern_framework_paper.md +0 -1870
- package/public/css/style.css +0 -87
- package/public/index.html +0 -34
- package/public/js/app.js +0 -27
- package/src/advanced/cache/InMemoryCacheStore.ts +0 -68
- package/src/advanced/cache/MultiTierCache.ts +0 -194
- package/src/advanced/cache/RedisCacheStore.ts +0 -341
- package/src/advanced/cache/index.ts +0 -5
- package/src/advanced/cache/types.ts +0 -40
- package/src/advanced/graphql/SimpleDataLoader.ts +0 -42
- package/src/advanced/graphql/index.ts +0 -22
- package/src/advanced/graphql/server.ts +0 -252
- package/src/advanced/graphql/types.ts +0 -42
- package/src/advanced/jobs/InMemoryQueueStore.ts +0 -68
- package/src/advanced/jobs/JobQueue.ts +0 -556
- package/src/advanced/jobs/RedisQueueStore.ts +0 -367
- package/src/advanced/jobs/index.ts +0 -5
- package/src/advanced/jobs/types.ts +0 -70
- package/src/advanced/observability/APMManager.ts +0 -163
- package/src/advanced/observability/AlertManager.ts +0 -109
- package/src/advanced/observability/MetricRegistry.ts +0 -151
- package/src/advanced/observability/ObservabilityCenter.ts +0 -304
- package/src/advanced/observability/StructuredLogger.ts +0 -154
- package/src/advanced/observability/TracingManager.ts +0 -117
- package/src/advanced/observability/adapters.ts +0 -304
- package/src/advanced/observability/createObservabilityMiddleware.ts +0 -63
- package/src/advanced/observability/index.ts +0 -11
- package/src/advanced/observability/types.ts +0 -174
- package/src/advanced/playground/extractPathParams.ts +0 -6
- package/src/advanced/playground/generateFieldExample.ts +0 -31
- package/src/advanced/playground/generatePlaygroundHTML.ts +0 -1956
- package/src/advanced/playground/generateSummary.ts +0 -19
- package/src/advanced/playground/getTagFromPath.ts +0 -9
- package/src/advanced/playground/index.ts +0 -8
- package/src/advanced/playground/playground.ts +0 -250
- package/src/advanced/playground/types.ts +0 -49
- package/src/advanced/playground/zodToExample.ts +0 -16
- package/src/advanced/playground/zodToParams.ts +0 -15
- package/src/advanced/postman/buildAuth.ts +0 -31
- package/src/advanced/postman/buildBody.ts +0 -15
- package/src/advanced/postman/buildQueryParams.ts +0 -27
- package/src/advanced/postman/buildRequestItem.ts +0 -36
- package/src/advanced/postman/buildResponses.ts +0 -11
- package/src/advanced/postman/buildUrl.ts +0 -33
- package/src/advanced/postman/capitalize.ts +0 -4
- package/src/advanced/postman/generateCollection.ts +0 -59
- package/src/advanced/postman/generateEnvironment.ts +0 -34
- package/src/advanced/postman/generateExampleFromZod.ts +0 -21
- package/src/advanced/postman/generateFieldExample.ts +0 -45
- package/src/advanced/postman/generateName.ts +0 -20
- package/src/advanced/postman/generateUUID.ts +0 -11
- package/src/advanced/postman/getTagFromPath.ts +0 -10
- package/src/advanced/postman/index.ts +0 -28
- package/src/advanced/postman/postman.ts +0 -156
- package/src/advanced/postman/slugify.ts +0 -7
- package/src/advanced/postman/types.ts +0 -140
- package/src/advanced/realtime/index.ts +0 -18
- package/src/advanced/realtime/websocket.ts +0 -231
- package/src/advanced/sentry/index.ts +0 -1236
- package/src/advanced/sentry/types.ts +0 -355
- package/src/advanced/static/generateDirectoryListing.ts +0 -47
- package/src/advanced/static/generateETag.ts +0 -7
- package/src/advanced/static/getMimeType.ts +0 -9
- package/src/advanced/static/index.ts +0 -32
- package/src/advanced/static/isSafePath.ts +0 -13
- package/src/advanced/static/publicDir.ts +0 -21
- package/src/advanced/static/serveStatic.ts +0 -225
- package/src/advanced/static/spa.ts +0 -24
- package/src/advanced/static/types.ts +0 -159
- package/src/advanced/swagger/SwaggerGenerator.ts +0 -66
- package/src/advanced/swagger/buildOperation.ts +0 -61
- package/src/advanced/swagger/buildParameters.ts +0 -61
- package/src/advanced/swagger/buildRequestBody.ts +0 -21
- package/src/advanced/swagger/buildResponses.ts +0 -54
- package/src/advanced/swagger/capitalize.ts +0 -5
- package/src/advanced/swagger/convertPath.ts +0 -9
- package/src/advanced/swagger/createSwagger.ts +0 -12
- package/src/advanced/swagger/generateOperationId.ts +0 -21
- package/src/advanced/swagger/generateSpec.ts +0 -105
- package/src/advanced/swagger/generateSummary.ts +0 -24
- package/src/advanced/swagger/generateSwaggerUI.ts +0 -70
- package/src/advanced/swagger/generateThemeCss.ts +0 -53
- package/src/advanced/swagger/index.ts +0 -25
- package/src/advanced/swagger/swagger.ts +0 -237
- package/src/advanced/swagger/types.ts +0 -206
- package/src/advanced/swagger/zodFieldToOpenAPI.ts +0 -94
- package/src/advanced/swagger/zodSchemaToOpenAPI.ts +0 -50
- package/src/advanced/swagger/zodToOpenAPI.ts +0 -22
- package/src/advanced/testing/factory.ts +0 -509
- package/src/advanced/testing/harness.ts +0 -612
- package/src/advanced/testing/index.ts +0 -430
- package/src/advanced/testing/load-test.ts +0 -618
- package/src/advanced/testing/mock-server.ts +0 -498
- package/src/advanced/testing/mock.ts +0 -670
- package/src/cli/bin.ts +0 -9
- package/src/cli/cli.ts +0 -158
- package/src/cli/commands/add.ts +0 -178
- package/src/cli/commands/build.ts +0 -73
- package/src/cli/commands/create.ts +0 -166
- package/src/cli/commands/dev.ts +0 -85
- package/src/cli/commands/generate.ts +0 -99
- package/src/cli/commands/help.ts +0 -95
- package/src/cli/commands/init.ts +0 -91
- package/src/cli/commands/version.ts +0 -38
- package/src/cli/index.ts +0 -6
- package/src/cli/templates/generators.ts +0 -359
- package/src/cli/templates/index.ts +0 -680
- package/src/cli/utils/exec.ts +0 -52
- package/src/cli/utils/file-system.ts +0 -78
- package/src/cli/utils/logger.ts +0 -111
- package/src/core/adapter.ts +0 -88
- package/src/core/application.ts +0 -1453
- package/src/core/context-pool.ts +0 -79
- package/src/core/context.ts +0 -856
- package/src/core/index.ts +0 -94
- package/src/core/middleware.ts +0 -272
- package/src/core/performance/buffer-pool.ts +0 -108
- package/src/core/performance/middleware-optimizer.ts +0 -162
- package/src/core/plugin/PluginManager.ts +0 -435
- package/src/core/plugin/builder.ts +0 -358
- package/src/core/plugin/index.ts +0 -50
- package/src/core/plugin/types.ts +0 -214
- package/src/core/router/file-router.ts +0 -623
- package/src/core/router/index.ts +0 -260
- package/src/core/router/radix-tree.ts +0 -242
- package/src/core/serializer.ts +0 -397
- package/src/core/store/index.ts +0 -30
- package/src/core/store/registry.ts +0 -178
- package/src/core/store/request-store.ts +0 -240
- package/src/core/store/types.ts +0 -233
- package/src/core/types.ts +0 -616
- package/src/database/adapter.ts +0 -35
- package/src/database/adapters/index.ts +0 -1
- package/src/database/adapters/mysql.ts +0 -669
- package/src/database/database.ts +0 -70
- package/src/database/dialect.ts +0 -388
- package/src/database/index.ts +0 -12
- package/src/database/migrations.ts +0 -86
- package/src/database/optimizer.ts +0 -125
- package/src/database/query-builder.ts +0 -404
- package/src/database/realtime.ts +0 -53
- package/src/database/schema.ts +0 -71
- package/src/database/transactions.ts +0 -56
- package/src/database/types.ts +0 -87
- package/src/deployment/cluster.ts +0 -471
- package/src/deployment/config.ts +0 -454
- package/src/deployment/docker.ts +0 -599
- package/src/deployment/graceful-shutdown.ts +0 -373
- package/src/deployment/index.ts +0 -56
- package/src/index.ts +0 -281
- package/src/security/adapter.ts +0 -318
- package/src/security/auth/JWTPlugin.ts +0 -234
- package/src/security/auth/JWTProvider.ts +0 -316
- package/src/security/auth/adapter.ts +0 -12
- package/src/security/auth/jwt.ts +0 -234
- package/src/security/auth/middleware.ts +0 -188
- package/src/security/csrf.ts +0 -220
- package/src/security/headers.ts +0 -108
- package/src/security/index.ts +0 -60
- package/src/security/rate-limit/adapter.ts +0 -7
- package/src/security/rate-limit/memory.ts +0 -108
- package/src/security/rate-limit/middleware.ts +0 -181
- package/src/security/sanitization.ts +0 -75
- package/src/security/types.ts +0 -240
- package/src/security/utils.ts +0 -52
- package/tsconfig.json +0 -39
|
@@ -1,612 +0,0 @@
|
|
|
1
|
-
import { request as httpRequest, RequestOptions } from 'http';
|
|
2
|
-
import { AddressInfo } from 'net';
|
|
3
|
-
import { URLSearchParams } from 'url';
|
|
4
|
-
import { Application } from '../../core/application';
|
|
5
|
-
|
|
6
|
-
// Re-export all testing utilities
|
|
7
|
-
export * from './mock';
|
|
8
|
-
export * from './factory';
|
|
9
|
-
export * from './load-test';
|
|
10
|
-
export * from './mock-server';
|
|
11
|
-
|
|
12
|
-
export interface TestRequestOptions {
|
|
13
|
-
headers?: Record<string, string>;
|
|
14
|
-
body?: any;
|
|
15
|
-
query?: Record<string, string | number | boolean>;
|
|
16
|
-
timeout?: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface TestResponse<T = any> {
|
|
20
|
-
status: number;
|
|
21
|
-
headers: Record<string, string | string[]>;
|
|
22
|
-
body: string;
|
|
23
|
-
duration: number;
|
|
24
|
-
json(): T;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface PerformanceAssertion {
|
|
28
|
-
toBeLessThan(ms: number): void;
|
|
29
|
-
toBeGreaterThan(ms: number): void;
|
|
30
|
-
toBeBetween(min: number, max: number): void;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Snapshot storage for snapshot testing
|
|
35
|
-
*/
|
|
36
|
-
class SnapshotStore {
|
|
37
|
-
private snapshots: Map<string, string> = new Map();
|
|
38
|
-
private updateMode: boolean = false;
|
|
39
|
-
|
|
40
|
-
setUpdateMode(update: boolean): void {
|
|
41
|
-
this.updateMode = update;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
save(name: string, value: string): void {
|
|
45
|
-
this.snapshots.set(name, value);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
get(name: string): string | undefined {
|
|
49
|
-
return this.snapshots.get(name);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
match(name: string, value: any): { matched: boolean; expected?: string; actual: string } {
|
|
53
|
-
const actual = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
|
54
|
-
const expected = this.snapshots.get(name);
|
|
55
|
-
|
|
56
|
-
if (this.updateMode || !expected) {
|
|
57
|
-
this.save(name, actual);
|
|
58
|
-
return { matched: true, actual };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
matched: actual === expected,
|
|
63
|
-
expected,
|
|
64
|
-
actual
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
clear(): void {
|
|
69
|
-
this.snapshots.clear();
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const snapshotStore = new SnapshotStore();
|
|
74
|
-
|
|
75
|
-
export class TestClient {
|
|
76
|
-
private app: Application;
|
|
77
|
-
private server?: ReturnType<Application['listen']>;
|
|
78
|
-
private baseUrl?: string;
|
|
79
|
-
private defaultHeaders: Record<string, string> = {};
|
|
80
|
-
private authToken?: string;
|
|
81
|
-
private authUser?: any;
|
|
82
|
-
|
|
83
|
-
constructor(app: Application) {
|
|
84
|
-
this.app = app;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Get the current authenticated user (if any)
|
|
89
|
-
*/
|
|
90
|
-
getAuthUser(): any {
|
|
91
|
-
return this.authUser;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Set default headers for all requests
|
|
96
|
-
*/
|
|
97
|
-
setDefaultHeaders(headers: Record<string, string>): this {
|
|
98
|
-
this.defaultHeaders = { ...this.defaultHeaders, ...headers };
|
|
99
|
-
return this;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Authenticate requests with a token
|
|
104
|
-
*/
|
|
105
|
-
authenticate(token: string): this;
|
|
106
|
-
authenticate(user: { token: string; [key: string]: any }): this;
|
|
107
|
-
authenticate(tokenOrUser: string | { token: string; [key: string]: any }): this {
|
|
108
|
-
if (typeof tokenOrUser === 'string') {
|
|
109
|
-
this.authToken = tokenOrUser;
|
|
110
|
-
} else {
|
|
111
|
-
this.authToken = tokenOrUser.token;
|
|
112
|
-
this.authUser = tokenOrUser;
|
|
113
|
-
}
|
|
114
|
-
return this;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Clear authentication
|
|
119
|
-
*/
|
|
120
|
-
clearAuth(): this {
|
|
121
|
-
this.authToken = undefined;
|
|
122
|
-
this.authUser = undefined;
|
|
123
|
-
return this;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async start() {
|
|
127
|
-
if (this.server) return;
|
|
128
|
-
await new Promise<void>((resolve) => {
|
|
129
|
-
this.server = this.app.listen(0, () => resolve());
|
|
130
|
-
});
|
|
131
|
-
const address = this.server!.address() as AddressInfo;
|
|
132
|
-
this.baseUrl = `http://127.0.0.1:${address.port}`;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async stop() {
|
|
136
|
-
if (!this.server) return;
|
|
137
|
-
await new Promise<void>((resolve, reject) => {
|
|
138
|
-
this.server!.close((error) => (error ? reject(error) : resolve()));
|
|
139
|
-
});
|
|
140
|
-
this.server = undefined;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async request(method: string, path: string, options: TestRequestOptions = {}): Promise<TestResponse> {
|
|
144
|
-
await this.start();
|
|
145
|
-
const url = new URL(path, this.baseUrl);
|
|
146
|
-
|
|
147
|
-
if (options.query) {
|
|
148
|
-
const params = new URLSearchParams();
|
|
149
|
-
for (const [key, value] of Object.entries(options.query)) {
|
|
150
|
-
params.append(key, String(value));
|
|
151
|
-
}
|
|
152
|
-
url.search = params.toString();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const body = options.body
|
|
156
|
-
? (typeof options.body === 'string' ? options.body : JSON.stringify(options.body))
|
|
157
|
-
: undefined;
|
|
158
|
-
|
|
159
|
-
// Merge headers
|
|
160
|
-
const headers: Record<string, string> = {
|
|
161
|
-
...this.defaultHeaders,
|
|
162
|
-
...(options.headers || {})
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
// Add auth header if authenticated
|
|
166
|
-
if (this.authToken && !headers['Authorization']) {
|
|
167
|
-
headers['Authorization'] = `Bearer ${this.authToken}`;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Add content headers for body
|
|
171
|
-
if (body) {
|
|
172
|
-
headers['Content-Type'] = headers['Content-Type'] || 'application/json';
|
|
173
|
-
headers['Content-Length'] = Buffer.byteLength(body).toString();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const requestOptions: RequestOptions = {
|
|
177
|
-
method,
|
|
178
|
-
headers
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const start = Date.now();
|
|
182
|
-
return new Promise<TestResponse>((resolve, reject) => {
|
|
183
|
-
const req = httpRequest(url, requestOptions, res => {
|
|
184
|
-
const chunks: Buffer[] = [];
|
|
185
|
-
res.on('data', (chunk) => chunks.push(chunk));
|
|
186
|
-
res.on('end', () => {
|
|
187
|
-
const responseBody = Buffer.concat(chunks).toString('utf-8');
|
|
188
|
-
const duration = Date.now() - start;
|
|
189
|
-
resolve({
|
|
190
|
-
status: res.statusCode || 0,
|
|
191
|
-
headers: res.headers as Record<string, string | string[]>,
|
|
192
|
-
body: responseBody,
|
|
193
|
-
duration,
|
|
194
|
-
json() {
|
|
195
|
-
return JSON.parse(responseBody || '{}');
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
req.on('error', reject);
|
|
202
|
-
|
|
203
|
-
if (options.timeout) {
|
|
204
|
-
req.setTimeout(options.timeout, () => {
|
|
205
|
-
req.destroy(new Error('Request timeout'));
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (body) {
|
|
210
|
-
req.write(body);
|
|
211
|
-
}
|
|
212
|
-
req.end();
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
get(path: string, options?: TestRequestOptions) {
|
|
217
|
-
return this.request('GET', path, options);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
post(path: string, options?: TestRequestOptions) {
|
|
221
|
-
return this.request('POST', path, options);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
put(path: string, options?: TestRequestOptions) {
|
|
225
|
-
return this.request('PUT', path, options);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
patch(path: string, options?: TestRequestOptions) {
|
|
229
|
-
return this.request('PATCH', path, options);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
delete(path: string, options?: TestRequestOptions) {
|
|
233
|
-
return this.request('DELETE', path, options);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// ============================================================================
|
|
238
|
-
// Assertion Helpers
|
|
239
|
-
// ============================================================================
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Create performance assertion for response duration
|
|
243
|
-
*/
|
|
244
|
-
export function expectDuration(response: TestResponse): PerformanceAssertion {
|
|
245
|
-
return {
|
|
246
|
-
toBeLessThan(ms: number) {
|
|
247
|
-
if (response.duration >= ms) {
|
|
248
|
-
throw new Error(
|
|
249
|
-
`Expected response duration (${response.duration}ms) to be less than ${ms}ms`
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
toBeGreaterThan(ms: number) {
|
|
254
|
-
if (response.duration <= ms) {
|
|
255
|
-
throw new Error(
|
|
256
|
-
`Expected response duration (${response.duration}ms) to be greater than ${ms}ms`
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
},
|
|
260
|
-
toBeBetween(min: number, max: number) {
|
|
261
|
-
if (response.duration < min || response.duration > max) {
|
|
262
|
-
throw new Error(
|
|
263
|
-
`Expected response duration (${response.duration}ms) to be between ${min}ms and ${max}ms`
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Snapshot testing
|
|
272
|
-
*/
|
|
273
|
-
export function expectSnapshot(name: string, value: any): { toMatch(): void; toMatchSnapshot(): void } {
|
|
274
|
-
return {
|
|
275
|
-
toMatch() {
|
|
276
|
-
const result = snapshotStore.match(name, value);
|
|
277
|
-
if (!result.matched) {
|
|
278
|
-
throw new Error(
|
|
279
|
-
`Snapshot mismatch for "${name}":\n` +
|
|
280
|
-
`Expected:\n${result.expected}\n\n` +
|
|
281
|
-
`Actual:\n${result.actual}`
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
},
|
|
285
|
-
toMatchSnapshot() {
|
|
286
|
-
this.toMatch();
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Enable snapshot update mode
|
|
293
|
-
*/
|
|
294
|
-
export function updateSnapshots(update: boolean = true): void {
|
|
295
|
-
snapshotStore.setUpdateMode(update);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// ============================================================================
|
|
299
|
-
// Test Suite Helpers
|
|
300
|
-
// ============================================================================
|
|
301
|
-
|
|
302
|
-
export interface TestContext {
|
|
303
|
-
client: TestClient;
|
|
304
|
-
baseUrl: string;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
export interface TestSuiteOptions {
|
|
308
|
-
app: Application;
|
|
309
|
-
beforeAll?: () => Promise<void>;
|
|
310
|
-
afterAll?: () => Promise<void>;
|
|
311
|
-
beforeEach?: () => Promise<void>;
|
|
312
|
-
afterEach?: () => Promise<void>;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Create a test suite with automatic setup/teardown
|
|
317
|
-
*/
|
|
318
|
-
export function createTestSuite(options: TestSuiteOptions) {
|
|
319
|
-
const client = new TestClient(options.app);
|
|
320
|
-
|
|
321
|
-
return {
|
|
322
|
-
client,
|
|
323
|
-
|
|
324
|
-
async setup(): Promise<TestContext> {
|
|
325
|
-
if (options.beforeAll) {
|
|
326
|
-
await options.beforeAll();
|
|
327
|
-
}
|
|
328
|
-
await client.start();
|
|
329
|
-
return {
|
|
330
|
-
client,
|
|
331
|
-
baseUrl: (client as any).baseUrl
|
|
332
|
-
};
|
|
333
|
-
},
|
|
334
|
-
|
|
335
|
-
async teardown(): Promise<void> {
|
|
336
|
-
await client.stop();
|
|
337
|
-
if (options.afterAll) {
|
|
338
|
-
await options.afterAll();
|
|
339
|
-
}
|
|
340
|
-
},
|
|
341
|
-
|
|
342
|
-
async runBeforeEach(): Promise<void> {
|
|
343
|
-
if (options.beforeEach) {
|
|
344
|
-
await options.beforeEach();
|
|
345
|
-
}
|
|
346
|
-
},
|
|
347
|
-
|
|
348
|
-
async runAfterEach(): Promise<void> {
|
|
349
|
-
if (options.afterEach) {
|
|
350
|
-
await options.afterEach();
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// ============================================================================
|
|
357
|
-
// Database Seeding Helpers
|
|
358
|
-
// ============================================================================
|
|
359
|
-
|
|
360
|
-
export interface SeedConfig<T> {
|
|
361
|
-
table: string;
|
|
362
|
-
data: T[];
|
|
363
|
-
truncate?: boolean;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Database seeder for test data
|
|
368
|
-
*/
|
|
369
|
-
export class TestSeeder {
|
|
370
|
-
private seeders: Map<string, SeedConfig<any>> = new Map();
|
|
371
|
-
private insertFn?: (table: string, data: any[]) => Promise<void>;
|
|
372
|
-
private truncateFn?: (table: string) => Promise<void>;
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Configure database functions
|
|
376
|
-
*/
|
|
377
|
-
configure(options: {
|
|
378
|
-
insert: (table: string, data: any[]) => Promise<void>;
|
|
379
|
-
truncate?: (table: string) => Promise<void>;
|
|
380
|
-
}): this {
|
|
381
|
-
this.insertFn = options.insert;
|
|
382
|
-
this.truncateFn = options.truncate;
|
|
383
|
-
return this;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Define seed data for a table
|
|
388
|
-
*/
|
|
389
|
-
define<T>(table: string, data: T[], truncate: boolean = true): this {
|
|
390
|
-
this.seeders.set(table, { table, data, truncate });
|
|
391
|
-
return this;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Seed a specific table
|
|
396
|
-
*/
|
|
397
|
-
async seed<T>(table: string, data?: T[]): Promise<void> {
|
|
398
|
-
if (!this.insertFn) {
|
|
399
|
-
throw new Error('Seeder not configured. Call configure() first.');
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const config = data ? { table, data, truncate: true } : this.seeders.get(table);
|
|
403
|
-
if (!config) {
|
|
404
|
-
throw new Error(`No seed data defined for table: ${table}`);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
if (config.truncate && this.truncateFn) {
|
|
408
|
-
await this.truncateFn(table);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
await this.insertFn(table, config.data);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* Seed all defined tables
|
|
416
|
-
*/
|
|
417
|
-
async seedAll(): Promise<void> {
|
|
418
|
-
for (const [table] of this.seeders) {
|
|
419
|
-
await this.seed(table);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Truncate all seeded tables
|
|
425
|
-
*/
|
|
426
|
-
async truncateAll(): Promise<void> {
|
|
427
|
-
if (!this.truncateFn) {
|
|
428
|
-
throw new Error('Truncate function not configured');
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
for (const [table] of this.seeders) {
|
|
432
|
-
await this.truncateFn(table);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// ============================================================================
|
|
438
|
-
// Test Utilities
|
|
439
|
-
// ============================================================================
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Wait for a condition to be true
|
|
443
|
-
*/
|
|
444
|
-
export async function waitFor(
|
|
445
|
-
condition: () => boolean | Promise<boolean>,
|
|
446
|
-
options: { timeout?: number; interval?: number } = {}
|
|
447
|
-
): Promise<void> {
|
|
448
|
-
const timeout = options.timeout ?? 5000;
|
|
449
|
-
const interval = options.interval ?? 100;
|
|
450
|
-
const startTime = Date.now();
|
|
451
|
-
|
|
452
|
-
while (Date.now() - startTime < timeout) {
|
|
453
|
-
if (await condition()) {
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
await new Promise(resolve => setTimeout(resolve, interval));
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
throw new Error(`Condition not met within ${timeout}ms`);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Retry a function until it succeeds
|
|
464
|
-
*/
|
|
465
|
-
export async function retry<T>(
|
|
466
|
-
fn: () => Promise<T>,
|
|
467
|
-
options: { attempts?: number; delay?: number; backoff?: boolean } = {}
|
|
468
|
-
): Promise<T> {
|
|
469
|
-
const attempts = options.attempts ?? 3;
|
|
470
|
-
const delay = options.delay ?? 100;
|
|
471
|
-
const backoff = options.backoff ?? false;
|
|
472
|
-
|
|
473
|
-
let lastError: Error | undefined;
|
|
474
|
-
|
|
475
|
-
for (let i = 0; i < attempts; i++) {
|
|
476
|
-
try {
|
|
477
|
-
return await fn();
|
|
478
|
-
} catch (error) {
|
|
479
|
-
lastError = error as Error;
|
|
480
|
-
if (i < attempts - 1) {
|
|
481
|
-
const waitTime = backoff ? delay * Math.pow(2, i) : delay;
|
|
482
|
-
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
throw lastError;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Create a spy on an object method
|
|
492
|
-
*/
|
|
493
|
-
export function spyOn<T extends object, K extends keyof T>(
|
|
494
|
-
obj: T,
|
|
495
|
-
method: K
|
|
496
|
-
): { calls: any[][]; restore: () => void } {
|
|
497
|
-
const original = obj[method];
|
|
498
|
-
const calls: any[][] = [];
|
|
499
|
-
|
|
500
|
-
(obj as any)[method] = (...args: any[]) => {
|
|
501
|
-
calls.push(args);
|
|
502
|
-
if (typeof original === 'function') {
|
|
503
|
-
return original.apply(obj, args);
|
|
504
|
-
}
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
return {
|
|
508
|
-
calls,
|
|
509
|
-
restore: () => {
|
|
510
|
-
(obj as any)[method] = original;
|
|
511
|
-
}
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* Create test request helper (compatible with common test frameworks)
|
|
517
|
-
*/
|
|
518
|
-
export function createTestRequest(app: Application) {
|
|
519
|
-
const client = new TestClient(app);
|
|
520
|
-
|
|
521
|
-
return {
|
|
522
|
-
async close() {
|
|
523
|
-
await client.stop();
|
|
524
|
-
},
|
|
525
|
-
|
|
526
|
-
get(path: string) {
|
|
527
|
-
return new RequestBuilder(client, 'GET', path);
|
|
528
|
-
},
|
|
529
|
-
|
|
530
|
-
post(path: string) {
|
|
531
|
-
return new RequestBuilder(client, 'POST', path);
|
|
532
|
-
},
|
|
533
|
-
|
|
534
|
-
put(path: string) {
|
|
535
|
-
return new RequestBuilder(client, 'PUT', path);
|
|
536
|
-
},
|
|
537
|
-
|
|
538
|
-
patch(path: string) {
|
|
539
|
-
return new RequestBuilder(client, 'PATCH', path);
|
|
540
|
-
},
|
|
541
|
-
|
|
542
|
-
delete(path: string) {
|
|
543
|
-
return new RequestBuilder(client, 'DELETE', path);
|
|
544
|
-
}
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
class RequestBuilder {
|
|
549
|
-
private client: TestClient;
|
|
550
|
-
private method: string;
|
|
551
|
-
private path: string;
|
|
552
|
-
private options: TestRequestOptions = {};
|
|
553
|
-
private expectedStatus?: number;
|
|
554
|
-
|
|
555
|
-
constructor(client: TestClient, method: string, path: string) {
|
|
556
|
-
this.client = client;
|
|
557
|
-
this.method = method;
|
|
558
|
-
this.path = path;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
set(key: string, value: string): this {
|
|
562
|
-
this.options.headers = { ...this.options.headers, [key]: value };
|
|
563
|
-
return this;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
auth(token: string): this {
|
|
567
|
-
return this.set('Authorization', `Bearer ${token}`);
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
send(body: any): this {
|
|
571
|
-
this.options.body = body;
|
|
572
|
-
return this;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
query(params: Record<string, string | number | boolean>): this {
|
|
576
|
-
this.options.query = params;
|
|
577
|
-
return this;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
timeout(ms: number): this {
|
|
581
|
-
this.options.timeout = ms;
|
|
582
|
-
return this;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
expect(status: number): this {
|
|
586
|
-
this.expectedStatus = status;
|
|
587
|
-
return this;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
async then<T>(
|
|
591
|
-
resolve: (value: TestResponse) => T | PromiseLike<T>,
|
|
592
|
-
reject?: (reason: any) => any
|
|
593
|
-
): Promise<T> {
|
|
594
|
-
try {
|
|
595
|
-
const response = await this.client.request(this.method, this.path, this.options);
|
|
596
|
-
|
|
597
|
-
if (this.expectedStatus !== undefined && response.status !== this.expectedStatus) {
|
|
598
|
-
throw new Error(
|
|
599
|
-
`Expected status ${this.expectedStatus} but got ${response.status}`
|
|
600
|
-
);
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
return resolve(response);
|
|
604
|
-
} catch (error) {
|
|
605
|
-
if (reject) {
|
|
606
|
-
return reject(error);
|
|
607
|
-
}
|
|
608
|
-
throw error;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|