@onebun/core 0.1.23 → 0.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onebun/core",
3
- "version": "0.1.23",
3
+ "version": "0.2.0",
4
4
  "description": "Core package for OneBun framework - decorators, DI, modules, controllers",
5
5
  "license": "LGPL-3.0",
6
6
  "author": "RemRyahirev",
@@ -41,17 +41,17 @@
41
41
  "dependencies": {
42
42
  "effect": "^3.13.10",
43
43
  "arktype": "^2.0.0",
44
- "@onebun/logger": "^0.1.7",
45
- "@onebun/envs": "^0.1.4",
46
- "@onebun/metrics": "^0.1.6",
47
- "@onebun/requests": "^0.1.3",
48
- "@onebun/trace": "^0.1.4"
44
+ "@onebun/logger": "^0.2.0",
45
+ "@onebun/envs": "^0.2.0",
46
+ "@onebun/metrics": "^0.2.0",
47
+ "@onebun/requests": "^0.2.0",
48
+ "@onebun/trace": "^0.2.0"
49
49
  },
50
50
  "devDependencies": {
51
- "bun-types": "1.2.2",
51
+ "bun-types": "^1.3.8",
52
52
  "testcontainers": "^11.7.1"
53
53
  },
54
54
  "engines": {
55
- "bun": "1.2.2"
55
+ "bun": ">=1.2.12"
56
56
  }
57
57
  }
@@ -21,6 +21,8 @@ import {
21
21
  Query,
22
22
  Body,
23
23
  Header,
24
+ Cookie,
25
+ Req,
24
26
  } from '../decorators/decorators';
25
27
  import { Controller as BaseController } from '../module/controller';
26
28
  import { makeMockLoggerLayer } from '../testing/test-utils';
@@ -38,6 +40,99 @@ function createTestApp(
38
40
  });
39
41
  }
40
42
 
