@hazeljs/core 0.2.0-beta.1

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 (194) hide show
  1. package/README.md +522 -0
  2. package/dist/__tests__/container.test.d.ts +2 -0
  3. package/dist/__tests__/container.test.d.ts.map +1 -0
  4. package/dist/__tests__/container.test.js +454 -0
  5. package/dist/__tests__/decorators.test.d.ts +2 -0
  6. package/dist/__tests__/decorators.test.d.ts.map +1 -0
  7. package/dist/__tests__/decorators.test.js +693 -0
  8. package/dist/__tests__/errors/http.error.test.d.ts +2 -0
  9. package/dist/__tests__/errors/http.error.test.d.ts.map +1 -0
  10. package/dist/__tests__/errors/http.error.test.js +117 -0
  11. package/dist/__tests__/filters/exception-filter.test.d.ts +2 -0
  12. package/dist/__tests__/filters/exception-filter.test.d.ts.map +1 -0
  13. package/dist/__tests__/filters/exception-filter.test.js +135 -0
  14. package/dist/__tests__/filters/http-exception.filter.test.d.ts +2 -0
  15. package/dist/__tests__/filters/http-exception.filter.test.d.ts.map +1 -0
  16. package/dist/__tests__/filters/http-exception.filter.test.js +119 -0
  17. package/dist/__tests__/hazel-app.test.d.ts +2 -0
  18. package/dist/__tests__/hazel-app.test.d.ts.map +1 -0
  19. package/dist/__tests__/hazel-app.test.js +682 -0
  20. package/dist/__tests__/hazel-module.test.d.ts +2 -0
  21. package/dist/__tests__/hazel-module.test.d.ts.map +1 -0
  22. package/dist/__tests__/hazel-module.test.js +408 -0
  23. package/dist/__tests__/hazel-response.test.d.ts +2 -0
  24. package/dist/__tests__/hazel-response.test.d.ts.map +1 -0
  25. package/dist/__tests__/hazel-response.test.js +138 -0
  26. package/dist/__tests__/health.test.d.ts +2 -0
  27. package/dist/__tests__/health.test.d.ts.map +1 -0
  28. package/dist/__tests__/health.test.js +147 -0
  29. package/dist/__tests__/index.test.d.ts +2 -0
  30. package/dist/__tests__/index.test.d.ts.map +1 -0
  31. package/dist/__tests__/index.test.js +239 -0
  32. package/dist/__tests__/interceptors/interceptor.test.d.ts +2 -0
  33. package/dist/__tests__/interceptors/interceptor.test.d.ts.map +1 -0
  34. package/dist/__tests__/interceptors/interceptor.test.js +166 -0
  35. package/dist/__tests__/logger.test.d.ts +2 -0
  36. package/dist/__tests__/logger.test.d.ts.map +1 -0
  37. package/dist/__tests__/logger.test.js +141 -0
  38. package/dist/__tests__/middleware/cors.test.d.ts +2 -0
  39. package/dist/__tests__/middleware/cors.test.d.ts.map +1 -0
  40. package/dist/__tests__/middleware/cors.test.js +129 -0
  41. package/dist/__tests__/middleware/csrf.test.d.ts +2 -0
  42. package/dist/__tests__/middleware/csrf.test.d.ts.map +1 -0
  43. package/dist/__tests__/middleware/csrf.test.js +247 -0
  44. package/dist/__tests__/middleware/global-middleware.test.d.ts +2 -0
  45. package/dist/__tests__/middleware/global-middleware.test.d.ts.map +1 -0
  46. package/dist/__tests__/middleware/global-middleware.test.js +259 -0
  47. package/dist/__tests__/middleware/rate-limit.test.d.ts +2 -0
  48. package/dist/__tests__/middleware/rate-limit.test.d.ts.map +1 -0
  49. package/dist/__tests__/middleware/rate-limit.test.js +264 -0
  50. package/dist/__tests__/middleware/security-headers.test.d.ts +2 -0
  51. package/dist/__tests__/middleware/security-headers.test.d.ts.map +1 -0
  52. package/dist/__tests__/middleware/security-headers.test.js +229 -0
  53. package/dist/__tests__/middleware/timeout.test.d.ts +2 -0
  54. package/dist/__tests__/middleware/timeout.test.d.ts.map +1 -0
  55. package/dist/__tests__/middleware/timeout.test.js +132 -0
  56. package/dist/__tests__/middleware.test.d.ts +2 -0
  57. package/dist/__tests__/middleware.test.d.ts.map +1 -0
  58. package/dist/__tests__/middleware.test.js +180 -0
  59. package/dist/__tests__/pipes/pipe.test.d.ts +2 -0
  60. package/dist/__tests__/pipes/pipe.test.d.ts.map +1 -0
  61. package/dist/__tests__/pipes/pipe.test.js +245 -0
  62. package/dist/__tests__/pipes/validation.pipe.test.d.ts +2 -0
  63. package/dist/__tests__/pipes/validation.pipe.test.d.ts.map +1 -0
  64. package/dist/__tests__/pipes/validation.pipe.test.js +297 -0
  65. package/dist/__tests__/request-parser.test.d.ts +2 -0
  66. package/dist/__tests__/request-parser.test.d.ts.map +1 -0
  67. package/dist/__tests__/request-parser.test.js +182 -0
  68. package/dist/__tests__/router.test.d.ts +2 -0
  69. package/dist/__tests__/router.test.d.ts.map +1 -0
  70. package/dist/__tests__/router.test.js +680 -0
  71. package/dist/__tests__/routing/route-matcher.test.d.ts +2 -0
  72. package/dist/__tests__/routing/route-matcher.test.d.ts.map +1 -0
  73. package/dist/__tests__/routing/route-matcher.test.js +219 -0
  74. package/dist/__tests__/routing/version.decorator.test.d.ts +2 -0
  75. package/dist/__tests__/routing/version.decorator.test.d.ts.map +1 -0
  76. package/dist/__tests__/routing/version.decorator.test.js +298 -0
  77. package/dist/__tests__/service.test.d.ts +2 -0
  78. package/dist/__tests__/service.test.d.ts.map +1 -0
  79. package/dist/__tests__/service.test.js +121 -0
  80. package/dist/__tests__/shutdown.test.d.ts +2 -0
  81. package/dist/__tests__/shutdown.test.d.ts.map +1 -0
  82. package/dist/__tests__/shutdown.test.js +250 -0
  83. package/dist/__tests__/testing/testing.module.test.d.ts +2 -0
  84. package/dist/__tests__/testing/testing.module.test.d.ts.map +1 -0
  85. package/dist/__tests__/testing/testing.module.test.js +370 -0
  86. package/dist/__tests__/upload/file-upload.test.d.ts +2 -0
  87. package/dist/__tests__/upload/file-upload.test.d.ts.map +1 -0
  88. package/dist/__tests__/upload/file-upload.test.js +498 -0
  89. package/dist/__tests__/utils/sanitize.test.d.ts +2 -0
  90. package/dist/__tests__/utils/sanitize.test.d.ts.map +1 -0
  91. package/dist/__tests__/utils/sanitize.test.js +291 -0
  92. package/dist/__tests__/validator.test.d.ts +2 -0
  93. package/dist/__tests__/validator.test.d.ts.map +1 -0
  94. package/dist/__tests__/validator.test.js +300 -0
  95. package/dist/container.d.ts +80 -0
  96. package/dist/container.d.ts.map +1 -0
  97. package/dist/container.js +271 -0
  98. package/dist/decorators.d.ts +92 -0
  99. package/dist/decorators.d.ts.map +1 -0
  100. package/dist/decorators.js +343 -0
  101. package/dist/errors/http.error.d.ts +31 -0
  102. package/dist/errors/http.error.d.ts.map +1 -0
  103. package/dist/errors/http.error.js +62 -0
  104. package/dist/filters/exception-filter.d.ts +39 -0
  105. package/dist/filters/exception-filter.d.ts.map +1 -0
  106. package/dist/filters/exception-filter.js +38 -0
  107. package/dist/filters/http-exception.filter.d.ts +9 -0
  108. package/dist/filters/http-exception.filter.d.ts.map +1 -0
  109. package/dist/filters/http-exception.filter.js +42 -0
  110. package/dist/hazel-app.d.ts +78 -0
  111. package/dist/hazel-app.d.ts.map +1 -0
  112. package/dist/hazel-app.js +453 -0
  113. package/dist/hazel-module.d.ts +20 -0
  114. package/dist/hazel-module.d.ts.map +1 -0
  115. package/dist/hazel-module.js +109 -0
  116. package/dist/hazel-response.d.ts +20 -0
  117. package/dist/hazel-response.d.ts.map +1 -0
  118. package/dist/hazel-response.js +68 -0
  119. package/dist/health.d.ts +73 -0
  120. package/dist/health.d.ts.map +1 -0
  121. package/dist/health.js +174 -0
  122. package/dist/index.d.ts +41 -0
  123. package/dist/index.d.ts.map +1 -0
  124. package/dist/index.js +140 -0
  125. package/dist/interceptors/interceptor.d.ts +22 -0
  126. package/dist/interceptors/interceptor.d.ts.map +1 -0
  127. package/dist/interceptors/interceptor.js +46 -0
  128. package/dist/logger.d.ts +8 -0
  129. package/dist/logger.d.ts.map +1 -0
  130. package/dist/logger.js +238 -0
  131. package/dist/middleware/cors.middleware.d.ts +44 -0
  132. package/dist/middleware/cors.middleware.d.ts.map +1 -0
  133. package/dist/middleware/cors.middleware.js +118 -0
  134. package/dist/middleware/csrf.middleware.d.ts +82 -0
  135. package/dist/middleware/csrf.middleware.d.ts.map +1 -0
  136. package/dist/middleware/csrf.middleware.js +183 -0
  137. package/dist/middleware/global-middleware.d.ts +111 -0
  138. package/dist/middleware/global-middleware.d.ts.map +1 -0
  139. package/dist/middleware/global-middleware.js +179 -0
  140. package/dist/middleware/rate-limit.middleware.d.ts +73 -0
  141. package/dist/middleware/rate-limit.middleware.d.ts.map +1 -0
  142. package/dist/middleware/rate-limit.middleware.js +124 -0
  143. package/dist/middleware/security-headers.middleware.d.ts +76 -0
  144. package/dist/middleware/security-headers.middleware.d.ts.map +1 -0
  145. package/dist/middleware/security-headers.middleware.js +123 -0
  146. package/dist/middleware/timeout.middleware.d.ts +25 -0
  147. package/dist/middleware/timeout.middleware.d.ts.map +1 -0
  148. package/dist/middleware/timeout.middleware.js +74 -0
  149. package/dist/middleware.d.ts +13 -0
  150. package/dist/middleware.d.ts.map +1 -0
  151. package/dist/middleware.js +47 -0
  152. package/dist/pipes/pipe.d.ts +50 -0
  153. package/dist/pipes/pipe.d.ts.map +1 -0
  154. package/dist/pipes/pipe.js +96 -0
  155. package/dist/pipes/validation.pipe.d.ts +6 -0
  156. package/dist/pipes/validation.pipe.d.ts.map +1 -0
  157. package/dist/pipes/validation.pipe.js +61 -0
  158. package/dist/request-context.d.ts +17 -0
  159. package/dist/request-context.d.ts.map +1 -0
  160. package/dist/request-context.js +2 -0
  161. package/dist/request-parser.d.ts +7 -0
  162. package/dist/request-parser.d.ts.map +1 -0
  163. package/dist/request-parser.js +60 -0
  164. package/dist/router.d.ts +33 -0
  165. package/dist/router.d.ts.map +1 -0
  166. package/dist/router.js +426 -0
  167. package/dist/routing/route-matcher.d.ts +39 -0
  168. package/dist/routing/route-matcher.d.ts.map +1 -0
  169. package/dist/routing/route-matcher.js +93 -0
  170. package/dist/routing/version.decorator.d.ts +36 -0
  171. package/dist/routing/version.decorator.d.ts.map +1 -0
  172. package/dist/routing/version.decorator.js +89 -0
  173. package/dist/service.d.ts +9 -0
  174. package/dist/service.d.ts.map +1 -0
  175. package/dist/service.js +39 -0
  176. package/dist/shutdown.d.ts +32 -0
  177. package/dist/shutdown.d.ts.map +1 -0
  178. package/dist/shutdown.js +109 -0
  179. package/dist/testing/testing.module.d.ts +83 -0
  180. package/dist/testing/testing.module.d.ts.map +1 -0
  181. package/dist/testing/testing.module.js +164 -0
  182. package/dist/types.d.ts +76 -0
  183. package/dist/types.d.ts.map +1 -0
  184. package/dist/types.js +2 -0
  185. package/dist/upload/file-upload.d.ts +75 -0
  186. package/dist/upload/file-upload.d.ts.map +1 -0
  187. package/dist/upload/file-upload.js +261 -0
  188. package/dist/utils/sanitize.d.ts +45 -0
  189. package/dist/utils/sanitize.d.ts.map +1 -0
  190. package/dist/utils/sanitize.js +165 -0
  191. package/dist/validator.d.ts +7 -0
  192. package/dist/validator.d.ts.map +1 -0
  193. package/dist/validator.js +119 -0
  194. package/package.json +65 -0
