@stacksjs/bun-router 0.0.17 → 0.0.18
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/cli.js +1 -1
- package/dist/container/service-provider.d.ts +3 -3
- package/dist/development/performance-profiler.d.ts +3 -3
- package/dist/development/route-debugger.d.ts +1 -1
- package/dist/development/route-inspector.d.ts +2 -2
- package/dist/observability/integration.d.ts +1 -1
- package/dist/response/macros.d.ts +1 -1
- package/dist/router/index.d.ts +7 -7
- package/dist/router/router.d.ts +0 -32
- package/dist/testing/performance-testing.d.ts +2 -2
- package/dist/testing/test-client.d.ts +1 -1
- package/dist/testing/websocket-testing.d.ts +1 -1
- package/package.json +2 -3
- package/src/auth.ts +0 -489
- package/src/cache/lru-cache.ts +0 -457
- package/src/cache/middleware-memoization.ts +0 -531
- package/src/cache/route-cache-warmer.ts +0 -486
- package/src/cache/sqlite-cache.ts +0 -783
- package/src/cache/streaming-cache.ts +0 -572
- package/src/cli/colors.ts +0 -29
- package/src/cli/index.ts +0 -287
- package/src/cli/middleware.ts +0 -291
- package/src/cli/openapi.ts +0 -407
- package/src/cli/router.ts +0 -188
- package/src/cli/routes.ts +0 -265
- package/src/cli/utils.ts +0 -531
- package/src/cli.ts +0 -5
- package/src/config.ts +0 -322
- package/src/container/container.ts +0 -786
- package/src/container/contextual-binding.ts +0 -606
- package/src/container/decorators.ts +0 -368
- package/src/container/index.ts +0 -49
- package/src/container/service-provider.ts +0 -596
- package/src/development/hot-reload.ts +0 -673
- package/src/development/index.ts +0 -499
- package/src/development/performance-profiler.ts +0 -717
- package/src/development/route-debugger.ts +0 -527
- package/src/development/route-inspector.ts +0 -749
- package/src/development/typescript-utilities.ts +0 -682
- package/src/docs.ts +0 -397
- package/src/errors/circuit-breaker.ts +0 -733
- package/src/errors/error-handler.ts +0 -569
- package/src/errors/error-reporting.ts +0 -672
- package/src/errors/exceptions.ts +0 -536
- package/src/errors/graceful-degradation.ts +0 -623
- package/src/errors/index.ts +0 -21
- package/src/errors/router-errors.ts +0 -632
- package/src/file-serving/static-files.ts +0 -718
- package/src/index.ts +0 -66
- package/src/middleware/auth.ts +0 -220
- package/src/middleware/content_security_policy.ts +0 -215
- package/src/middleware/cors.ts +0 -142
- package/src/middleware/csrf.ts +0 -166
- package/src/middleware/ddos_protection.ts +0 -279
- package/src/middleware/file_security.ts +0 -191
- package/src/middleware/file_upload.ts +0 -275
- package/src/middleware/helmet.ts +0 -268
- package/src/middleware/index.ts +0 -117
- package/src/middleware/input_validation.ts +0 -453
- package/src/middleware/json_body.ts +0 -37
- package/src/middleware/performance_alerting.ts +0 -540
- package/src/middleware/performance_dashboard.ts +0 -661
- package/src/middleware/performance_monitor.ts +0 -945
- package/src/middleware/pipeline.ts +0 -489
- package/src/middleware/rate_limit.ts +0 -251
- package/src/middleware/request_id.ts +0 -36
- package/src/middleware/request_signing.ts +0 -638
- package/src/middleware/request_tracer.ts +0 -653
- package/src/middleware/response_cache.ts +0 -778
- package/src/middleware/security.ts +0 -482
- package/src/middleware/security_suite.ts +0 -257
- package/src/middleware/session.ts +0 -91
- package/src/model-binding/index.ts +0 -18
- package/src/model-binding/model-middleware.ts +0 -425
- package/src/model-binding/model-registry.ts +0 -550
- package/src/model-binding.ts +0 -370
- package/src/model-resolver-factory.ts +0 -106
- package/src/observability/correlation.ts +0 -691
- package/src/observability/health-checks.ts +0 -729
- package/src/observability/index.ts +0 -184
- package/src/observability/integration.ts +0 -548
- package/src/observability/metrics.ts +0 -753
- package/src/observability/tracing.ts +0 -638
- package/src/optimization/bun-utilities.ts +0 -778
- package/src/query-builder-integration.ts +0 -137
- package/src/request/context.ts +0 -109
- package/src/request/enhanced-request.ts +0 -876
- package/src/request/macros.ts +0 -822
- package/src/response/macros.ts +0 -665
- package/src/response/response-factory.ts +0 -596
- package/src/router/api-routes.ts +0 -243
- package/src/router/file-based-routing.ts +0 -697
- package/src/router/file-streaming.ts +0 -383
- package/src/router/fluent-routing.ts +0 -866
- package/src/router/group-organization.ts +0 -229
- package/src/router/handler-resolver.ts +0 -327
- package/src/router/http-methods.ts +0 -407
- package/src/router/index.ts +0 -193
- package/src/router/middleware-groups.ts +0 -222
- package/src/router/middleware-integration.ts +0 -399
- package/src/router/middleware.ts +0 -234
- package/src/router/model-binding.ts +0 -215
- package/src/router/optimized-route-matching.ts +0 -302
- package/src/router/route-building.ts +0 -221
- package/src/router/route-compiler.ts +0 -691
- package/src/router/route-matching.ts +0 -368
- package/src/router/route-trie.ts +0 -450
- package/src/router/router.ts +0 -1616
- package/src/router/server.ts +0 -684
- package/src/router/validation-integration.ts +0 -449
- package/src/router/view-rendering.ts +0 -233
- package/src/router/websocket.ts +0 -100
- package/src/routing/route-caching.ts +0 -402
- package/src/routing/route-throttling.ts +0 -469
- package/src/routing/subdomain-routing.ts +0 -492
- package/src/session/database-store.ts +0 -109
- package/src/session/file-store.ts +0 -148
- package/src/session/index.ts +0 -244
- package/src/session/memory-store.ts +0 -88
- package/src/session/redis-store.ts +0 -93
- package/src/streaming/index.ts +0 -17
- package/src/streaming/sse-handler.ts +0 -482
- package/src/streaming/stream-handler.ts +0 -552
- package/src/testing/auth-testing.ts +0 -448
- package/src/testing/file-upload-testing.ts +0 -543
- package/src/testing/index.ts +0 -10
- package/src/testing/middleware-testing.ts +0 -322
- package/src/testing/model-binding-testing.ts +0 -645
- package/src/testing/performance-testing.ts +0 -738
- package/src/testing/test-client.ts +0 -310
- package/src/testing/test-request.ts +0 -315
- package/src/testing/test-response.ts +0 -332
- package/src/testing/types.ts +0 -224
- package/src/testing/websocket-testing.ts +0 -590
- package/src/types/controller-types.ts +0 -385
- package/src/types/core.ts +0 -683
- package/src/types/middleware-types.ts +0 -422
- package/src/types/request-response-augmentation.ts +0 -489
- package/src/types/route-inference.ts +0 -372
- package/src/types.ts +0 -2123
- package/src/url.ts +0 -131
- package/src/utils/index.ts +0 -1
- package/src/utils/query-preservation.ts +0 -201
- package/src/utils.ts +0 -395
- package/src/validation/validator.ts +0 -688
- package/src/websocket/clustering.ts +0 -769
|
@@ -1,645 +0,0 @@
|
|
|
1
|
-
import type { EnhancedRequest } from '../types'
|
|
2
|
-
import type { ModelBindingTestOptions } from './types'
|
|
3
|
-
import { mock } from 'bun:test'
|
|
4
|
-
import { createMockRequest } from './test-request'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Generic mock model base class for type annotations
|
|
8
|
-
*/
|
|
9
|
-
export class GenericMockModel {
|
|
10
|
-
public id: any
|
|
11
|
-
public attributes: Record<string, any>
|
|
12
|
-
|
|
13
|
-
constructor(data: Record<string, any> = {}) {
|
|
14
|
-
this.id = data.id
|
|
15
|
-
this.attributes = data
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
static modelName: string = 'GenericMockModel'
|
|
19
|
-
|
|
20
|
-
static async find(_id: any): Promise<GenericMockModel | null> {
|
|
21
|
-
return null
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
static async findOrFail(_id: any): Promise<GenericMockModel> {
|
|
25
|
-
throw new Error('Not implemented')
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
static async where(_conditions: Record<string, any>): Promise<GenericMockModel[]> {
|
|
29
|
-
return []
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
static async create(_data: Record<string, any>): Promise<GenericMockModel> {
|
|
33
|
-
throw new Error('Not implemented')
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async save(): Promise<GenericMockModel> {
|
|
37
|
-
return this
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async delete(): Promise<void> {
|
|
41
|
-
// Mock deletion
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
toJSON(): Record<string, any> {
|
|
45
|
-
return { id: this.id, ...this.attributes }
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Mock User class for type annotations
|
|
51
|
-
*/
|
|
52
|
-
export class MockUserClass {
|
|
53
|
-
public id: number
|
|
54
|
-
public email: string
|
|
55
|
-
public name: string
|
|
56
|
-
|
|
57
|
-
constructor(data: { id: number, email?: string, name?: string } = { id: 1 }) {
|
|
58
|
-
this.id = data.id
|
|
59
|
-
this.email = data.email || 'test@example.com'
|
|
60
|
-
this.name = data.name || 'Test User'
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
static async find(_id: any): Promise<MockUserClass | null> {
|
|
64
|
-
return null
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
static async findOrFail(_id: any): Promise<MockUserClass> {
|
|
68
|
-
throw new Error('Not implemented')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
toJSON(): Record<string, any> {
|
|
72
|
-
return { id: this.id, email: this.email, name: this.name }
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Mock Post class for type annotations
|
|
78
|
-
*/
|
|
79
|
-
export class MockPostClass {
|
|
80
|
-
public id: number
|
|
81
|
-
public title: string
|
|
82
|
-
public content: string
|
|
83
|
-
public userId: number
|
|
84
|
-
|
|
85
|
-
constructor(data: { id: number, title?: string, content?: string, userId?: number } = { id: 1 }) {
|
|
86
|
-
this.id = data.id
|
|
87
|
-
this.title = data.title || 'Test Post'
|
|
88
|
-
this.content = data.content || 'Test content'
|
|
89
|
-
this.userId = data.userId || 1
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
static async find(_id: any): Promise<MockPostClass | null> {
|
|
93
|
-
return null
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
static async findOrFail(_id: any): Promise<MockPostClass> {
|
|
97
|
-
throw new Error('Not implemented')
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async belongsTo(_userId: number): Promise<boolean> {
|
|
101
|
-
return false
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
toJSON(): Record<string, any> {
|
|
105
|
-
return { id: this.id, title: this.title, content: this.content, userId: this.userId }
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Model binding testing utilities
|
|
111
|
-
*/
|
|
112
|
-
export class ModelBindingTester {
|
|
113
|
-
private request: EnhancedRequest
|
|
114
|
-
private options: ModelBindingTestOptions
|
|
115
|
-
|
|
116
|
-
constructor(options: ModelBindingTestOptions = {}) {
|
|
117
|
-
this.options = options
|
|
118
|
-
this.request = createMockRequest()
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Set the request for testing
|
|
123
|
-
*/
|
|
124
|
-
withRequest(request: EnhancedRequest): ModelBindingTester {
|
|
125
|
-
this.request = request
|
|
126
|
-
return this
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Set route parameters
|
|
131
|
-
*/
|
|
132
|
-
withParams(params: Record<string, string>): ModelBindingTester {
|
|
133
|
-
this.request.params = { ...this.request.params, ...params }
|
|
134
|
-
return this
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Set the model class to test
|
|
139
|
-
*/
|
|
140
|
-
withModel(modelClass: any): ModelBindingTester {
|
|
141
|
-
this.options.modelClass = modelClass
|
|
142
|
-
return this
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Set the route key for model binding
|
|
147
|
-
*/
|
|
148
|
-
withRouteKey(routeKey: string): ModelBindingTester {
|
|
149
|
-
this.options.routeKey = routeKey
|
|
150
|
-
return this
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Set constraints for model binding
|
|
155
|
-
*/
|
|
156
|
-
withConstraints(constraints: Record<string, any>): ModelBindingTester {
|
|
157
|
-
this.options.constraints = constraints
|
|
158
|
-
return this
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Set a custom resolver function
|
|
163
|
-
*/
|
|
164
|
-
withResolver(resolver: (value: string) => Promise<any>): ModelBindingTester {
|
|
165
|
-
this.options.customResolver = resolver
|
|
166
|
-
return this
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Test model resolution
|
|
171
|
-
*/
|
|
172
|
-
async testResolution(paramValue: string): Promise<any> {
|
|
173
|
-
if (this.options.customResolver) {
|
|
174
|
-
return await this.options.customResolver(paramValue)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Mock default resolution behavior
|
|
178
|
-
if (this.options.modelClass) {
|
|
179
|
-
return new (this.options.modelClass as any)({ id: paramValue })
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return { id: paramValue }
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Test model not found scenario
|
|
187
|
-
*/
|
|
188
|
-
async testNotFound(paramValue: string): Promise<void> {
|
|
189
|
-
try {
|
|
190
|
-
await this.testResolution(paramValue)
|
|
191
|
-
throw new Error('Expected model not found error')
|
|
192
|
-
}
|
|
193
|
-
catch (error) {
|
|
194
|
-
if (error instanceof Error && error.message !== 'Model not found') {
|
|
195
|
-
throw error
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Get the configured request
|
|
202
|
-
*/
|
|
203
|
-
getRequest(): EnhancedRequest {
|
|
204
|
-
return this.request
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Mock model factories
|
|
210
|
-
*/
|
|
211
|
-
export const modelMocks = {
|
|
212
|
-
/**
|
|
213
|
-
* Create a mock model class
|
|
214
|
-
*/
|
|
215
|
-
createModel: (name: string, attributes: Record<string, any> = {}): typeof GenericMockModel => {
|
|
216
|
-
return class MockModel {
|
|
217
|
-
public id: any
|
|
218
|
-
public attributes: Record<string, any>
|
|
219
|
-
|
|
220
|
-
constructor(data: Record<string, any> = {}) {
|
|
221
|
-
this.id = data.id
|
|
222
|
-
this.attributes = { ...attributes, ...data }
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
static modelName: string = name
|
|
226
|
-
|
|
227
|
-
static async find(id: any): Promise<MockModel | null> {
|
|
228
|
-
if (id === 'not-found')
|
|
229
|
-
return null
|
|
230
|
-
return new MockModel({ id, ...attributes })
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
static async findOrFail(id: any): Promise<MockModel> {
|
|
234
|
-
const model = await this.find(id)
|
|
235
|
-
if (!model)
|
|
236
|
-
throw new Error('Model not found')
|
|
237
|
-
return model
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
static async where(conditions: Record<string, any>): Promise<MockModel[]> {
|
|
241
|
-
return [new MockModel({ id: 1, ...attributes, ...conditions })]
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
static async create(data: Record<string, any>): Promise<MockModel> {
|
|
245
|
-
return new MockModel({ id: Date.now(), ...data })
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
async save(): Promise<MockModel> {
|
|
249
|
-
return this
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async delete(): Promise<void> {
|
|
253
|
-
// Mock deletion
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
toJSON(): Record<string, any> {
|
|
257
|
-
return { id: this.id, ...this.attributes }
|
|
258
|
-
}
|
|
259
|
-
} as typeof GenericMockModel
|
|
260
|
-
},
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Create a User model mock
|
|
264
|
-
*/
|
|
265
|
-
User: (class MockUser {
|
|
266
|
-
public id: number
|
|
267
|
-
public email: string
|
|
268
|
-
public name: string
|
|
269
|
-
|
|
270
|
-
constructor(data: { id: number, email?: string, name?: string } = { id: 1 }) {
|
|
271
|
-
this.id = data.id
|
|
272
|
-
this.email = data.email || 'test@example.com'
|
|
273
|
-
this.name = data.name || 'Test User'
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
static async find(id: any): Promise<MockUser | null> {
|
|
277
|
-
if (id === 'not-found')
|
|
278
|
-
return null
|
|
279
|
-
return new MockUser({ id: Number.parseInt(id) })
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
static async findOrFail(id: any): Promise<MockUser> {
|
|
283
|
-
const user = await this.find(id)
|
|
284
|
-
if (!user)
|
|
285
|
-
throw new Error('User not found')
|
|
286
|
-
return user
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
toJSON(): Record<string, any> {
|
|
290
|
-
return { id: this.id, email: this.email, name: this.name }
|
|
291
|
-
}
|
|
292
|
-
}) as typeof MockUserClass,
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Create a Post model mock
|
|
296
|
-
*/
|
|
297
|
-
Post: (class MockPost {
|
|
298
|
-
public id: number
|
|
299
|
-
public title: string
|
|
300
|
-
public content: string
|
|
301
|
-
public userId: number
|
|
302
|
-
|
|
303
|
-
constructor(data: { id: number, title?: string, content?: string, userId?: number } = { id: 1 }) {
|
|
304
|
-
this.id = data.id
|
|
305
|
-
this.title = data.title || 'Test Post'
|
|
306
|
-
this.content = data.content || 'Test content'
|
|
307
|
-
this.userId = data.userId || 1
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
static async find(id: any): Promise<MockPost | null> {
|
|
311
|
-
if (id === 'not-found')
|
|
312
|
-
return null
|
|
313
|
-
return new MockPost({ id: Number.parseInt(id) })
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
static async findOrFail(id: any): Promise<MockPost> {
|
|
317
|
-
const post = await this.find(id)
|
|
318
|
-
if (!post)
|
|
319
|
-
throw new Error('Post not found')
|
|
320
|
-
return post
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
async belongsTo(userId: number): Promise<boolean> {
|
|
324
|
-
return this.userId === userId
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
toJSON(): Record<string, any> {
|
|
328
|
-
return { id: this.id, title: this.title, content: this.content, userId: this.userId }
|
|
329
|
-
}
|
|
330
|
-
}) as typeof MockPostClass,
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/** Model constraint function type */
|
|
334
|
-
export type ModelConstraintFn = (_model: any, _request: EnhancedRequest) => Promise<boolean>
|
|
335
|
-
|
|
336
|
-
/** Simple model constraint function type (no request) */
|
|
337
|
-
export type SimpleModelConstraintFn = (_model: any) => Promise<boolean>
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Model binding constraint helpers
|
|
341
|
-
*/
|
|
342
|
-
export const constraintHelpers: {
|
|
343
|
-
belongsTo: (userIdField?: string) => ModelConstraintFn
|
|
344
|
-
belongsToUser: () => ModelConstraintFn
|
|
345
|
-
published: () => SimpleModelConstraintFn
|
|
346
|
-
visible: () => SimpleModelConstraintFn
|
|
347
|
-
custom: (constraintFn: ModelConstraintFn) => ModelConstraintFn
|
|
348
|
-
combine: (...constraints: ModelConstraintFn[]) => ModelConstraintFn
|
|
349
|
-
} = {
|
|
350
|
-
/**
|
|
351
|
-
* Create a belongsTo constraint
|
|
352
|
-
*/
|
|
353
|
-
belongsTo: (userIdField: string = 'userId'): ModelConstraintFn => {
|
|
354
|
-
const fn = async (model: any, request: EnhancedRequest): Promise<boolean> => {
|
|
355
|
-
if (!request.user)
|
|
356
|
-
return false
|
|
357
|
-
return model[userIdField] === request.user.id
|
|
358
|
-
}
|
|
359
|
-
return mock(fn)
|
|
360
|
-
},
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Create a belongsToUser constraint
|
|
364
|
-
*/
|
|
365
|
-
belongsToUser: (): ModelConstraintFn => {
|
|
366
|
-
const fn = async (model: any, request: EnhancedRequest): Promise<boolean> => {
|
|
367
|
-
if (!request.user)
|
|
368
|
-
return false
|
|
369
|
-
return model.userId === request.user.id
|
|
370
|
-
}
|
|
371
|
-
return mock(fn)
|
|
372
|
-
},
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Create a published constraint
|
|
376
|
-
*/
|
|
377
|
-
published: (): SimpleModelConstraintFn => {
|
|
378
|
-
const fn = async (model: any): Promise<boolean> => {
|
|
379
|
-
return model.published === true || model.status === 'published'
|
|
380
|
-
}
|
|
381
|
-
return mock(fn)
|
|
382
|
-
},
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Create a visible constraint
|
|
386
|
-
*/
|
|
387
|
-
visible: (): SimpleModelConstraintFn => {
|
|
388
|
-
const fn = async (model: any): Promise<boolean> => {
|
|
389
|
-
return model.visible !== false && model.hidden !== true
|
|
390
|
-
}
|
|
391
|
-
return mock(fn)
|
|
392
|
-
},
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Create a custom constraint
|
|
396
|
-
*/
|
|
397
|
-
custom: (constraintFn: ModelConstraintFn): ModelConstraintFn => {
|
|
398
|
-
return mock(constraintFn)
|
|
399
|
-
},
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Combine multiple constraints with AND logic
|
|
403
|
-
*/
|
|
404
|
-
combine: (...constraints: ModelConstraintFn[]): ModelConstraintFn => {
|
|
405
|
-
const fn = async (model: any, request: EnhancedRequest): Promise<boolean> => {
|
|
406
|
-
for (const constraint of constraints) {
|
|
407
|
-
if (!(await constraint(model, request))) {
|
|
408
|
-
return false
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
return true
|
|
412
|
-
}
|
|
413
|
-
return mock(fn)
|
|
414
|
-
},
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Model resolver factory for testing
|
|
419
|
-
*/
|
|
420
|
-
export class ModelResolverTester {
|
|
421
|
-
private resolvers: Map<string, (value: string) => Promise<any>> = new Map()
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Register a model resolver
|
|
425
|
-
*/
|
|
426
|
-
register(key: string, resolver: (value: string) => Promise<any>): ModelResolverTester {
|
|
427
|
-
this.resolvers.set(key, mock(resolver))
|
|
428
|
-
return this
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Register a model class resolver
|
|
433
|
-
*/
|
|
434
|
-
registerModel(key: string, modelClass: any): ModelResolverTester {
|
|
435
|
-
const resolver = mock(async (value: string) => {
|
|
436
|
-
if (modelClass.find) {
|
|
437
|
-
const model = await modelClass.find(value)
|
|
438
|
-
if (!model)
|
|
439
|
-
throw new Error(`${modelClass.name || 'Model'} not found`)
|
|
440
|
-
return model
|
|
441
|
-
}
|
|
442
|
-
return new (modelClass as any)({ id: value })
|
|
443
|
-
})
|
|
444
|
-
|
|
445
|
-
this.resolvers.set(key, resolver)
|
|
446
|
-
return this
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* Test model resolution
|
|
451
|
-
*/
|
|
452
|
-
async resolve(key: string, value: string): Promise<any> {
|
|
453
|
-
const resolver = this.resolvers.get(key)
|
|
454
|
-
if (!resolver) {
|
|
455
|
-
throw new Error(`No resolver registered for key: ${key}`)
|
|
456
|
-
}
|
|
457
|
-
return await resolver(value)
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Test all registered resolvers
|
|
462
|
-
*/
|
|
463
|
-
async testAll(testValue: string = '1'): Promise<Record<string, any>> {
|
|
464
|
-
const results: Record<string, any> = {}
|
|
465
|
-
|
|
466
|
-
for (const [key, resolver] of this.resolvers) {
|
|
467
|
-
try {
|
|
468
|
-
results[key] = await resolver(testValue)
|
|
469
|
-
}
|
|
470
|
-
catch (error) {
|
|
471
|
-
results[key] = { error: error instanceof Error ? error.message : 'Unknown error' }
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
return results
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Get all registered resolver keys
|
|
480
|
-
*/
|
|
481
|
-
getKeys(): string[] {
|
|
482
|
-
return Array.from(this.resolvers.keys())
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Clear all resolvers
|
|
487
|
-
*/
|
|
488
|
-
clear(): ModelResolverTester {
|
|
489
|
-
this.resolvers.clear()
|
|
490
|
-
return this
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/** Route handler function type */
|
|
495
|
-
export type RouteHandlerFn = (_request: EnhancedRequest) => Promise<Response>
|
|
496
|
-
|
|
497
|
-
/** Binding middleware function type */
|
|
498
|
-
export type BindingMiddlewareFn = (request: EnhancedRequest, next: () => Promise<Response>) => Promise<Response>
|
|
499
|
-
|
|
500
|
-
/**
|
|
501
|
-
* Route model binding test helpers
|
|
502
|
-
*/
|
|
503
|
-
export const routeModelHelpers: {
|
|
504
|
-
testRoute: (path: string, params: Record<string, string>, modelBindings: Record<string, any>) => Promise<EnhancedRequest>
|
|
505
|
-
createHandler: (expectedModels: string[]) => RouteHandlerFn
|
|
506
|
-
testBindingMiddleware: (bindings: Record<string, any>) => BindingMiddlewareFn
|
|
507
|
-
} = {
|
|
508
|
-
/**
|
|
509
|
-
* Test route with model binding
|
|
510
|
-
*/
|
|
511
|
-
testRoute: async (
|
|
512
|
-
path: string,
|
|
513
|
-
params: Record<string, string>,
|
|
514
|
-
modelBindings: Record<string, any>,
|
|
515
|
-
): Promise<EnhancedRequest> => {
|
|
516
|
-
const request = createMockRequest('GET', path)
|
|
517
|
-
request.params = params
|
|
518
|
-
|
|
519
|
-
// Simulate model binding resolution
|
|
520
|
-
for (const [key, model] of Object.entries(modelBindings)) {
|
|
521
|
-
request.context = request.context || {}
|
|
522
|
-
request.context[key] = model
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
return request
|
|
526
|
-
},
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Create a route handler that expects model binding
|
|
530
|
-
*/
|
|
531
|
-
createHandler: (expectedModels: string[]): RouteHandlerFn => {
|
|
532
|
-
const fn = async (request: EnhancedRequest): Promise<Response> => {
|
|
533
|
-
const context = request.context || {}
|
|
534
|
-
|
|
535
|
-
for (const modelKey of expectedModels) {
|
|
536
|
-
if (!context[modelKey]) {
|
|
537
|
-
return new Response(`Model ${modelKey} not found`, { status: 404 })
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
return new Response(JSON.stringify({
|
|
542
|
-
params: request.params,
|
|
543
|
-
models: Object.fromEntries(
|
|
544
|
-
expectedModels.map(key => [key, context[key]]),
|
|
545
|
-
),
|
|
546
|
-
}), {
|
|
547
|
-
headers: { 'Content-Type': 'application/json' },
|
|
548
|
-
})
|
|
549
|
-
}
|
|
550
|
-
return mock(fn)
|
|
551
|
-
},
|
|
552
|
-
|
|
553
|
-
/**
|
|
554
|
-
* Test model binding middleware
|
|
555
|
-
*/
|
|
556
|
-
testBindingMiddleware: (bindings: Record<string, any>): BindingMiddlewareFn => {
|
|
557
|
-
const fn = async (request: EnhancedRequest, next: () => Promise<Response>): Promise<Response> => {
|
|
558
|
-
request.context = request.context || {}
|
|
559
|
-
|
|
560
|
-
for (const [key, resolver] of Object.entries(bindings)) {
|
|
561
|
-
const paramValue = request.params[key]
|
|
562
|
-
if (paramValue) {
|
|
563
|
-
try {
|
|
564
|
-
if (typeof resolver === 'function') {
|
|
565
|
-
request.context[key] = await resolver(paramValue)
|
|
566
|
-
}
|
|
567
|
-
else {
|
|
568
|
-
request.context[key] = resolver
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
catch (error) {
|
|
572
|
-
console.error(`Error resolving model ${key}:`, error)
|
|
573
|
-
return new Response(`${key} not found`, { status: 404 })
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
return await next()
|
|
579
|
-
}
|
|
580
|
-
return mock(fn)
|
|
581
|
-
},
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
/**
|
|
585
|
-
* Factory functions
|
|
586
|
-
*/
|
|
587
|
-
export function createModelBindingTester(options?: ModelBindingTestOptions): ModelBindingTester {
|
|
588
|
-
return new ModelBindingTester(options)
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
export function createModelResolverTester(): ModelResolverTester {
|
|
592
|
-
return new ModelResolverTester()
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
/** User scope constraint function */
|
|
596
|
-
export type UserScopeConstraint = (_model: any, _user: any) => Promise<boolean>
|
|
597
|
-
|
|
598
|
-
/** Default user scope constraint */
|
|
599
|
-
const defaultUserScopeConstraint: UserScopeConstraint = async (m, u) => m.userId === u.id
|
|
600
|
-
|
|
601
|
-
/**
|
|
602
|
-
* Test utilities for scoped model binding
|
|
603
|
-
*/
|
|
604
|
-
export const scopedBindingHelpers: {
|
|
605
|
-
testUserScoped: (model: any, user: any, constraint?: UserScopeConstraint) => Promise<boolean>
|
|
606
|
-
testRoleAccess: (model: any, user: any, requiredRoles: string[]) => Promise<boolean>
|
|
607
|
-
testPermissionAccess: (model: any, user: any, requiredPermissions: string[]) => Promise<boolean>
|
|
608
|
-
} = {
|
|
609
|
-
/**
|
|
610
|
-
* Test user-scoped model binding
|
|
611
|
-
*/
|
|
612
|
-
testUserScoped: async (
|
|
613
|
-
model: any,
|
|
614
|
-
user: any,
|
|
615
|
-
constraint: UserScopeConstraint = defaultUserScopeConstraint,
|
|
616
|
-
): Promise<boolean> => {
|
|
617
|
-
return await constraint(model, user)
|
|
618
|
-
},
|
|
619
|
-
|
|
620
|
-
/**
|
|
621
|
-
* Test role-based model access
|
|
622
|
-
*/
|
|
623
|
-
testRoleAccess: async (
|
|
624
|
-
model: any,
|
|
625
|
-
user: any,
|
|
626
|
-
requiredRoles: string[],
|
|
627
|
-
): Promise<boolean> => {
|
|
628
|
-
if (!user.roles)
|
|
629
|
-
return false
|
|
630
|
-
return requiredRoles.some(role => user.roles.includes(role))
|
|
631
|
-
},
|
|
632
|
-
|
|
633
|
-
/**
|
|
634
|
-
* Test permission-based model access
|
|
635
|
-
*/
|
|
636
|
-
testPermissionAccess: async (
|
|
637
|
-
model: any,
|
|
638
|
-
user: any,
|
|
639
|
-
requiredPermissions: string[],
|
|
640
|
-
): Promise<boolean> => {
|
|
641
|
-
if (!user.permissions)
|
|
642
|
-
return false
|
|
643
|
-
return requiredPermissions.every(permission => user.permissions.includes(permission))
|
|
644
|
-
},
|
|
645
|
-
}
|