@dangao/bun-server 1.0.1 → 1.1.2

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 (262) hide show
  1. package/dist/controller/controller.d.ts +1 -1
  2. package/dist/controller/controller.d.ts.map +1 -1
  3. package/dist/core/application.d.ts.map +1 -1
  4. package/dist/database/database-extension.d.ts.map +1 -1
  5. package/dist/database/database-module.d.ts.map +1 -1
  6. package/dist/database/orm/transaction-decorator.d.ts +1 -0
  7. package/dist/database/orm/transaction-decorator.d.ts.map +1 -1
  8. package/dist/database/orm/transaction-interceptor.d.ts +12 -3
  9. package/dist/database/orm/transaction-interceptor.d.ts.map +1 -1
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +678 -310
  13. package/dist/interceptor/base-interceptor.d.ts +94 -0
  14. package/dist/interceptor/base-interceptor.d.ts.map +1 -0
  15. package/dist/interceptor/builtin/cache-interceptor.d.ts +69 -0
  16. package/dist/interceptor/builtin/cache-interceptor.d.ts.map +1 -0
  17. package/dist/interceptor/builtin/index.d.ts +4 -0
  18. package/dist/interceptor/builtin/index.d.ts.map +1 -0
  19. package/dist/interceptor/builtin/log-interceptor.d.ts +56 -0
  20. package/dist/interceptor/builtin/log-interceptor.d.ts.map +1 -0
  21. package/dist/interceptor/builtin/permission-interceptor.d.ts +70 -0
  22. package/dist/interceptor/builtin/permission-interceptor.d.ts.map +1 -0
  23. package/dist/interceptor/index.d.ts +7 -0
  24. package/dist/interceptor/index.d.ts.map +1 -0
  25. package/dist/interceptor/interceptor-chain.d.ts +22 -0
  26. package/dist/interceptor/interceptor-chain.d.ts.map +1 -0
  27. package/dist/interceptor/interceptor-registry.d.ts +59 -0
  28. package/dist/interceptor/interceptor-registry.d.ts.map +1 -0
  29. package/dist/interceptor/metadata.d.ts +12 -0
  30. package/dist/interceptor/metadata.d.ts.map +1 -0
  31. package/dist/interceptor/types.d.ts +42 -0
  32. package/dist/interceptor/types.d.ts.map +1 -0
  33. package/dist/middleware/decorators.d.ts +2 -1
  34. package/dist/middleware/decorators.d.ts.map +1 -1
  35. package/dist/router/decorators.d.ts.map +1 -1
  36. package/dist/router/registry.d.ts +2 -1
  37. package/dist/router/registry.d.ts.map +1 -1
  38. package/dist/router/route.d.ts +3 -2
  39. package/dist/router/route.d.ts.map +1 -1
  40. package/dist/router/router.d.ts +2 -1
  41. package/dist/router/router.d.ts.map +1 -1
  42. package/dist/websocket/decorators.d.ts +2 -1
  43. package/dist/websocket/decorators.d.ts.map +1 -1
  44. package/package.json +5 -3
  45. package/readme.md +163 -2
  46. package/src/auth/controller.ts +148 -0
  47. package/src/auth/decorators.ts +81 -0
  48. package/src/auth/index.ts +12 -0
  49. package/src/auth/jwt.ts +169 -0
  50. package/src/auth/oauth2.ts +244 -0
  51. package/src/auth/types.ts +248 -0
  52. package/src/cache/cache-module.ts +67 -0
  53. package/src/cache/decorators.ts +202 -0
  54. package/src/cache/index.ts +27 -0
  55. package/src/cache/service.ts +151 -0
  56. package/src/cache/types.ts +420 -0
  57. package/src/config/config-module.ts +76 -0
  58. package/src/config/index.ts +8 -0
  59. package/src/config/service.ts +93 -0
  60. package/src/config/types.ts +27 -0
  61. package/src/controller/controller.ts +278 -0
  62. package/src/controller/decorators.ts +84 -0
  63. package/src/controller/index.ts +7 -0
  64. package/src/controller/metadata.ts +27 -0
  65. package/src/controller/param-binder.ts +157 -0
  66. package/src/core/application.ts +239 -0
  67. package/src/core/context.ts +228 -0
  68. package/src/core/index.ts +4 -0
  69. package/src/core/server.ts +128 -0
  70. package/src/core/types.ts +2 -0
  71. package/src/database/connection-manager.ts +239 -0
  72. package/src/database/connection-pool.ts +322 -0
  73. package/src/database/database-extension.ts +83 -0
  74. package/src/database/database-module.ts +121 -0
  75. package/src/database/health-indicator.ts +51 -0
  76. package/src/database/index.ts +47 -0
  77. package/src/database/orm/decorators.ts +155 -0
  78. package/src/database/orm/drizzle-repository.ts +39 -0
  79. package/src/database/orm/index.ts +23 -0
  80. package/src/database/orm/repository-decorator.ts +39 -0
  81. package/src/database/orm/repository.ts +103 -0
  82. package/src/database/orm/service.ts +49 -0
  83. package/src/database/orm/transaction-decorator.ts +76 -0
  84. package/src/database/orm/transaction-interceptor.ts +263 -0
  85. package/src/database/orm/transaction-manager.ts +276 -0
  86. package/src/database/orm/transaction-types.ts +140 -0
  87. package/src/database/orm/types.ts +99 -0
  88. package/src/database/service.ts +221 -0
  89. package/src/database/types.ts +171 -0
  90. package/src/di/container.ts +398 -0
  91. package/src/di/decorators.ts +228 -0
  92. package/src/di/index.ts +4 -0
  93. package/src/di/module-registry.ts +188 -0
  94. package/src/di/module.ts +65 -0
  95. package/src/di/types.ts +67 -0
  96. package/src/error/error-codes.ts +222 -0
  97. package/src/error/filter.ts +43 -0
  98. package/src/error/handler.ts +66 -0
  99. package/src/error/http-exception.ts +115 -0
  100. package/src/error/i18n.ts +217 -0
  101. package/src/error/index.ts +16 -0
  102. package/src/extensions/index.ts +5 -0
  103. package/src/extensions/logger-extension.ts +31 -0
  104. package/src/extensions/logger-module.ts +69 -0
  105. package/src/extensions/types.ts +14 -0
  106. package/src/files/index.ts +5 -0
  107. package/src/files/static-middleware.ts +53 -0
  108. package/src/files/storage.ts +67 -0
  109. package/src/files/types.ts +33 -0
  110. package/src/files/upload-middleware.ts +45 -0
  111. package/src/health/controller.ts +76 -0
  112. package/src/health/health-module.ts +51 -0
  113. package/src/health/index.ts +12 -0
  114. package/src/health/types.ts +28 -0
  115. package/src/index.ts +292 -0
  116. package/src/interceptor/base-interceptor.ts +203 -0
  117. package/src/interceptor/builtin/cache-interceptor.ts +169 -0
  118. package/src/interceptor/builtin/index.ts +28 -0
  119. package/src/interceptor/builtin/log-interceptor.ts +178 -0
  120. package/src/interceptor/builtin/permission-interceptor.ts +173 -0
  121. package/src/interceptor/index.ts +26 -0
  122. package/src/interceptor/interceptor-chain.ts +79 -0
  123. package/src/interceptor/interceptor-registry.ts +132 -0
  124. package/src/interceptor/metadata.ts +40 -0
  125. package/src/interceptor/types.ts +52 -0
  126. package/src/metrics/collector.ts +209 -0
  127. package/src/metrics/controller.ts +40 -0
  128. package/src/metrics/index.ts +15 -0
  129. package/src/metrics/metrics-module.ts +58 -0
  130. package/src/metrics/middleware.ts +46 -0
  131. package/src/metrics/prometheus.ts +79 -0
  132. package/src/metrics/types.ts +103 -0
  133. package/src/middleware/builtin/cors.ts +60 -0
  134. package/src/middleware/builtin/error-handler.ts +90 -0
  135. package/src/middleware/builtin/file-upload.ts +42 -0
  136. package/src/middleware/builtin/index.ts +14 -0
  137. package/src/middleware/builtin/logger.ts +91 -0
  138. package/src/middleware/builtin/rate-limit.ts +252 -0
  139. package/src/middleware/builtin/static-file.ts +88 -0
  140. package/src/middleware/decorators.ts +92 -0
  141. package/src/middleware/index.ts +11 -0
  142. package/src/middleware/middleware.ts +13 -0
  143. package/src/middleware/pipeline.ts +93 -0
  144. package/src/queue/decorators.ts +110 -0
  145. package/src/queue/index.ts +26 -0
  146. package/src/queue/queue-module.ts +64 -0
  147. package/src/queue/service.ts +302 -0
  148. package/src/queue/types.ts +341 -0
  149. package/src/request/body-parser.ts +133 -0
  150. package/src/request/file-handler.ts +46 -0
  151. package/src/request/index.ts +5 -0
  152. package/src/request/request.ts +107 -0
  153. package/src/request/response.ts +150 -0
  154. package/src/router/decorators.ts +123 -0
  155. package/src/router/index.ts +6 -0
  156. package/src/router/registry.ts +99 -0
  157. package/src/router/route.ts +141 -0
  158. package/src/router/router.ts +242 -0
  159. package/src/router/types.ts +27 -0
  160. package/src/security/access-decision-manager.ts +34 -0
  161. package/src/security/authentication-manager.ts +47 -0
  162. package/src/security/context.ts +92 -0
  163. package/src/security/filter.ts +162 -0
  164. package/src/security/index.ts +8 -0
  165. package/src/security/providers/index.ts +3 -0
  166. package/src/security/providers/jwt-provider.ts +60 -0
  167. package/src/security/providers/oauth2-provider.ts +70 -0
  168. package/src/security/security-module.ts +145 -0
  169. package/src/security/types.ts +165 -0
  170. package/src/session/decorators.ts +45 -0
  171. package/src/session/index.ts +19 -0
  172. package/src/session/middleware.ts +143 -0
  173. package/src/session/service.ts +218 -0
  174. package/src/session/session-module.ts +69 -0
  175. package/src/session/types.ts +373 -0
  176. package/src/swagger/decorators.ts +133 -0
  177. package/src/swagger/generator.ts +234 -0
  178. package/src/swagger/index.ts +7 -0
  179. package/src/swagger/swagger-extension.ts +41 -0
  180. package/src/swagger/swagger-module.ts +83 -0
  181. package/src/swagger/types.ts +188 -0
  182. package/src/swagger/ui.ts +98 -0
  183. package/src/testing/harness.ts +96 -0
  184. package/src/validation/decorators.ts +95 -0
  185. package/src/validation/errors.ts +28 -0
  186. package/src/validation/index.ts +14 -0
  187. package/src/validation/types.ts +35 -0
  188. package/src/validation/validator.ts +63 -0
  189. package/src/websocket/decorators.ts +53 -0
  190. package/src/websocket/index.ts +12 -0
  191. package/src/websocket/registry.ts +133 -0
  192. package/tests/cache/cache-module.test.ts +212 -0
  193. package/tests/config/config-module.test.ts +151 -0
  194. package/tests/controller/controller.test.ts +189 -0
  195. package/tests/controller/path-combination.test.ts +207 -0
  196. package/tests/core/application.test.ts +57 -0
  197. package/tests/core/context-body.test.ts +44 -0
  198. package/tests/core/context.test.ts +86 -0
  199. package/tests/core/edge-cases.test.ts +432 -0
  200. package/tests/database/database-module.test.ts +385 -0
  201. package/tests/database/orm.test.ts +164 -0
  202. package/tests/database/postgres-mysql-integration.test.ts +395 -0
  203. package/tests/database/transaction.test.ts +238 -0
  204. package/tests/di/container.test.ts +264 -0
  205. package/tests/di/module.test.ts +128 -0
  206. package/tests/error/error-codes.test.ts +121 -0
  207. package/tests/error/error-handler.test.ts +68 -0
  208. package/tests/error/error-handling.test.ts +254 -0
  209. package/tests/error/http-exception.test.ts +37 -0
  210. package/tests/error/i18n-integration.test.ts +175 -0
  211. package/tests/extensions/logger-extension.test.ts +40 -0
  212. package/tests/files/static-middleware.test.ts +67 -0
  213. package/tests/files/upload-middleware.test.ts +43 -0
  214. package/tests/health/health-module.test.ts +116 -0
  215. package/tests/integration/application-router.test.ts +85 -0
  216. package/tests/integration/body-parsing.test.ts +88 -0
  217. package/tests/integration/cache-e2e.test.ts +114 -0
  218. package/tests/integration/oauth2-e2e.test.ts +615 -0
  219. package/tests/integration/session-e2e.test.ts +207 -0
  220. package/tests/interceptor/builtin/cache-interceptor.test.ts +137 -0
  221. package/tests/interceptor/builtin/permission-interceptor.test.ts +182 -0
  222. package/tests/interceptor/interceptor-advanced-integration.test.ts +592 -0
  223. package/tests/interceptor/interceptor-arg-modification.test.ts +76 -0
  224. package/tests/interceptor/interceptor-chain.test.ts +199 -0
  225. package/tests/interceptor/interceptor-integration.test.ts +230 -0
  226. package/tests/interceptor/interceptor-registry.test.ts +200 -0
  227. package/tests/interceptor/perf/interceptor-performance.test.ts +341 -0
  228. package/tests/metrics/metrics-module.test.ts +178 -0
  229. package/tests/middleware/builtin.test.ts +206 -0
  230. package/tests/middleware/file-upload.test.ts +41 -0
  231. package/tests/middleware/middleware.test.ts +120 -0
  232. package/tests/middleware/pipeline.test.ts +72 -0
  233. package/tests/middleware/rate-limit.test.ts +314 -0
  234. package/tests/middleware/static-file.test.ts +62 -0
  235. package/tests/perf/harness.test.ts +48 -0
  236. package/tests/perf/optimization.test.ts +183 -0
  237. package/tests/perf/regression.test.ts +120 -0
  238. package/tests/queue/queue-module.test.ts +217 -0
  239. package/tests/request/body-parser.test.ts +96 -0
  240. package/tests/request/response.test.ts +99 -0
  241. package/tests/router/decorators.test.ts +46 -0
  242. package/tests/router/registry.test.ts +51 -0
  243. package/tests/router/route.test.ts +71 -0
  244. package/tests/router/router-normalization.test.ts +106 -0
  245. package/tests/router/router.test.ts +133 -0
  246. package/tests/security/access-decision-manager.test.ts +84 -0
  247. package/tests/security/authentication-manager.test.ts +81 -0
  248. package/tests/security/context.test.ts +302 -0
  249. package/tests/security/filter.test.ts +225 -0
  250. package/tests/security/jwt-provider.test.ts +106 -0
  251. package/tests/security/oauth2-provider.test.ts +269 -0
  252. package/tests/security/security-module.test.ts +143 -0
  253. package/tests/session/session-module.test.ts +307 -0
  254. package/tests/stress/di-stress.test.ts +30 -0
  255. package/tests/swagger/decorators.test.ts +153 -0
  256. package/tests/swagger/generator.test.ts +202 -0
  257. package/tests/swagger/swagger-extension.test.ts +72 -0
  258. package/tests/swagger/swagger-module.test.ts +79 -0
  259. package/tests/utils/test-port.ts +10 -0
  260. package/tests/validation/controller-validation.test.ts +64 -0
  261. package/tests/validation/validation.test.ts +42 -0
  262. package/tests/websocket/gateway.test.ts +68 -0
