@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,543 +0,0 @@
|
|
|
1
|
-
import type { EnhancedRequest, UploadedFile } from '../types'
|
|
2
|
-
import type { TestFile } from './types'
|
|
3
|
-
import { mock } from 'bun:test'
|
|
4
|
-
import { createMockRequest } from './test-request'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* File upload testing utilities
|
|
8
|
-
*/
|
|
9
|
-
export class FileUploadTester {
|
|
10
|
-
private request: EnhancedRequest
|
|
11
|
-
private files: UploadedFile[]
|
|
12
|
-
|
|
13
|
-
constructor(request?: EnhancedRequest) {
|
|
14
|
-
this.request = request || createMockRequest()
|
|
15
|
-
this.files = []
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Add a test file to the request
|
|
20
|
-
*/
|
|
21
|
-
addFile(testFile: TestFile): FileUploadTester {
|
|
22
|
-
const uploadedFile: UploadedFile = {
|
|
23
|
-
fieldName: testFile.fieldName,
|
|
24
|
-
originalName: testFile.filename,
|
|
25
|
-
filename: `test-${Date.now()}-${testFile.filename}`,
|
|
26
|
-
path: `/tmp/test-uploads/${testFile.filename}`,
|
|
27
|
-
size: testFile.content instanceof ArrayBuffer
|
|
28
|
-
? testFile.content.byteLength
|
|
29
|
-
: new TextEncoder().encode(testFile.content.toString()).length,
|
|
30
|
-
mimetype: testFile.mimetype || this.detectMimeType(testFile.filename),
|
|
31
|
-
buffer: testFile.content instanceof ArrayBuffer
|
|
32
|
-
? testFile.content
|
|
33
|
-
: new TextEncoder().encode(testFile.content.toString()).buffer as ArrayBuffer,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
this.files.push(uploadedFile)
|
|
37
|
-
this.request.files = [...(this.request.files || []), uploadedFile]
|
|
38
|
-
return this
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Add multiple test files
|
|
43
|
-
*/
|
|
44
|
-
addFiles(testFiles: TestFile[]): FileUploadTester {
|
|
45
|
-
testFiles.forEach(file => this.addFile(file))
|
|
46
|
-
return this
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Create an image file for testing
|
|
51
|
-
*/
|
|
52
|
-
addImage(fieldName: string, filename: string = 'test.jpg', size: number = 1024): FileUploadTester {
|
|
53
|
-
const content = new ArrayBuffer(size)
|
|
54
|
-
return this.addFile({
|
|
55
|
-
fieldName,
|
|
56
|
-
filename,
|
|
57
|
-
content,
|
|
58
|
-
mimetype: 'image/jpeg',
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Create a document file for testing
|
|
64
|
-
*/
|
|
65
|
-
addDocument(fieldName: string, filename: string = 'test.pdf', content: string = 'Test PDF content'): FileUploadTester {
|
|
66
|
-
return this.addFile({
|
|
67
|
-
fieldName,
|
|
68
|
-
filename,
|
|
69
|
-
content,
|
|
70
|
-
mimetype: 'application/pdf',
|
|
71
|
-
})
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Create a text file for testing
|
|
76
|
-
*/
|
|
77
|
-
addTextFile(fieldName: string, filename: string = 'test.txt', content: string = 'Test content'): FileUploadTester {
|
|
78
|
-
return this.addFile({
|
|
79
|
-
fieldName,
|
|
80
|
-
filename,
|
|
81
|
-
content,
|
|
82
|
-
mimetype: 'text/plain',
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Create a large file for testing upload limits
|
|
88
|
-
*/
|
|
89
|
-
addLargeFile(fieldName: string, sizeInMB: number = 10): FileUploadTester {
|
|
90
|
-
const size = sizeInMB * 1024 * 1024
|
|
91
|
-
const content = new ArrayBuffer(size)
|
|
92
|
-
return this.addFile({
|
|
93
|
-
fieldName,
|
|
94
|
-
filename: `large-file-${sizeInMB}mb.bin`,
|
|
95
|
-
content,
|
|
96
|
-
mimetype: 'application/octet-stream',
|
|
97
|
-
})
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Create a file with invalid extension
|
|
102
|
-
*/
|
|
103
|
-
addInvalidFile(fieldName: string, filename: string = 'malicious.exe'): FileUploadTester {
|
|
104
|
-
return this.addFile({
|
|
105
|
-
fieldName,
|
|
106
|
-
filename,
|
|
107
|
-
content: 'Potentially malicious content',
|
|
108
|
-
mimetype: 'application/x-executable',
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Set form data alongside files
|
|
114
|
-
*/
|
|
115
|
-
withFormData(data: Record<string, any>): FileUploadTester {
|
|
116
|
-
this.request.formBody = { ...this.request.formBody, ...data }
|
|
117
|
-
return this
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Get the configured request
|
|
122
|
-
*/
|
|
123
|
-
getRequest(): EnhancedRequest {
|
|
124
|
-
return this.request
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Get uploaded files
|
|
129
|
-
*/
|
|
130
|
-
getFiles(): UploadedFile[] {
|
|
131
|
-
return this.files
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Clear all files
|
|
136
|
-
*/
|
|
137
|
-
clearFiles(): FileUploadTester {
|
|
138
|
-
this.files = []
|
|
139
|
-
this.request.files = []
|
|
140
|
-
return this
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
private detectMimeType(filename: string): string {
|
|
144
|
-
const ext = filename.split('.').pop()?.toLowerCase()
|
|
145
|
-
const mimeTypes: Record<string, string> = {
|
|
146
|
-
jpg: 'image/jpeg',
|
|
147
|
-
jpeg: 'image/jpeg',
|
|
148
|
-
png: 'image/png',
|
|
149
|
-
gif: 'image/gif',
|
|
150
|
-
pdf: 'application/pdf',
|
|
151
|
-
txt: 'text/plain',
|
|
152
|
-
csv: 'text/csv',
|
|
153
|
-
json: 'application/json',
|
|
154
|
-
xml: 'application/xml',
|
|
155
|
-
zip: 'application/zip',
|
|
156
|
-
doc: 'application/msword',
|
|
157
|
-
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
158
|
-
}
|
|
159
|
-
return mimeTypes[ext || ''] || 'application/octet-stream'
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* File validation testing utilities
|
|
165
|
-
*/
|
|
166
|
-
export class FileValidationTester {
|
|
167
|
-
/**
|
|
168
|
-
* Test file size validation
|
|
169
|
-
*/
|
|
170
|
-
static testFileSize(file: UploadedFile, maxSize: number): boolean {
|
|
171
|
-
return file.size <= maxSize
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Test file type validation
|
|
176
|
-
*/
|
|
177
|
-
static testFileType(file: UploadedFile, allowedTypes: string[]): boolean {
|
|
178
|
-
return allowedTypes.includes(file.mimetype)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Test file extension validation
|
|
183
|
-
*/
|
|
184
|
-
static testFileExtension(file: UploadedFile, allowedExtensions: string[]): boolean {
|
|
185
|
-
const ext = file.originalName.split('.').pop()?.toLowerCase()
|
|
186
|
-
return ext ? allowedExtensions.includes(ext) : false
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Test filename validation (security)
|
|
191
|
-
*/
|
|
192
|
-
static testFilename(file: UploadedFile): boolean {
|
|
193
|
-
const filename = file.originalName
|
|
194
|
-
// Check for path traversal attempts
|
|
195
|
-
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
|
196
|
-
return false
|
|
197
|
-
}
|
|
198
|
-
// Check for null bytes
|
|
199
|
-
if (filename.includes('\0')) {
|
|
200
|
-
return false
|
|
201
|
-
}
|
|
202
|
-
// Check for reserved names (Windows)
|
|
203
|
-
const reservedNames = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']
|
|
204
|
-
const baseName = filename.split('.')[0].toUpperCase()
|
|
205
|
-
if (reservedNames.includes(baseName)) {
|
|
206
|
-
return false
|
|
207
|
-
}
|
|
208
|
-
return true
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Test image file validation
|
|
213
|
-
*/
|
|
214
|
-
static testImageFile(file: UploadedFile): boolean {
|
|
215
|
-
const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml']
|
|
216
|
-
return imageTypes.includes(file.mimetype)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Test document file validation
|
|
221
|
-
*/
|
|
222
|
-
static testDocumentFile(file: UploadedFile): boolean {
|
|
223
|
-
const documentTypes = [
|
|
224
|
-
'application/pdf',
|
|
225
|
-
'application/msword',
|
|
226
|
-
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
227
|
-
'text/plain',
|
|
228
|
-
'text/csv',
|
|
229
|
-
]
|
|
230
|
-
return documentTypes.includes(file.mimetype)
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/** Middleware function type */
|
|
235
|
-
export type MiddlewareFn = (req: EnhancedRequest, next: () => Promise<Response>) => Promise<Response>
|
|
236
|
-
|
|
237
|
-
/** File storage service interface */
|
|
238
|
-
export interface FileStorageService {
|
|
239
|
-
store: (file: UploadedFile, path?: string) => Promise<string>
|
|
240
|
-
delete: (path: string) => Promise<void>
|
|
241
|
-
exists: (path: string) => Promise<boolean>
|
|
242
|
-
getUrl: (path: string) => string
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/** Virus scanner interface */
|
|
246
|
-
export interface VirusScanResult {
|
|
247
|
-
clean: boolean
|
|
248
|
-
threat?: string
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
export interface VirusScanner {
|
|
252
|
-
scan: (file: UploadedFile) => Promise<VirusScanResult>
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/** Validation middleware options */
|
|
256
|
-
export interface ValidationMiddlewareOptions {
|
|
257
|
-
maxSize?: number
|
|
258
|
-
allowedTypes?: string[]
|
|
259
|
-
maxFiles?: number
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/** Image upload middleware options */
|
|
263
|
-
export interface ImageUploadMiddlewareOptions {
|
|
264
|
-
maxSize?: number
|
|
265
|
-
maxWidth?: number
|
|
266
|
-
maxHeight?: number
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* File upload mock factories
|
|
271
|
-
*/
|
|
272
|
-
export const fileUploadMocks: {
|
|
273
|
-
validationMiddleware: (options?: ValidationMiddlewareOptions) => MiddlewareFn
|
|
274
|
-
imageUploadMiddleware: (options?: ImageUploadMiddlewareOptions) => MiddlewareFn
|
|
275
|
-
storageService: FileStorageService
|
|
276
|
-
virusScanner: VirusScanner
|
|
277
|
-
} = {
|
|
278
|
-
/**
|
|
279
|
-
* Mock file upload middleware that validates files
|
|
280
|
-
*/
|
|
281
|
-
validationMiddleware: (options: ValidationMiddlewareOptions = {}): MiddlewareFn => {
|
|
282
|
-
const fn = async (req: EnhancedRequest, next: () => Promise<Response>): Promise<Response> => {
|
|
283
|
-
const files = req.files || []
|
|
284
|
-
const { maxSize = 5 * 1024 * 1024, allowedTypes = [], maxFiles = 10 } = options
|
|
285
|
-
|
|
286
|
-
// Check file count
|
|
287
|
-
if (files.length > maxFiles) {
|
|
288
|
-
return new Response(`Too many files. Maximum ${maxFiles} allowed.`, { status: 400 })
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Validate each file
|
|
292
|
-
for (const file of files) {
|
|
293
|
-
// Check file size
|
|
294
|
-
if (file.size > maxSize) {
|
|
295
|
-
return new Response(`File ${file.originalName} is too large. Maximum size: ${maxSize} bytes.`, { status: 400 })
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Check file type
|
|
299
|
-
if (allowedTypes.length > 0 && !allowedTypes.includes(file.mimetype)) {
|
|
300
|
-
return new Response(`File type ${file.mimetype} not allowed.`, { status: 400 })
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Check filename security
|
|
304
|
-
if (!FileValidationTester.testFilename(file)) {
|
|
305
|
-
return new Response(`Invalid filename: ${file.originalName}`, { status: 400 })
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return await next()
|
|
310
|
-
}
|
|
311
|
-
return mock(fn) as MiddlewareFn
|
|
312
|
-
},
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Mock image upload middleware
|
|
316
|
-
*/
|
|
317
|
-
imageUploadMiddleware: (options: ImageUploadMiddlewareOptions = {}): MiddlewareFn => {
|
|
318
|
-
const fn = async (req: EnhancedRequest, next: () => Promise<Response>): Promise<Response> => {
|
|
319
|
-
const files = req.files || []
|
|
320
|
-
const { maxSize = 2 * 1024 * 1024 } = options
|
|
321
|
-
|
|
322
|
-
for (const file of files) {
|
|
323
|
-
if (!FileValidationTester.testImageFile(file)) {
|
|
324
|
-
return new Response(`File ${file.originalName} is not a valid image.`, { status: 400 })
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if (file.size > maxSize) {
|
|
328
|
-
return new Response(`Image ${file.originalName} is too large.`, { status: 400 })
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
return await next()
|
|
333
|
-
}
|
|
334
|
-
return mock(fn) as MiddlewareFn
|
|
335
|
-
},
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Mock file storage service
|
|
339
|
-
*/
|
|
340
|
-
storageService: {
|
|
341
|
-
store: mock(async (file: UploadedFile, path?: string): Promise<string> => {
|
|
342
|
-
const storagePath = path || `/uploads/${Date.now()}-${file.filename}`
|
|
343
|
-
return storagePath
|
|
344
|
-
}),
|
|
345
|
-
|
|
346
|
-
delete: mock(async (_path: string): Promise<void> => {
|
|
347
|
-
// Mock file deletion
|
|
348
|
-
}),
|
|
349
|
-
|
|
350
|
-
exists: mock(async (path: string): Promise<boolean> => {
|
|
351
|
-
return !path.includes('not-found')
|
|
352
|
-
}),
|
|
353
|
-
|
|
354
|
-
getUrl: mock((path: string): string => {
|
|
355
|
-
return `https://example.com/storage${path}`
|
|
356
|
-
}),
|
|
357
|
-
},
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Mock virus scanner
|
|
361
|
-
*/
|
|
362
|
-
virusScanner: {
|
|
363
|
-
scan: mock(async (file: UploadedFile): Promise<VirusScanResult> => {
|
|
364
|
-
// Mock virus scanning - mark .exe files as threats
|
|
365
|
-
if (file.originalName.endsWith('.exe') || file.mimetype === 'application/x-executable') {
|
|
366
|
-
return { clean: false, threat: 'Potentially malicious executable' }
|
|
367
|
-
}
|
|
368
|
-
return { clean: true }
|
|
369
|
-
}),
|
|
370
|
-
},
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* File upload test scenarios
|
|
375
|
-
*/
|
|
376
|
-
export const fileUploadScenarios = {
|
|
377
|
-
/**
|
|
378
|
-
* Test successful single file upload
|
|
379
|
-
*/
|
|
380
|
-
singleFileUpload: (): FileUploadTester => {
|
|
381
|
-
return new FileUploadTester()
|
|
382
|
-
.addImage('avatar', 'profile.jpg')
|
|
383
|
-
.withFormData({ description: 'Profile picture' })
|
|
384
|
-
},
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Test multiple file upload
|
|
388
|
-
*/
|
|
389
|
-
multipleFileUpload: (): FileUploadTester => {
|
|
390
|
-
return new FileUploadTester()
|
|
391
|
-
.addImage('images', 'photo1.jpg')
|
|
392
|
-
.addImage('images', 'photo2.png')
|
|
393
|
-
.addDocument('documents', 'report.pdf')
|
|
394
|
-
.withFormData({ album: 'vacation' })
|
|
395
|
-
},
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Test file size limit exceeded
|
|
399
|
-
*/
|
|
400
|
-
fileSizeExceeded: (): FileUploadTester => {
|
|
401
|
-
return new FileUploadTester()
|
|
402
|
-
.addLargeFile('file', 50) // 50MB file
|
|
403
|
-
},
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* Test invalid file type
|
|
407
|
-
*/
|
|
408
|
-
invalidFileType: (): FileUploadTester => {
|
|
409
|
-
return new FileUploadTester()
|
|
410
|
-
.addInvalidFile('file', 'virus.exe')
|
|
411
|
-
},
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Test empty file upload
|
|
415
|
-
*/
|
|
416
|
-
emptyFileUpload: (): FileUploadTester => {
|
|
417
|
-
return new FileUploadTester()
|
|
418
|
-
.addFile({
|
|
419
|
-
fieldName: 'file',
|
|
420
|
-
filename: 'empty.txt',
|
|
421
|
-
content: '',
|
|
422
|
-
mimetype: 'text/plain',
|
|
423
|
-
})
|
|
424
|
-
},
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* Test file with malicious filename
|
|
428
|
-
*/
|
|
429
|
-
maliciousFilename: (): FileUploadTester => {
|
|
430
|
-
return new FileUploadTester()
|
|
431
|
-
.addFile({
|
|
432
|
-
fieldName: 'file',
|
|
433
|
-
filename: '../../../etc/passwd',
|
|
434
|
-
content: 'malicious content',
|
|
435
|
-
mimetype: 'text/plain',
|
|
436
|
-
})
|
|
437
|
-
},
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* File upload assertion helpers
|
|
442
|
-
*/
|
|
443
|
-
export const fileUploadAssertions = {
|
|
444
|
-
/**
|
|
445
|
-
* Assert that request has files
|
|
446
|
-
*/
|
|
447
|
-
hasFiles: (request: EnhancedRequest, count?: number): void => {
|
|
448
|
-
const files = request.files || []
|
|
449
|
-
if (count !== undefined && files.length !== count) {
|
|
450
|
-
throw new Error(`Expected ${count} files, but got ${files.length}`)
|
|
451
|
-
}
|
|
452
|
-
if (files.length === 0) {
|
|
453
|
-
throw new Error('Expected request to have files')
|
|
454
|
-
}
|
|
455
|
-
},
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* Assert that request has file with specific field name
|
|
459
|
-
*/
|
|
460
|
-
hasFileWithField: (request: EnhancedRequest, fieldName: string): void => {
|
|
461
|
-
const files = request.files || []
|
|
462
|
-
const hasField = files.some(file => file.fieldName === fieldName)
|
|
463
|
-
if (!hasField) {
|
|
464
|
-
throw new Error(`Expected file with field name '${fieldName}'`)
|
|
465
|
-
}
|
|
466
|
-
},
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Assert file properties
|
|
470
|
-
*/
|
|
471
|
-
fileMatches: (file: UploadedFile, expected: Partial<UploadedFile>): void => {
|
|
472
|
-
Object.entries(expected).forEach(([key, value]) => {
|
|
473
|
-
if (file[key as keyof UploadedFile] !== value) {
|
|
474
|
-
throw new Error(`Expected file.${key} to be '${value}', got '${file[key as keyof UploadedFile]}'`)
|
|
475
|
-
}
|
|
476
|
-
})
|
|
477
|
-
},
|
|
478
|
-
|
|
479
|
-
/**
|
|
480
|
-
* Assert file size is within limits
|
|
481
|
-
*/
|
|
482
|
-
fileSizeWithinLimit: (file: UploadedFile, maxSize: number): void => {
|
|
483
|
-
if (file.size > maxSize) {
|
|
484
|
-
throw new Error(`File size ${file.size} exceeds limit of ${maxSize}`)
|
|
485
|
-
}
|
|
486
|
-
},
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Assert file type is allowed
|
|
490
|
-
*/
|
|
491
|
-
fileTypeAllowed: (file: UploadedFile, allowedTypes: string[]): void => {
|
|
492
|
-
if (!allowedTypes.includes(file.mimetype)) {
|
|
493
|
-
throw new Error(`File type '${file.mimetype}' is not allowed. Allowed types: ${allowedTypes.join(', ')}`)
|
|
494
|
-
}
|
|
495
|
-
},
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* Factory functions
|
|
500
|
-
*/
|
|
501
|
-
export function createFileUploadTester(request?: EnhancedRequest): FileUploadTester {
|
|
502
|
-
return new FileUploadTester(request)
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* Helper to create test files
|
|
507
|
-
*/
|
|
508
|
-
export const testFiles = {
|
|
509
|
-
image: (fieldName: string = 'image', filename: string = 'test.jpg'): TestFile => ({
|
|
510
|
-
fieldName,
|
|
511
|
-
filename,
|
|
512
|
-
content: new ArrayBuffer(1024),
|
|
513
|
-
mimetype: 'image/jpeg',
|
|
514
|
-
}),
|
|
515
|
-
|
|
516
|
-
document: (fieldName: string = 'document', filename: string = 'test.pdf'): TestFile => ({
|
|
517
|
-
fieldName,
|
|
518
|
-
filename,
|
|
519
|
-
content: 'PDF content',
|
|
520
|
-
mimetype: 'application/pdf',
|
|
521
|
-
}),
|
|
522
|
-
|
|
523
|
-
text: (fieldName: string = 'file', filename: string = 'test.txt', content: string = 'Test content'): TestFile => ({
|
|
524
|
-
fieldName,
|
|
525
|
-
filename,
|
|
526
|
-
content,
|
|
527
|
-
mimetype: 'text/plain',
|
|
528
|
-
}),
|
|
529
|
-
|
|
530
|
-
large: (fieldName: string = 'file', sizeInMB: number = 10): TestFile => ({
|
|
531
|
-
fieldName,
|
|
532
|
-
filename: `large-${sizeInMB}mb.bin`,
|
|
533
|
-
content: new ArrayBuffer(sizeInMB * 1024 * 1024),
|
|
534
|
-
mimetype: 'application/octet-stream',
|
|
535
|
-
}),
|
|
536
|
-
|
|
537
|
-
malicious: (fieldName: string = 'file'): TestFile => ({
|
|
538
|
-
fieldName,
|
|
539
|
-
filename: '../../../malicious.exe',
|
|
540
|
-
content: 'malicious content',
|
|
541
|
-
mimetype: 'application/x-executable',
|
|
542
|
-
}),
|
|
543
|
-
}
|
package/src/testing/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export * from './auth-testing'
|
|
2
|
-
export * from './file-upload-testing'
|
|
3
|
-
export * from './middleware-testing'
|
|
4
|
-
export * from './model-binding-testing'
|
|
5
|
-
export * from './performance-testing'
|
|
6
|
-
export * from './test-client'
|
|
7
|
-
export * from './test-request'
|
|
8
|
-
export * from './test-response'
|
|
9
|
-
export * from './types'
|
|
10
|
-
export * from './websocket-testing'
|