43
+ /**
44
+ * Match a request path against a route pattern with :param support.
45
+ * Returns extracted params or null if no match.
46
+ */
47
+ function matchRoutePattern(pattern: string, path: string): Record<string, string> | null {
48
+ if (pattern === path) {
49
+ return {};
50
+ }
51
+ if (!pattern.includes(':')) {
52
+ return null;
53
+ }
54
+
55
+ const patternParts = pattern.split('/');
56
+ const pathParts = path.split('/');
57
+
58
+ if (patternParts.length !== pathParts.length) {
59
+ return null;
60
+ }
61
+
62
+ const params: Record<string, string> = {};
63
+ for (let i = 0; i < patternParts.length; i++) {
64
+ if (patternParts[i].startsWith(':')) {
65
+ params[patternParts[i].slice(1)] = pathParts[i];
66
+ } else if (patternParts[i] !== pathParts[i]) {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ return params;
72
+ }
73
+
74
+ /**
75
+ * Create a mock Bun.serve that captures routes and emulates Bun route matching.
76
+ * The mock's fetchHandler resolves routes by pattern matching and creates
77
+ * BunRequest-like objects with params and cookies.
78
+ */
79
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
+ function createRoutesAwareMock(mockServer: any) {
81
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
82
+ return mock((options: any) => {
83
+ const routes = options.routes || {};
84
+ const fetchFallback = options.fetch;
85
+
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ (mockServer as any).fetchHandler = async (request: Request) => {
88
+ const url = new URL(request.url);
89
+ const path = url.pathname;
90
+ const method = request.method;
91
+
92
+ // Try to find matching route (iterate all registered patterns)
93
+ for (const [pattern, handlers] of Object.entries(routes)) {
94
+ const params = matchRoutePattern(pattern, path);
95
+ if (params !== null) {
96
+ const routeHandlers = typeof handlers === 'function'
97
+ ? { [method]: handlers as Function }
98
+ : handlers as Record<string, Function>;
99
+ const handler = routeHandlers[method];
100
+ if (handler) {
101
+ // Create BunRequest-like object with params and cookies
102
+ // Parse Cookie header into a Map to emulate CookieMap
103
+ const cookieHeader = request.headers.get('cookie') || '';
104
+ const cookieMap = new Map<string, string>();
105
+ if (cookieHeader) {
106
+ for (const pair of cookieHeader.split(';')) {
107
+ const [key, ...rest] = pair.split('=');
108
+ if (key) {
109
+ cookieMap.set(key.trim(), rest.join('=').trim());
110
+ }
111
+ }
112
+ }
113
+
114
+ const bunReq = Object.assign(request, {
115
+ params,
116
+ cookies: cookieMap,
117
+ });
118
+
119
+ return handler(bunReq, mockServer);
120
+ }
121
+ }
122
+ }
123
+
124
+ // Fall through to fetch handler
125
+ if (fetchFallback) {
126
+ return fetchFallback(request, mockServer);
127
+ }
128
+
129
+ return new Response('Not Found', { status: 404 });
130
+ };
131
+
132
+ return mockServer;
133
+ });
134
+ }
135
+
41
136
  describe('OneBunApplication', () => {
42
137
  beforeEach(() => {
43
138
  register.clear();
@@ -145,7 +240,7 @@ describe('OneBunApplication', () => {
145
240
  expect(config).toBeDefined();
146
241
  });
147
242
 
148
- test('should create config service when envSchema provided', () => {
243
+ test('should create config service when envSchema provided (duplicate check)', () => {
149
244
  @Module({})
150
245
  class TestModule {}
151
246
 
@@ -159,12 +254,9 @@ describe('OneBunApplication', () => {
159
254
 
160
255
  const app = createTestApp(TestModule, { envSchema });
161
256
 
162
- // Just check that config service was created
257
+ // Config service is created eagerly in the constructor
163
258
  const config = app.getConfig();
164
259
  expect(config).toBeDefined();
165
-
166
- // The actual value access might need the config to be fully initialized
167
- // which happens during runtime, not during construction
168
260
  });
169
261
 
170
262
  test('should provide typed access to config values via getConfig()', () => {
@@ -261,17 +353,34 @@ describe('OneBunApplication', () => {
261
353
  });
262
354
 
263
355
  describe('Layer methods', () => {
264
- test('should return layer from root module', () => {
356
+ let originalServe: typeof Bun.serve;
357
+
358
+ beforeEach(() => {
359
+ originalServe = Bun.serve;
360
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
361
+ (Bun as any).serve = mock(() => ({
362
+ stop: mock(),
363
+ port: 3000,
364
+ }));
365
+ });
366
+
367
+ afterEach(() => {
368
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
369
+ (Bun as any).serve = originalServe;
370
+ });
371
+
372
+ test('should return layer from root module', async () => {
265
373
  @Module({})
266
374
  class TestModule {}
267
375
 
268
376
  const app = createTestApp(TestModule);
377
+ await app.start();
269
378
 
270
379
  const layer = app.getLayer();
271
380
  expect(layer).toBeDefined();
272
381
  });
273
382
 
274
- test('should return layer for complex module structure', () => {
383
+ test('should return layer for complex module structure', async () => {
275
384
  class TestController {}
276
385
  class TestService {}
277
386
 
@@ -282,6 +391,7 @@ describe('OneBunApplication', () => {
282
391
  class TestModule {}
283
392
 
284
393
  const app = createTestApp(TestModule);
394
+ await app.start();
285
395
 
286
396
  const layer = app.getLayer();
287
397
  expect(layer).toBeDefined();
@@ -394,14 +504,13 @@ describe('OneBunApplication', () => {
394
504
  });
395
505
 
396
506
  describe('Module class handling', () => {
397
- test('should throw error for plain class without decorator', () => {
507
+ test('should throw error for plain class without decorator', async () => {
398
508
  class PlainModule {}
399
509
 
400
- // This should throw error without @Module decorator
401
- expect(() => {
402
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
403
- createTestApp(PlainModule as any);
404
- }).toThrow('Module PlainModule does not have @Module decorator');
510
+ // Module creation now happens in start(), so the error is thrown there
511
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
512
+ const app = createTestApp(PlainModule as any);
513
+ await expect(app.start()).rejects.toThrow('Module PlainModule does not have @Module decorator');
405
514
  });
406
515
 
407
516
  test('should handle class with constructor parameters', () => {
@@ -971,12 +1080,7 @@ describe('OneBunApplication', () => {
971
1080
 
972
1081
  originalServe = Bun.serve;
973
1082
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
974
- (Bun as any).serve = mock((options: any) => {
975
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
976
- (mockServer as any).fetchHandler = options.fetch;
977
-
978
- return mockServer;
979
- });
1083
+ (Bun as any).serve = createRoutesAwareMock(mockServer);
980
1084
  });
981
1085
 
982
1086
  afterEach(() => {
@@ -1038,13 +1142,7 @@ describe('OneBunApplication', () => {
1038
1142
 
1039
1143
  originalServe = Bun.serve;
1040
1144
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1041
- (Bun as any).serve = mock((options: any) => {
1042
- // Store the fetch handler for testing
1043
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1044
- (mockServer as any).fetchHandler = options.fetch;
1045
-
1046
- return mockServer;
1047
- });
1145
+ (Bun as any).serve = createRoutesAwareMock(mockServer);
1048
1146
  });
1049
1147
 
1050
1148
  afterEach(() => {
@@ -2446,12 +2544,7 @@ describe('OneBunApplication', () => {
2446
2544
 
2447
2545
  originalServe = Bun.serve;
2448
2546
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
2449
- (Bun as any).serve = mock((options: any) => {
2450
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2451
- (mockServer as any).fetchHandler = options.fetch;
2452
-
2453
- return mockServer;
2454
- });
2547
+ (Bun as any).serve = createRoutesAwareMock(mockServer);
2455
2548
  });
2456
2549
 
2457
2550
  afterEach(() => {
@@ -2532,6 +2625,400 @@ describe('OneBunApplication', () => {
2532
2625
  });
2533
2626
  });
2534
2627
 
2628
+ describe('Cookies, Headers, and @Req()', () => {
2629
+ let originalServe: typeof Bun.serve;
2630
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2631
+ let mockServer: any;
2632
+
2633
+ beforeEach(() => {
2634
+ register.clear();
2635
+
2636
+ mockServer = {
2637
+ stop: mock(),
2638
+ hostname: 'localhost',
2639
+ port: 3000,
2640
+ };
2641
+
2642
+ originalServe = Bun.serve;
2643
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2644
+ (Bun as any).serve = createRoutesAwareMock(mockServer);
2645
+ });
2646
+
2647
+ afterEach(() => {
2648
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2649
+ (Bun as any).serve = originalServe;
2650
+ });
2651
+
2652
+ test('should extract cookie value with @Cookie decorator', async () => {
2653
+ @Controller('/api')
2654
+ class ApiController extends BaseController {
2655
+ @Get('/me')
2656
+ async getMe(@Cookie('session') session?: string) {
2657
+ return { session };
2658
+ }
2659
+ }
2660
+
2661
+ @Module({
2662
+ controllers: [ApiController],
2663
+ })
2664
+ class TestModule {}
2665
+
2666
+ const app = createTestApp(TestModule);
2667
+ await app.start();
2668
+
2669
+ const request = new Request('http://localhost:3000/api/me', {
2670
+ method: 'GET',
2671
+ headers: {
2672
+ // eslint-disable-next-line @typescript-eslint/naming-convention
2673
+ 'Cookie': 'session=abc123; theme=dark',
2674
+ },
2675
+ });
2676
+
2677
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2678
+ const response = await (mockServer as any).fetchHandler(request);
2679
+ const body = await response.json();
2680
+
2681
+ expect(response.status).toBe(200);
2682
+ expect(body.result.session).toBe('abc123');
2683
+ });
2684
+
2685
+ test('should return undefined for missing cookie with @Cookie decorator', async () => {
2686
+ @Controller('/api')
2687
+ class ApiController extends BaseController {
2688
+ @Get('/me')
2689
+ async getMe(@Cookie('missing_cookie') value?: string) {
2690
+ return { value, isUndefined: value === undefined };
2691
+ }
2692
+ }
2693
+
2694
+ @Module({
2695
+ controllers: [ApiController],
2696
+ })
2697
+ class TestModule {}
2698
+
2699
+ const app = createTestApp(TestModule);
2700
+ await app.start();
2701
+
2702
+ const request = new Request('http://localhost:3000/api/me', {
2703
+ method: 'GET',
2704
+ });
2705
+
2706
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2707
+ const response = await (mockServer as any).fetchHandler(request);
2708
+ const body = await response.json();
2709
+
2710
+ expect(response.status).toBe(200);
2711
+ expect(body.result.value).toBeUndefined();
2712
+ expect(body.result.isUndefined).toBe(true);
2713
+ });
2714
+
2715
+ test('should return 500 when required cookie is missing', async () => {
2716
+ @Controller('/api')
2717
+ class ApiController extends BaseController {
2718
+ @Get('/auth')
2719
+ async auth(@Cookie('token', { required: true }) token: string) {
2720
+ return { token };
2721
+ }
2722
+ }
2723
+
2724
+ @Module({
2725
+ controllers: [ApiController],
2726
+ })
2727
+ class TestModule {}
2728
+
2729
+ const app = createTestApp(TestModule);
2730
+ await app.start();
2731
+
2732
+ // No cookies sent
2733
+ const request = new Request('http://localhost:3000/api/auth', {
2734
+ method: 'GET',
2735
+ });
2736
+
2737
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2738
+ const response = await (mockServer as any).fetchHandler(request);
2739
+
2740
+ expect(response.status).toBe(500);
2741
+ });
2742
+
2743
+ test('should extract multiple cookies with @Cookie decorator', async () => {
2744
+ @Controller('/api')
2745
+ class ApiController extends BaseController {
2746
+ @Get('/prefs')
2747
+ async prefs(
2748
+ @Cookie('theme') theme?: string,
2749
+ @Cookie('lang') lang?: string,
2750
+ @Cookie('session') session?: string,
2751
+ ) {
2752
+ return { theme, lang, session };
2753
+ }
2754
+ }
2755
+
2756
+ @Module({
2757
+ controllers: [ApiController],
2758
+ })
2759
+ class TestModule {}
2760
+
2761
+ const app = createTestApp(TestModule);
2762
+ await app.start();
2763
+
2764
+ const request = new Request('http://localhost:3000/api/prefs', {
2765
+ method: 'GET',
2766
+ headers: {
2767
+ // eslint-disable-next-line @typescript-eslint/naming-convention
2768
+ 'Cookie': 'theme=dark; lang=en; session=xyz789',
2769
+ },
2770
+ });
2771
+
2772
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2773
+ const response = await (mockServer as any).fetchHandler(request);
2774
+ const body = await response.json();
2775
+
2776
+ expect(response.status).toBe(200);
2777
+ expect(body.result.theme).toBe('dark');
2778
+ expect(body.result.lang).toBe('en');
2779
+ expect(body.result.session).toBe('xyz789');
2780
+ });
2781
+
2782
+ test('should inject BunRequest-like object with @Req() that has cookies and params', async () => {
2783
+ @Controller('/api')
2784
+ class ApiController extends BaseController {
2785
+ @Get('/users/:id')
2786
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2787
+ async getUser(@Req() req: any) {
2788
+ return {
2789
+ hasCookies: typeof req.cookies?.get === 'function',
2790
+ hasParams: req.params !== undefined,
2791
+ paramId: req.params?.id,
2792
+ cookieSession: req.cookies?.get('session') ?? null,
2793
+ };
2794
+ }
2795
+ }
2796
+
2797
+ @Module({
2798
+ controllers: [ApiController],
2799
+ })
2800
+ class TestModule {}
2801
+
2802
+ const app = createTestApp(TestModule);
2803
+ await app.start();
2804
+
2805
+ const request = new Request('http://localhost:3000/api/users/42', {
2806
+ method: 'GET',
2807
+ headers: {
2808
+ // eslint-disable-next-line @typescript-eslint/naming-convention
2809
+ 'Cookie': 'session=test-session',
2810
+ },
2811
+ });
2812
+
2813
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2814
+ const response = await (mockServer as any).fetchHandler(request);
2815
+ const body = await response.json();
2816
+
2817
+ expect(response.status).toBe(200);
2818
+ expect(body.result.hasCookies).toBe(true);
2819
+ expect(body.result.hasParams).toBe(true);
2820
+ expect(body.result.paramId).toBe('42');
2821
+ expect(body.result.cookieSession).toBe('test-session');
2822
+ });
2823
+
2824
+ test('should preserve custom headers in Response returned from handler', async () => {
2825
+ @Controller('/api')
2826
+ class ApiController extends BaseController {
2827
+ @Get('/custom-headers')
2828
+ async customHeaders() {
2829
+ return new Response(JSON.stringify({ message: 'ok' }), {
2830
+ status: 200,
2831
+ headers: {
2832
+ // eslint-disable-next-line @typescript-eslint/naming-convention
2833
+ 'Content-Type': 'application/json',
2834
+ // eslint-disable-next-line @typescript-eslint/naming-convention
2835
+ 'X-Custom-Header': 'custom-value',
2836
+ // eslint-disable-next-line @typescript-eslint/naming-convention
2837
+ 'X-Request-ID': 'req-123',
2838
+ },
2839
+ });
2840
+ }
2841
+ }
2842
+
2843
+ @Module({
2844
+ controllers: [ApiController],
2845
+ })
2846
+ class TestModule {}
2847
+
2848
+ const app = createTestApp(TestModule);
2849
+ await app.start();
2850
+
2851
+ const request = new Request('http://localhost:3000/api/custom-headers', {
2852
+ method: 'GET',
2853
+ });
2854
+
2855
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2856
+ const response = await (mockServer as any).fetchHandler(request);
2857
+
2858
+ expect(response.status).toBe(200);
2859
+ expect(response.headers.get('X-Custom-Header')).toBe('custom-value');
2860
+ expect(response.headers.get('X-Request-ID')).toBe('req-123');
2861
+ });
2862
+
2863
+ test('should preserve single Set-Cookie header in Response', async () => {
2864
+ @Controller('/api')
2865
+ class ApiController extends BaseController {
2866
+ @Get('/login')
2867
+ async login() {
2868
+ return new Response(JSON.stringify({ loggedIn: true }), {
2869
+ status: 200,
2870
+ headers: {
2871
+ // eslint-disable-next-line @typescript-eslint/naming-convention
2872
+ 'Content-Type': 'application/json',
2873
+ // eslint-disable-next-line @typescript-eslint/naming-convention
2874
+ 'Set-Cookie': 'session=abc123; Path=/; HttpOnly',
2875
+ },
2876
+ });
2877
+ }
2878
+ }
2879
+
2880
+ @Module({
2881
+ controllers: [ApiController],
2882
+ })
2883
+ class TestModule {}
2884
+
2885
+ const app = createTestApp(TestModule);
2886
+ await app.start();
2887
+
2888
+ const request = new Request('http://localhost:3000/api/login', {
2889
+ method: 'GET',
2890
+ });
2891
+
2892
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2893
+ const response = await (mockServer as any).fetchHandler(request);
2894
+
2895
+ expect(response.status).toBe(200);
2896
+ const setCookies = response.headers.getSetCookie();
2897
+ expect(setCookies.length).toBeGreaterThanOrEqual(1);
2898
+ expect(setCookies[0]).toContain('session=abc123');
2899
+ });
2900
+
2901
+ test('should preserve multiple Set-Cookie headers in Response', async () => {
2902
+ @Controller('/api')
2903
+ class ApiController extends BaseController {
2904
+ @Get('/multi-cookie')
2905
+ async multiCookie() {
2906
+ const headers = new Headers();
2907
+ headers.append('Content-Type', 'application/json');
2908
+ headers.append('Set-Cookie', 'session=abc; Path=/; HttpOnly');
2909
+ headers.append('Set-Cookie', 'theme=dark; Path=/');
2910
+ headers.append('Set-Cookie', 'lang=en; Path=/');
2911
+
2912
+ return new Response(JSON.stringify({ ok: true }), {
2913
+ status: 200,
2914
+ headers,
2915
+ });
2916
+ }
2917
+ }
2918
+
2919
+ @Module({
2920
+ controllers: [ApiController],
2921
+ })
2922
+ class TestModule {}
2923
+
2924
+ const app = createTestApp(TestModule);
2925
+ await app.start();
2926
+
2927
+ const request = new Request('http://localhost:3000/api/multi-cookie', {
2928
+ method: 'GET',
2929
+ });
2930
+
2931
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2932
+ const response = await (mockServer as any).fetchHandler(request);
2933
+
2934
+ expect(response.status).toBe(200);
2935
+ const setCookies = response.headers.getSetCookie();
2936
+ expect(setCookies.length).toBe(3);
2937
+ expect(setCookies).toContainEqual(expect.stringContaining('session=abc'));
2938
+ expect(setCookies).toContainEqual(expect.stringContaining('theme=dark'));
2939
+ expect(setCookies).toContainEqual(expect.stringContaining('lang=en'));
2940
+ });
2941
+
2942
+ test('should handle route params via req.params in @Req()', async () => {
2943
+ @Controller('/api')
2944
+ class ApiController extends BaseController {
2945
+ @Get('/items/:category/:id')
2946
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2947
+ async getItem(@Req() req: any) {
2948
+ return {
2949
+ category: req.params?.category,
2950
+ id: req.params?.id,
2951
+ };
2952
+ }
2953
+ }
2954
+
2955
+ @Module({
2956
+ controllers: [ApiController],
2957
+ })
2958
+ class TestModule {}
2959
+
2960
+ const app = createTestApp(TestModule);
2961
+ await app.start();
2962
+
2963
+ const request = new Request('http://localhost:3000/api/items/electronics/42', {
2964
+ method: 'GET',
2965
+ });
2966
+
2967
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2968
+ const response = await (mockServer as any).fetchHandler(request);
2969
+ const body = await response.json();
2970
+
2971
+ expect(response.status).toBe(200);
2972
+ expect(body.result.category).toBe('electronics');
2973
+ expect(body.result.id).toBe('42');
2974
+ });
2975
+
2976
+ test('should combine @Cookie, @Param, @Query, and @Header in same handler', async () => {
2977
+ @Controller('/api')
2978
+ class ApiController extends BaseController {
2979
+ @Get('/combined/:id')
2980
+ async combined(
2981
+ @Param('id') id: string,
2982
+ @Query('sort') sort: string,
2983
+ @Header('Authorization') auth: string,
2984
+ @Cookie('session') session?: string,
2985
+ ) {
2986
+ return {
2987
+ id: parseInt(id), sort, auth, session,
2988
+ };
2989
+ }
2990
+ }
2991
+
2992
+ @Module({
2993
+ controllers: [ApiController],
2994
+ })
2995
+ class TestModule {}
2996
+
2997
+ const app = createTestApp(TestModule);
2998
+ await app.start();
2999
+
3000
+ const request = new Request('http://localhost:3000/api/combined/99?sort=name', {
3001
+ method: 'GET',
3002
+ headers: {
3003
+ // eslint-disable-next-line @typescript-eslint/naming-convention
3004
+ 'Authorization': 'Bearer token456',
3005
+ // eslint-disable-next-line @typescript-eslint/naming-convention
3006
+ 'Cookie': 'session=sess789',
3007
+ },
3008
+ });
3009
+
3010
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3011
+ const response = await (mockServer as any).fetchHandler(request);
3012
+ const body = await response.json();
3013
+
3014
+ expect(response.status).toBe(200);
3015
+ expect(body.result.id).toBe(99);
3016
+ expect(body.result.sort).toBe('name');
3017
+ expect(body.result.auth).toBe('Bearer token456');
3018
+ expect(body.result.session).toBe('sess789');
3019
+ });
3020
+ });
3021
+
2535
3022
  describe('Graceful shutdown', () => {
2536
3023
  let originalServe: typeof Bun.serve;
2537
3024
  // eslint-disable-next-line @typescript-eslint/no-explicit-any