@@ -0,0 +1,207 @@
1
+ import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+ import {
4
+ Application,
5
+ SessionModule,
6
+ SessionService,
7
+ SESSION_SERVICE_TOKEN,
8
+ ConfigModule,
9
+ Controller,
10
+ GET,
11
+ POST,
12
+ Inject,
13
+ Injectable,
14
+ Module,
15
+ Param,
16
+ Body,
17
+ Session,
18
+ createSessionMiddleware,
19
+ } from '../../src';
20
+ import { getTestPort } from '../utils/test-port';
21
+
22
+ @Injectable()
23
+ class AuthService {
24
+ public constructor(
25
+ @Inject(SESSION_SERVICE_TOKEN) private readonly sessionService: SessionService,
26
+ ) {}
27
+
28
+ public async login(username: string, sessionId?: string): Promise<{ sessionId: string }> {
29
+ // 如果提供了 sessionId,使用现有 Session,否则创建新 Session
30
+ let session;
31
+ if (sessionId) {
32
+ session = await this.sessionService.get(sessionId);
33
+ if (session) {
34
+ // 更新现有 Session 的数据
35
+ await this.sessionService.set(sessionId, {
36
+ username,
37
+ loginTime: Date.now(),
38
+ });
39
+ session = await this.sessionService.get(sessionId);
40
+ }
41
+ }
42
+
43
+ if (!session) {
44
+ session = await this.sessionService.create({
45
+ username,
46
+ loginTime: Date.now(),
47
+ });
48
+ }
49
+
50
+ return { sessionId: session.id };
51
+ }
52
+ }
53
+
54
+ @Controller('/api/auth')
55
+ class AuthController {
56
+ public constructor(
57
+ @Inject(AuthService) private readonly authService: AuthService,
58
+ ) {}
59
+
60
+ @POST('/login')
61
+ public async login(
62
+ @Body() body: { username: string },
63
+ @Session() session: any,
64
+ ) {
65
+ // 使用中间件创建的 Session(如果存在)
66
+ const sessionId = session?.id;
67
+ return await this.authService.login(body.username, sessionId);
68
+ }
69
+ }
70
+
71
+ @Controller('/api/session')
72
+ class SessionController {
73
+ public constructor(
74
+ @Inject(SESSION_SERVICE_TOKEN) private readonly sessionService: SessionService,
75
+ ) {}
76
+
77
+ @GET('/info')
78
+ public async getInfo(@Session() session: any) {
79
+ if (!session) {
80
+ return { error: 'No session' };
81
+ }
82
+ return {
83
+ sessionId: session.id,
84
+ username: session.data.username,
85
+ };
86
+ }
87
+
88
+ @POST('/set')
89
+ public async setValue(
90
+ @Session() session: any,
91
+ @Body() body: { key: string; value: unknown },
92
+ ) {
93
+ if (!session) {
94
+ return { error: 'No session' };
95
+ }
96
+ await this.sessionService.setValue(session.id, body.key, body.value);
97
+ return { success: true };
98
+ }
99
+ }
100
+
101
+ @Module({
102
+ controllers: [AuthController, SessionController],
103
+ providers: [AuthService],
104
+ })
105
+ class AppModule {}
106
+
107
+ describe('Session E2E', () => {
108
+ let app: Application;
109
+ let port: number;
110
+
111
+ beforeEach(async () => {
112
+ port = getTestPort();
113
+ ConfigModule.forRoot({
114
+ defaultConfig: { app: { name: 'Session E2E Test', port } },
115
+ });
116
+ SessionModule.forRoot({
117
+ name: 'sessionId',
118
+ maxAge: 86400000,
119
+ rolling: true,
120
+ });
121
+ app = new Application({ port });
122
+ app.registerModule(SessionModule);
123
+ const container = app.getContainer();
124
+ app.use(createSessionMiddleware(container));
125
+ app.registerModule(AppModule);
126
+ await app.listen();
127
+ });
128
+
129
+ afterEach(async () => {
130
+ await app.stop();
131
+ });
132
+
133
+ test('should create session on login', async () => {
134
+ const response = await fetch(`http://localhost:${port}/api/auth/login`, {
135
+ method: 'POST',
136
+ headers: { 'Content-Type': 'application/json' },
137
+ body: JSON.stringify({ username: 'testuser' }),
138
+ });
139
+
140
+ expect(response.status).toBe(200);
141
+ const data = await response.json();
142
+ expect(data.sessionId).toBeDefined();
143
+
144
+ // 检查 Cookie
145
+ const setCookie = response.headers.get('Set-Cookie');
146
+ expect(setCookie).toBeTruthy();
147
+ // Set-Cookie 可能返回字符串或数组
148
+ const cookieStr = Array.isArray(setCookie) ? setCookie[0] : setCookie;
149
+ if (cookieStr && typeof cookieStr === 'string') {
150
+ expect(cookieStr.includes('sessionId=')).toBe(true);
151
+ }
152
+ });
153
+
154
+ test('should get session info', async () => {
155
+ // 先登录获取 session
156
+ const loginResponse = await fetch(`http://localhost:${port}/api/auth/login`, {
157
+ method: 'POST',
158
+ headers: { 'Content-Type': 'application/json' },
159
+ body: JSON.stringify({ username: 'testuser' }),
160
+ });
161
+ const loginData = await loginResponse.json();
162
+ const setCookie = loginResponse.headers.get('Set-Cookie');
163
+
164
+ // Set-Cookie 可能返回字符串或数组
165
+ const cookieStr = Array.isArray(setCookie) ? setCookie[0] : setCookie;
166
+ if (!cookieStr || typeof cookieStr !== 'string') {
167
+ throw new Error('Set-Cookie header not found');
168
+ }
169
+
170
+ // 提取 sessionId=value 部分(第一个分号之前的内容)
171
+ const cookieValue = cookieStr.split(';')[0].trim();
172
+
173
+ // 使用 Cookie 访问受保护端点
174
+ const infoResponse = await fetch(`http://localhost:${port}/api/session/info`, {
175
+ headers: {
176
+ Cookie: cookieValue,
177
+ },
178
+ });
179
+
180
+ expect(infoResponse.status).toBe(200);
181
+ const infoData = await infoResponse.json();
182
+ expect(infoData.sessionId).toBe(loginData.sessionId);
183
+ expect(infoData.username).toBe('testuser');
184
+ });
185
+
186
+ test('should set session value', async () => {
187
+ const loginResponse = await fetch(`http://localhost:${port}/api/auth/login`, {
188
+ method: 'POST',
189
+ headers: { 'Content-Type': 'application/json' },
190
+ body: JSON.stringify({ username: 'testuser' }),
191
+ });
192
+ const cookies = loginResponse.headers.get('Set-Cookie') || '';
193
+
194
+ const setResponse = await fetch(`http://localhost:${port}/api/session/set`, {
195
+ method: 'POST',
196
+ headers: {
197
+ 'Content-Type': 'application/json',
198
+ Cookie: cookies.split(';')[0],
199
+ },
200
+ body: JSON.stringify({ key: 'cart', value: ['item1', 'item2'] }),
201
+ });
202
+
203
+ expect(setResponse.status).toBe(200);
204
+ const data = await setResponse.json();
205
+ expect(data.success).toBe(true);
206
+ });
207
+ });
@@ -0,0 +1,137 @@
1
+ import 'reflect-metadata';
2
+ import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
3
+ import { Application } from '../../../src/core/application';
4
+ import { Controller, ControllerRegistry } from '../../../src/controller/controller';
5
+ import { GET } from '../../../src/router/decorators';
6
+ import { RouteRegistry } from '../../../src/router/registry';
7
+ import {
8
+ Cache,
9
+ CacheInterceptor,
10
+ CACHE_METADATA_KEY,
11
+ InterceptorRegistry,
12
+ INTERCEPTOR_REGISTRY_TOKEN,
13
+ } from '../../../src/interceptor';
14
+ import { getTestPort } from '../../utils/test-port';
15
+
16
+ describe('CacheInterceptor', () => {
17
+ let app: Application;
18
+ let port: number;
19
+ let interceptorRegistry: InterceptorRegistry;
20
+ let callCount = 0;
21
+
22
+ beforeEach(() => {
23
+ port = getTestPort();
24
+ app = new Application({ port });
25
+ interceptorRegistry = app.getContainer().resolve<InterceptorRegistry>(
26
+ INTERCEPTOR_REGISTRY_TOKEN,
27
+ );
28
+ interceptorRegistry.register(CACHE_METADATA_KEY, new CacheInterceptor());
29
+ callCount = 0;
30
+ CacheInterceptor.clearCache();
31
+ });
32
+
33
+ afterEach(async () => {
34
+ if (app) {
35
+ await app.stop();
36
+ }
37
+ RouteRegistry.getInstance().clear();
38
+ ControllerRegistry.getInstance().clear();
39
+ interceptorRegistry.clear();
40
+ CacheInterceptor.clearCache();
41
+ });
42
+
43
+ test('should cache method result', async () => {
44
+ @Controller('/api/test')
45
+ class TestController {
46
+ @GET('/')
47
+ @Cache({ ttl: 1000 })
48
+ public getData() {
49
+ callCount++;
50
+ return { data: 'cached', count: callCount };
51
+ }
52
+ }
53
+
54
+ app.registerController(TestController);
55
+ await app.listen();
56
+
57
+ // 第一次调用
58
+ const response1 = await fetch(`http://localhost:${port}/api/test`);
59
+ expect(response1.status).toBe(200);
60
+ const data1 = await response1.json();
61
+ expect(data1.count).toBe(1);
62
+ expect(callCount).toBe(1);
63
+
64
+ // 第二次调用(应该使用缓存)
65
+ const response2 = await fetch(`http://localhost:${port}/api/test`);
66
+ expect(response2.status).toBe(200);
67
+ const data2 = await response2.json();
68
+ expect(data2.count).toBe(1); // 缓存的结果
69
+ expect(callCount).toBe(1); // 方法没有被再次调用
70
+ });
71
+
72
+ test('should use custom cache key', async () => {
73
+ @Controller('/api/test')
74
+ class TestController {
75
+ @GET('/')
76
+ @Cache({ ttl: 1000, key: 'custom-key' })
77
+ public getData() {
78
+ callCount++;
79
+ return { data: 'cached' };
80
+ }
81
+ }
82
+
83
+ app.registerController(TestController);
84
+ await app.listen();
85
+
86
+ await fetch(`http://localhost:${port}/api/test`);
87
+ await fetch(`http://localhost:${port}/api/test`);
88
+
89
+ expect(callCount).toBe(1); // 应该只调用一次(使用缓存)
90
+ });
91
+
92
+ test('should expire cache after TTL', async () => {
93
+ @Controller('/api/test')
94
+ class TestController {
95
+ @GET('/')
96
+ @Cache({ ttl: 100 }) // 100ms TTL
97
+ public getData() {
98
+ callCount++;
99
+ return { data: 'cached', count: callCount };
100
+ }
101
+ }
102
+
103
+ app.registerController(TestController);
104
+ await app.listen();
105
+
106
+ // 第一次调用
107
+ await fetch(`http://localhost:${port}/api/test`);
108
+ expect(callCount).toBe(1);
109
+
110
+ // 等待缓存过期
111
+ await new Promise((resolve) => setTimeout(resolve, 150));
112
+
113
+ // 第二次调用(缓存已过期)
114
+ await fetch(`http://localhost:${port}/api/test`);
115
+ expect(callCount).toBe(2);
116
+ });
117
+
118
+ test('should work without cache decorator', async () => {
119
+ @Controller('/api/test')
120
+ class TestController {
121
+ @GET('/')
122
+ public getData() {
123
+ callCount++;
124
+ return { data: 'no-cache' };
125
+ }
126
+ }
127
+
128
+ app.registerController(TestController);
129
+ await app.listen();
130
+
131
+ await fetch(`http://localhost:${port}/api/test`);
132
+ await fetch(`http://localhost:${port}/api/test`);
133
+
134
+ expect(callCount).toBe(2); // 没有缓存,应该调用两次
135
+ });
136
+ });
137
+
@@ -0,0 +1,182 @@
1
+ import 'reflect-metadata';
2
+ import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
3
+ import { Application } from '../../../src/core/application';
4
+ import { Controller, ControllerRegistry } from '../../../src/controller/controller';
5
+ import { GET } from '../../../src/router/decorators';
6
+ import { RouteRegistry } from '../../../src/router/registry';
7
+ import {
8
+ Permission,
9
+ PermissionInterceptor,
10
+ PERMISSION_METADATA_KEY,
11
+ InterceptorRegistry,
12
+ INTERCEPTOR_REGISTRY_TOKEN,
13
+ type PermissionService,
14
+ } from '../../../src/interceptor';
15
+ import { ForbiddenException } from '../../../src/error';
16
+ import { getTestPort } from '../../utils/test-port';
17
+
18
+ describe('PermissionInterceptor', () => {
19
+ let app: Application;
20
+ let port: number;
21
+ let interceptorRegistry: InterceptorRegistry;
22
+
23
+ beforeEach(() => {
24
+ port = getTestPort();
25
+ app = new Application({ port });
26
+ interceptorRegistry = app.getContainer().resolve<InterceptorRegistry>(
27
+ INTERCEPTOR_REGISTRY_TOKEN,
28
+ );
29
+ interceptorRegistry.register(PERMISSION_METADATA_KEY, new PermissionInterceptor());
30
+ });
31
+
32
+ afterEach(async () => {
33
+ if (app) {
34
+ await app.stop();
35
+ }
36
+ RouteRegistry.getInstance().clear();
37
+ ControllerRegistry.getInstance().clear();
38
+ interceptorRegistry.clear();
39
+ });
40
+
41
+ test('should allow access when permission check passes', async () => {
42
+ // 创建权限服务实现
43
+ class MockPermissionService implements PermissionService {
44
+ public async check(
45
+ userId: string | null,
46
+ resource: string,
47
+ action: string,
48
+ ): Promise<boolean> {
49
+ return userId === 'user1' && resource === 'user' && action === 'read';
50
+ }
51
+ }
52
+
53
+ // 注册权限服务
54
+ app.getContainer().registerInstance(
55
+ PermissionInterceptor.PERMISSION_SERVICE_TOKEN,
56
+ new MockPermissionService(),
57
+ );
58
+
59
+ @Controller('/api/users')
60
+ class UserController {
61
+ @GET('/:id')
62
+ @Permission({ resource: 'user', action: 'read' })
63
+ public getUser() {
64
+ return { id: '123', name: 'Test User' };
65
+ }
66
+ }
67
+
68
+ app.registerController(UserController);
69
+ await app.listen();
70
+
71
+ const response = await fetch(`http://localhost:${port}/api/users/123`, {
72
+ headers: { 'X-User-Id': 'user1' },
73
+ });
74
+
75
+ expect(response.status).toBe(200);
76
+ const data = await response.json();
77
+ expect(data.id).toBe('123');
78
+ });
79
+
80
+ test('should deny access when permission check fails', async () => {
81
+ // 创建权限服务实现
82
+ class MockPermissionService implements PermissionService {
83
+ public async check(
84
+ userId: string | null,
85
+ resource: string,
86
+ action: string,
87
+ ): Promise<boolean> {
88
+ return false; // 总是拒绝
89
+ }
90
+ }
91
+
92
+ // 注册权限服务
93
+ app.getContainer().registerInstance(
94
+ PermissionInterceptor.PERMISSION_SERVICE_TOKEN,
95
+ new MockPermissionService(),
96
+ );
97
+
98
+ @Controller('/api/users')
99
+ class UserController {
100
+ @GET('/:id')
101
+ @Permission({ resource: 'user', action: 'read' })
102
+ public getUser() {
103
+ return { id: '123', name: 'Test User' };
104
+ }
105
+ }
106
+
107
+ app.registerController(UserController);
108
+ await app.listen();
109
+
110
+ const response = await fetch(`http://localhost:${port}/api/users/123`, {
111
+ headers: { 'X-User-Id': 'user1' },
112
+ });
113
+
114
+ expect(response.status).toBe(403);
115
+ });
116
+
117
+ test('should allow anonymous access when allowAnonymous is true', async () => {
118
+ @Controller('/api/public')
119
+ class PublicController {
120
+ @GET('/')
121
+ @Permission({
122
+ resource: 'public',
123
+ action: 'read',
124
+ allowAnonymous: true,
125
+ })
126
+ public getPublicData() {
127
+ return { data: 'public' };
128
+ }
129
+ }
130
+
131
+ app.registerController(PublicController);
132
+ await app.listen();
133
+
134
+ // 没有提供用户 ID
135
+ const response = await fetch(`http://localhost:${port}/api/public`);
136
+
137
+ expect(response.status).toBe(200);
138
+ const data = await response.json();
139
+ expect(data.data).toBe('public');
140
+ });
141
+
142
+ test('should work without permission decorator', async () => {
143
+ @Controller('/api/test')
144
+ class TestController {
145
+ @GET('/')
146
+ public getData() {
147
+ return { data: 'no-permission-check' };
148
+ }
149
+ }
150
+
151
+ app.registerController(TestController);
152
+ await app.listen();
153
+
154
+ const response = await fetch(`http://localhost:${port}/api/test`);
155
+
156
+ expect(response.status).toBe(200);
157
+ const data = await response.json();
158
+ expect(data.data).toBe('no-permission-check');
159
+ });
160
+
161
+ test('should throw error when PermissionService not registered', async () => {
162
+ @Controller('/api/users')
163
+ class UserController {
164
+ @GET('/:id')
165
+ @Permission({ resource: 'user', action: 'read' })
166
+ public getUser() {
167
+ return { id: '123' };
168
+ }
169
+ }
170
+
171
+ app.registerController(UserController);
172
+ await app.listen();
173
+
174
+ const response = await fetch(`http://localhost:${port}/api/users/123`, {
175
+ headers: { 'X-User-Id': 'user1' },
176
+ });
177
+
178
+ // 应该返回 500 错误(PermissionService 未注册)
179
+ expect(response.status).toBe(500);
180
+ });
181
+ });
182
+