bxo 0.0.5-dev.51 → 0.0.5-dev.52
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/REFACTOR_README.md +209 -0
- package/index.ts +4 -1549
- package/package.json +9 -1
- package/src/core/bxo.ts +437 -0
- package/src/handlers/request-handler.ts +229 -0
- package/src/index.ts +54 -0
- package/src/types/index.ts +170 -0
- package/src/utils/context-factory.ts +158 -0
- package/src/utils/helpers.ts +40 -0
- package/src/utils/index.ts +258 -0
- package/src/utils/response-handler.ts +216 -0
- package/src/utils/route-matcher.ts +191 -0
- package/tests/README.md +359 -0
- package/tests/integration/bxo.test.ts +413 -0
- package/tests/run-tests.ts +44 -0
- package/tests/unit/context-factory.test.ts +386 -0
- package/tests/unit/helpers.test.ts +253 -0
- package/tests/unit/response-handler.test.ts +301 -0
- package/tests/unit/route-matcher.test.ts +181 -0
- package/tests/unit/utils.test.ts +310 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { describe, it, expect } from 'bun:test';
|
|
2
|
+
import {
|
|
3
|
+
parseQuery,
|
|
4
|
+
parseHeaders,
|
|
5
|
+
parseCookies,
|
|
6
|
+
validateData,
|
|
7
|
+
validateResponse,
|
|
8
|
+
cookiesToHeaders,
|
|
9
|
+
mergeHeadersWithCookies,
|
|
10
|
+
createRedirectResponse,
|
|
11
|
+
isFileUpload,
|
|
12
|
+
getFileFromUpload,
|
|
13
|
+
getFileInfo,
|
|
14
|
+
getFileUploads,
|
|
15
|
+
getFormFields
|
|
16
|
+
} from '../../src/utils';
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
|
|
19
|
+
describe('Utility Functions', () => {
|
|
20
|
+
describe('parseQuery', () => {
|
|
21
|
+
it('should parse URLSearchParams correctly', () => {
|
|
22
|
+
const searchParams = new URLSearchParams('name=john&age=25&city=new%20york');
|
|
23
|
+
const result = parseQuery(searchParams);
|
|
24
|
+
|
|
25
|
+
expect(result).toEqual({
|
|
26
|
+
name: 'john',
|
|
27
|
+
age: '25',
|
|
28
|
+
city: 'new york'
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should handle empty search params', () => {
|
|
33
|
+
const searchParams = new URLSearchParams('');
|
|
34
|
+
const result = parseQuery(searchParams);
|
|
35
|
+
|
|
36
|
+
expect(result).toEqual({});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('parseHeaders', () => {
|
|
41
|
+
it('should parse Headers object correctly', () => {
|
|
42
|
+
const headers = new Headers();
|
|
43
|
+
headers.set('content-type', 'application/json');
|
|
44
|
+
headers.set('authorization', 'Bearer token123');
|
|
45
|
+
|
|
46
|
+
const result = parseHeaders(headers);
|
|
47
|
+
|
|
48
|
+
expect(result).toEqual({
|
|
49
|
+
'content-type': 'application/json',
|
|
50
|
+
'authorization': 'Bearer token123'
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('parseCookies', () => {
|
|
56
|
+
it('should parse cookie header correctly', () => {
|
|
57
|
+
const cookieHeader = 'name=john; age=25; city=new%20york';
|
|
58
|
+
const result = parseCookies(cookieHeader);
|
|
59
|
+
|
|
60
|
+
expect(result).toEqual({
|
|
61
|
+
name: 'john',
|
|
62
|
+
age: '25',
|
|
63
|
+
city: 'new york'
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should handle null cookie header', () => {
|
|
68
|
+
const result = parseCookies(null);
|
|
69
|
+
expect(result).toEqual({});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should handle empty cookie header', () => {
|
|
73
|
+
const result = parseCookies('');
|
|
74
|
+
expect(result).toEqual({});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('validateData', () => {
|
|
79
|
+
it('should validate data with schema', () => {
|
|
80
|
+
const schema = z.object({
|
|
81
|
+
name: z.string(),
|
|
82
|
+
age: z.number()
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const data = { name: 'john', age: 25 };
|
|
86
|
+
const result = validateData(schema, data);
|
|
87
|
+
|
|
88
|
+
expect(result).toEqual(data);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should return data without validation when no schema', () => {
|
|
92
|
+
const data = { name: 'john', age: 25 };
|
|
93
|
+
const result = validateData(undefined, data);
|
|
94
|
+
|
|
95
|
+
expect(result).toEqual(data);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should throw error for invalid data', () => {
|
|
99
|
+
const schema = z.object({
|
|
100
|
+
name: z.string(),
|
|
101
|
+
age: z.number()
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const data = { name: 'john', age: 'invalid' };
|
|
105
|
+
|
|
106
|
+
expect(() => validateData(schema, data)).toThrow();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('validateResponse', () => {
|
|
111
|
+
it('should validate response with simple schema', () => {
|
|
112
|
+
const schema = z.object({
|
|
113
|
+
message: z.string()
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const data = { message: 'success' };
|
|
117
|
+
const result = validateResponse(schema, data);
|
|
118
|
+
|
|
119
|
+
expect(result).toEqual(data);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should validate response with status-based schema', () => {
|
|
123
|
+
const schema = {
|
|
124
|
+
200: z.object({ message: z.string() }),
|
|
125
|
+
400: z.object({ error: z.string() })
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const data = { message: 'success' };
|
|
129
|
+
const result = validateResponse(schema, data, 200);
|
|
130
|
+
|
|
131
|
+
expect(result).toEqual(data);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should return data without validation when no schema', () => {
|
|
135
|
+
const data = { message: 'success' };
|
|
136
|
+
const result = validateResponse(undefined, data);
|
|
137
|
+
|
|
138
|
+
expect(result).toEqual(data);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('cookiesToHeaders', () => {
|
|
143
|
+
it('should convert internal cookies to header strings', () => {
|
|
144
|
+
const cookies = [
|
|
145
|
+
{
|
|
146
|
+
name: 'session',
|
|
147
|
+
value: 'abc123',
|
|
148
|
+
domain: 'example.com',
|
|
149
|
+
path: '/',
|
|
150
|
+
secure: true,
|
|
151
|
+
httpOnly: true
|
|
152
|
+
}
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
const result = cookiesToHeaders(cookies);
|
|
156
|
+
|
|
157
|
+
expect(result).toHaveLength(1);
|
|
158
|
+
expect(result[0]).toContain('session=abc123');
|
|
159
|
+
expect(result[0]).toContain('Domain=example.com');
|
|
160
|
+
expect(result[0]).toContain('Path=/');
|
|
161
|
+
expect(result[0]).toContain('Secure');
|
|
162
|
+
expect(result[0]).toContain('HttpOnly');
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('mergeHeadersWithCookies', () => {
|
|
167
|
+
it('should merge headers with cookies', () => {
|
|
168
|
+
const headers = { 'content-type': 'application/json' };
|
|
169
|
+
const cookies = [
|
|
170
|
+
{ name: 'session', value: 'abc123' }
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
const result = mergeHeadersWithCookies(headers, cookies);
|
|
174
|
+
|
|
175
|
+
expect(result.get('content-type')).toBe('application/json');
|
|
176
|
+
expect(result.get('set-cookie')).toBe('session=abc123');
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('createRedirectResponse', () => {
|
|
181
|
+
it('should create redirect response with default status', () => {
|
|
182
|
+
const result = createRedirectResponse('/new-location');
|
|
183
|
+
|
|
184
|
+
expect(result.status).toBe(302);
|
|
185
|
+
expect(result.headers.get('location')).toBe('/new-location');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should create redirect response with custom status', () => {
|
|
189
|
+
const result = createRedirectResponse('/new-location', 301);
|
|
190
|
+
|
|
191
|
+
expect(result.status).toBe(301);
|
|
192
|
+
expect(result.headers.get('location')).toBe('/new-location');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should include additional headers', () => {
|
|
196
|
+
const headers = { 'x-custom': 'value' };
|
|
197
|
+
const result = createRedirectResponse('/new-location', 302, headers);
|
|
198
|
+
|
|
199
|
+
expect(result.headers.get('x-custom')).toBe('value');
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('File Upload Utilities', () => {
|
|
204
|
+
it('should identify file uploads correctly', () => {
|
|
205
|
+
const fileUpload = {
|
|
206
|
+
type: 'file',
|
|
207
|
+
name: 'test.jpg',
|
|
208
|
+
size: 1024,
|
|
209
|
+
lastModified: 1234567890,
|
|
210
|
+
file: new File(['test'], 'test.jpg'),
|
|
211
|
+
filename: 'test.jpg',
|
|
212
|
+
mimetype: 'image/jpeg'
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
expect(isFileUpload(fileUpload)).toBe(true);
|
|
216
|
+
expect(isFileUpload('not a file')).toBe(false);
|
|
217
|
+
expect(isFileUpload({ type: 'text' })).toBe(false);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should extract file from upload', () => {
|
|
221
|
+
const file = new File(['test'], 'test.jpg');
|
|
222
|
+
const fileUpload = {
|
|
223
|
+
type: 'file',
|
|
224
|
+
name: 'test.jpg',
|
|
225
|
+
size: 1024,
|
|
226
|
+
lastModified: 1234567890,
|
|
227
|
+
file,
|
|
228
|
+
filename: 'test.jpg',
|
|
229
|
+
mimetype: 'image/jpeg'
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const result = getFileFromUpload(fileUpload);
|
|
233
|
+
expect(result).toBe(file);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should return null for non-file uploads', () => {
|
|
237
|
+
const result = getFileFromUpload('not a file');
|
|
238
|
+
expect(result).toBeNull();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should get file info', () => {
|
|
242
|
+
const fileUpload = {
|
|
243
|
+
type: 'file',
|
|
244
|
+
name: 'test.jpg',
|
|
245
|
+
size: 1024,
|
|
246
|
+
lastModified: 1234567890,
|
|
247
|
+
file: new File(['test'], 'test.jpg'),
|
|
248
|
+
filename: 'test.jpg',
|
|
249
|
+
mimetype: 'image/jpeg'
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const result = getFileInfo(fileUpload);
|
|
253
|
+
expect(result).toEqual({
|
|
254
|
+
name: 'test.jpg',
|
|
255
|
+
size: 1024,
|
|
256
|
+
mimetype: 'image/jpeg',
|
|
257
|
+
lastModified: 1234567890
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should return null for non-file uploads when getting info', () => {
|
|
262
|
+
const result = getFileInfo('not a file');
|
|
263
|
+
expect(result).toBeNull();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should get all file uploads from form data', () => {
|
|
267
|
+
const formData = {
|
|
268
|
+
name: 'john',
|
|
269
|
+
avatar: {
|
|
270
|
+
type: 'file',
|
|
271
|
+
name: 'avatar.jpg',
|
|
272
|
+
size: 1024,
|
|
273
|
+
lastModified: 1234567890,
|
|
274
|
+
file: new File(['test'], 'avatar.jpg'),
|
|
275
|
+
filename: 'avatar.jpg',
|
|
276
|
+
mimetype: 'image/jpeg'
|
|
277
|
+
},
|
|
278
|
+
email: 'john@example.com'
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const result = getFileUploads(formData);
|
|
282
|
+
|
|
283
|
+
expect(Object.keys(result)).toHaveLength(1);
|
|
284
|
+
expect(result.avatar).toBeInstanceOf(File);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should get all non-file fields from form data', () => {
|
|
288
|
+
const formData = {
|
|
289
|
+
name: 'john',
|
|
290
|
+
avatar: {
|
|
291
|
+
type: 'file',
|
|
292
|
+
name: 'avatar.jpg',
|
|
293
|
+
size: 1024,
|
|
294
|
+
lastModified: 1234567890,
|
|
295
|
+
file: new File(['test'], 'avatar.jpg'),
|
|
296
|
+
filename: 'avatar.jpg',
|
|
297
|
+
mimetype: 'image/jpeg'
|
|
298
|
+
},
|
|
299
|
+
email: 'john@example.com'
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const result = getFormFields(formData);
|
|
303
|
+
|
|
304
|
+
expect(result).toEqual({
|
|
305
|
+
name: 'john',
|
|
306
|
+
email: 'john@example.com'
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
});
|