@dangao/bun-server 1.0.0 → 1.0.3

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.
Files changed (200) hide show
  1. package/package.json +4 -2
  2. package/readme.md +163 -2
  3. package/src/auth/controller.ts +148 -0
  4. package/src/auth/decorators.ts +81 -0
  5. package/src/auth/index.ts +12 -0
  6. package/src/auth/jwt.ts +169 -0
  7. package/src/auth/oauth2.ts +244 -0
  8. package/src/auth/types.ts +248 -0
  9. package/src/cache/cache-module.ts +67 -0
  10. package/src/cache/decorators.ts +202 -0
  11. package/src/cache/index.ts +27 -0
  12. package/src/cache/service.ts +151 -0
  13. package/src/cache/types.ts +420 -0
  14. package/src/config/config-module.ts +76 -0
  15. package/src/config/index.ts +8 -0
  16. package/src/config/service.ts +93 -0
  17. package/src/config/types.ts +27 -0
  18. package/src/controller/controller.ts +251 -0
  19. package/src/controller/decorators.ts +84 -0
  20. package/src/controller/index.ts +7 -0
  21. package/src/controller/metadata.ts +27 -0
  22. package/src/controller/param-binder.ts +157 -0
  23. package/src/core/application.ts +233 -0
  24. package/src/core/context.ts +228 -0
  25. package/src/core/index.ts +4 -0
  26. package/src/core/server.ts +128 -0
  27. package/src/core/types.ts +2 -0
  28. package/src/database/connection-manager.ts +239 -0
  29. package/src/database/connection-pool.ts +322 -0
  30. package/src/database/database-extension.ts +62 -0
  31. package/src/database/database-module.ts +115 -0
  32. package/src/database/health-indicator.ts +51 -0
  33. package/src/database/index.ts +47 -0
  34. package/src/database/orm/decorators.ts +155 -0
  35. package/src/database/orm/drizzle-repository.ts +39 -0
  36. package/src/database/orm/index.ts +23 -0
  37. package/src/database/orm/repository-decorator.ts +39 -0
  38. package/src/database/orm/repository.ts +103 -0
  39. package/src/database/orm/service.ts +49 -0
  40. package/src/database/orm/transaction-decorator.ts +45 -0
  41. package/src/database/orm/transaction-interceptor.ts +243 -0
  42. package/src/database/orm/transaction-manager.ts +276 -0
  43. package/src/database/orm/transaction-types.ts +140 -0
  44. package/src/database/orm/types.ts +99 -0
  45. package/src/database/service.ts +221 -0
  46. package/src/database/types.ts +171 -0
  47. package/src/di/container.ts +398 -0
  48. package/src/di/decorators.ts +228 -0
  49. package/src/di/index.ts +4 -0
  50. package/src/di/module-registry.ts +188 -0
  51. package/src/di/module.ts +65 -0
  52. package/src/di/types.ts +67 -0
  53. package/src/error/error-codes.ts +222 -0
  54. package/src/error/filter.ts +43 -0
  55. package/src/error/handler.ts +66 -0
  56. package/src/error/http-exception.ts +115 -0
  57. package/src/error/i18n.ts +217 -0
  58. package/src/error/index.ts +16 -0
  59. package/src/extensions/index.ts +5 -0
  60. package/src/extensions/logger-extension.ts +31 -0
  61. package/src/extensions/logger-module.ts +69 -0
  62. package/src/extensions/types.ts +14 -0
  63. package/src/files/index.ts +5 -0
  64. package/src/files/static-middleware.ts +53 -0
  65. package/src/files/storage.ts +67 -0
  66. package/src/files/types.ts +33 -0
  67. package/src/files/upload-middleware.ts +45 -0
  68. package/src/health/controller.ts +76 -0
  69. package/src/health/health-module.ts +51 -0
  70. package/src/health/index.ts +12 -0
  71. package/src/health/types.ts +28 -0
  72. package/src/index.ts +270 -0
  73. package/src/metrics/collector.ts +209 -0
  74. package/src/metrics/controller.ts +40 -0
  75. package/src/metrics/index.ts +15 -0
  76. package/src/metrics/metrics-module.ts +58 -0
  77. package/src/metrics/middleware.ts +46 -0
  78. package/src/metrics/prometheus.ts +79 -0
  79. package/src/metrics/types.ts +103 -0
  80. package/src/middleware/builtin/cors.ts +60 -0
  81. package/src/middleware/builtin/error-handler.ts +90 -0
  82. package/src/middleware/builtin/file-upload.ts +42 -0
  83. package/src/middleware/builtin/index.ts +14 -0
  84. package/src/middleware/builtin/logger.ts +91 -0
  85. package/src/middleware/builtin/rate-limit.ts +252 -0
  86. package/src/middleware/builtin/static-file.ts +88 -0
  87. package/src/middleware/decorators.ts +91 -0
  88. package/src/middleware/index.ts +11 -0
  89. package/src/middleware/middleware.ts +13 -0
  90. package/src/middleware/pipeline.ts +93 -0
  91. package/src/queue/decorators.ts +110 -0
  92. package/src/queue/index.ts +26 -0
  93. package/src/queue/queue-module.ts +64 -0
  94. package/src/queue/service.ts +302 -0
  95. package/src/queue/types.ts +341 -0
  96. package/src/request/body-parser.ts +133 -0
  97. package/src/request/file-handler.ts +46 -0
  98. package/src/request/index.ts +5 -0
  99. package/src/request/request.ts +107 -0
  100. package/src/request/response.ts +150 -0
  101. package/src/router/decorators.ts +122 -0
  102. package/src/router/index.ts +6 -0
  103. package/src/router/registry.ts +98 -0
  104. package/src/router/route.ts +140 -0
  105. package/src/router/router.ts +241 -0
  106. package/src/router/types.ts +27 -0
  107. package/src/security/access-decision-manager.ts +34 -0
  108. package/src/security/authentication-manager.ts +47 -0
  109. package/src/security/context.ts +92 -0
  110. package/src/security/filter.ts +162 -0
  111. package/src/security/index.ts +8 -0
  112. package/src/security/providers/index.ts +3 -0
  113. package/src/security/providers/jwt-provider.ts +60 -0
  114. package/src/security/providers/oauth2-provider.ts +70 -0
  115. package/src/security/security-module.ts +145 -0
  116. package/src/security/types.ts +165 -0
  117. package/src/session/decorators.ts +45 -0
  118. package/src/session/index.ts +19 -0
  119. package/src/session/middleware.ts +143 -0
  120. package/src/session/service.ts +218 -0
  121. package/src/session/session-module.ts +69 -0
  122. package/src/session/types.ts +373 -0
  123. package/src/swagger/decorators.ts +133 -0
  124. package/src/swagger/generator.ts +234 -0
  125. package/src/swagger/index.ts +7 -0
  126. package/src/swagger/swagger-extension.ts +41 -0
  127. package/src/swagger/swagger-module.ts +83 -0
  128. package/src/swagger/types.ts +188 -0
  129. package/src/swagger/ui.ts +98 -0
  130. package/src/testing/harness.ts +96 -0
  131. package/src/validation/decorators.ts +95 -0
  132. package/src/validation/errors.ts +28 -0
  133. package/src/validation/index.ts +14 -0
  134. package/src/validation/types.ts +35 -0
  135. package/src/validation/validator.ts +63 -0
  136. package/src/websocket/decorators.ts +51 -0
  137. package/src/websocket/index.ts +12 -0
  138. package/src/websocket/registry.ts +133 -0
  139. package/tests/cache/cache-module.test.ts +212 -0
  140. package/tests/config/config-module.test.ts +151 -0
  141. package/tests/controller/controller.test.ts +189 -0
  142. package/tests/core/application.test.ts +57 -0
  143. package/tests/core/context-body.test.ts +44 -0
  144. package/tests/core/context.test.ts +86 -0
  145. package/tests/core/edge-cases.test.ts +432 -0
  146. package/tests/database/database-module.test.ts +385 -0
  147. package/tests/database/orm.test.ts +164 -0
  148. package/tests/database/postgres-mysql-integration.test.ts +395 -0
  149. package/tests/database/transaction.test.ts +238 -0
  150. package/tests/di/container.test.ts +264 -0
  151. package/tests/di/module.test.ts +128 -0
  152. package/tests/error/error-codes.test.ts +121 -0
  153. package/tests/error/error-handler.test.ts +68 -0
  154. package/tests/error/error-handling.test.ts +254 -0
  155. package/tests/error/http-exception.test.ts +37 -0
  156. package/tests/error/i18n-integration.test.ts +175 -0
  157. package/tests/extensions/logger-extension.test.ts +40 -0
  158. package/tests/files/static-middleware.test.ts +67 -0
  159. package/tests/files/upload-middleware.test.ts +43 -0
  160. package/tests/health/health-module.test.ts +116 -0
  161. package/tests/integration/application-router.test.ts +85 -0
  162. package/tests/integration/body-parsing.test.ts +88 -0
  163. package/tests/integration/cache-e2e.test.ts +114 -0
  164. package/tests/integration/oauth2-e2e.test.ts +615 -0
  165. package/tests/integration/session-e2e.test.ts +207 -0
  166. package/tests/metrics/metrics-module.test.ts +178 -0
  167. package/tests/middleware/builtin.test.ts +206 -0
  168. package/tests/middleware/file-upload.test.ts +41 -0
  169. package/tests/middleware/middleware.test.ts +120 -0
  170. package/tests/middleware/pipeline.test.ts +72 -0
  171. package/tests/middleware/rate-limit.test.ts +314 -0
  172. package/tests/middleware/static-file.test.ts +62 -0
  173. package/tests/perf/harness.test.ts +48 -0
  174. package/tests/perf/optimization.test.ts +183 -0
  175. package/tests/perf/regression.test.ts +120 -0
  176. package/tests/queue/queue-module.test.ts +217 -0
  177. package/tests/request/body-parser.test.ts +96 -0
  178. package/tests/request/response.test.ts +99 -0
  179. package/tests/router/decorators.test.ts +48 -0
  180. package/tests/router/registry.test.ts +51 -0
  181. package/tests/router/route.test.ts +71 -0
  182. package/tests/router/router-normalization.test.ts +106 -0
  183. package/tests/router/router.test.ts +133 -0
  184. package/tests/security/access-decision-manager.test.ts +84 -0
  185. package/tests/security/authentication-manager.test.ts +81 -0
  186. package/tests/security/context.test.ts +302 -0
  187. package/tests/security/filter.test.ts +225 -0
  188. package/tests/security/jwt-provider.test.ts +106 -0
  189. package/tests/security/oauth2-provider.test.ts +269 -0
  190. package/tests/security/security-module.test.ts +143 -0
  191. package/tests/session/session-module.test.ts +307 -0
  192. package/tests/stress/di-stress.test.ts +30 -0
  193. package/tests/swagger/decorators.test.ts +153 -0
  194. package/tests/swagger/generator.test.ts +202 -0
  195. package/tests/swagger/swagger-extension.test.ts +72 -0
  196. package/tests/swagger/swagger-module.test.ts +79 -0
  197. package/tests/utils/test-port.ts +10 -0
  198. package/tests/validation/controller-validation.test.ts +64 -0
  199. package/tests/validation/validation.test.ts +42 -0
  200. package/tests/websocket/gateway.test.ts +68 -0
