bxo 0.0.5-dev.50 → 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.
@@ -0,0 +1,386 @@
1
+ import { describe, it, expect, beforeEach } from 'bun:test';
2
+ import { createContext, createOptionsContext, getInternalCookies } from '../../src/utils/context-factory';
3
+ import { z } from 'zod';
4
+
5
+ describe('Context Factory', () => {
6
+ let mockRequest: Request;
7
+ let mockRouteConfig: any;
8
+
9
+ beforeEach(() => {
10
+ mockRequest = new Request('http://localhost:3000/test');
11
+ mockRouteConfig = {
12
+ params: z.object({ id: z.string() }),
13
+ query: z.object({ page: z.string().optional() }),
14
+ body: z.object({ name: z.string() }),
15
+ headers: z.object({ 'user-agent': z.string() }),
16
+ cookies: z.object({ session: z.string() })
17
+ };
18
+ });
19
+
20
+ describe('createContext', () => {
21
+ it('should create context with validation enabled', () => {
22
+ const params = { id: '123' };
23
+ const query = { page: '1' };
24
+ const body = { name: 'John' };
25
+ const headers = { 'user-agent': 'Mozilla/5.0' };
26
+ const cookies = { session: 'abc123' };
27
+ const pathname = '/users/123';
28
+
29
+ const ctx = createContext(
30
+ params,
31
+ query,
32
+ body,
33
+ headers,
34
+ cookies,
35
+ pathname,
36
+ mockRequest,
37
+ mockRouteConfig,
38
+ true
39
+ );
40
+
41
+ expect(ctx.params).toEqual(params);
42
+ expect(ctx.query).toEqual(query);
43
+ expect(ctx.body).toEqual(body);
44
+ expect(ctx.headers).toEqual(headers);
45
+ expect(ctx.cookies).toEqual(cookies);
46
+ expect(ctx.path).toBe(pathname);
47
+ expect(ctx.request).toBe(mockRequest);
48
+ expect(ctx.set.status).toBe(200);
49
+ expect(ctx.set.headers).toEqual({});
50
+ });
51
+
52
+ it('should create context without validation when disabled', () => {
53
+ const params = { id: '123' };
54
+ const query = { page: '1' };
55
+ const body = { name: 'John' };
56
+ const headers = { 'user-agent': 'Mozilla/5.0' };
57
+ const cookies = { session: 'abc123' };
58
+ const pathname = '/users/123';
59
+
60
+ const ctx = createContext(
61
+ params,
62
+ query,
63
+ body,
64
+ headers,
65
+ cookies,
66
+ pathname,
67
+ mockRequest,
68
+ mockRouteConfig,
69
+ false
70
+ );
71
+
72
+ expect(ctx.params).toEqual(params);
73
+ expect(ctx.query).toEqual(query);
74
+ expect(ctx.body).toEqual(body);
75
+ expect(ctx.headers).toEqual(headers);
76
+ expect(ctx.cookies).toEqual(cookies);
77
+ });
78
+
79
+ it('should create context without route config', () => {
80
+ const params = { id: '123' };
81
+ const query = { page: '1' };
82
+ const body = { name: 'John' };
83
+ const headers = { 'user-agent': 'Mozilla/5.0' };
84
+ const cookies = { session: 'abc123' };
85
+ const pathname = '/users/123';
86
+
87
+ const ctx = createContext(
88
+ params,
89
+ query,
90
+ body,
91
+ headers,
92
+ cookies,
93
+ pathname,
94
+ mockRequest,
95
+ undefined,
96
+ true
97
+ );
98
+
99
+ expect(ctx.params).toEqual(params);
100
+ expect(ctx.query).toEqual(query);
101
+ expect(ctx.body).toEqual(body);
102
+ expect(ctx.headers).toEqual(headers);
103
+ expect(ctx.cookies).toEqual(cookies);
104
+ });
105
+
106
+ it('should handle cookie setting', () => {
107
+ const ctx = createContext(
108
+ {},
109
+ {},
110
+ {},
111
+ {},
112
+ {},
113
+ '/',
114
+ mockRequest,
115
+ undefined,
116
+ true
117
+ );
118
+
119
+ ctx.set.cookies('session', 'abc123', {
120
+ domain: 'example.com',
121
+ path: '/',
122
+ secure: true,
123
+ httpOnly: true
124
+ });
125
+
126
+ const internalCookies = getInternalCookies(ctx);
127
+ expect(internalCookies).toHaveLength(1);
128
+ expect(internalCookies[0]).toEqual({
129
+ name: 'session',
130
+ value: 'abc123',
131
+ domain: 'example.com',
132
+ path: '/',
133
+ secure: true,
134
+ httpOnly: true
135
+ });
136
+ });
137
+
138
+ it('should handle status setting', () => {
139
+ const ctx = createContext(
140
+ {},
141
+ {},
142
+ {},
143
+ {},
144
+ {},
145
+ '/',
146
+ mockRequest,
147
+ undefined,
148
+ true
149
+ );
150
+
151
+ ctx.status(404, 'Not Found');
152
+ expect(ctx.set.status).toBe(404);
153
+ });
154
+
155
+ it('should handle redirect', () => {
156
+ const ctx = createContext(
157
+ {},
158
+ {},
159
+ {},
160
+ {},
161
+ {},
162
+ '/',
163
+ mockRequest,
164
+ undefined,
165
+ true
166
+ );
167
+
168
+ const response = ctx.redirect('/new-location', 301);
169
+
170
+ expect(response.status).toBe(301);
171
+ expect(response.headers.get('location')).toBe('/new-location');
172
+ expect(ctx.set.redirect).toEqual({
173
+ location: '/new-location',
174
+ status: 301
175
+ });
176
+ });
177
+
178
+ it('should handle redirect with cookies', () => {
179
+ const ctx = createContext(
180
+ {},
181
+ {},
182
+ {},
183
+ {},
184
+ {},
185
+ '/',
186
+ mockRequest,
187
+ undefined,
188
+ true
189
+ );
190
+
191
+ ctx.set.cookies('session', 'abc123');
192
+ const response = ctx.redirect('/new-location');
193
+
194
+ expect(response.status).toBe(302);
195
+ expect(response.headers.get('location')).toBe('/new-location');
196
+ expect(response.headers.get('set-cookie')).toBe('session=abc123');
197
+ });
198
+
199
+ it('should handle clearRedirect', () => {
200
+ const ctx = createContext(
201
+ {},
202
+ {},
203
+ {},
204
+ {},
205
+ {},
206
+ '/',
207
+ mockRequest,
208
+ undefined,
209
+ true
210
+ );
211
+
212
+ ctx.redirect('/new-location');
213
+ expect(ctx.set.redirect).toBeDefined();
214
+
215
+ ctx.clearRedirect();
216
+ expect(ctx.set.redirect).toBeUndefined();
217
+ });
218
+ });
219
+
220
+ describe('createOptionsContext', () => {
221
+ it('should create minimal context for OPTIONS requests', () => {
222
+ const headers = { 'origin': 'http://localhost:3000' };
223
+ const pathname = '/api/users';
224
+
225
+ const ctx = createOptionsContext(pathname, mockRequest, headers);
226
+
227
+ expect(ctx.params).toEqual({});
228
+ expect(ctx.query).toEqual({});
229
+ expect(ctx.body).toEqual({});
230
+ expect(ctx.headers).toEqual(headers);
231
+ expect(ctx.cookies).toEqual({});
232
+ expect(ctx.path).toBe(pathname);
233
+ expect(ctx.request).toBe(mockRequest);
234
+ });
235
+
236
+ it('should handle redirect in options context', () => {
237
+ const headers = { 'origin': 'http://localhost:3000' };
238
+ const pathname = '/api/users';
239
+
240
+ const ctx = createOptionsContext(pathname, mockRequest, headers);
241
+ const response = ctx.redirect('/new-location', 301);
242
+
243
+ expect(response.status).toBe(301);
244
+ expect(response.headers.get('location')).toBe('/new-location');
245
+ });
246
+ });
247
+
248
+ describe('getInternalCookies', () => {
249
+ it('should return internal cookies from context', () => {
250
+ const ctx = createContext(
251
+ {},
252
+ {},
253
+ {},
254
+ {},
255
+ {},
256
+ '/',
257
+ mockRequest,
258
+ undefined,
259
+ true
260
+ );
261
+
262
+ ctx.set.cookies('session', 'abc123');
263
+ ctx.set.cookies('theme', 'dark');
264
+
265
+ const internalCookies = getInternalCookies(ctx);
266
+ expect(internalCookies).toHaveLength(2);
267
+ expect(internalCookies[0].name).toBe('session');
268
+ expect(internalCookies[1].name).toBe('theme');
269
+ });
270
+
271
+ it('should return empty array when no cookies set', () => {
272
+ const ctx = createContext(
273
+ {},
274
+ {},
275
+ {},
276
+ {},
277
+ {},
278
+ '/',
279
+ mockRequest,
280
+ undefined,
281
+ true
282
+ );
283
+
284
+ const internalCookies = getInternalCookies(ctx);
285
+ expect(internalCookies).toEqual([]);
286
+ });
287
+
288
+ it('should handle context without internal cookies', () => {
289
+ const ctx = {
290
+ params: {},
291
+ query: {},
292
+ body: {},
293
+ headers: {},
294
+ cookies: {},
295
+ path: '/',
296
+ request: mockRequest,
297
+ set: { status: 200, headers: {} }
298
+ } as any;
299
+
300
+ const internalCookies = getInternalCookies(ctx);
301
+ expect(internalCookies).toEqual([]);
302
+ });
303
+ });
304
+
305
+ describe('Validation Integration', () => {
306
+ it('should validate params with schema', () => {
307
+ const config = {
308
+ params: z.object({ id: z.string().min(1) })
309
+ };
310
+
311
+ const ctx = createContext(
312
+ { id: '123' },
313
+ {},
314
+ {},
315
+ {},
316
+ {},
317
+ '/users/123',
318
+ mockRequest,
319
+ config,
320
+ true
321
+ );
322
+
323
+ expect(ctx.params.id).toBe('123');
324
+ });
325
+
326
+ it('should throw error for invalid params', () => {
327
+ const config = {
328
+ params: z.object({ id: z.string().min(1) })
329
+ };
330
+
331
+ expect(() => {
332
+ createContext(
333
+ { id: '' },
334
+ {},
335
+ {},
336
+ {},
337
+ {},
338
+ '/users/',
339
+ mockRequest,
340
+ config,
341
+ true
342
+ );
343
+ }).toThrow();
344
+ });
345
+
346
+ it('should validate body with schema', () => {
347
+ const config = {
348
+ body: z.object({ name: z.string().min(1) })
349
+ };
350
+
351
+ const ctx = createContext(
352
+ {},
353
+ {},
354
+ { name: 'John' },
355
+ {},
356
+ {},
357
+ '/users',
358
+ mockRequest,
359
+ config,
360
+ true
361
+ );
362
+
363
+ expect(ctx.body.name).toBe('John');
364
+ });
365
+
366
+ it('should throw error for invalid body', () => {
367
+ const config = {
368
+ body: z.object({ name: z.string().min(1) })
369
+ };
370
+
371
+ expect(() => {
372
+ createContext(
373
+ {},
374
+ {},
375
+ { name: '' },
376
+ {},
377
+ {},
378
+ '/users',
379
+ mockRequest,
380
+ config,
381
+ true
382
+ );
383
+ }).toThrow();
384
+ });
385
+ });
386
+ });
@@ -0,0 +1,253 @@
1
+ import { describe, it, expect } from 'bun:test';
2
+ import { error, file, redirect, createCookieOptions } from '../../src/utils/helpers';
3
+
4
+ describe('Helper Functions', () => {
5
+ describe('error', () => {
6
+ it('should create error response with Error object', () => {
7
+ const errorObj = new Error('Something went wrong');
8
+ const response = error(errorObj, 500);
9
+
10
+ expect(response.status).toBe(500);
11
+ expect(response.headers.get('content-type')).toBe('application/json');
12
+ });
13
+
14
+ it('should create error response with string', () => {
15
+ const response = error('Custom error message', 400);
16
+
17
+ expect(response.status).toBe(400);
18
+ expect(response.headers.get('content-type')).toBe('application/json');
19
+ });
20
+
21
+ it('should use default status when not provided', () => {
22
+ const response = error('Error message');
23
+
24
+ expect(response.status).toBe(500);
25
+ });
26
+
27
+ it('should serialize error message correctly', async () => {
28
+ const errorObj = new Error('Test error');
29
+ const response = error(errorObj, 422);
30
+
31
+ const body = await response.json();
32
+ expect(body.error).toBe('Test error');
33
+ });
34
+
35
+ it('should serialize string message correctly', async () => {
36
+ const response = error('String error', 400);
37
+
38
+ const body = await response.json();
39
+ expect(body.error).toBe('String error');
40
+ });
41
+ });
42
+
43
+ describe('file', () => {
44
+ it('should create file response with default options', () => {
45
+ const result = file('./test.txt');
46
+
47
+ expect(result).toBeDefined();
48
+ expect(result.size).toBeDefined();
49
+ expect(result.type).toBeDefined();
50
+ });
51
+
52
+ it('should override MIME type when provided', () => {
53
+ const result = file('./test.txt', { type: 'text/plain' });
54
+
55
+ expect(result.type).toBe('text/plain');
56
+ });
57
+
58
+ it('should include custom headers when provided', () => {
59
+ const customHeaders = {
60
+ 'x-custom': 'value',
61
+ 'authorization': 'Bearer token'
62
+ };
63
+
64
+ const result = file('./test.txt', { headers: customHeaders });
65
+
66
+ expect(result.headers).toEqual(customHeaders);
67
+ });
68
+
69
+ it('should handle both type and headers', () => {
70
+ const result = file('./test.txt', {
71
+ type: 'application/json',
72
+ headers: { 'x-format': 'json' }
73
+ });
74
+
75
+ expect(result.type).toBe('application/json');
76
+ expect(result.headers).toEqual({ 'x-format': 'json' });
77
+ });
78
+
79
+ it('should preserve original file properties', () => {
80
+ const result = file('./test.txt');
81
+
82
+ // These properties should still exist from the original Bun.file
83
+ expect(result.stream).toBeDefined();
84
+ expect(result.size).toBeDefined();
85
+ });
86
+ });
87
+
88
+ describe('redirect', () => {
89
+ it('should create redirect response with default status', () => {
90
+ const response = redirect('/new-location');
91
+
92
+ expect(response.status).toBe(302);
93
+ expect(response.headers.get('location')).toBe('/new-location');
94
+ });
95
+
96
+ it('should create redirect response with custom status', () => {
97
+ const response = redirect('/new-location', 301);
98
+
99
+ expect(response.status).toBe(301);
100
+ expect(response.headers.get('location')).toBe('/new-location');
101
+ });
102
+
103
+ it('should create redirect response with 307 status', () => {
104
+ const response = redirect('/new-location', 307);
105
+
106
+ expect(response.status).toBe(307);
107
+ expect(response.headers.get('location')).toBe('/new-location');
108
+ });
109
+
110
+ it('should handle relative URLs', () => {
111
+ const response = redirect('../parent');
112
+
113
+ expect(response.status).toBe(302);
114
+ expect(response.headers.get('location')).toBe('../parent');
115
+ });
116
+
117
+ it('should handle absolute URLs', () => {
118
+ const response = redirect('https://example.com');
119
+
120
+ expect(response.status).toBe(302);
121
+ expect(response.headers.get('location')).toBe('https://example.com');
122
+ });
123
+
124
+ it('should handle root path', () => {
125
+ const response = redirect('/');
126
+
127
+ expect(response.status).toBe(302);
128
+ expect(response.headers.get('location')).toBe('/');
129
+ });
130
+
131
+ it('should handle empty path', () => {
132
+ const response = redirect('', 301);
133
+
134
+ expect(response.status).toBe(301);
135
+ expect(response.headers.get('location')).toBe('');
136
+ });
137
+ });
138
+
139
+ describe('createCookieOptions', () => {
140
+ it('should create cookie options with default values', () => {
141
+ const options = createCookieOptions();
142
+
143
+ expect(options).toEqual({});
144
+ });
145
+
146
+ it('should preserve provided cookie options', () => {
147
+ const providedOptions = {
148
+ domain: 'example.com',
149
+ path: '/',
150
+ secure: true,
151
+ httpOnly: true,
152
+ sameSite: 'Strict' as const
153
+ };
154
+
155
+ const options = createCookieOptions(providedOptions);
156
+
157
+ expect(options).toEqual(providedOptions);
158
+ });
159
+
160
+ it('should handle partial cookie options', () => {
161
+ const providedOptions = {
162
+ secure: true,
163
+ httpOnly: true
164
+ };
165
+
166
+ const options = createCookieOptions(providedOptions);
167
+
168
+ expect(options).toEqual(providedOptions);
169
+ expect(options.domain).toBeUndefined();
170
+ expect(options.path).toBeUndefined();
171
+ });
172
+
173
+ it('should handle cookie options with dates', () => {
174
+ const expires = new Date('2024-12-31');
175
+ const providedOptions = {
176
+ expires,
177
+ maxAge: 3600
178
+ };
179
+
180
+ const options = createCookieOptions(providedOptions);
181
+
182
+ expect(options.expires).toBe(expires);
183
+ expect(options.maxAge).toBe(3600);
184
+ });
185
+
186
+ it('should handle cookie options with boolean values', () => {
187
+ const providedOptions = {
188
+ secure: true,
189
+ httpOnly: false
190
+ };
191
+
192
+ const options = createCookieOptions(providedOptions);
193
+
194
+ expect(options.secure).toBe(true);
195
+ expect(options.httpOnly).toBe(false);
196
+ });
197
+
198
+ it('should handle sameSite values', () => {
199
+ const strictOptions = createCookieOptions({ sameSite: 'Strict' });
200
+ const laxOptions = createCookieOptions({ sameSite: 'Lax' });
201
+ const noneOptions = createCookieOptions({ sameSite: 'None' });
202
+
203
+ expect(strictOptions.sameSite).toBe('Strict');
204
+ expect(laxOptions.sameSite).toBe('Lax');
205
+ expect(noneOptions.sameSite).toBe('None');
206
+ });
207
+ });
208
+
209
+ describe('Integration', () => {
210
+ it('should work together in typical scenarios', () => {
211
+ // Simulate a typical error handling scenario
212
+ const errorResponse = error('User not found', 404);
213
+ expect(errorResponse.status).toBe(404);
214
+
215
+ // Simulate a typical redirect scenario
216
+ const redirectResponse = redirect('/login', 302);
217
+ expect(redirectResponse.status).toBe(302);
218
+
219
+ // Simulate a typical file serving scenario
220
+ const fileResponse = file('./public/avatar.jpg', { type: 'image/jpeg' });
221
+ expect(fileResponse.type).toBe('image/jpeg');
222
+
223
+ // Simulate a typical cookie scenario
224
+ const cookieOptions = createCookieOptions({
225
+ secure: true,
226
+ httpOnly: true,
227
+ sameSite: 'Strict'
228
+ });
229
+ expect(cookieOptions.secure).toBe(true);
230
+ });
231
+
232
+ it('should handle edge cases gracefully', () => {
233
+ // Error with empty string
234
+ const emptyError = error('', 400);
235
+ expect(emptyError.status).toBe(400);
236
+
237
+ // Redirect with special characters
238
+ const specialRedirect = redirect('/path/with%20spaces', 301);
239
+ expect(specialRedirect.headers.get('location')).toBe('/path/with%20spaces');
240
+
241
+ // File with empty path
242
+ const emptyFile = file('');
243
+ expect(emptyFile).toBeDefined();
244
+
245
+ // Cookie options with undefined values
246
+ const undefinedOptions = createCookieOptions({
247
+ domain: undefined,
248
+ path: undefined
249
+ });
250
+ expect(undefinedOptions.domain).toBeUndefined();
251
+ });
252
+ });
253
+ });