@kiyasov/platform-hono 1.6.1 → 2.0.1
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/.claude/settings.local.json +7 -1
- package/dist/cjs/src/adapters/hono-adapter.d.ts +1 -0
- package/dist/cjs/src/adapters/hono-adapter.js +14 -3
- package/dist/cjs/src/adapters/hono-adapter.js.map +1 -1
- package/dist/cjs/src/drivers/graphQLUpload/GraphQLUpload.d.ts +1 -1
- package/dist/cjs/src/drivers/graphQLUpload/Upload.d.ts +11 -8
- package/dist/cjs/src/drivers/graphQLUpload/Upload.js.map +1 -1
- package/dist/cjs/src/drivers/graphQLUpload/fs-capacitor.d.ts +20 -8
- package/dist/cjs/src/drivers/graphQLUpload/fs-capacitor.js +111 -58
- package/dist/cjs/src/drivers/graphQLUpload/fs-capacitor.js.map +1 -1
- package/dist/cjs/src/drivers/graphQLUpload/index.d.ts +9 -3
- package/dist/cjs/src/drivers/graphQLUpload/index.js +21 -3
- package/dist/cjs/src/drivers/graphQLUpload/index.js.map +1 -1
- package/dist/cjs/src/drivers/graphQLUpload/processRequest.d.ts +8 -1
- package/dist/cjs/src/drivers/graphQLUpload/processRequest.js +43 -37
- package/dist/cjs/src/drivers/graphQLUpload/processRequest.js.map +1 -1
- package/dist/cjs/src/drivers/graphQLUpload/storage/capacitor-storage.d.ts +15 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/capacitor-storage.js +47 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/capacitor-storage.js.map +1 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/index.d.ts +3 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/index.js +20 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/index.js.map +1 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/memory-storage.d.ts +13 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/memory-storage.js +31 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/memory-storage.js.map +1 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/storage.d.ts +17 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/storage.js +3 -0
- package/dist/cjs/src/drivers/graphQLUpload/storage/storage.js.map +1 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/file.d.ts +6 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/file.js +62 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/file.js.map +1 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/index.d.ts +2 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/index.js +19 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/index.js.map +1 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/validators.d.ts +18 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/validators.js +171 -0
- package/dist/cjs/src/drivers/graphQLUpload/utils/validators.js.map +1 -0
- package/dist/cjs/src/multer/index.d.ts +1 -0
- package/dist/cjs/src/multer/index.js +1 -0
- package/dist/cjs/src/multer/index.js.map +1 -1
- package/dist/cjs/src/multer/interceptors/any-files-interceptor.d.ts +2 -2
- package/dist/cjs/src/multer/interceptors/any-files-interceptor.js +6 -23
- package/dist/cjs/src/multer/interceptors/any-files-interceptor.js.map +1 -1
- package/dist/cjs/src/multer/interceptors/base-interceptor.d.ts +6 -0
- package/dist/cjs/src/multer/interceptors/base-interceptor.js +26 -0
- package/dist/cjs/src/multer/interceptors/base-interceptor.js.map +1 -0
- package/dist/cjs/src/multer/interceptors/file-fields-interceptor.d.ts +2 -2
- package/dist/cjs/src/multer/interceptors/file-fields-interceptor.js +7 -24
- package/dist/cjs/src/multer/interceptors/file-fields-interceptor.js.map +1 -1
- package/dist/cjs/src/multer/interceptors/file-interceptor.d.ts +2 -2
- package/dist/cjs/src/multer/interceptors/file-interceptor.js +6 -23
- package/dist/cjs/src/multer/interceptors/file-interceptor.js.map +1 -1
- package/dist/cjs/src/multer/interceptors/files-interceptor.d.ts +2 -2
- package/dist/cjs/src/multer/interceptors/files-interceptor.js +6 -23
- package/dist/cjs/src/multer/interceptors/files-interceptor.js.map +1 -1
- package/dist/cjs/src/multer/interceptors/index.d.ts +1 -0
- package/dist/cjs/src/multer/interceptors/index.js +1 -0
- package/dist/cjs/src/multer/interceptors/index.js.map +1 -1
- package/dist/cjs/src/multer/multipart/handlers/any-files.d.ts +2 -8
- package/dist/cjs/src/multer/multipart/handlers/any-files.js +12 -25
- package/dist/cjs/src/multer/multipart/handlers/any-files.js.map +1 -1
- package/dist/cjs/src/multer/multipart/handlers/base-handler.d.ts +42 -0
- package/dist/cjs/src/multer/multipart/handlers/base-handler.js +106 -0
- package/dist/cjs/src/multer/multipart/handlers/base-handler.js.map +1 -0
- package/dist/cjs/src/multer/multipart/handlers/file-fields.d.ts +3 -10
- package/dist/cjs/src/multer/multipart/handlers/file-fields.js +19 -33
- package/dist/cjs/src/multer/multipart/handlers/file-fields.js.map +1 -1
- package/dist/cjs/src/multer/multipart/handlers/index.d.ts +6 -1
- package/dist/cjs/src/multer/multipart/handlers/index.js +13 -0
- package/dist/cjs/src/multer/multipart/handlers/index.js.map +1 -1
- package/dist/cjs/src/multer/multipart/handlers/multiple-files.d.ts +2 -8
- package/dist/cjs/src/multer/multipart/handlers/multiple-files.js +18 -36
- package/dist/cjs/src/multer/multipart/handlers/multiple-files.js.map +1 -1
- package/dist/cjs/src/multer/multipart/handlers/single-file.d.ts +2 -8
- package/dist/cjs/src/multer/multipart/handlers/single-file.js +11 -33
- package/dist/cjs/src/multer/multipart/handlers/single-file.js.map +1 -1
- package/dist/cjs/src/multer/multipart/index.d.ts +1 -1
- package/dist/cjs/src/multer/multipart/options.d.ts +10 -16
- package/dist/cjs/src/multer/multipart/options.js.map +1 -1
- package/dist/cjs/src/multer/multipart/request.js +14 -3
- package/dist/cjs/src/multer/multipart/request.js.map +1 -1
- package/dist/cjs/src/multer/storage/disk-storage.d.ts +2 -1
- package/dist/cjs/src/multer/storage/disk-storage.js +2 -1
- package/dist/cjs/src/multer/storage/disk-storage.js.map +1 -1
- package/dist/cjs/src/multer/storage/memory-storage.d.ts +2 -11
- package/dist/cjs/src/multer/storage/memory-storage.js +6 -4
- package/dist/cjs/src/multer/storage/memory-storage.js.map +1 -1
- package/dist/cjs/src/multer/storage/storage.d.ts +6 -5
- package/dist/cjs/src/multer/utils/file.d.ts +6 -0
- package/dist/cjs/src/multer/utils/file.js +62 -0
- package/dist/cjs/src/multer/utils/file.js.map +1 -0
- package/dist/cjs/src/multer/utils/index.d.ts +2 -0
- package/dist/cjs/src/multer/utils/index.js +19 -0
- package/dist/cjs/src/multer/utils/index.js.map +1 -0
- package/dist/cjs/src/multer/utils/validators.d.ts +18 -0
- package/dist/cjs/src/multer/utils/validators.js +171 -0
- package/dist/cjs/src/multer/utils/validators.js.map +1 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/adapters/hono-adapter.d.ts +1 -0
- package/dist/esm/src/adapters/hono-adapter.js +14 -3
- package/dist/esm/src/adapters/hono-adapter.js.map +1 -1
- package/dist/esm/src/drivers/graphQLUpload/GraphQLUpload.d.ts +1 -1
- package/dist/esm/src/drivers/graphQLUpload/Upload.d.ts +11 -8
- package/dist/esm/src/drivers/graphQLUpload/Upload.js.map +1 -1
- package/dist/esm/src/drivers/graphQLUpload/fs-capacitor.d.ts +20 -8
- package/dist/esm/src/drivers/graphQLUpload/fs-capacitor.js +112 -59
- package/dist/esm/src/drivers/graphQLUpload/fs-capacitor.js.map +1 -1
- package/dist/esm/src/drivers/graphQLUpload/index.d.ts +9 -3
- package/dist/esm/src/drivers/graphQLUpload/index.js +7 -3
- package/dist/esm/src/drivers/graphQLUpload/index.js.map +1 -1
- package/dist/esm/src/drivers/graphQLUpload/processRequest.d.ts +8 -1
- package/dist/esm/src/drivers/graphQLUpload/processRequest.js +42 -36
- package/dist/esm/src/drivers/graphQLUpload/processRequest.js.map +1 -1
- package/dist/esm/src/drivers/graphQLUpload/storage/capacitor-storage.d.ts +15 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/capacitor-storage.js +43 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/capacitor-storage.js.map +1 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/index.d.ts +3 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/index.js +4 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/index.js.map +1 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/memory-storage.d.ts +13 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/memory-storage.js +27 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/memory-storage.js.map +1 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/storage.d.ts +17 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/storage.js +2 -0
- package/dist/esm/src/drivers/graphQLUpload/storage/storage.js.map +1 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/file.d.ts +6 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/file.js +54 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/file.js.map +1 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/index.d.ts +2 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/index.js +3 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/index.js.map +1 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/validators.d.ts +18 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/validators.js +167 -0
- package/dist/esm/src/drivers/graphQLUpload/utils/validators.js.map +1 -0
- package/dist/esm/src/multer/index.d.ts +1 -0
- package/dist/esm/src/multer/index.js +1 -0
- package/dist/esm/src/multer/index.js.map +1 -1
- package/dist/esm/src/multer/interceptors/any-files-interceptor.d.ts +2 -2
- package/dist/esm/src/multer/interceptors/any-files-interceptor.js +6 -23
- package/dist/esm/src/multer/interceptors/any-files-interceptor.js.map +1 -1
- package/dist/esm/src/multer/interceptors/base-interceptor.d.ts +6 -0
- package/dist/esm/src/multer/interceptors/base-interceptor.js +23 -0
- package/dist/esm/src/multer/interceptors/base-interceptor.js.map +1 -0
- package/dist/esm/src/multer/interceptors/file-fields-interceptor.d.ts +2 -2
- package/dist/esm/src/multer/interceptors/file-fields-interceptor.js +7 -24
- package/dist/esm/src/multer/interceptors/file-fields-interceptor.js.map +1 -1
- package/dist/esm/src/multer/interceptors/file-interceptor.d.ts +2 -2
- package/dist/esm/src/multer/interceptors/file-interceptor.js +6 -23
- package/dist/esm/src/multer/interceptors/file-interceptor.js.map +1 -1
- package/dist/esm/src/multer/interceptors/files-interceptor.d.ts +2 -2
- package/dist/esm/src/multer/interceptors/files-interceptor.js +6 -23
- package/dist/esm/src/multer/interceptors/files-interceptor.js.map +1 -1
- package/dist/esm/src/multer/interceptors/index.d.ts +1 -0
- package/dist/esm/src/multer/interceptors/index.js +1 -0
- package/dist/esm/src/multer/interceptors/index.js.map +1 -1
- package/dist/esm/src/multer/multipart/handlers/any-files.d.ts +2 -8
- package/dist/esm/src/multer/multipart/handlers/any-files.js +12 -25
- package/dist/esm/src/multer/multipart/handlers/any-files.js.map +1 -1
- package/dist/esm/src/multer/multipart/handlers/base-handler.d.ts +42 -0
- package/dist/esm/src/multer/multipart/handlers/base-handler.js +102 -0
- package/dist/esm/src/multer/multipart/handlers/base-handler.js.map +1 -0
- package/dist/esm/src/multer/multipart/handlers/file-fields.d.ts +3 -10
- package/dist/esm/src/multer/multipart/handlers/file-fields.js +19 -33
- package/dist/esm/src/multer/multipart/handlers/file-fields.js.map +1 -1
- package/dist/esm/src/multer/multipart/handlers/index.d.ts +6 -1
- package/dist/esm/src/multer/multipart/handlers/index.js +6 -1
- package/dist/esm/src/multer/multipart/handlers/index.js.map +1 -1
- package/dist/esm/src/multer/multipart/handlers/multiple-files.d.ts +2 -8
- package/dist/esm/src/multer/multipart/handlers/multiple-files.js +18 -36
- package/dist/esm/src/multer/multipart/handlers/multiple-files.js.map +1 -1
- package/dist/esm/src/multer/multipart/handlers/single-file.d.ts +2 -8
- package/dist/esm/src/multer/multipart/handlers/single-file.js +11 -33
- package/dist/esm/src/multer/multipart/handlers/single-file.js.map +1 -1
- package/dist/esm/src/multer/multipart/index.d.ts +1 -1
- package/dist/esm/src/multer/multipart/options.d.ts +10 -16
- package/dist/esm/src/multer/multipart/options.js.map +1 -1
- package/dist/esm/src/multer/multipart/request.js +14 -3
- package/dist/esm/src/multer/multipart/request.js.map +1 -1
- package/dist/esm/src/multer/storage/disk-storage.d.ts +2 -1
- package/dist/esm/src/multer/storage/disk-storage.js +2 -1
- package/dist/esm/src/multer/storage/disk-storage.js.map +1 -1
- package/dist/esm/src/multer/storage/memory-storage.d.ts +2 -11
- package/dist/esm/src/multer/storage/memory-storage.js +6 -4
- package/dist/esm/src/multer/storage/memory-storage.js.map +1 -1
- package/dist/esm/src/multer/storage/storage.d.ts +6 -5
- package/dist/esm/src/multer/utils/file.d.ts +6 -0
- package/dist/esm/src/multer/utils/file.js +54 -0
- package/dist/esm/src/multer/utils/file.js.map +1 -0
- package/dist/esm/src/multer/utils/index.d.ts +2 -0
- package/dist/esm/src/multer/utils/index.js +3 -0
- package/dist/esm/src/multer/utils/index.js.map +1 -0
- package/dist/esm/src/multer/utils/validators.d.ts +18 -0
- package/dist/esm/src/multer/utils/validators.js +167 -0
- package/dist/esm/src/multer/utils/validators.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +6 -4
- package/src/adapters/hono-adapter.ts +18 -3
- package/src/drivers/graphQLUpload/Upload.ts +34 -14
- package/src/drivers/graphQLUpload/fs-capacitor.ts +240 -116
- package/src/drivers/graphQLUpload/index.ts +37 -3
- package/src/drivers/graphQLUpload/processRequest.ts +92 -38
- package/src/drivers/graphQLUpload/storage/capacitor-storage.ts +82 -0
- package/src/drivers/graphQLUpload/storage/index.ts +3 -0
- package/src/drivers/graphQLUpload/storage/memory-storage.ts +58 -0
- package/src/drivers/graphQLUpload/storage/storage.ts +52 -0
- package/src/drivers/graphQLUpload/utils/file.ts +109 -0
- package/src/drivers/graphQLUpload/utils/index.ts +2 -0
- package/src/drivers/graphQLUpload/utils/validators.ts +219 -0
- package/src/multer/index.ts +1 -0
- package/src/multer/interceptors/any-files-interceptor.ts +12 -43
- package/src/multer/interceptors/base-interceptor.ts +54 -0
- package/src/multer/interceptors/file-fields-interceptor.ts +14 -48
- package/src/multer/interceptors/file-interceptor.ts +12 -44
- package/src/multer/interceptors/files-interceptor.ts +13 -45
- package/src/multer/interceptors/index.ts +1 -0
- package/src/multer/multipart/handlers/any-files.ts +14 -32
- package/src/multer/multipart/handlers/base-handler.ts +204 -0
- package/src/multer/multipart/handlers/file-fields.ts +29 -57
- package/src/multer/multipart/handlers/index.ts +11 -1
- package/src/multer/multipart/handlers/multiple-files.ts +23 -54
- package/src/multer/multipart/handlers/single-file.ts +14 -47
- package/src/multer/multipart/index.ts +1 -1
- package/src/multer/multipart/options.ts +26 -8
- package/src/multer/multipart/request.ts +19 -3
- package/src/multer/storage/disk-storage.ts +2 -1
- package/src/multer/storage/memory-storage.ts +13 -6
- package/src/multer/storage/storage.ts +12 -5
- package/src/multer/utils/file.ts +109 -0
- package/src/multer/utils/index.ts +2 -0
- package/src/multer/utils/validators.ts +219 -0
- package/test/README.md +247 -0
- package/test/graphql-upload.test.ts +509 -0
- package/test/helpers.ts +70 -0
- package/test/integration.test.ts +197 -0
- package/test/interceptors-e2e.test.ts +362 -0
- package/test/multipart-upload.test.ts +354 -0
- package/test/smoke.test.ts +227 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { THonoRequest } from '../src/multer/multipart/request';
|
|
4
|
+
import { MemoryStorage } from '../src/multer/storage/memory-storage';
|
|
5
|
+
import { StorageFile } from '../src/multer/storage/storage';
|
|
6
|
+
|
|
7
|
+
describe('Multipart Upload Handlers', () => {
|
|
8
|
+
describe('FileHandler', () => {
|
|
9
|
+
test('should process single file upload', async () => {
|
|
10
|
+
const { FileHandler } =
|
|
11
|
+
await import('../src/multer/multipart/handlers/base-handler');
|
|
12
|
+
const storage = new MemoryStorage();
|
|
13
|
+
const options = {
|
|
14
|
+
storage,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Create mock file
|
|
18
|
+
const file = new File(['test content'], 'test.txt', {
|
|
19
|
+
type: 'text/plain',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const mockReq = {
|
|
23
|
+
body: {
|
|
24
|
+
file,
|
|
25
|
+
otherField: 'value',
|
|
26
|
+
},
|
|
27
|
+
header: (name: string) =>
|
|
28
|
+
name === 'content-type' ? 'multipart/form-data' : null,
|
|
29
|
+
} as unknown as THonoRequest;
|
|
30
|
+
|
|
31
|
+
const handler = new FileHandler(mockReq, options);
|
|
32
|
+
let uploadedFile: StorageFile | undefined;
|
|
33
|
+
|
|
34
|
+
await handler.process(async (fieldName, part) => {
|
|
35
|
+
if (part instanceof File) {
|
|
36
|
+
const storageFile = await handler.handleSingleFile(fieldName, part);
|
|
37
|
+
if (storageFile) {
|
|
38
|
+
uploadedFile = storageFile;
|
|
39
|
+
handler.addFile(fieldName, storageFile);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
expect(uploadedFile).toBeDefined();
|
|
45
|
+
expect(uploadedFile?.fieldName).toBe('file');
|
|
46
|
+
expect(uploadedFile?.originalFilename).toBe('test.txt');
|
|
47
|
+
expect(uploadedFile?.mimetype).toContain('text/plain');
|
|
48
|
+
expect(uploadedFile?.size).toBe(12); // 'test content' length
|
|
49
|
+
|
|
50
|
+
// Check body
|
|
51
|
+
const body = handler.getBody();
|
|
52
|
+
expect(body.otherField).toBe('value');
|
|
53
|
+
// Note: file is removed from body during processing
|
|
54
|
+
|
|
55
|
+
// Cleanup
|
|
56
|
+
await handler.cleanup();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should validate field name', async () => {
|
|
60
|
+
const { FileHandler } =
|
|
61
|
+
await import('../src/multer/multipart/handlers/base-handler');
|
|
62
|
+
const storage = new MemoryStorage();
|
|
63
|
+
const options = { storage };
|
|
64
|
+
|
|
65
|
+
const file = new File(['test'], 'test.txt', { type: 'text/plain' });
|
|
66
|
+
const mockReq = {
|
|
67
|
+
body: { wrongField: file },
|
|
68
|
+
header: () => 'multipart/form-data',
|
|
69
|
+
} as unknown as THonoRequest;
|
|
70
|
+
|
|
71
|
+
const handler = new FileHandler(mockReq, options);
|
|
72
|
+
|
|
73
|
+
let errorThrown = false;
|
|
74
|
+
try {
|
|
75
|
+
await handler.process(async (fieldName) => {
|
|
76
|
+
handler.validateFieldName(fieldName, 'correctField');
|
|
77
|
+
});
|
|
78
|
+
} catch (err) {
|
|
79
|
+
errorThrown = true;
|
|
80
|
+
expect(err instanceof Error && err.message).toContain(
|
|
81
|
+
"doesn't accept file",
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
expect(errorThrown).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('should validate max count', async () => {
|
|
89
|
+
const { FileHandler } =
|
|
90
|
+
await import('../src/multer/multipart/handlers/base-handler');
|
|
91
|
+
const storage = new MemoryStorage();
|
|
92
|
+
const options = { storage };
|
|
93
|
+
|
|
94
|
+
const file = new File(['test'], 'test.txt', { type: 'text/plain' });
|
|
95
|
+
const mockReq = {
|
|
96
|
+
body: { files: [file, file] },
|
|
97
|
+
header: () => 'multipart/form-data',
|
|
98
|
+
} as unknown as THonoRequest;
|
|
99
|
+
|
|
100
|
+
const handler = new FileHandler(mockReq, options);
|
|
101
|
+
let fileCount = 0;
|
|
102
|
+
const maxCount = 1;
|
|
103
|
+
|
|
104
|
+
let errorThrown = false;
|
|
105
|
+
try {
|
|
106
|
+
await handler.process(async (fieldName) => {
|
|
107
|
+
handler.validateMaxCount(fieldName, fileCount, maxCount);
|
|
108
|
+
fileCount++;
|
|
109
|
+
});
|
|
110
|
+
} catch (err) {
|
|
111
|
+
errorThrown = true;
|
|
112
|
+
expect(err instanceof Error && err.message).toContain(
|
|
113
|
+
'accepts max 1 files',
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
expect(errorThrown).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('should prevent double cleanup', async () => {
|
|
121
|
+
const { FileHandler } =
|
|
122
|
+
await import('../src/multer/multipart/handlers/base-handler');
|
|
123
|
+
const storage = new MemoryStorage();
|
|
124
|
+
const options = { storage };
|
|
125
|
+
|
|
126
|
+
const file = new File(['test'], 'test.txt', { type: 'text/plain' });
|
|
127
|
+
const mockReq = {
|
|
128
|
+
body: { file },
|
|
129
|
+
header: () => 'multipart/form-data',
|
|
130
|
+
} as unknown as THonoRequest;
|
|
131
|
+
|
|
132
|
+
const handler = new FileHandler(mockReq, options);
|
|
133
|
+
|
|
134
|
+
await handler.process(async (fieldName, part) => {
|
|
135
|
+
const storageFile = await handler.handleSingleFile(fieldName, part);
|
|
136
|
+
if (storageFile) {
|
|
137
|
+
handler.addFile(fieldName, storageFile);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const removeFn = handler.createRemoveFunction();
|
|
142
|
+
|
|
143
|
+
// First cleanup
|
|
144
|
+
await removeFn();
|
|
145
|
+
|
|
146
|
+
// Second cleanup should be no-op
|
|
147
|
+
await removeFn();
|
|
148
|
+
|
|
149
|
+
// Files array should be cleared
|
|
150
|
+
expect(handler.getFiles()).toHaveLength(0);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('should handle filtered file', async () => {
|
|
154
|
+
const { FileHandler } =
|
|
155
|
+
await import('../src/multer/multipart/handlers/base-handler');
|
|
156
|
+
const storage = new MemoryStorage();
|
|
157
|
+
const options = {
|
|
158
|
+
storage,
|
|
159
|
+
filter: () => {
|
|
160
|
+
return false; // Reject file
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const file = new File(['test'], 'test.exe', {
|
|
165
|
+
type: 'application/x-executable',
|
|
166
|
+
});
|
|
167
|
+
const mockReq = {
|
|
168
|
+
body: { file },
|
|
169
|
+
header: () => 'multipart/form-data',
|
|
170
|
+
} as unknown as THonoRequest;
|
|
171
|
+
|
|
172
|
+
const handler = new FileHandler(mockReq, options);
|
|
173
|
+
|
|
174
|
+
await handler.process(async (fieldName, part) => {
|
|
175
|
+
await handler.handleSingleFile(fieldName, part);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// File should not be in the list (filtered out)
|
|
179
|
+
expect(handler.getFiles()).toHaveLength(0);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('handleMultipartSingleFile', () => {
|
|
184
|
+
test('should handle single file upload successfully', async () => {
|
|
185
|
+
const { handleMultipartSingleFile } =
|
|
186
|
+
await import('../src/multer/multipart/handlers/single-file');
|
|
187
|
+
|
|
188
|
+
const storage = new MemoryStorage();
|
|
189
|
+
const options = { storage };
|
|
190
|
+
|
|
191
|
+
const file = new File(['content'], 'photo.jpg', { type: 'image/jpeg' });
|
|
192
|
+
const mockReq = {
|
|
193
|
+
body: { photo: file, name: 'John' },
|
|
194
|
+
header: () => 'multipart/form-data',
|
|
195
|
+
} as unknown as THonoRequest;
|
|
196
|
+
|
|
197
|
+
const result = await handleMultipartSingleFile(mockReq, 'photo', options);
|
|
198
|
+
|
|
199
|
+
expect(result.file).toBeDefined();
|
|
200
|
+
expect(result.file?.fieldName).toBe('photo');
|
|
201
|
+
expect(result.file?.originalFilename).toBe('photo.jpg');
|
|
202
|
+
expect(result.body.name).toBe('John');
|
|
203
|
+
// Note: The file is removed from body during processing, so this is expected
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('handleMultipartMultipleFiles', () => {
|
|
208
|
+
test('should handle multiple files upload', async () => {
|
|
209
|
+
const { handleMultipartMultipleFiles } =
|
|
210
|
+
await import('../src/multer/multipart/handlers/multiple-files');
|
|
211
|
+
|
|
212
|
+
const storage = new MemoryStorage();
|
|
213
|
+
const options = { storage };
|
|
214
|
+
|
|
215
|
+
const file1 = new File(['content1'], 'file1.txt', { type: 'text/plain' });
|
|
216
|
+
const file2 = new File(['content2'], 'file2.txt', { type: 'text/plain' });
|
|
217
|
+
const mockReq = {
|
|
218
|
+
body: { files: [file1, file2] },
|
|
219
|
+
header: () => 'multipart/form-data',
|
|
220
|
+
} as unknown as THonoRequest;
|
|
221
|
+
|
|
222
|
+
const result = await handleMultipartMultipleFiles(
|
|
223
|
+
mockReq,
|
|
224
|
+
'files',
|
|
225
|
+
5,
|
|
226
|
+
options,
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
expect(result.files).toHaveLength(2);
|
|
230
|
+
expect(result.files[0].fieldName).toBe('files');
|
|
231
|
+
expect(result.files[1].fieldName).toBe('files');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('should enforce max count limit', async () => {
|
|
235
|
+
const { handleMultipartMultipleFiles } =
|
|
236
|
+
await import('../src/multer/multipart/handlers/multiple-files');
|
|
237
|
+
|
|
238
|
+
const storage = new MemoryStorage();
|
|
239
|
+
const options = { storage };
|
|
240
|
+
|
|
241
|
+
const file1 = new File(['content1'], 'file1.txt', {
|
|
242
|
+
type: 'text/plain',
|
|
243
|
+
});
|
|
244
|
+
const file2 = new File(['content2'], 'file2.txt', {
|
|
245
|
+
type: 'text/plain',
|
|
246
|
+
});
|
|
247
|
+
const file3 = new File(['content3'], 'file3.txt', {
|
|
248
|
+
type: 'text/plain',
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const mockReq = {
|
|
252
|
+
body: { files: [file1, file2, file3] },
|
|
253
|
+
header: () => 'multipart/form-data',
|
|
254
|
+
} as unknown as THonoRequest;
|
|
255
|
+
|
|
256
|
+
let errorThrown = false;
|
|
257
|
+
try {
|
|
258
|
+
await handleMultipartMultipleFiles(mockReq, 'files', 2, options);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
errorThrown = true;
|
|
261
|
+
expect(err instanceof Error && err.message).toContain(
|
|
262
|
+
'accepts max 2 files',
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
expect(errorThrown).toBe(true);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('handleMultipartFileFields', () => {
|
|
271
|
+
test('should handle multiple file fields', async () => {
|
|
272
|
+
const { handleMultipartFileFields, uploadFieldsToMap } =
|
|
273
|
+
await import('../src/multer/multipart/handlers/file-fields');
|
|
274
|
+
|
|
275
|
+
const storage = new MemoryStorage();
|
|
276
|
+
const options = { storage };
|
|
277
|
+
|
|
278
|
+
const avatar = new File(['avatar'], 'avatar.jpg', { type: 'image/jpeg' });
|
|
279
|
+
const document = new File(['doc'], 'doc.pdf', {
|
|
280
|
+
type: 'application/pdf',
|
|
281
|
+
});
|
|
282
|
+
const mockReq = {
|
|
283
|
+
body: { avatar, document },
|
|
284
|
+
header: () => 'multipart/form-data',
|
|
285
|
+
} as unknown as THonoRequest;
|
|
286
|
+
|
|
287
|
+
const fieldsMap = uploadFieldsToMap([
|
|
288
|
+
{ name: 'avatar', maxCount: 1 },
|
|
289
|
+
{ name: 'document', maxCount: 1 },
|
|
290
|
+
]);
|
|
291
|
+
|
|
292
|
+
const result = await handleMultipartFileFields(
|
|
293
|
+
mockReq,
|
|
294
|
+
fieldsMap,
|
|
295
|
+
options,
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
expect(result.files.avatar).toHaveLength(1);
|
|
299
|
+
expect(result.files.document).toHaveLength(1);
|
|
300
|
+
expect(result.files.avatar[0].originalFilename).toBe('avatar.jpg');
|
|
301
|
+
expect(result.files.document[0].originalFilename).toBe('doc.pdf');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('should reject unknown file fields', async () => {
|
|
305
|
+
const { handleMultipartFileFields, uploadFieldsToMap } =
|
|
306
|
+
await import('../src/multer/multipart/handlers/file-fields');
|
|
307
|
+
|
|
308
|
+
const storage = new MemoryStorage();
|
|
309
|
+
const options = { storage };
|
|
310
|
+
|
|
311
|
+
const file = new File(['content'], 'unknown.txt', { type: 'text/plain' });
|
|
312
|
+
const mockReq = {
|
|
313
|
+
body: { unknown: file },
|
|
314
|
+
header: () => 'multipart/form-data',
|
|
315
|
+
} as unknown as THonoRequest;
|
|
316
|
+
|
|
317
|
+
const fieldsMap = uploadFieldsToMap([{ name: 'allowed', maxCount: 1 }]);
|
|
318
|
+
|
|
319
|
+
let errorThrown = false;
|
|
320
|
+
try {
|
|
321
|
+
await handleMultipartFileFields(mockReq, fieldsMap, options);
|
|
322
|
+
} catch (err) {
|
|
323
|
+
errorThrown = true;
|
|
324
|
+
expect(err instanceof Error && err.message).toContain(
|
|
325
|
+
"doesn't accept files",
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
expect(errorThrown).toBe(true);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
describe('handleMultipartAnyFiles', () => {
|
|
334
|
+
test('should accept any files', async () => {
|
|
335
|
+
const { handleMultipartAnyFiles } =
|
|
336
|
+
await import('../src/multer/multipart/handlers/any-files');
|
|
337
|
+
|
|
338
|
+
const storage = new MemoryStorage();
|
|
339
|
+
const options = { storage };
|
|
340
|
+
|
|
341
|
+
const file1 = new File(['content1'], 'file1.txt', { type: 'text/plain' });
|
|
342
|
+
const file2 = new File(['content2'], 'file2.jpg', { type: 'image/jpeg' });
|
|
343
|
+
const mockReq = {
|
|
344
|
+
body: { file1, file2, name: 'test' },
|
|
345
|
+
header: () => 'multipart/form-data',
|
|
346
|
+
} as unknown as THonoRequest;
|
|
347
|
+
|
|
348
|
+
const result = await handleMultipartAnyFiles(mockReq, options);
|
|
349
|
+
|
|
350
|
+
expect(result.files).toHaveLength(2);
|
|
351
|
+
expect(result.body.name).toBe('test');
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
});
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { THonoRequest } from '../src/multer/multipart/request';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Simple smoke tests to verify core functionality works
|
|
7
|
+
*/
|
|
8
|
+
describe('File Upload Smoke Tests', () => {
|
|
9
|
+
test('MemoryStorage should store and retrieve file', async () => {
|
|
10
|
+
const { MemoryStorage } =
|
|
11
|
+
await import('../src/multer/storage/memory-storage');
|
|
12
|
+
const storage = new MemoryStorage();
|
|
13
|
+
|
|
14
|
+
const file = new File(['test content'], 'test.txt', {
|
|
15
|
+
type: 'text/plain',
|
|
16
|
+
});
|
|
17
|
+
const mockReq = {
|
|
18
|
+
header: () => 'multipart/form-data',
|
|
19
|
+
} as unknown as THonoRequest;
|
|
20
|
+
|
|
21
|
+
const storedFile = await storage.handleFile(file, mockReq, 'file');
|
|
22
|
+
|
|
23
|
+
expect(storedFile).toBeDefined();
|
|
24
|
+
expect(storedFile.fieldName).toBe('file');
|
|
25
|
+
expect(storedFile.originalFilename).toBe('test.txt');
|
|
26
|
+
expect(storedFile.size).toBe(12);
|
|
27
|
+
expect(storedFile.buffer).toBeInstanceOf(Buffer);
|
|
28
|
+
expect(storedFile.buffer.byteLength).toBe(12);
|
|
29
|
+
|
|
30
|
+
// Cleanup
|
|
31
|
+
await storage.removeFile(storedFile);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('FileHandler should process file without errors', async () => {
|
|
35
|
+
const { FileHandler } =
|
|
36
|
+
await import('../src/multer/multipart/handlers/base-handler');
|
|
37
|
+
const { MemoryStorage } =
|
|
38
|
+
await import('../src/multer/storage/memory-storage');
|
|
39
|
+
|
|
40
|
+
const storage = new MemoryStorage();
|
|
41
|
+
const options = { storage };
|
|
42
|
+
|
|
43
|
+
const file = new File(['test'], 'test.txt', { type: 'text/plain' });
|
|
44
|
+
const mockReq = {
|
|
45
|
+
body: { file },
|
|
46
|
+
header: () => 'multipart/form-data',
|
|
47
|
+
} as unknown as THonoRequest;
|
|
48
|
+
|
|
49
|
+
const handler = new FileHandler(mockReq, options);
|
|
50
|
+
|
|
51
|
+
let processed = false;
|
|
52
|
+
await handler.process(async (fieldName, part) => {
|
|
53
|
+
if (part instanceof File) {
|
|
54
|
+
const storageFile = await handler.handleSingleFile(fieldName, part);
|
|
55
|
+
if (storageFile) {
|
|
56
|
+
processed = true;
|
|
57
|
+
expect(storageFile.fieldName).toBe('file');
|
|
58
|
+
expect(storageFile.originalFilename).toBe('test.txt');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(processed).toBe(true);
|
|
64
|
+
|
|
65
|
+
// Cleanup
|
|
66
|
+
await handler.cleanup();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('createRemoveFunction should prevent multiple calls', async () => {
|
|
70
|
+
const { FileHandler } =
|
|
71
|
+
await import('../src/multer/multipart/handlers/base-handler');
|
|
72
|
+
const { MemoryStorage } =
|
|
73
|
+
await import('../src/multer/storage/memory-storage');
|
|
74
|
+
|
|
75
|
+
const storage = new MemoryStorage();
|
|
76
|
+
const options = { storage };
|
|
77
|
+
|
|
78
|
+
const file = new File(['test'], 'test.txt', { type: 'text/plain' });
|
|
79
|
+
const mockReq = {
|
|
80
|
+
body: { file },
|
|
81
|
+
header: () => 'multipart/form-data',
|
|
82
|
+
} as unknown as THonoRequest;
|
|
83
|
+
|
|
84
|
+
const handler = new FileHandler(mockReq, options);
|
|
85
|
+
|
|
86
|
+
await handler.process(async (fieldName, part) => {
|
|
87
|
+
const storageFile = await handler.handleSingleFile(fieldName, part);
|
|
88
|
+
if (storageFile) {
|
|
89
|
+
handler.addFile(fieldName, storageFile);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const removeFn = handler.createRemoveFunction();
|
|
94
|
+
|
|
95
|
+
// First call should work
|
|
96
|
+
await removeFn();
|
|
97
|
+
|
|
98
|
+
// Verify files are cleared
|
|
99
|
+
expect(handler.getFiles()).toHaveLength(0);
|
|
100
|
+
|
|
101
|
+
// Second call should be no-op (should not throw)
|
|
102
|
+
await removeFn();
|
|
103
|
+
|
|
104
|
+
// Should still be empty
|
|
105
|
+
expect(handler.getFiles()).toHaveLength(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('cleanup should happen automatically on error', async () => {
|
|
109
|
+
const { FileHandler } =
|
|
110
|
+
await import('../src/multer/multipart/handlers/base-handler');
|
|
111
|
+
const { MemoryStorage } =
|
|
112
|
+
await import('../src/multer/storage/memory-storage');
|
|
113
|
+
|
|
114
|
+
const storage = new MemoryStorage();
|
|
115
|
+
const options = { storage };
|
|
116
|
+
|
|
117
|
+
const file = new File(['test'], 'test.txt', { type: 'text/plain' });
|
|
118
|
+
const mockReq = {
|
|
119
|
+
body: { file },
|
|
120
|
+
header: () => 'multipart/form-data',
|
|
121
|
+
} as unknown as THonoRequest;
|
|
122
|
+
|
|
123
|
+
const handler = new FileHandler(mockReq, options);
|
|
124
|
+
|
|
125
|
+
let errorThrown = false;
|
|
126
|
+
try {
|
|
127
|
+
await handler.process(async (fieldName, part) => {
|
|
128
|
+
const storageFile = await handler.handleSingleFile(fieldName, part);
|
|
129
|
+
if (storageFile) {
|
|
130
|
+
handler.addFile(fieldName, storageFile);
|
|
131
|
+
}
|
|
132
|
+
// Simulate error
|
|
133
|
+
throw new Error('Test error');
|
|
134
|
+
});
|
|
135
|
+
} catch (err) {
|
|
136
|
+
errorThrown = true;
|
|
137
|
+
expect(err instanceof Error && err.message).toBe('Test error');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
expect(errorThrown).toBe(true);
|
|
141
|
+
|
|
142
|
+
// Cleanup should have happened automatically
|
|
143
|
+
expect(handler.getFiles()).toHaveLength(0);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('getParts should validate file size limits', async () => {
|
|
147
|
+
const { getParts } = await import('../src/multer/multipart/request');
|
|
148
|
+
|
|
149
|
+
const largeFile = new File(['x'.repeat(1000)], 'large.txt', {
|
|
150
|
+
type: 'text/plain',
|
|
151
|
+
});
|
|
152
|
+
const smallFile = new File(['small'], 'small.txt', { type: 'text/plain' });
|
|
153
|
+
|
|
154
|
+
const mockReq = {
|
|
155
|
+
body: {
|
|
156
|
+
largeFile,
|
|
157
|
+
smallFile,
|
|
158
|
+
},
|
|
159
|
+
header: () => 'multipart/form-data',
|
|
160
|
+
} as unknown as THonoRequest;
|
|
161
|
+
|
|
162
|
+
// Should throw error for large file
|
|
163
|
+
let errorThrown = false;
|
|
164
|
+
try {
|
|
165
|
+
getParts(mockReq, { limits: { fileSize: 100 } });
|
|
166
|
+
} catch (err) {
|
|
167
|
+
errorThrown = true;
|
|
168
|
+
expect(err instanceof Error && err.message).toContain('too large');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
expect(errorThrown).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('uploadFieldsToMap should create proper map', async () => {
|
|
175
|
+
const { uploadFieldsToMap } =
|
|
176
|
+
await import('../src/multer/multipart/handlers/file-fields');
|
|
177
|
+
|
|
178
|
+
const fields = uploadFieldsToMap([
|
|
179
|
+
{ name: 'avatar', maxCount: 1 },
|
|
180
|
+
{ name: 'photos', maxCount: 5 },
|
|
181
|
+
{ name: 'documents' },
|
|
182
|
+
]);
|
|
183
|
+
|
|
184
|
+
expect(fields.get('avatar')).toEqual({ maxCount: 1 });
|
|
185
|
+
expect(fields.get('photos')).toEqual({ maxCount: 5 });
|
|
186
|
+
expect(fields.get('documents')).toEqual({ maxCount: 1 }); // default
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('handleMultipartSingleFile should return expected structure', async () => {
|
|
190
|
+
const { handleMultipartSingleFile } =
|
|
191
|
+
await import('../src/multer/multipart/handlers/single-file');
|
|
192
|
+
const { MemoryStorage } =
|
|
193
|
+
await import('../src/multer/storage/memory-storage');
|
|
194
|
+
|
|
195
|
+
const storage = new MemoryStorage();
|
|
196
|
+
const options = { storage };
|
|
197
|
+
|
|
198
|
+
const file = new File(['content'], 'photo.jpg', { type: 'image/jpeg' });
|
|
199
|
+
const mockReq = {
|
|
200
|
+
body: { photo: file, title: 'My Photo' },
|
|
201
|
+
header: () => 'multipart/form-data',
|
|
202
|
+
} as unknown as THonoRequest;
|
|
203
|
+
|
|
204
|
+
const result = await handleMultipartSingleFile(mockReq, 'photo', options);
|
|
205
|
+
|
|
206
|
+
expect(result).toHaveProperty('body');
|
|
207
|
+
expect(result).toHaveProperty('file');
|
|
208
|
+
expect(result).toHaveProperty('remove');
|
|
209
|
+
expect(result.file).toBeDefined();
|
|
210
|
+
expect(result.body.title).toBe('My Photo');
|
|
211
|
+
|
|
212
|
+
// Test remove function
|
|
213
|
+
await result.remove();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('transformUploadOptions should provide defaults', async () => {
|
|
217
|
+
const { transformUploadOptions } =
|
|
218
|
+
await import('../src/multer/multipart/options');
|
|
219
|
+
|
|
220
|
+
const noOptions = transformUploadOptions();
|
|
221
|
+
expect(noOptions.storage).toBeDefined();
|
|
222
|
+
|
|
223
|
+
const withOptions = transformUploadOptions({ dest: '/tmp' });
|
|
224
|
+
expect(withOptions.storage).toBeDefined();
|
|
225
|
+
expect(withOptions.dest).toBe('/tmp');
|
|
226
|
+
});
|
|
227
|
+
});
|