@hazeljs/core 0.2.0-beta.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/README.md +522 -0
- package/dist/__tests__/container.test.d.ts +2 -0
- package/dist/__tests__/container.test.d.ts.map +1 -0
- package/dist/__tests__/container.test.js +454 -0
- package/dist/__tests__/decorators.test.d.ts +2 -0
- package/dist/__tests__/decorators.test.d.ts.map +1 -0
- package/dist/__tests__/decorators.test.js +693 -0
- package/dist/__tests__/errors/http.error.test.d.ts +2 -0
- package/dist/__tests__/errors/http.error.test.d.ts.map +1 -0
- package/dist/__tests__/errors/http.error.test.js +117 -0
- package/dist/__tests__/filters/exception-filter.test.d.ts +2 -0
- package/dist/__tests__/filters/exception-filter.test.d.ts.map +1 -0
- package/dist/__tests__/filters/exception-filter.test.js +135 -0
- package/dist/__tests__/filters/http-exception.filter.test.d.ts +2 -0
- package/dist/__tests__/filters/http-exception.filter.test.d.ts.map +1 -0
- package/dist/__tests__/filters/http-exception.filter.test.js +119 -0
- package/dist/__tests__/hazel-app.test.d.ts +2 -0
- package/dist/__tests__/hazel-app.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-app.test.js +682 -0
- package/dist/__tests__/hazel-module.test.d.ts +2 -0
- package/dist/__tests__/hazel-module.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-module.test.js +408 -0
- package/dist/__tests__/hazel-response.test.d.ts +2 -0
- package/dist/__tests__/hazel-response.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-response.test.js +138 -0
- package/dist/__tests__/health.test.d.ts +2 -0
- package/dist/__tests__/health.test.d.ts.map +1 -0
- package/dist/__tests__/health.test.js +147 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.js +239 -0
- package/dist/__tests__/interceptors/interceptor.test.d.ts +2 -0
- package/dist/__tests__/interceptors/interceptor.test.d.ts.map +1 -0
- package/dist/__tests__/interceptors/interceptor.test.js +166 -0
- package/dist/__tests__/logger.test.d.ts +2 -0
- package/dist/__tests__/logger.test.d.ts.map +1 -0
- package/dist/__tests__/logger.test.js +141 -0
- package/dist/__tests__/middleware/cors.test.d.ts +2 -0
- package/dist/__tests__/middleware/cors.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/cors.test.js +129 -0
- package/dist/__tests__/middleware/csrf.test.d.ts +2 -0
- package/dist/__tests__/middleware/csrf.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/csrf.test.js +247 -0
- package/dist/__tests__/middleware/global-middleware.test.d.ts +2 -0
- package/dist/__tests__/middleware/global-middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/global-middleware.test.js +259 -0
- package/dist/__tests__/middleware/rate-limit.test.d.ts +2 -0
- package/dist/__tests__/middleware/rate-limit.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/rate-limit.test.js +264 -0
- package/dist/__tests__/middleware/security-headers.test.d.ts +2 -0
- package/dist/__tests__/middleware/security-headers.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/security-headers.test.js +229 -0
- package/dist/__tests__/middleware/timeout.test.d.ts +2 -0
- package/dist/__tests__/middleware/timeout.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/timeout.test.js +132 -0
- package/dist/__tests__/middleware.test.d.ts +2 -0
- package/dist/__tests__/middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware.test.js +180 -0
- package/dist/__tests__/pipes/pipe.test.d.ts +2 -0
- package/dist/__tests__/pipes/pipe.test.d.ts.map +1 -0
- package/dist/__tests__/pipes/pipe.test.js +245 -0
- package/dist/__tests__/pipes/validation.pipe.test.d.ts +2 -0
- package/dist/__tests__/pipes/validation.pipe.test.d.ts.map +1 -0
- package/dist/__tests__/pipes/validation.pipe.test.js +297 -0
- package/dist/__tests__/request-parser.test.d.ts +2 -0
- package/dist/__tests__/request-parser.test.d.ts.map +1 -0
- package/dist/__tests__/request-parser.test.js +182 -0
- package/dist/__tests__/router.test.d.ts +2 -0
- package/dist/__tests__/router.test.d.ts.map +1 -0
- package/dist/__tests__/router.test.js +680 -0
- package/dist/__tests__/routing/route-matcher.test.d.ts +2 -0
- package/dist/__tests__/routing/route-matcher.test.d.ts.map +1 -0
- package/dist/__tests__/routing/route-matcher.test.js +219 -0
- package/dist/__tests__/routing/version.decorator.test.d.ts +2 -0
- package/dist/__tests__/routing/version.decorator.test.d.ts.map +1 -0
- package/dist/__tests__/routing/version.decorator.test.js +298 -0
- package/dist/__tests__/service.test.d.ts +2 -0
- package/dist/__tests__/service.test.d.ts.map +1 -0
- package/dist/__tests__/service.test.js +121 -0
- package/dist/__tests__/shutdown.test.d.ts +2 -0
- package/dist/__tests__/shutdown.test.d.ts.map +1 -0
- package/dist/__tests__/shutdown.test.js +250 -0
- package/dist/__tests__/testing/testing.module.test.d.ts +2 -0
- package/dist/__tests__/testing/testing.module.test.d.ts.map +1 -0
- package/dist/__tests__/testing/testing.module.test.js +370 -0
- package/dist/__tests__/upload/file-upload.test.d.ts +2 -0
- package/dist/__tests__/upload/file-upload.test.d.ts.map +1 -0
- package/dist/__tests__/upload/file-upload.test.js +498 -0
- package/dist/__tests__/utils/sanitize.test.d.ts +2 -0
- package/dist/__tests__/utils/sanitize.test.d.ts.map +1 -0
- package/dist/__tests__/utils/sanitize.test.js +291 -0
- package/dist/__tests__/validator.test.d.ts +2 -0
- package/dist/__tests__/validator.test.d.ts.map +1 -0
- package/dist/__tests__/validator.test.js +300 -0
- package/dist/container.d.ts +80 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +271 -0
- package/dist/decorators.d.ts +92 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +343 -0
- package/dist/errors/http.error.d.ts +31 -0
- package/dist/errors/http.error.d.ts.map +1 -0
- package/dist/errors/http.error.js +62 -0
- package/dist/filters/exception-filter.d.ts +39 -0
- package/dist/filters/exception-filter.d.ts.map +1 -0
- package/dist/filters/exception-filter.js +38 -0
- package/dist/filters/http-exception.filter.d.ts +9 -0
- package/dist/filters/http-exception.filter.d.ts.map +1 -0
- package/dist/filters/http-exception.filter.js +42 -0
- package/dist/hazel-app.d.ts +78 -0
- package/dist/hazel-app.d.ts.map +1 -0
- package/dist/hazel-app.js +453 -0
- package/dist/hazel-module.d.ts +20 -0
- package/dist/hazel-module.d.ts.map +1 -0
- package/dist/hazel-module.js +109 -0
- package/dist/hazel-response.d.ts +20 -0
- package/dist/hazel-response.d.ts.map +1 -0
- package/dist/hazel-response.js +68 -0
- package/dist/health.d.ts +73 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +174 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +140 -0
- package/dist/interceptors/interceptor.d.ts +22 -0
- package/dist/interceptors/interceptor.d.ts.map +1 -0
- package/dist/interceptors/interceptor.js +46 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +238 -0
- package/dist/middleware/cors.middleware.d.ts +44 -0
- package/dist/middleware/cors.middleware.d.ts.map +1 -0
- package/dist/middleware/cors.middleware.js +118 -0
- package/dist/middleware/csrf.middleware.d.ts +82 -0
- package/dist/middleware/csrf.middleware.d.ts.map +1 -0
- package/dist/middleware/csrf.middleware.js +183 -0
- package/dist/middleware/global-middleware.d.ts +111 -0
- package/dist/middleware/global-middleware.d.ts.map +1 -0
- package/dist/middleware/global-middleware.js +179 -0
- package/dist/middleware/rate-limit.middleware.d.ts +73 -0
- package/dist/middleware/rate-limit.middleware.d.ts.map +1 -0
- package/dist/middleware/rate-limit.middleware.js +124 -0
- package/dist/middleware/security-headers.middleware.d.ts +76 -0
- package/dist/middleware/security-headers.middleware.d.ts.map +1 -0
- package/dist/middleware/security-headers.middleware.js +123 -0
- package/dist/middleware/timeout.middleware.d.ts +25 -0
- package/dist/middleware/timeout.middleware.d.ts.map +1 -0
- package/dist/middleware/timeout.middleware.js +74 -0
- package/dist/middleware.d.ts +13 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +47 -0
- package/dist/pipes/pipe.d.ts +50 -0
- package/dist/pipes/pipe.d.ts.map +1 -0
- package/dist/pipes/pipe.js +96 -0
- package/dist/pipes/validation.pipe.d.ts +6 -0
- package/dist/pipes/validation.pipe.d.ts.map +1 -0
- package/dist/pipes/validation.pipe.js +61 -0
- package/dist/request-context.d.ts +17 -0
- package/dist/request-context.d.ts.map +1 -0
- package/dist/request-context.js +2 -0
- package/dist/request-parser.d.ts +7 -0
- package/dist/request-parser.d.ts.map +1 -0
- package/dist/request-parser.js +60 -0
- package/dist/router.d.ts +33 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +426 -0
- package/dist/routing/route-matcher.d.ts +39 -0
- package/dist/routing/route-matcher.d.ts.map +1 -0
- package/dist/routing/route-matcher.js +93 -0
- package/dist/routing/version.decorator.d.ts +36 -0
- package/dist/routing/version.decorator.d.ts.map +1 -0
- package/dist/routing/version.decorator.js +89 -0
- package/dist/service.d.ts +9 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +39 -0
- package/dist/shutdown.d.ts +32 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +109 -0
- package/dist/testing/testing.module.d.ts +83 -0
- package/dist/testing/testing.module.d.ts.map +1 -0
- package/dist/testing/testing.module.js +164 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/upload/file-upload.d.ts +75 -0
- package/dist/upload/file-upload.d.ts.map +1 -0
- package/dist/upload/file-upload.js +261 -0
- package/dist/utils/sanitize.d.ts +45 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +165 -0
- package/dist/validator.d.ts +7 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +119 -0
- package/package.json +65 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
42
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
43
|
+
};
|
|
44
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
45
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
46
|
+
};
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
const file_upload_1 = require("../../upload/file-upload");
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
require("reflect-metadata");
|
|
51
|
+
// Mock logger
|
|
52
|
+
jest.mock('../../logger', () => ({
|
|
53
|
+
info: jest.fn(),
|
|
54
|
+
debug: jest.fn(),
|
|
55
|
+
warn: jest.fn(),
|
|
56
|
+
error: jest.fn(),
|
|
57
|
+
}));
|
|
58
|
+
// Mock fs
|
|
59
|
+
jest.mock('fs', () => ({
|
|
60
|
+
existsSync: jest.fn(),
|
|
61
|
+
mkdirSync: jest.fn(),
|
|
62
|
+
promises: {
|
|
63
|
+
writeFile: jest.fn(),
|
|
64
|
+
},
|
|
65
|
+
}));
|
|
66
|
+
describe('FileUploadInterceptor', () => {
|
|
67
|
+
let uploadDir;
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
uploadDir = './test-uploads';
|
|
70
|
+
jest.clearAllMocks();
|
|
71
|
+
fs.existsSync.mockReturnValue(false);
|
|
72
|
+
});
|
|
73
|
+
describe('constructor', () => {
|
|
74
|
+
it('should create interceptor with default options', () => {
|
|
75
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
76
|
+
expect(interceptor).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
it('should create interceptor with custom destination', () => {
|
|
79
|
+
const interceptor = new file_upload_1.FileUploadInterceptor({
|
|
80
|
+
destination: uploadDir,
|
|
81
|
+
});
|
|
82
|
+
expect(interceptor).toBeDefined();
|
|
83
|
+
expect(fs.mkdirSync).toHaveBeenCalledWith(uploadDir, { recursive: true });
|
|
84
|
+
});
|
|
85
|
+
it('should create interceptor with custom filename function', () => {
|
|
86
|
+
const customFilename = jest.fn(() => 'custom-name.txt');
|
|
87
|
+
const interceptor = new file_upload_1.FileUploadInterceptor({
|
|
88
|
+
filename: customFilename,
|
|
89
|
+
});
|
|
90
|
+
expect(interceptor).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
it('should create interceptor with file size limits', () => {
|
|
93
|
+
const interceptor = new file_upload_1.FileUploadInterceptor({
|
|
94
|
+
limits: {
|
|
95
|
+
fileSize: 1024 * 1024, // 1MB
|
|
96
|
+
files: 5,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
expect(interceptor).toBeDefined();
|
|
100
|
+
});
|
|
101
|
+
it('should create interceptor with file filter', () => {
|
|
102
|
+
const fileFilter = jest.fn(() => true);
|
|
103
|
+
const interceptor = new file_upload_1.FileUploadInterceptor({
|
|
104
|
+
fileFilter,
|
|
105
|
+
});
|
|
106
|
+
expect(interceptor).toBeDefined();
|
|
107
|
+
});
|
|
108
|
+
it('should create interceptor with memory storage', () => {
|
|
109
|
+
const interceptor = new file_upload_1.FileUploadInterceptor({
|
|
110
|
+
storage: 'memory',
|
|
111
|
+
});
|
|
112
|
+
expect(interceptor).toBeDefined();
|
|
113
|
+
});
|
|
114
|
+
it('should not create directory if it exists', () => {
|
|
115
|
+
fs.existsSync.mockReturnValue(true);
|
|
116
|
+
const interceptor = new file_upload_1.FileUploadInterceptor({
|
|
117
|
+
destination: uploadDir,
|
|
118
|
+
});
|
|
119
|
+
expect(interceptor).toBeDefined();
|
|
120
|
+
expect(fs.mkdirSync).not.toHaveBeenCalled();
|
|
121
|
+
});
|
|
122
|
+
it('should not create directory for memory storage', () => {
|
|
123
|
+
const interceptor = new file_upload_1.FileUploadInterceptor({
|
|
124
|
+
storage: 'memory',
|
|
125
|
+
destination: uploadDir,
|
|
126
|
+
});
|
|
127
|
+
expect(interceptor).toBeDefined();
|
|
128
|
+
expect(fs.mkdirSync).not.toHaveBeenCalled();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe('parseMultipart', () => {
|
|
132
|
+
it('should throw error for non-multipart content type', async () => {
|
|
133
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
134
|
+
const mockReq = {
|
|
135
|
+
headers: {
|
|
136
|
+
'content-type': 'application/json',
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
await expect(interceptor.parseMultipart(mockReq)).rejects.toThrow('Content-Type must be multipart/form-data');
|
|
140
|
+
});
|
|
141
|
+
it('should throw error for missing boundary', async () => {
|
|
142
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
143
|
+
const mockReq = {
|
|
144
|
+
headers: {
|
|
145
|
+
'content-type': 'multipart/form-data',
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
await expect(interceptor.parseMultipart(mockReq)).rejects.toThrow('Missing boundary in Content-Type');
|
|
149
|
+
});
|
|
150
|
+
it('should parse multipart data with boundary', async () => {
|
|
151
|
+
const interceptor = new file_upload_1.FileUploadInterceptor({ storage: 'memory' });
|
|
152
|
+
const boundary = '----WebKitFormBoundary';
|
|
153
|
+
const fileContent = 'test file content';
|
|
154
|
+
const multipartData = [
|
|
155
|
+
`------WebKitFormBoundary`,
|
|
156
|
+
`Content-Disposition: form-data; name="field1"`,
|
|
157
|
+
``,
|
|
158
|
+
`value1`,
|
|
159
|
+
`------WebKitFormBoundary`,
|
|
160
|
+
`Content-Disposition: form-data; name="file"; filename="test.txt"`,
|
|
161
|
+
`Content-Type: text/plain`,
|
|
162
|
+
``,
|
|
163
|
+
fileContent,
|
|
164
|
+
`------WebKitFormBoundary--`,
|
|
165
|
+
].join('\r\n');
|
|
166
|
+
const mockReq = {
|
|
167
|
+
headers: {
|
|
168
|
+
'content-type': `multipart/form-data; boundary=${boundary}`,
|
|
169
|
+
},
|
|
170
|
+
on: jest.fn((event, handler) => {
|
|
171
|
+
if (event === 'data') {
|
|
172
|
+
handler(Buffer.from(multipartData));
|
|
173
|
+
}
|
|
174
|
+
if (event === 'end') {
|
|
175
|
+
handler();
|
|
176
|
+
}
|
|
177
|
+
}),
|
|
178
|
+
};
|
|
179
|
+
const result = await interceptor.parseMultipart(mockReq);
|
|
180
|
+
expect(result.fields).toBeDefined();
|
|
181
|
+
expect(result.files).toBeDefined();
|
|
182
|
+
});
|
|
183
|
+
it('should handle request error', async () => {
|
|
184
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
185
|
+
const error = new Error('Request error');
|
|
186
|
+
const mockReq = {
|
|
187
|
+
headers: {
|
|
188
|
+
'content-type': 'multipart/form-data; boundary=test',
|
|
189
|
+
},
|
|
190
|
+
on: jest.fn((event, handler) => {
|
|
191
|
+
if (event === 'error') {
|
|
192
|
+
handler(error);
|
|
193
|
+
}
|
|
194
|
+
}),
|
|
195
|
+
};
|
|
196
|
+
await expect(interceptor.parseMultipart(mockReq)).rejects.toThrow('Request error');
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
describe('extractBoundary', () => {
|
|
200
|
+
it('should extract boundary from content type', () => {
|
|
201
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
202
|
+
const contentType = 'multipart/form-data; boundary=----WebKitFormBoundary';
|
|
203
|
+
const boundary = interceptor.extractBoundary(contentType);
|
|
204
|
+
expect(boundary).toBe('----WebKitFormBoundary');
|
|
205
|
+
});
|
|
206
|
+
it('should trim boundary whitespace', () => {
|
|
207
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
208
|
+
const contentType = 'multipart/form-data; boundary= ----WebKitFormBoundary ';
|
|
209
|
+
const boundary = interceptor.extractBoundary(contentType);
|
|
210
|
+
expect(boundary).toBe('----WebKitFormBoundary');
|
|
211
|
+
});
|
|
212
|
+
it('should return null for missing boundary', () => {
|
|
213
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
214
|
+
const contentType = 'multipart/form-data';
|
|
215
|
+
const boundary = interceptor.extractBoundary(contentType);
|
|
216
|
+
expect(boundary).toBeNull();
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
describe('splitBuffer', () => {
|
|
220
|
+
it('should split buffer by boundary', () => {
|
|
221
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
222
|
+
const boundary = Buffer.from('--boundary');
|
|
223
|
+
const buffer = Buffer.from('part1--boundarypart2--boundarypart3');
|
|
224
|
+
const parts = interceptor.splitBuffer(buffer, boundary);
|
|
225
|
+
expect(parts.length).toBeGreaterThan(0);
|
|
226
|
+
});
|
|
227
|
+
it('should handle empty buffer', () => {
|
|
228
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
229
|
+
const boundary = Buffer.from('--boundary');
|
|
230
|
+
const buffer = Buffer.from('');
|
|
231
|
+
const parts = interceptor.splitBuffer(buffer, boundary);
|
|
232
|
+
expect(parts).toEqual([]);
|
|
233
|
+
});
|
|
234
|
+
it('should handle buffer without boundary', () => {
|
|
235
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
236
|
+
const boundary = Buffer.from('--boundary');
|
|
237
|
+
const buffer = Buffer.from('no boundary here');
|
|
238
|
+
const parts = interceptor.splitBuffer(buffer, boundary);
|
|
239
|
+
expect(parts).toEqual([]);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
describe('parsePart', () => {
|
|
243
|
+
it('should parse part with headers and body', () => {
|
|
244
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
245
|
+
const part = Buffer.from('Content-Disposition: form-data; name="field"\r\n' +
|
|
246
|
+
'Content-Type: text/plain\r\n' +
|
|
247
|
+
'\r\n' +
|
|
248
|
+
'body content');
|
|
249
|
+
const result = interceptor.parsePart(part);
|
|
250
|
+
expect(result.headers).toBeDefined();
|
|
251
|
+
expect(result.headers['content-disposition']).toContain('form-data');
|
|
252
|
+
expect(result.body.toString()).toBe('body content');
|
|
253
|
+
});
|
|
254
|
+
it('should handle part without headers', () => {
|
|
255
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
256
|
+
const part = Buffer.from('just body');
|
|
257
|
+
const result = interceptor.parsePart(part);
|
|
258
|
+
expect(result.headers).toEqual({});
|
|
259
|
+
expect(result.body).toEqual(part);
|
|
260
|
+
});
|
|
261
|
+
it('should parse multiple headers', () => {
|
|
262
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
263
|
+
const part = Buffer.from('Content-Disposition: form-data\r\n' +
|
|
264
|
+
'Content-Type: application/json\r\n' +
|
|
265
|
+
'X-Custom-Header: value\r\n' +
|
|
266
|
+
'\r\n' +
|
|
267
|
+
'body');
|
|
268
|
+
const result = interceptor.parsePart(part);
|
|
269
|
+
expect(result.headers['content-disposition']).toBe('form-data');
|
|
270
|
+
expect(result.headers['content-type']).toBe('application/json');
|
|
271
|
+
expect(result.headers['x-custom-header']).toBe('value');
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
describe('processFile', () => {
|
|
275
|
+
it('should process file with disk storage', async () => {
|
|
276
|
+
fs.promises.writeFile.mockResolvedValue(undefined);
|
|
277
|
+
const interceptor = new file_upload_1.FileUploadInterceptor({
|
|
278
|
+
storage: 'disk',
|
|
279
|
+
destination: uploadDir,
|
|
280
|
+
});
|
|
281
|
+
const file = {
|
|
282
|
+
fieldname: 'file',
|
|
283
|
+
originalname: 'test.txt',
|
|
284
|
+
encoding: '7bit',
|
|
285
|
+
mimetype: 'text/plain',
|
|
286
|
+
size: 100,
|
|
287
|
+
buffer: Buffer.from('test content'),
|
|
288
|
+
};
|
|
289
|
+
const result = await interceptor.processFile(file);
|
|
290
|
+
expect(result).toBeDefined();
|
|
291
|
+
expect(result.originalname).toBe('test.txt');
|
|
292
|
+
expect(result.destination).toBe(uploadDir);
|
|
293
|
+
expect(fs.promises.writeFile).toHaveBeenCalled();
|
|
294
|
+
});
|
|
295
|
+
it('should process file with memory storage', async () => {
|
|
296
|
+
const interceptor = new file_upload_1.FileUploadInterceptor({
|
|
297
|
+
storage: 'memory',
|
|
298
|
+
});
|
|
299
|
+
const file = {
|
|
300
|
+
fieldname: 'file',
|
|
301
|
+
originalname: 'test.txt',
|
|
302
|
+
encoding: '7bit',
|
|
303
|
+
mimetype: 'text/plain',
|
|
304
|
+
size: 100,
|
|
305
|
+
buffer: Buffer.from('test content'),
|
|
306
|
+
};
|
|
307
|
+
const result = await interceptor.processFile(file);
|
|
308
|
+
expect(result).toBeDefined();
|
|
309
|
+
expect(result.buffer).toBeDefined();
|
|
310
|
+
expect(fs.promises.writeFile).not.toHaveBeenCalled();
|
|
311
|
+
});
|
|
312
|
+
it('should reject file by filter', async () => {
|
|
313
|
+
const fileFilter = jest.fn(() => false);
|
|
314
|
+
const interceptor = new file_upload_1.FileUploadInterceptor({
|
|
315
|
+
fileFilter,
|
|
316
|
+
});
|
|
317
|
+
const file = {
|
|
318
|
+
fieldname: 'file',
|
|
319
|
+
originalname: 'test.exe',
|
|
320
|
+
encoding: '7bit',
|
|
321
|
+
mimetype: 'application/x-msdownload',
|
|
322
|
+
size: 100,
|
|
323
|
+
buffer: Buffer.from('test'),
|
|
324
|
+
};
|
|
325
|
+
const result = await interceptor.processFile(file);
|
|
326
|
+
expect(result).toBeNull();
|
|
327
|
+
expect(fileFilter).toHaveBeenCalledWith(file);
|
|
328
|
+
});
|
|
329
|
+
it('should throw error for file too large', async () => {
|
|
330
|
+
const interceptor = new file_upload_1.FileUploadInterceptor({
|
|
331
|
+
limits: {
|
|
332
|
+
fileSize: 50,
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
const file = {
|
|
336
|
+
fieldname: 'file',
|
|
337
|
+
originalname: 'large.txt',
|
|
338
|
+
encoding: '7bit',
|
|
339
|
+
mimetype: 'text/plain',
|
|
340
|
+
size: 100,
|
|
341
|
+
buffer: Buffer.from('x'.repeat(100)),
|
|
342
|
+
};
|
|
343
|
+
await expect(interceptor.processFile(file)).rejects.toThrow('File too large');
|
|
344
|
+
});
|
|
345
|
+
it('should use custom filename function', async () => {
|
|
346
|
+
const customFilename = jest.fn(() => 'custom-123.txt');
|
|
347
|
+
const interceptor = new file_upload_1.FileUploadInterceptor({
|
|
348
|
+
storage: 'memory',
|
|
349
|
+
filename: customFilename,
|
|
350
|
+
});
|
|
351
|
+
const file = {
|
|
352
|
+
fieldname: 'file',
|
|
353
|
+
originalname: 'original.txt',
|
|
354
|
+
encoding: '7bit',
|
|
355
|
+
mimetype: 'text/plain',
|
|
356
|
+
size: 100,
|
|
357
|
+
buffer: Buffer.from('test'),
|
|
358
|
+
};
|
|
359
|
+
const result = await interceptor.processFile(file);
|
|
360
|
+
expect(customFilename).toHaveBeenCalledWith(file);
|
|
361
|
+
expect(result.filename).toBe('custom-123.txt');
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
describe('defaultFilename', () => {
|
|
365
|
+
it('should generate unique filename', () => {
|
|
366
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
367
|
+
const file = {
|
|
368
|
+
originalname: 'test.txt',
|
|
369
|
+
};
|
|
370
|
+
const filename1 = interceptor.defaultFilename(file);
|
|
371
|
+
const filename2 = interceptor.defaultFilename(file);
|
|
372
|
+
expect(filename1).toContain('.txt');
|
|
373
|
+
expect(filename2).toContain('.txt');
|
|
374
|
+
expect(filename1).not.toBe(filename2);
|
|
375
|
+
});
|
|
376
|
+
it('should preserve file extension', () => {
|
|
377
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
378
|
+
const file = {
|
|
379
|
+
originalname: 'document.pdf',
|
|
380
|
+
};
|
|
381
|
+
const filename = interceptor.defaultFilename(file);
|
|
382
|
+
expect(filename).toContain('.pdf');
|
|
383
|
+
});
|
|
384
|
+
it('should handle files without extension', () => {
|
|
385
|
+
const interceptor = new file_upload_1.FileUploadInterceptor();
|
|
386
|
+
const file = {
|
|
387
|
+
originalname: 'README',
|
|
388
|
+
};
|
|
389
|
+
const filename = interceptor.defaultFilename(file);
|
|
390
|
+
expect(filename).toBeDefined();
|
|
391
|
+
expect(filename).not.toContain('.');
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
describe('UploadedFileDecorator', () => {
|
|
396
|
+
it('should set metadata for file parameter', () => {
|
|
397
|
+
class TestController {
|
|
398
|
+
upload(file) {
|
|
399
|
+
return file;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
__decorate([
|
|
403
|
+
__param(0, (0, file_upload_1.UploadedFileDecorator)('avatar')),
|
|
404
|
+
__metadata("design:type", Function),
|
|
405
|
+
__metadata("design:paramtypes", [Object]),
|
|
406
|
+
__metadata("design:returntype", void 0)
|
|
407
|
+
], TestController.prototype, "upload", null);
|
|
408
|
+
const metadata = Reflect.getMetadata('hazel:inject', TestController, 'upload');
|
|
409
|
+
expect(metadata).toBeDefined();
|
|
410
|
+
expect(metadata[0]).toEqual({
|
|
411
|
+
type: 'file',
|
|
412
|
+
fieldname: 'avatar',
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
it('should work without fieldname', () => {
|
|
416
|
+
class TestController {
|
|
417
|
+
upload(file) {
|
|
418
|
+
return file;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
__decorate([
|
|
422
|
+
__param(0, (0, file_upload_1.UploadedFileDecorator)()),
|
|
423
|
+
__metadata("design:type", Function),
|
|
424
|
+
__metadata("design:paramtypes", [Object]),
|
|
425
|
+
__metadata("design:returntype", void 0)
|
|
426
|
+
], TestController.prototype, "upload", null);
|
|
427
|
+
const metadata = Reflect.getMetadata('hazel:inject', TestController, 'upload');
|
|
428
|
+
expect(metadata).toBeDefined();
|
|
429
|
+
expect(metadata[0].type).toBe('file');
|
|
430
|
+
});
|
|
431
|
+
it('should throw error when used outside method parameter', () => {
|
|
432
|
+
expect(() => {
|
|
433
|
+
const decorator = (0, file_upload_1.UploadedFileDecorator)();
|
|
434
|
+
decorator({}, undefined, 0);
|
|
435
|
+
}).toThrow('UploadedFile decorator must be used on a method parameter');
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
describe('UploadedFilesDecorator', () => {
|
|
439
|
+
it('should set metadata for files parameter', () => {
|
|
440
|
+
class TestController {
|
|
441
|
+
uploadMultiple(files) {
|
|
442
|
+
return files;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
__decorate([
|
|
446
|
+
__param(0, (0, file_upload_1.UploadedFilesDecorator)('photos')),
|
|
447
|
+
__metadata("design:type", Function),
|
|
448
|
+
__metadata("design:paramtypes", [Object]),
|
|
449
|
+
__metadata("design:returntype", void 0)
|
|
450
|
+
], TestController.prototype, "uploadMultiple", null);
|
|
451
|
+
const metadata = Reflect.getMetadata('hazel:inject', TestController, 'uploadMultiple');
|
|
452
|
+
expect(metadata).toBeDefined();
|
|
453
|
+
expect(metadata[0]).toEqual({
|
|
454
|
+
type: 'files',
|
|
455
|
+
fieldname: 'photos',
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
it('should work without fieldname', () => {
|
|
459
|
+
class TestController {
|
|
460
|
+
uploadMultiple(files) {
|
|
461
|
+
return files;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
__decorate([
|
|
465
|
+
__param(0, (0, file_upload_1.UploadedFilesDecorator)()),
|
|
466
|
+
__metadata("design:type", Function),
|
|
467
|
+
__metadata("design:paramtypes", [Object]),
|
|
468
|
+
__metadata("design:returntype", void 0)
|
|
469
|
+
], TestController.prototype, "uploadMultiple", null);
|
|
470
|
+
const metadata = Reflect.getMetadata('hazel:inject', TestController, 'uploadMultiple');
|
|
471
|
+
expect(metadata).toBeDefined();
|
|
472
|
+
expect(metadata[0].type).toBe('files');
|
|
473
|
+
});
|
|
474
|
+
it('should throw error when used outside method parameter', () => {
|
|
475
|
+
expect(() => {
|
|
476
|
+
const decorator = (0, file_upload_1.UploadedFilesDecorator)();
|
|
477
|
+
decorator({}, undefined, 0);
|
|
478
|
+
}).toThrow('UploadedFiles decorator must be used on a method parameter');
|
|
479
|
+
});
|
|
480
|
+
it('should handle multiple parameters', () => {
|
|
481
|
+
class TestController {
|
|
482
|
+
upload(avatar, photos) {
|
|
483
|
+
return { avatar, photos };
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
__decorate([
|
|
487
|
+
__param(0, (0, file_upload_1.UploadedFileDecorator)('avatar')),
|
|
488
|
+
__param(1, (0, file_upload_1.UploadedFilesDecorator)('photos')),
|
|
489
|
+
__metadata("design:type", Function),
|
|
490
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
491
|
+
__metadata("design:returntype", void 0)
|
|
492
|
+
], TestController.prototype, "upload", null);
|
|
493
|
+
const metadata = Reflect.getMetadata('hazel:inject', TestController, 'upload');
|
|
494
|
+
expect(metadata).toBeDefined();
|
|
495
|
+
expect(metadata[0].type).toBe('file');
|
|
496
|
+
expect(metadata[1].type).toBe('files');
|
|
497
|
+
});
|
|
498
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/utils/sanitize.test.ts"],"names":[],"mappings":""}
|