@@ -0,0 +1,680 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const router_1 = require("../router");
4
+ const container_1 = require("../container");
5
+ const pipe_1 = require("../pipes/pipe");
6
+ const http_error_1 = require("../errors/http.error");
7
+ require("reflect-metadata");
8
+ // Mock logger
9
+ jest.mock('../logger', () => ({
10
+ info: jest.fn(),
11
+ debug: jest.fn(),
12
+ warn: jest.fn(),
13
+ error: jest.fn(),
14
+ isDebugEnabled: jest.fn().mockReturnValue(false),
15
+ }));
16
+ // Mock RequestParser
17
+ jest.mock('../request-parser', () => ({
18
+ RequestParser: {
19
+ parseRequest: jest.fn().mockResolvedValue({
20
+ params: {},
21
+ query: {},
22
+ body: {},
23
+ headers: {},
24
+ method: 'GET',
25
+ url: '/',
26
+ }),
27
+ },
28
+ }));
29
+ describe('Router', () => {
30
+ let router;
31
+ let container;
32
+ let mockReq;
33
+ let mockRes;
34
+ beforeEach(() => {
35
+ container = container_1.Container.createTestInstance();
36
+ router = new router_1.Router(container);
37
+ mockReq = {
38
+ method: 'GET',
39
+ url: '/test',
40
+ headers: {},
41
+ params: {},
42
+ query: {},
43
+ body: {},
44
+ };
45
+ mockRes = {
46
+ setHeader: jest.fn(),
47
+ status: jest.fn().mockReturnThis(),
48
+ json: jest.fn(),
49
+ send: jest.fn(),
50
+ end: jest.fn(),
51
+ };
52
+ });
53
+ describe('normalizePath', () => {
54
+ it('should normalize paths correctly', () => {
55
+ // Access private method via any
56
+ const normalize = router.normalizePath.bind(router);
57
+ expect(normalize('/test')).toBe('/test');
58
+ expect(normalize('test')).toBe('/test');
59
+ expect(normalize('/test/')).toBe('/test');
60
+ expect(normalize('/')).toBe('/');
61
+ });
62
+ });
63
+ describe('matchPath', () => {
64
+ it('should match exact paths', () => {
65
+ const matchPath = router.matchPath.bind(router);
66
+ expect(matchPath('/users', '/users')).toBe(true);
67
+ expect(matchPath('/users', '/posts')).toBe(false);
68
+ });
69
+ it('should match paths with parameters', () => {
70
+ const matchPath = router.matchPath.bind(router);
71
+ expect(matchPath('/users/123', '/users/:id')).toBe(true);
72
+ expect(matchPath('/users/123/posts/456', '/users/:userId/posts/:postId')).toBe(true);
73
+ });
74
+ it('should not match paths with different lengths', () => {
75
+ const matchPath = router.matchPath.bind(router);
76
+ expect(matchPath('/users/123', '/users')).toBe(false);
77
+ expect(matchPath('/users', '/users/123')).toBe(false);
78
+ });
79
+ });
80
+ describe('extractParams', () => {
81
+ it('should extract route parameters', () => {
82
+ const extractParams = router.extractParams.bind(router);
83
+ const params = extractParams('/users/123', '/users/:id');
84
+ expect(params).toEqual({ id: '123' });
85
+ });
86
+ it('should extract multiple parameters', () => {
87
+ const extractParams = router.extractParams.bind(router);
88
+ const params = extractParams('/users/123/posts/456', '/users/:userId/posts/:postId');
89
+ expect(params).toEqual({ userId: '123', postId: '456' });
90
+ });
91
+ it('should return empty object for paths without parameters', () => {
92
+ const extractParams = router.extractParams.bind(router);
93
+ const params = extractParams('/users', '/users');
94
+ expect(params).toEqual({});
95
+ });
96
+ });
97
+ describe('createRoutePattern', () => {
98
+ it('should create pattern for root path', () => {
99
+ const createPattern = router.createRoutePattern.bind(router);
100
+ const pattern = createPattern('/');
101
+ expect(pattern.test('/')).toBe(true);
102
+ expect(pattern.test('')).toBe(true);
103
+ });
104
+ it('should create pattern for static paths', () => {
105
+ const createPattern = router.createRoutePattern.bind(router);
106
+ const pattern = createPattern('/users');
107
+ expect(pattern.test('/users')).toBe(true);
108
+ expect(pattern.test('/users/')).toBe(true);
109
+ expect(pattern.test('/posts')).toBe(false);
110
+ });
111
+ it('should create pattern for parameterized paths', () => {
112
+ const createPattern = router.createRoutePattern.bind(router);
113
+ const pattern = createPattern('/users/:id');
114
+ expect(pattern.test('/users/123')).toBe(true);
115
+ expect(pattern.test('/users/abc')).toBe(true);
116
+ expect(pattern.test('/users')).toBe(false);
117
+ });
118
+ });
119
+ describe('addRoute methods', () => {
120
+ it('should add GET route', () => {
121
+ const handler = jest.fn();
122
+ router.get('/test', [handler]);
123
+ const routes = router.routes;
124
+ expect(routes.has('GET /test')).toBe(true);
125
+ });
126
+ it('should add POST route', () => {
127
+ const handler = jest.fn();
128
+ router.post('/test', [handler]);
129
+ const routes = router.routes;
130
+ expect(routes.has('POST /test')).toBe(true);
131
+ });
132
+ it('should add PUT route', () => {
133
+ const handler = jest.fn();
134
+ router.put('/test', [handler]);
135
+ const routes = router.routes;
136
+ expect(routes.has('PUT /test')).toBe(true);
137
+ });
138
+ it('should add DELETE route', () => {
139
+ const handler = jest.fn();
140
+ router.delete('/test', [handler]);
141
+ const routes = router.routes;
142
+ expect(routes.has('DELETE /test')).toBe(true);
143
+ });
144
+ });
145
+ describe('match', () => {
146
+ beforeEach(() => {
147
+ const handler = jest.fn();
148
+ router.get('/users/:id', [handler]);
149
+ // Also add to method-specific cache
150
+ const methodRoutes = router.routesByMethod.get('GET');
151
+ if (methodRoutes) {
152
+ methodRoutes.set('/users/:id', [handler]);
153
+ }
154
+ });
155
+ it('should match registered route', async () => {
156
+ const context = {
157
+ params: {},
158
+ query: {},
159
+ body: {},
160
+ headers: {},
161
+ method: 'GET',
162
+ url: '/users/123',
163
+ };
164
+ const match = await router.match('GET', '/users/123', context);
165
+ expect(match).not.toBeNull();
166
+ expect(match?.context.params).toEqual({ id: '123' });
167
+ });
168
+ it('should return null for unmatched route', async () => {
169
+ const context = {
170
+ params: {},
171
+ query: {},
172
+ body: {},
173
+ headers: {},
174
+ method: 'GET',
175
+ url: '/posts/123',
176
+ };
177
+ const match = await router.match('GET', '/posts/123', context);
178
+ expect(match).toBeNull();
179
+ });
180
+ it('should return null for wrong method', async () => {
181
+ const context = {
182
+ params: {},
183
+ query: {},
184
+ body: {},
185
+ headers: {},
186
+ method: 'POST',
187
+ url: '/users/123',
188
+ };
189
+ const match = await router.match('POST', '/users/123', context);
190
+ expect(match).toBeNull();
191
+ });
192
+ it('should handle query strings in URL', async () => {
193
+ const context = {
194
+ params: {},
195
+ query: { filter: 'active' },
196
+ body: {},
197
+ headers: {},
198
+ method: 'GET',
199
+ url: '/users/123?filter=active',
200
+ };
201
+ const match = await router.match('GET', '/users/123?filter=active', context);
202
+ expect(match).not.toBeNull();
203
+ expect(match?.context.params).toEqual({ id: '123' });
204
+ });
205
+ });
206
+ describe('handleRequest', () => {
207
+ it('should return 404 for unmatched routes', async () => {
208
+ mockReq.method = 'GET';
209
+ mockReq.url = '/nonexistent';
210
+ await router.handleRequest(mockReq, mockRes);
211
+ expect(mockRes.status).toHaveBeenCalledWith(404);
212
+ expect(mockRes.json).toHaveBeenCalledWith({ error: 'Not Found' });
213
+ });
214
+ it('should handle errors gracefully', async () => {
215
+ // Mock match to throw error
216
+ jest.spyOn(router, 'match').mockRejectedValue(new Error('Test error'));
217
+ mockReq.method = 'GET';
218
+ mockReq.url = '/test';
219
+ await router.handleRequest(mockReq, mockRes);
220
+ expect(mockRes.status).toHaveBeenCalledWith(500);
221
+ expect(mockRes.json).toHaveBeenCalledWith({ error: 'Test error' });
222
+ });
223
+ });
224
+ describe('registerController', () => {
225
+ it('should register controller with routes', () => {
226
+ class TestController {
227
+ getTest() {
228
+ return { message: 'test' };
229
+ }
230
+ }
231
+ // Set up metadata
232
+ Reflect.defineMetadata('hazel:controller', { path: '/api' }, TestController);
233
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/test', propertyKey: 'getTest' }], TestController);
234
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'getTest');
235
+ router.registerController(TestController);
236
+ const routes = router.routes;
237
+ expect(routes.has('GET /api/test')).toBe(true);
238
+ });
239
+ it('should handle controller without base path', () => {
240
+ class TestController {
241
+ getRoot() {
242
+ return { message: 'root' };
243
+ }
244
+ }
245
+ Reflect.defineMetadata('hazel:controller', {}, TestController);
246
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/', propertyKey: 'getRoot' }], TestController);
247
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'getRoot');
248
+ router.registerController(TestController);
249
+ const routes = router.routes;
250
+ expect(routes.has('GET /')).toBe(true);
251
+ });
252
+ it('should register multiple routes for same controller', () => {
253
+ class TestController {
254
+ getUsers() {
255
+ return [];
256
+ }
257
+ createUser() {
258
+ return { id: 1 };
259
+ }
260
+ }
261
+ Reflect.defineMetadata('hazel:controller', { path: '/users' }, TestController);
262
+ Reflect.defineMetadata('hazel:routes', [
263
+ { method: 'GET', path: '/', propertyKey: 'getUsers' },
264
+ { method: 'POST', path: '/', propertyKey: 'createUser' },
265
+ ], TestController);
266
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'getUsers');
267
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'createUser');
268
+ router.registerController(TestController);
269
+ const routes = router.routes;
270
+ expect(routes.has('GET /users')).toBe(true);
271
+ expect(routes.has('POST /users')).toBe(true);
272
+ });
273
+ });
274
+ describe('pipes and interceptors', () => {
275
+ it('should apply pipes to route parameters', async () => {
276
+ class TestPipe {
277
+ async transform(value) {
278
+ return `transformed-${value}`;
279
+ }
280
+ }
281
+ class TestController {
282
+ getUser() {
283
+ return { id: 1 };
284
+ }
285
+ }
286
+ container.registerProvider({ token: TestPipe, useClass: TestPipe });
287
+ Reflect.defineMetadata('hazel:controller', { path: '/users' }, TestController);
288
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/:id', propertyKey: 'getUser', pipes: [{ type: TestPipe }] }], TestController);
289
+ Reflect.defineMetadata('hazel:inject', ['param:id'], TestController, 'getUser');
290
+ router.registerController(TestController);
291
+ const context = { params: { id: '123' }, query: {}, body: {}, headers: {}, method: 'GET', url: '/users/123' };
292
+ const route = await router.match('GET', '/users/123', context);
293
+ expect(route).toBeDefined();
294
+ });
295
+ it('should apply interceptors to routes', async () => {
296
+ class TestInterceptor {
297
+ async intercept(context, next) {
298
+ const result = await next();
299
+ return { intercepted: true, result };
300
+ }
301
+ }
302
+ class TestController {
303
+ getUser() {
304
+ return { id: 1 };
305
+ }
306
+ }
307
+ container.registerProvider({ token: TestInterceptor, useClass: TestInterceptor });
308
+ Reflect.defineMetadata('hazel:controller', { path: '/users' }, TestController);
309
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/:id', propertyKey: 'getUser', interceptors: [{ type: TestInterceptor }] }], TestController);
310
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'getUser');
311
+ router.registerController(TestController);
312
+ const context = { params: { id: '123' }, query: {}, body: {}, headers: {}, method: 'GET', url: '/users/123' };
313
+ const route = await router.match('GET', '/users/123', context);
314
+ expect(route).toBeDefined();
315
+ });
316
+ });
317
+ describe('parameter injection', () => {
318
+ it('should inject body parameter', async () => {
319
+ class TestController {
320
+ createUser(body) {
321
+ return body;
322
+ }
323
+ }
324
+ Reflect.defineMetadata('hazel:controller', { path: '/users' }, TestController);
325
+ Reflect.defineMetadata('hazel:routes', [{ method: 'POST', path: '/', propertyKey: 'createUser' }], TestController);
326
+ Reflect.defineMetadata('hazel:inject', ['body'], TestController, 'createUser');
327
+ router.registerController(TestController);
328
+ const routes = router.routes;
329
+ expect(routes.has('POST /users')).toBe(true);
330
+ });
331
+ it('should inject query parameter', async () => {
332
+ class TestController {
333
+ searchUsers(query) {
334
+ return query;
335
+ }
336
+ }
337
+ Reflect.defineMetadata('hazel:controller', { path: '/users' }, TestController);
338
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/search', propertyKey: 'searchUsers' }], TestController);
339
+ Reflect.defineMetadata('hazel:inject', ['query'], TestController, 'searchUsers');
340
+ router.registerController(TestController);
341
+ const routes = router.routes;
342
+ expect(routes.has('GET /users/search')).toBe(true);
343
+ });
344
+ it('should inject specific param', async () => {
345
+ class TestController {
346
+ getUser(id) {
347
+ return { id };
348
+ }
349
+ }
350
+ Reflect.defineMetadata('hazel:controller', { path: '/users' }, TestController);
351
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/:id', propertyKey: 'getUser' }], TestController);
352
+ Reflect.defineMetadata('hazel:inject', ['param:id'], TestController, 'getUser');
353
+ router.registerController(TestController);
354
+ const routes = router.routes;
355
+ expect(routes.has('GET /users/:id')).toBe(true);
356
+ });
357
+ it('should inject request and response objects', async () => {
358
+ class TestController {
359
+ handleRequest(req, res) {
360
+ return { req, res };
361
+ }
362
+ }
363
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
364
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/', propertyKey: 'handleRequest' }], TestController);
365
+ Reflect.defineMetadata('hazel:inject', ['request', 'response'], TestController, 'handleRequest');
366
+ router.registerController(TestController);
367
+ const routes = router.routes;
368
+ expect(routes.has('GET /test')).toBe(true);
369
+ });
370
+ });
371
+ describe('error handling', () => {
372
+ it('should handle controller without routes metadata', () => {
373
+ class TestController {
374
+ }
375
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
376
+ router.registerController(TestController);
377
+ const routes = router.routes;
378
+ expect(routes.size).toBeGreaterThanOrEqual(0);
379
+ });
380
+ it('should handle route without injections', () => {
381
+ class TestController {
382
+ getTest() {
383
+ return 'test';
384
+ }
385
+ }
386
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
387
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/', propertyKey: 'getTest' }], TestController);
388
+ router.registerController(TestController);
389
+ const routes = router.routes;
390
+ expect(routes.has('GET /test')).toBe(true);
391
+ });
392
+ });
393
+ describe('route normalization', () => {
394
+ it('should normalize paths with trailing slashes', () => {
395
+ class TestController {
396
+ getTest() {
397
+ return 'test';
398
+ }
399
+ }
400
+ Reflect.defineMetadata('hazel:controller', { path: '/test/' }, TestController);
401
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/', propertyKey: 'getTest' }], TestController);
402
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'getTest');
403
+ router.registerController(TestController);
404
+ const routes = router.routes;
405
+ // Path normalization may keep the trailing slash
406
+ expect(routes.has('GET /test') || routes.has('GET /test/')).toBe(true);
407
+ });
408
+ it('should handle empty controller path', () => {
409
+ class TestController {
410
+ getRoot() {
411
+ return 'root';
412
+ }
413
+ }
414
+ Reflect.defineMetadata('hazel:controller', { path: '' }, TestController);
415
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/test', propertyKey: 'getRoot' }], TestController);
416
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'getRoot');
417
+ router.registerController(TestController);
418
+ const routes = router.routes;
419
+ expect(routes.has('GET /test')).toBe(true);
420
+ });
421
+ });
422
+ describe('route handler execution', () => {
423
+ it('should execute route handler and return result', async () => {
424
+ class TestController {
425
+ getTest() {
426
+ return { message: 'test' };
427
+ }
428
+ }
429
+ container.register(TestController, new TestController());
430
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
431
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/', propertyKey: 'getTest' }], TestController);
432
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'getTest');
433
+ router.registerController(TestController);
434
+ const context = {
435
+ params: {},
436
+ query: {},
437
+ body: {},
438
+ headers: {},
439
+ method: 'GET',
440
+ url: '/test',
441
+ };
442
+ const route = await router.match('GET', '/test', context);
443
+ expect(route).toBeDefined();
444
+ if (route) {
445
+ await route.handler(mockReq, mockRes, context);
446
+ expect(mockRes.json).toHaveBeenCalledWith({ message: 'test' });
447
+ }
448
+ });
449
+ it('should handle route with body parameter', async () => {
450
+ class CreateDto {
451
+ }
452
+ class TestController {
453
+ create(body) {
454
+ return { created: true, data: body };
455
+ }
456
+ }
457
+ container.register(TestController, new TestController());
458
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
459
+ Reflect.defineMetadata('hazel:routes', [{ method: 'POST', path: '/', propertyKey: 'create' }], TestController);
460
+ Reflect.defineMetadata('hazel:inject', [{ type: 'body', dtoType: CreateDto }], TestController, 'create');
461
+ router.registerController(TestController);
462
+ const context = {
463
+ params: {},
464
+ query: {},
465
+ body: { name: 'test' },
466
+ headers: {},
467
+ method: 'POST',
468
+ url: '/test',
469
+ };
470
+ const route = await router.match('POST', '/test', context);
471
+ expect(route).toBeDefined();
472
+ if (route) {
473
+ await route.handler(mockReq, mockRes, context);
474
+ expect(mockRes.json).toHaveBeenCalled();
475
+ }
476
+ });
477
+ it('should handle route with param parameter', async () => {
478
+ class TestController {
479
+ getById(id) {
480
+ return { id };
481
+ }
482
+ }
483
+ container.register(TestController, new TestController());
484
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
485
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/:id', propertyKey: 'getById' }], TestController);
486
+ Reflect.defineMetadata('hazel:inject', [{ type: 'param', name: 'id' }], TestController, 'getById');
487
+ router.registerController(TestController);
488
+ const context = {
489
+ params: { id: '123' },
490
+ query: {},
491
+ body: {},
492
+ headers: {},
493
+ method: 'GET',
494
+ url: '/test/123',
495
+ };
496
+ const route = await router.match('GET', '/test/123', context);
497
+ expect(route).toBeDefined();
498
+ if (route) {
499
+ await route.handler(mockReq, mockRes, context);
500
+ expect(mockRes.json).toHaveBeenCalledWith({ id: '123' });
501
+ }
502
+ });
503
+ it('should handle route with response parameter', async () => {
504
+ class TestController {
505
+ customResponse(res) {
506
+ res.status(201).json({ custom: true });
507
+ }
508
+ }
509
+ container.register(TestController, new TestController());
510
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
511
+ Reflect.defineMetadata('hazel:routes', [{ method: 'POST', path: '/', propertyKey: 'customResponse' }], TestController);
512
+ Reflect.defineMetadata('hazel:inject', [{ type: 'response' }], TestController, 'customResponse');
513
+ router.registerController(TestController);
514
+ const context = {
515
+ params: {},
516
+ query: {},
517
+ body: {},
518
+ headers: {},
519
+ method: 'POST',
520
+ url: '/test',
521
+ };
522
+ const route = await router.match('POST', '/test', context);
523
+ expect(route).toBeDefined();
524
+ if (route) {
525
+ await route.handler(mockReq, mockRes, context);
526
+ expect(mockRes.status).toHaveBeenCalledWith(201);
527
+ }
528
+ });
529
+ it('should handle HTML response', async () => {
530
+ class TestController {
531
+ getHtml() {
532
+ return '<!DOCTYPE html><html><body>Test</body></html>';
533
+ }
534
+ }
535
+ container.register(TestController, new TestController());
536
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
537
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/html', propertyKey: 'getHtml' }], TestController);
538
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'getHtml');
539
+ router.registerController(TestController);
540
+ const context = {
541
+ params: {},
542
+ query: {},
543
+ body: {},
544
+ headers: {},
545
+ method: 'GET',
546
+ url: '/test/html',
547
+ };
548
+ const route = await router.match('GET', '/test/html', context);
549
+ expect(route).toBeDefined();
550
+ if (route) {
551
+ await route.handler(mockReq, mockRes, context);
552
+ expect(mockRes.setHeader).toHaveBeenCalledWith('Content-Type', 'text/html');
553
+ expect(mockRes.send).toHaveBeenCalled();
554
+ }
555
+ });
556
+ it('should handle route returning undefined', async () => {
557
+ class TestController {
558
+ noReturn() {
559
+ // Returns undefined
560
+ }
561
+ }
562
+ container.register(TestController, new TestController());
563
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
564
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/no-return', propertyKey: 'noReturn' }], TestController);
565
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'noReturn');
566
+ router.registerController(TestController);
567
+ const context = {
568
+ params: {},
569
+ query: {},
570
+ body: {},
571
+ headers: {},
572
+ method: 'GET',
573
+ url: '/test/no-return',
574
+ };
575
+ const route = await router.match('GET', '/test/no-return', context);
576
+ expect(route).toBeDefined();
577
+ if (route) {
578
+ await route.handler(mockReq, mockRes, context);
579
+ // Should not call json or send when result is undefined
580
+ expect(mockRes.json).not.toHaveBeenCalled();
581
+ }
582
+ });
583
+ it('should handle ValidationError', async () => {
584
+ class TestController {
585
+ throwValidation() {
586
+ throw new pipe_1.ValidationError('Validation failed', [
587
+ {
588
+ property: 'name',
589
+ constraints: { required: 'Required' },
590
+ value: undefined
591
+ }
592
+ ]);
593
+ }
594
+ }
595
+ container.register(TestController, new TestController());
596
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
597
+ Reflect.defineMetadata('hazel:routes', [{ method: 'POST', path: '/validate', propertyKey: 'throwValidation' }], TestController);
598
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'throwValidation');
599
+ router.registerController(TestController);
600
+ const context = {
601
+ params: {},
602
+ query: {},
603
+ body: {},
604
+ headers: {},
605
+ method: 'POST',
606
+ url: '/test/validate',
607
+ };
608
+ const route = await router.match('POST', '/test/validate', context);
609
+ expect(route).toBeDefined();
610
+ if (route) {
611
+ await route.handler(mockReq, mockRes, context);
612
+ expect(mockRes.status).toHaveBeenCalledWith(400);
613
+ expect(mockRes.json).toHaveBeenCalledWith(expect.objectContaining({
614
+ statusCode: 400,
615
+ message: 'Validation failed',
616
+ }));
617
+ }
618
+ });
619
+ it('should handle HttpError', async () => {
620
+ class TestController {
621
+ throwHttp() {
622
+ throw new http_error_1.HttpError(404, 'Not found');
623
+ }
624
+ }
625
+ container.register(TestController, new TestController());
626
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
627
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/error', propertyKey: 'throwHttp' }], TestController);
628
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'throwHttp');
629
+ router.registerController(TestController);
630
+ const context = {
631
+ params: {},
632
+ query: {},
633
+ body: {},
634
+ headers: {},
635
+ method: 'GET',
636
+ url: '/test/error',
637
+ };
638
+ const route = await router.match('GET', '/test/error', context);
639
+ expect(route).toBeDefined();
640
+ if (route) {
641
+ await route.handler(mockReq, mockRes, context);
642
+ expect(mockRes.status).toHaveBeenCalledWith(404);
643
+ expect(mockRes.json).toHaveBeenCalledWith({
644
+ statusCode: 404,
645
+ message: 'Not found',
646
+ });
647
+ }
648
+ });
649
+ it('should handle generic errors', async () => {
650
+ class TestController {
651
+ throwGeneric() {
652
+ throw new Error('Generic error');
653
+ }
654
+ }
655
+ container.register(TestController, new TestController());
656
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
657
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/generic', propertyKey: 'throwGeneric' }], TestController);
658
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'throwGeneric');
659
+ router.registerController(TestController);
660
+ const context = {
661
+ params: {},
662
+ query: {},
663
+ body: {},
664
+ headers: {},
665
+ method: 'GET',
666
+ url: '/test/generic',
667
+ };
668
+ const route = await router.match('GET', '/test/generic', context);
669
+ expect(route).toBeDefined();
670
+ if (route) {
671
+ await route.handler(mockReq, mockRes, context);
672
+ expect(mockRes.status).toHaveBeenCalledWith(500);
673
+ expect(mockRes.json).toHaveBeenCalledWith(expect.objectContaining({
674
+ statusCode: 500,
675
+ message: 'Generic error',
676
+ }));
677
+ }
678
+ });
679
+ });
680
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=route-matcher.test.d.ts.map