@@ -0,0 +1,116 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import { MODULE_METADATA_KEY } from '../../src/di/module';
5
+ import {
6
+ HealthController,
7
+ HealthModule,
8
+ HEALTH_INDICATORS_TOKEN,
9
+ type HealthIndicator,
10
+ } from '../../src/health';
11
+
12
+ describe('HealthModule', () => {
13
+ test('should register controller and providers', () => {
14
+ const indicators: HealthIndicator[] = [
15
+ {
16
+ name: 'test',
17
+ check() {
18
+ return { status: 'up' };
19
+ },
20
+ },
21
+ ];
22
+
23
+ HealthModule.forRoot({ indicators });
24
+
25
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, HealthModule);
26
+ expect(metadata).toBeDefined();
27
+ expect(metadata.controllers).toContain(HealthController);
28
+
29
+ const indicatorProvider = metadata.providers.find(
30
+ (provider: any) => provider.provide === HEALTH_INDICATORS_TOKEN,
31
+ );
32
+ expect(indicatorProvider).toBeDefined();
33
+ expect(indicatorProvider.useValue).toBe(indicators);
34
+ });
35
+ });
36
+
37
+ describe('HealthController', () => {
38
+ test('should return up status when all indicators are up', async () => {
39
+ const indicators: HealthIndicator[] = [
40
+ {
41
+ name: 'db',
42
+ check() {
43
+ return { status: 'up' };
44
+ },
45
+ },
46
+ {
47
+ name: 'cache',
48
+ async check() {
49
+ return { status: 'up' };
50
+ },
51
+ },
52
+ ];
53
+
54
+ const controller = new HealthController(indicators, {});
55
+ const result = await controller.health();
56
+
57
+ expect(result.status).toBe('up');
58
+ expect(result.details.db.status).toBe('up');
59
+ expect(result.details.cache.status).toBe('up');
60
+ });
61
+
62
+ test('should return down status when any indicator is down', async () => {
63
+ const indicators: HealthIndicator[] = [
64
+ {
65
+ name: 'db',
66
+ check() {
67
+ return { status: 'up' };
68
+ },
69
+ },
70
+ {
71
+ name: 'cache',
72
+ check() {
73
+ return {
74
+ status: 'down',
75
+ details: { reason: 'unreachable' },
76
+ };
77
+ },
78
+ },
79
+ ];
80
+
81
+ const controller = new HealthController(indicators, {});
82
+ const result = await controller.ready();
83
+
84
+ expect(result.status).toBe('down');
85
+ expect(result.details.db.status).toBe('up');
86
+ expect(result.details.cache.status).toBe('down');
87
+ });
88
+
89
+ test('should mark indicator as down when check throws error', async () => {
90
+ const indicators: HealthIndicator[] = [
91
+ {
92
+ name: 'db',
93
+ check() {
94
+ throw new Error('connection failed');
95
+ },
96
+ },
97
+ ];
98
+
99
+ const controller = new HealthController(indicators, {});
100
+ const result = await controller.health();
101
+
102
+ expect(result.status).toBe('down');
103
+ expect(result.details.db.status).toBe('down');
104
+ expect(result.details.db.details?.error).toBe('connection failed');
105
+ });
106
+
107
+ test('should return up status when no indicators registered', async () => {
108
+ const controller = new HealthController([], {});
109
+ const result = await controller.health();
110
+
111
+ expect(result.status).toBe('up');
112
+ expect(Object.keys(result.details).length).toBe(0);
113
+ });
114
+ });
115
+
116
+
@@ -0,0 +1,85 @@
1
+ import { describe, expect, test, afterEach } from 'bun:test';
2
+ import { Application } from '../../src/core/application';
3
+ import { RouteRegistry } from '../../src/router/registry';
4
+ import { Context } from '../../src/core/context';
5
+ import { ControllerRegistry } from '../../src/controller/controller';
6
+ import { getTestPort } from '../utils/test-port';
7
+
8
+ describe('Application with Router', () => {
9
+ let app: Application;
10
+
11
+ afterEach(async () => {
12
+ if (app) {
13
+ await app.stop();
14
+ }
15
+ RouteRegistry.getInstance().clear();
16
+ ControllerRegistry.getInstance().clear();
17
+ });
18
+
19
+ test('should handle registered route', async () => {
20
+ const port = getTestPort();
21
+ app = new Application({ port });
22
+ const registry = RouteRegistry.getInstance();
23
+ registry.get('/api/users', (ctx: Context) => {
24
+ return ctx.createResponse({ message: 'Hello' });
25
+ });
26
+ await app.listen();
27
+
28
+ const response = await fetch(`http://localhost:${port}/api/users`);
29
+ expect(response.status).toBe(200);
30
+
31
+ const data = await response.json();
32
+ // @ts-ignore
33
+ expect(data.message).toBe('Hello');
34
+ });
35
+
36
+ test('should handle route with path parameter', async () => {
37
+ const port = getTestPort();
38
+ app = new Application({ port });
39
+ const registry = RouteRegistry.getInstance();
40
+ registry.get('/api/users/:id', (ctx: Context) => {
41
+ return ctx.createResponse({ id: ctx.getParam('id') });
42
+ });
43
+ await app.listen();
44
+
45
+ const response = await fetch(`http://localhost:${port}/api/users/123`);
46
+ expect(response.status).toBe(200);
47
+
48
+ const data = await response.json();
49
+ // @ts-ignore
50
+ expect(data.id).toBe('123');
51
+ });
52
+
53
+ test('should return 404 for non-existent route', async () => {
54
+ const port = getTestPort();
55
+ app = new Application({ port });
56
+ await app.listen();
57
+
58
+ const response = await fetch(`http://localhost:${port}/api/unknown`);
59
+ expect(response.status).toBe(404);
60
+
61
+ const data = await response.json();
62
+ // @ts-ignore
63
+ expect(data.error).toBe('Not Found');
64
+ });
65
+
66
+ test('should handle POST request', async () => {
67
+ const port = getTestPort();
68
+ app = new Application({ port });
69
+ const registry = RouteRegistry.getInstance();
70
+ registry.post('/api/users', (ctx: Context) => {
71
+ return ctx.createResponse({ message: 'Created' });
72
+ });
73
+ await app.listen();
74
+
75
+ const response = await fetch(`http://localhost:${port}/api/users`, {
76
+ method: 'POST',
77
+ });
78
+ expect(response.status).toBe(200);
79
+
80
+ const data = await response.json();
81
+ // @ts-ignore
82
+ expect(data.message).toBe('Created');
83
+ });
84
+ });
85
+
@@ -0,0 +1,88 @@
1
+ import { describe, expect, test, afterEach } from 'bun:test';
2
+ import { Application } from '../../src/core/application';
3
+ import { RouteRegistry } from '../../src/router/registry';
4
+ import type { Context } from '../../src/core/context';
5
+ import { ControllerRegistry } from '../../src/controller/controller';
6
+ import { getTestPort } from '../utils/test-port';
7
+
8
+ describe('Application Body Parsing Integration', () => {
9
+ let app: Application;
10
+
11
+ afterEach(async () => {
12
+ if (app) {
13
+ await app.stop();
14
+ }
15
+ RouteRegistry.getInstance().clear();
16
+ ControllerRegistry.getInstance().clear();
17
+ });
18
+
19
+ test('should parse JSON body in POST request', async () => {
20
+ const port = getTestPort();
21
+ app = new Application({ port });
22
+ const registry = RouteRegistry.getInstance();
23
+ registry.post('/api/users-json', async (ctx: Context) => {
24
+ const body = await ctx.getBody();
25
+ return ctx.createResponse({ received: body });
26
+ });
27
+ await app.listen();
28
+
29
+ const response = await fetch(`http://localhost:${port}/api/users-json`, {
30
+ method: 'POST',
31
+ headers: { 'Content-Type': 'application/json' },
32
+ body: JSON.stringify({ name: 'John', age: 30 }),
33
+ });
34
+
35
+ expect(response.status).toBe(200);
36
+ const data = await response.json();
37
+ expect(data.received).toEqual({ name: 'John', age: 30 });
38
+ });
39
+
40
+ test('should parse URLEncoded body', async () => {
41
+ const port = getTestPort();
42
+ app = new Application({ port });
43
+ const registry = RouteRegistry.getInstance();
44
+ registry.post('/api/users-urlencoded', async (ctx: Context) => {
45
+ const body = await ctx.getBody();
46
+ return ctx.createResponse({ received: body });
47
+ });
48
+ await app.listen();
49
+
50
+ const response = await fetch(`http://localhost:${port}/api/users-urlencoded`, {
51
+ method: 'POST',
52
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
53
+ body: 'name=John&age=30',
54
+ });
55
+
56
+ expect(response.status).toBe(200);
57
+ const data = await response.json();
58
+ expect(data.received).toEqual({ name: 'John', age: '30' });
59
+ });
60
+
61
+ test('should handle empty body', async () => {
62
+ const port = getTestPort();
63
+ app = new Application({ port });
64
+ const registry = RouteRegistry.getInstance();
65
+ registry.post('/api/users-empty', async (ctx: Context) => {
66
+ const body = await ctx.getBody();
67
+ // 空 body 时,body 应该是 undefined
68
+ // 返回一个简单的响应,验证 body 确实是 undefined
69
+ return ctx.createResponse({
70
+ message: 'Empty body handled',
71
+ bodyExists: body !== undefined,
72
+ });
73
+ });
74
+ await app.listen();
75
+
76
+ const response = await fetch(`http://localhost:${port}/api/users-empty`, {
77
+ method: 'POST',
78
+ });
79
+
80
+ expect(response.status).toBe(200);
81
+ const data = await response.json();
82
+ // 验证响应包含消息
83
+ expect(data.message).toBe('Empty body handled');
84
+ // 空 body 时,bodyExists 应该是 false
85
+ expect(data.bodyExists).toBe(false);
86
+ });
87
+ });
88
+
@@ -0,0 +1,114 @@
1
+ import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
2
+ import {
3
+ Application,
4
+ CacheModule,
5
+ CacheService,
6
+ CACHE_SERVICE_TOKEN,
7
+ ConfigModule,
8
+ Controller,
9
+ GET,
10
+ Inject,
11
+ Injectable,
12
+ Module,
13
+ Param,
14
+ } from '../../src';
15
+ import { getTestPort } from '../utils/test-port';
16
+
17
+ @Injectable()
18
+ class ProductService {
19
+ public constructor(
20
+ @Inject(CACHE_SERVICE_TOKEN) private readonly cache: CacheService,
21
+ ) {}
22
+
23
+ public async getProduct(id: string): Promise<{ id: string; name: string }> {
24
+ return await this.cache.getOrSet(
25
+ `product:${id}`,
26
+ async () => {
27
+ // 模拟数据库查询
28
+ await new Promise((resolve) => setTimeout(resolve, 50));
29
+ return { id, name: `Product ${id}` };
30
+ },
31
+ 60000,
32
+ );
33
+ }
34
+ }
35
+
36
+ @Controller('/api/products')
37
+ class ProductController {
38
+ public constructor(
39
+ @Inject(ProductService) private readonly productService: ProductService,
40
+ ) {}
41
+
42
+ @GET('/:id')
43
+ public async getProduct(@Param('id') id: string) {
44
+ return await this.productService.getProduct(id);
45
+ }
46
+ }
47
+
48
+ @Module({
49
+ controllers: [ProductController],
50
+ providers: [ProductService],
51
+ })
52
+ class AppModule {}
53
+
54
+ describe('Cache E2E', () => {
55
+ let app: Application;
56
+ let port: number;
57
+
58
+ beforeEach(async () => {
59
+ port = getTestPort();
60
+ ConfigModule.forRoot({
61
+ defaultConfig: { app: { name: 'Cache E2E Test', port } },
62
+ });
63
+ CacheModule.forRoot({
64
+ defaultTtl: 60000,
65
+ });
66
+ app = new Application({ port });
67
+ app.registerModule(CacheModule);
68
+ app.registerModule(AppModule);
69
+ await app.listen();
70
+ });
71
+
72
+ afterEach(async () => {
73
+ await app.stop();
74
+ });
75
+
76
+ test('should cache product data', async () => {
77
+ const start1 = Date.now();
78
+ const response1 = await fetch(`http://localhost:${port}/api/products/1`);
79
+ const time1 = Date.now() - start1;
80
+ if (response1.status !== 200) {
81
+ const errorText = await response1.text();
82
+ console.error('Error response:', errorText);
83
+ }
84
+ expect(response1.status).toBe(200);
85
+ const data1 = await response1.json();
86
+ expect(data1.id).toBe('1');
87
+ expect(data1.name).toBe('Product 1');
88
+ expect(time1).toBeGreaterThan(40); // 第一次查询需要时间
89
+
90
+ const start2 = Date.now();
91
+ const response2 = await fetch(`http://localhost:${port}/api/products/1`);
92
+ const time2 = Date.now() - start2;
93
+ expect(response2.status).toBe(200);
94
+ const data2 = await response2.json();
95
+ expect(data2).toEqual(data1);
96
+ expect(time2).toBeLessThan(10); // 第二次查询应该很快(从缓存)
97
+ });
98
+
99
+ test('should cache different products separately', async () => {
100
+ const response1 = await fetch(`http://localhost:${port}/api/products/1`);
101
+ const response2 = await fetch(`http://localhost:${port}/api/products/2`);
102
+
103
+ expect(response1.status).toBe(200);
104
+ expect(response2.status).toBe(200);
105
+
106
+ const data1 = await response1.json();
107
+ const data2 = await response2.json();
108
+
109
+ expect(data1.id).toBe('1');
110
+ expect(data2.id).toBe('2');
111
+ expect(data1.name).toBe('Product 1');
112
+ expect(data2.name).toBe('Product 2');
113
+ });
114
+ });