@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,385 @@
1
+ import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
2
+ import { DatabaseModule, DatabaseService, DATABASE_SERVICE_TOKEN } from '../../src/database';
3
+ import { Container } from '../../src/di/container';
4
+ import { ModuleRegistry } from '../../src/di/module-registry';
5
+ import { MODULE_METADATA_KEY } from '../../src/di/module';
6
+ import 'reflect-metadata';
7
+
8
+ describe('DatabaseModule', () => {
9
+ let container: Container;
10
+ let moduleRegistry: ModuleRegistry;
11
+
12
+ beforeEach(() => {
13
+ // 清除模块元数据
14
+ Reflect.deleteMetadata(MODULE_METADATA_KEY, DatabaseModule);
15
+
16
+ container = new Container();
17
+ moduleRegistry = ModuleRegistry.getInstance();
18
+ moduleRegistry.clear();
19
+ });
20
+
21
+ afterEach(() => {
22
+ moduleRegistry.clear();
23
+ });
24
+
25
+ test('should register database service provider', () => {
26
+ DatabaseModule.forRoot({
27
+ database: {
28
+ type: 'sqlite',
29
+ config: {
30
+ path: ':memory:',
31
+ },
32
+ },
33
+ });
34
+
35
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, DatabaseModule);
36
+ expect(metadata.providers).toBeDefined();
37
+ expect(metadata.providers.length).toBeGreaterThan(0);
38
+
39
+ // 检查是否注册了 DATABASE_SERVICE_TOKEN
40
+ const hasServiceToken = metadata.providers.some(
41
+ (p: unknown) =>
42
+ typeof p === 'object' &&
43
+ p !== null &&
44
+ 'provide' in p &&
45
+ p.provide === DATABASE_SERVICE_TOKEN,
46
+ );
47
+ expect(hasServiceToken).toBe(true);
48
+ });
49
+
50
+ test('should register database service instance', () => {
51
+ DatabaseModule.forRoot({
52
+ database: {
53
+ type: 'sqlite',
54
+ config: {
55
+ path: ':memory:',
56
+ },
57
+ },
58
+ });
59
+
60
+ moduleRegistry.register(DatabaseModule, container);
61
+ const service = container.resolve<DatabaseService>(DATABASE_SERVICE_TOKEN);
62
+
63
+ expect(service).toBeInstanceOf(DatabaseService);
64
+ expect(service.getDatabaseType()).toBe('sqlite');
65
+ });
66
+
67
+ test('should export database service', () => {
68
+ DatabaseModule.forRoot({
69
+ database: {
70
+ type: 'sqlite',
71
+ config: {
72
+ path: ':memory:',
73
+ },
74
+ },
75
+ });
76
+
77
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, DatabaseModule);
78
+ expect(metadata.exports).toBeDefined();
79
+ expect(metadata.exports).toContain(DATABASE_SERVICE_TOKEN);
80
+ expect(metadata.exports).toContain(DatabaseService);
81
+ });
82
+
83
+ test('should register database extension', () => {
84
+ DatabaseModule.forRoot({
85
+ database: {
86
+ type: 'sqlite',
87
+ config: {
88
+ path: ':memory:',
89
+ },
90
+ },
91
+ });
92
+
93
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, DatabaseModule);
94
+ expect(metadata.extensions).toBeDefined();
95
+ expect(metadata.extensions.length).toBe(1);
96
+ expect(metadata.extensions[0]).toBeDefined();
97
+ });
98
+
99
+ test('should support connection pool options', () => {
100
+ DatabaseModule.forRoot({
101
+ database: {
102
+ type: 'sqlite',
103
+ config: {
104
+ path: ':memory:',
105
+ },
106
+ },
107
+ pool: {
108
+ maxConnections: 20,
109
+ connectionTimeout: 60000,
110
+ retryCount: 5,
111
+ retryDelay: 2000,
112
+ },
113
+ });
114
+
115
+ moduleRegistry.register(DatabaseModule, container);
116
+ const service = container.resolve<DatabaseService>(DATABASE_SERVICE_TOKEN);
117
+
118
+ expect(service).toBeInstanceOf(DatabaseService);
119
+ });
120
+
121
+ test('should support disabling health check', () => {
122
+ DatabaseModule.forRoot({
123
+ database: {
124
+ type: 'sqlite',
125
+ config: {
126
+ path: ':memory:',
127
+ },
128
+ },
129
+ enableHealthCheck: false,
130
+ });
131
+
132
+ moduleRegistry.register(DatabaseModule, container);
133
+ const service = container.resolve<DatabaseService>(DATABASE_SERVICE_TOKEN);
134
+
135
+ expect(service).toBeInstanceOf(DatabaseService);
136
+ });
137
+ });
138
+
139
+ describe('DatabaseService', () => {
140
+ let service: DatabaseService;
141
+
142
+ beforeEach(async () => {
143
+ service = new DatabaseService({
144
+ database: {
145
+ type: 'sqlite',
146
+ config: {
147
+ path: ':memory:',
148
+ },
149
+ },
150
+ });
151
+ await service.initialize();
152
+ });
153
+
154
+ afterEach(async () => {
155
+ await service.close();
156
+ });
157
+
158
+ test('should initialize database connection', async () => {
159
+ const info = service.getConnectionInfo();
160
+ expect(info.status).toBe('connected');
161
+ expect(info.type).toBe('sqlite');
162
+ });
163
+
164
+ test('should get database connection', () => {
165
+ const connection = service.getConnection();
166
+ expect(connection).toBeDefined();
167
+ expect(connection).not.toBeNull();
168
+ });
169
+
170
+ test('should get database type', () => {
171
+ const type = service.getDatabaseType();
172
+ expect(type).toBe('sqlite');
173
+ });
174
+
175
+ test('should check health status', async () => {
176
+ const isHealthy = await service.healthCheck();
177
+ expect(isHealthy).toBe(true);
178
+ });
179
+
180
+ test('should get connection info', () => {
181
+ const info = service.getConnectionInfo();
182
+ expect(info).toBeDefined();
183
+ expect(info.status).toBe('connected');
184
+ expect(info.type).toBe('sqlite');
185
+ });
186
+
187
+ test('should execute SQL query', () => {
188
+ // 创建表
189
+ service.query('CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, name TEXT)');
190
+
191
+ // 插入数据
192
+ service.query('INSERT INTO test (name) VALUES (?)', ['test']);
193
+
194
+ // 查询数据(SQLite 返回同步结果)
195
+ const result = service.query<{ id: number; name: string }>('SELECT * FROM test');
196
+
197
+ expect(result).toBeDefined();
198
+ expect(Array.isArray(result)).toBe(true);
199
+ expect(result.length).toBe(1);
200
+ expect(result[0].name).toBe('test');
201
+ });
202
+
203
+ test('should close database connection', async () => {
204
+ await service.close();
205
+ const info = service.getConnectionInfo();
206
+ expect(info.status).toBe('disconnected');
207
+ });
208
+
209
+ test('should throw error when querying without connection', async () => {
210
+ await service.close();
211
+
212
+ expect(() => {
213
+ service.query('SELECT 1');
214
+ }).toThrow('Database connection is not established');
215
+ });
216
+ });
217
+
218
+ describe('ConnectionPool', () => {
219
+ test('should create connection pool', () => {
220
+ const { ConnectionPool } = require('../../src/database/connection-pool');
221
+ const pool = new ConnectionPool(
222
+ {
223
+ type: 'sqlite',
224
+ config: {
225
+ path: ':memory:',
226
+ },
227
+ },
228
+ {
229
+ maxConnections: 5,
230
+ },
231
+ );
232
+
233
+ expect(pool).toBeDefined();
234
+ const stats = pool.getPoolStats();
235
+ expect(stats.maxConnections).toBe(5);
236
+ expect(stats.total).toBe(0);
237
+ });
238
+
239
+ test('should acquire and release connections', async () => {
240
+ const { ConnectionPool } = require('../../src/database/connection-pool');
241
+ const pool = new ConnectionPool({
242
+ type: 'sqlite',
243
+ config: {
244
+ path: ':memory:',
245
+ },
246
+ });
247
+
248
+ const connection1 = await pool.acquire();
249
+ expect(connection1).toBeDefined();
250
+
251
+ const stats1 = pool.getPoolStats();
252
+ expect(stats1.total).toBe(1);
253
+ expect(stats1.inUse).toBe(1);
254
+
255
+ pool.release(connection1);
256
+ const stats2 = pool.getPoolStats();
257
+ expect(stats2.inUse).toBe(0);
258
+
259
+ await pool.close();
260
+ });
261
+
262
+ test('should limit max connections', async () => {
263
+ const { ConnectionPool } = require('../../src/database/connection-pool');
264
+ const pool = new ConnectionPool(
265
+ {
266
+ type: 'sqlite',
267
+ config: {
268
+ path: ':memory:',
269
+ },
270
+ },
271
+ {
272
+ maxConnections: 2,
273
+ },
274
+ );
275
+
276
+ const conn1 = await pool.acquire();
277
+ const conn2 = await pool.acquire();
278
+
279
+ const stats = pool.getPoolStats();
280
+ expect(stats.total).toBe(2);
281
+ expect(stats.inUse).toBe(2);
282
+
283
+ pool.release(conn1);
284
+ pool.release(conn2);
285
+
286
+ await pool.close();
287
+ });
288
+
289
+ test('should close pool and all connections', async () => {
290
+ const { ConnectionPool } = require('../../src/database/connection-pool');
291
+ const pool = new ConnectionPool({
292
+ type: 'sqlite',
293
+ config: {
294
+ path: ':memory:',
295
+ },
296
+ });
297
+
298
+ const conn1 = await pool.acquire();
299
+ const conn2 = await pool.acquire();
300
+
301
+ await pool.close();
302
+
303
+ const stats = pool.getPoolStats();
304
+ expect(stats.total).toBe(0);
305
+ });
306
+ });
307
+
308
+ describe('DatabaseConnectionManager', () => {
309
+ test('should connect to SQLite database', async () => {
310
+ const { DatabaseConnectionManager } = await import('../../src/database/connection-manager');
311
+ const manager = new DatabaseConnectionManager({
312
+ type: 'sqlite',
313
+ config: {
314
+ path: ':memory:',
315
+ },
316
+ });
317
+
318
+ await manager.connect();
319
+ const info = manager.getConnectionInfo();
320
+
321
+ expect(info.status).toBe('connected');
322
+ expect(info.type).toBe('sqlite');
323
+
324
+ await manager.disconnect();
325
+ });
326
+
327
+ test('should handle connection errors gracefully', async () => {
328
+ const { DatabaseConnectionManager } = await import('../../src/database/connection-manager');
329
+ const manager = new DatabaseConnectionManager({
330
+ type: 'sqlite',
331
+ config: {
332
+ path: ':memory:', // 使用内存数据库,应该总是成功
333
+ },
334
+ });
335
+
336
+ // SQLite 内存数据库应该总是成功连接
337
+ await manager.connect();
338
+
339
+ const info = manager.getConnectionInfo();
340
+ expect(info.status).toBe('connected');
341
+
342
+ await manager.disconnect();
343
+ });
344
+
345
+ test('should check health status', async () => {
346
+ const { DatabaseConnectionManager } = await import('../../src/database/connection-manager');
347
+ const manager = new DatabaseConnectionManager({
348
+ type: 'sqlite',
349
+ config: {
350
+ path: ':memory:',
351
+ },
352
+ });
353
+
354
+ await manager.connect();
355
+ const isHealthy = await manager.healthCheck();
356
+
357
+ expect(isHealthy).toBe(true);
358
+
359
+ await manager.disconnect();
360
+ });
361
+
362
+ test('should get pool stats', async () => {
363
+ const { DatabaseConnectionManager } = await import('../../src/database/connection-manager');
364
+ const manager = new DatabaseConnectionManager(
365
+ {
366
+ type: 'sqlite',
367
+ config: {
368
+ path: ':memory:',
369
+ },
370
+ },
371
+ {
372
+ maxConnections: 5,
373
+ },
374
+ );
375
+
376
+ await manager.connect();
377
+ const stats = manager.getPoolStats();
378
+
379
+ expect(stats).toBeDefined();
380
+ expect(stats.maxConnections).toBe(5);
381
+ expect(stats.total).toBeGreaterThanOrEqual(1);
382
+
383
+ await manager.disconnect();
384
+ });
385
+ });
@@ -0,0 +1,164 @@
1
+ import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
2
+ import {
3
+ Entity,
4
+ Column,
5
+ PrimaryKey,
6
+ Repository,
7
+ BaseRepository,
8
+ getEntityMetadata,
9
+ getColumnMetadata,
10
+ getRepositoryMetadata,
11
+ } from '../../src/database/orm';
12
+ import { DatabaseService, DATABASE_SERVICE_TOKEN } from '../../src/database';
13
+ import { Container } from '../../src/di/container';
14
+ import 'reflect-metadata';
15
+
16
+ // 测试实体
17
+ @Entity('test_users')
18
+ class TestUser {
19
+ @PrimaryKey()
20
+ @Column({ type: 'INTEGER', autoIncrement: true })
21
+ public id!: number;
22
+
23
+ @Column({ type: 'TEXT', nullable: false })
24
+ public name!: string;
25
+
26
+ @Column({ type: 'TEXT', nullable: true })
27
+ public email?: string;
28
+ }
29
+
30
+ // 测试 Repository
31
+ @Repository('test_users', 'id')
32
+ class TestUserRepository extends BaseRepository<TestUser> {
33
+ protected tableName = 'test_users';
34
+ protected primaryKey = 'id';
35
+ }
36
+
37
+ describe('ORM Decorators', () => {
38
+ test('should define entity metadata', () => {
39
+ const metadata = getEntityMetadata(TestUser);
40
+ expect(metadata).toBeDefined();
41
+ expect(metadata?.tableName).toBe('test_users');
42
+ });
43
+
44
+ test('should define column metadata', () => {
45
+ const columns = getColumnMetadata(TestUser);
46
+ expect(columns).toBeDefined();
47
+ expect(columns.length).toBeGreaterThan(0);
48
+
49
+ const idColumn = columns.find((col) => col.propertyKey === 'id');
50
+ expect(idColumn).toBeDefined();
51
+ expect(idColumn?.primaryKey).toBe(true);
52
+ expect(idColumn?.autoIncrement).toBe(true);
53
+
54
+ const nameColumn = columns.find((col) => col.propertyKey === 'name');
55
+ expect(nameColumn).toBeDefined();
56
+ expect(nameColumn?.name).toBe('name');
57
+ expect(nameColumn?.type).toBe('TEXT');
58
+ // Column 装饰器中 nullable 选项会正确设置
59
+ expect(nameColumn?.nullable).toBe(false);
60
+ });
61
+
62
+ test('should define repository metadata', () => {
63
+ const metadata = getRepositoryMetadata(TestUserRepository);
64
+ expect(metadata).toBeDefined();
65
+ expect(metadata?.tableName).toBe('test_users');
66
+ expect(metadata?.primaryKey).toBe('id');
67
+ });
68
+ });
69
+
70
+ describe('BaseRepository', () => {
71
+ let container: Container;
72
+ let databaseService: DatabaseService;
73
+
74
+ beforeEach(async () => {
75
+ container = new Container();
76
+ databaseService = new DatabaseService({
77
+ database: {
78
+ type: 'sqlite',
79
+ config: {
80
+ path: ':memory:',
81
+ },
82
+ },
83
+ });
84
+ await databaseService.initialize();
85
+
86
+ container.registerInstance(DATABASE_SERVICE_TOKEN, databaseService);
87
+ container.register(TestUserRepository);
88
+
89
+ // 创建测试表
90
+ databaseService.query(`
91
+ CREATE TABLE IF NOT EXISTS test_users (
92
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
93
+ name TEXT NOT NULL,
94
+ email TEXT
95
+ )
96
+ `);
97
+ });
98
+
99
+ afterEach(async () => {
100
+ await databaseService.closePool();
101
+ });
102
+
103
+ test('should create repository instance', () => {
104
+ const repository = container.resolve<TestUserRepository>(TestUserRepository);
105
+ expect(repository).toBeInstanceOf(BaseRepository);
106
+ expect(repository).toBeInstanceOf(TestUserRepository);
107
+ });
108
+
109
+ test('should find all records', async () => {
110
+ const repository = container.resolve<TestUserRepository>(TestUserRepository);
111
+
112
+ // 插入测试数据
113
+ await repository.create({ name: 'Alice', email: 'alice@example.com' });
114
+ await repository.create({ name: 'Bob', email: 'bob@example.com' });
115
+
116
+ const users = await repository.findAll();
117
+ expect(users.length).toBe(2);
118
+ expect(users[0].name).toBe('Alice');
119
+ expect(users[1].name).toBe('Bob');
120
+ });
121
+
122
+ test('should find by id', async () => {
123
+ const repository = container.resolve<TestUserRepository>(TestUserRepository);
124
+
125
+ const created = await repository.create({ name: 'Alice', email: 'alice@example.com' });
126
+ expect(created.id).toBeDefined();
127
+
128
+ const found = await repository.findById(created.id!);
129
+ expect(found).not.toBeNull();
130
+ expect(found?.name).toBe('Alice');
131
+ expect(found?.email).toBe('alice@example.com');
132
+ });
133
+
134
+ test('should create record', async () => {
135
+ const repository = container.resolve<TestUserRepository>(TestUserRepository);
136
+
137
+ const user = await repository.create({ name: 'Alice', email: 'alice@example.com' });
138
+ expect(user.id).toBeDefined();
139
+ expect(user.name).toBe('Alice');
140
+ expect(user.email).toBe('alice@example.com');
141
+ });
142
+
143
+ test('should update record', async () => {
144
+ const repository = container.resolve<TestUserRepository>(TestUserRepository);
145
+
146
+ const created = await repository.create({ name: 'Alice', email: 'alice@example.com' });
147
+ const updated = await repository.update(created.id!, { name: 'Alice Updated' });
148
+
149
+ expect(updated.name).toBe('Alice Updated');
150
+ expect(updated.email).toBe('alice@example.com');
151
+ });
152
+
153
+ test('should delete record', async () => {
154
+ const repository = container.resolve<TestUserRepository>(TestUserRepository);
155
+
156
+ const created = await repository.create({ name: 'Alice', email: 'alice@example.com' });
157
+ const deleted = await repository.delete(created.id!);
158
+
159
+ expect(deleted).toBe(true);
160
+
161
+ const found = await repository.findById(created.id!);
162
+ expect(found).toBeNull();
163
+ });
164
+ });