bxo 0.0.5-dev.6 → 0.0.5-dev.60
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 +99 -2
- package/index.ts +4 -787
- package/package.json +11 -5
- package/plugins/README.md +160 -0
- package/plugins/cors.ts +81 -57
- package/plugins/index.ts +4 -6
- package/plugins/ratelimit.ts +55 -59
- package/src/core/bxo.ts +438 -0
- package/src/handlers/request-handler.ts +229 -0
- package/src/index.ts +59 -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 +390 -0
- package/src/utils/response-handler.ts +286 -0
- package/src/utils/route-matcher.ts +191 -0
- package/tests/README.md +359 -0
- package/tests/integration/bxo.test.ts +598 -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 +431 -0
- package/example.ts +0 -183
- package/plugins/auth.ts +0 -119
- package/plugins/logger.ts +0 -109
|
@@ -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
|
+
});
|