@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.
- package/README.md +522 -0
- package/dist/__tests__/container.test.d.ts +2 -0
- package/dist/__tests__/container.test.d.ts.map +1 -0
- package/dist/__tests__/container.test.js +454 -0
- package/dist/__tests__/decorators.test.d.ts +2 -0
- package/dist/__tests__/decorators.test.d.ts.map +1 -0
- package/dist/__tests__/decorators.test.js +693 -0
- package/dist/__tests__/errors/http.error.test.d.ts +2 -0
- package/dist/__tests__/errors/http.error.test.d.ts.map +1 -0
- package/dist/__tests__/errors/http.error.test.js +117 -0
- package/dist/__tests__/filters/exception-filter.test.d.ts +2 -0
- package/dist/__tests__/filters/exception-filter.test.d.ts.map +1 -0
- package/dist/__tests__/filters/exception-filter.test.js +135 -0
- package/dist/__tests__/filters/http-exception.filter.test.d.ts +2 -0
- package/dist/__tests__/filters/http-exception.filter.test.d.ts.map +1 -0
- package/dist/__tests__/filters/http-exception.filter.test.js +119 -0
- package/dist/__tests__/hazel-app.test.d.ts +2 -0
- package/dist/__tests__/hazel-app.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-app.test.js +682 -0
- package/dist/__tests__/hazel-module.test.d.ts +2 -0
- package/dist/__tests__/hazel-module.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-module.test.js +408 -0
- package/dist/__tests__/hazel-response.test.d.ts +2 -0
- package/dist/__tests__/hazel-response.test.d.ts.map +1 -0
- package/dist/__tests__/hazel-response.test.js +138 -0
- package/dist/__tests__/health.test.d.ts +2 -0
- package/dist/__tests__/health.test.d.ts.map +1 -0
- package/dist/__tests__/health.test.js +147 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.js +239 -0
- package/dist/__tests__/interceptors/interceptor.test.d.ts +2 -0
- package/dist/__tests__/interceptors/interceptor.test.d.ts.map +1 -0
- package/dist/__tests__/interceptors/interceptor.test.js +166 -0
- package/dist/__tests__/logger.test.d.ts +2 -0
- package/dist/__tests__/logger.test.d.ts.map +1 -0
- package/dist/__tests__/logger.test.js +141 -0
- package/dist/__tests__/middleware/cors.test.d.ts +2 -0
- package/dist/__tests__/middleware/cors.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/cors.test.js +129 -0
- package/dist/__tests__/middleware/csrf.test.d.ts +2 -0
- package/dist/__tests__/middleware/csrf.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/csrf.test.js +247 -0
- package/dist/__tests__/middleware/global-middleware.test.d.ts +2 -0
- package/dist/__tests__/middleware/global-middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/global-middleware.test.js +259 -0
- package/dist/__tests__/middleware/rate-limit.test.d.ts +2 -0
- package/dist/__tests__/middleware/rate-limit.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/rate-limit.test.js +264 -0
- package/dist/__tests__/middleware/security-headers.test.d.ts +2 -0
- package/dist/__tests__/middleware/security-headers.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/security-headers.test.js +229 -0
- package/dist/__tests__/middleware/timeout.test.d.ts +2 -0
- package/dist/__tests__/middleware/timeout.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/timeout.test.js +132 -0
- package/dist/__tests__/middleware.test.d.ts +2 -0
- package/dist/__tests__/middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware.test.js +180 -0
- package/dist/__tests__/pipes/pipe.test.d.ts +2 -0
- package/dist/__tests__/pipes/pipe.test.d.ts.map +1 -0
- package/dist/__tests__/pipes/pipe.test.js +245 -0
- package/dist/__tests__/pipes/validation.pipe.test.d.ts +2 -0
- package/dist/__tests__/pipes/validation.pipe.test.d.ts.map +1 -0
- package/dist/__tests__/pipes/validation.pipe.test.js +297 -0
- package/dist/__tests__/request-parser.test.d.ts +2 -0
- package/dist/__tests__/request-parser.test.d.ts.map +1 -0
- package/dist/__tests__/request-parser.test.js +182 -0
- package/dist/__tests__/router.test.d.ts +2 -0
- package/dist/__tests__/router.test.d.ts.map +1 -0
- package/dist/__tests__/router.test.js +680 -0
- package/dist/__tests__/routing/route-matcher.test.d.ts +2 -0
- package/dist/__tests__/routing/route-matcher.test.d.ts.map +1 -0
- package/dist/__tests__/routing/route-matcher.test.js +219 -0
- package/dist/__tests__/routing/version.decorator.test.d.ts +2 -0
- package/dist/__tests__/routing/version.decorator.test.d.ts.map +1 -0
- package/dist/__tests__/routing/version.decorator.test.js +298 -0
- package/dist/__tests__/service.test.d.ts +2 -0
- package/dist/__tests__/service.test.d.ts.map +1 -0
- package/dist/__tests__/service.test.js +121 -0
- package/dist/__tests__/shutdown.test.d.ts +2 -0
- package/dist/__tests__/shutdown.test.d.ts.map +1 -0
- package/dist/__tests__/shutdown.test.js +250 -0
- package/dist/__tests__/testing/testing.module.test.d.ts +2 -0
- package/dist/__tests__/testing/testing.module.test.d.ts.map +1 -0
- package/dist/__tests__/testing/testing.module.test.js +370 -0
- package/dist/__tests__/upload/file-upload.test.d.ts +2 -0
- package/dist/__tests__/upload/file-upload.test.d.ts.map +1 -0
- package/dist/__tests__/upload/file-upload.test.js +498 -0
- package/dist/__tests__/utils/sanitize.test.d.ts +2 -0
- package/dist/__tests__/utils/sanitize.test.d.ts.map +1 -0
- package/dist/__tests__/utils/sanitize.test.js +291 -0
- package/dist/__tests__/validator.test.d.ts +2 -0
- package/dist/__tests__/validator.test.d.ts.map +1 -0
- package/dist/__tests__/validator.test.js +300 -0
- package/dist/container.d.ts +80 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +271 -0
- package/dist/decorators.d.ts +92 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +343 -0
- package/dist/errors/http.error.d.ts +31 -0
- package/dist/errors/http.error.d.ts.map +1 -0
- package/dist/errors/http.error.js +62 -0
- package/dist/filters/exception-filter.d.ts +39 -0
- package/dist/filters/exception-filter.d.ts.map +1 -0
- package/dist/filters/exception-filter.js +38 -0
- package/dist/filters/http-exception.filter.d.ts +9 -0
- package/dist/filters/http-exception.filter.d.ts.map +1 -0
- package/dist/filters/http-exception.filter.js +42 -0
- package/dist/hazel-app.d.ts +78 -0
- package/dist/hazel-app.d.ts.map +1 -0
- package/dist/hazel-app.js +453 -0
- package/dist/hazel-module.d.ts +20 -0
- package/dist/hazel-module.d.ts.map +1 -0
- package/dist/hazel-module.js +109 -0
- package/dist/hazel-response.d.ts +20 -0
- package/dist/hazel-response.d.ts.map +1 -0
- package/dist/hazel-response.js +68 -0
- package/dist/health.d.ts +73 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +174 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +140 -0
- package/dist/interceptors/interceptor.d.ts +22 -0
- package/dist/interceptors/interceptor.d.ts.map +1 -0
- package/dist/interceptors/interceptor.js +46 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +238 -0
- package/dist/middleware/cors.middleware.d.ts +44 -0
- package/dist/middleware/cors.middleware.d.ts.map +1 -0
- package/dist/middleware/cors.middleware.js +118 -0
- package/dist/middleware/csrf.middleware.d.ts +82 -0
- package/dist/middleware/csrf.middleware.d.ts.map +1 -0
- package/dist/middleware/csrf.middleware.js +183 -0
- package/dist/middleware/global-middleware.d.ts +111 -0
- package/dist/middleware/global-middleware.d.ts.map +1 -0
- package/dist/middleware/global-middleware.js +179 -0
- package/dist/middleware/rate-limit.middleware.d.ts +73 -0
- package/dist/middleware/rate-limit.middleware.d.ts.map +1 -0
- package/dist/middleware/rate-limit.middleware.js +124 -0
- package/dist/middleware/security-headers.middleware.d.ts +76 -0
- package/dist/middleware/security-headers.middleware.d.ts.map +1 -0
- package/dist/middleware/security-headers.middleware.js +123 -0
- package/dist/middleware/timeout.middleware.d.ts +25 -0
- package/dist/middleware/timeout.middleware.d.ts.map +1 -0
- package/dist/middleware/timeout.middleware.js +74 -0
- package/dist/middleware.d.ts +13 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +47 -0
- package/dist/pipes/pipe.d.ts +50 -0
- package/dist/pipes/pipe.d.ts.map +1 -0
- package/dist/pipes/pipe.js +96 -0
- package/dist/pipes/validation.pipe.d.ts +6 -0
- package/dist/pipes/validation.pipe.d.ts.map +1 -0
- package/dist/pipes/validation.pipe.js +61 -0
- package/dist/request-context.d.ts +17 -0
- package/dist/request-context.d.ts.map +1 -0
- package/dist/request-context.js +2 -0
- package/dist/request-parser.d.ts +7 -0
- package/dist/request-parser.d.ts.map +1 -0
- package/dist/request-parser.js +60 -0
- package/dist/router.d.ts +33 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +426 -0
- package/dist/routing/route-matcher.d.ts +39 -0
- package/dist/routing/route-matcher.d.ts.map +1 -0
- package/dist/routing/route-matcher.js +93 -0
- package/dist/routing/version.decorator.d.ts +36 -0
- package/dist/routing/version.decorator.d.ts.map +1 -0
- package/dist/routing/version.decorator.js +89 -0
- package/dist/service.d.ts +9 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +39 -0
- package/dist/shutdown.d.ts +32 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +109 -0
- package/dist/testing/testing.module.d.ts +83 -0
- package/dist/testing/testing.module.d.ts.map +1 -0
- package/dist/testing/testing.module.js +164 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/upload/file-upload.d.ts +75 -0
- package/dist/upload/file-upload.d.ts.map +1 -0
- package/dist/upload/file-upload.js +261 -0
- package/dist/utils/sanitize.d.ts +45 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +165 -0
- package/dist/validator.d.ts +7 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +119 -0
- package/package.json +65 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
7
|
+
describe('Logger', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
// Suppress console output during tests
|
|
10
|
+
jest.spyOn(console, 'log').mockImplementation();
|
|
11
|
+
jest.spyOn(console, 'error').mockImplementation();
|
|
12
|
+
jest.spyOn(console, 'warn').mockImplementation();
|
|
13
|
+
});
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
jest.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
describe('basic logging', () => {
|
|
18
|
+
it('should log info messages', () => {
|
|
19
|
+
logger_1.default.info('Test info message');
|
|
20
|
+
expect(logger_1.default).toBeDefined();
|
|
21
|
+
});
|
|
22
|
+
it('should log error messages', () => {
|
|
23
|
+
logger_1.default.error('Test error message');
|
|
24
|
+
expect(logger_1.default).toBeDefined();
|
|
25
|
+
});
|
|
26
|
+
it('should log warn messages', () => {
|
|
27
|
+
logger_1.default.warn('Test warn message');
|
|
28
|
+
expect(logger_1.default).toBeDefined();
|
|
29
|
+
});
|
|
30
|
+
it('should log debug messages', () => {
|
|
31
|
+
logger_1.default.debug('Test debug message');
|
|
32
|
+
expect(logger_1.default).toBeDefined();
|
|
33
|
+
});
|
|
34
|
+
it('should log http messages', () => {
|
|
35
|
+
logger_1.default.http('Test http message');
|
|
36
|
+
expect(logger_1.default).toBeDefined();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe('structured logging', () => {
|
|
40
|
+
it('should log with metadata object', () => {
|
|
41
|
+
logger_1.default.info('Test message', { userId: 123, action: 'login' });
|
|
42
|
+
expect(logger_1.default).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
it('should log errors with stack traces', () => {
|
|
45
|
+
const error = new Error('Test error');
|
|
46
|
+
logger_1.default.error('Error occurred', { error: error.message, stack: error.stack });
|
|
47
|
+
expect(logger_1.default).toBeDefined();
|
|
48
|
+
});
|
|
49
|
+
it('should log with multiple metadata fields', () => {
|
|
50
|
+
logger_1.default.info('Request received', {
|
|
51
|
+
method: 'GET',
|
|
52
|
+
url: '/api/users',
|
|
53
|
+
statusCode: 200,
|
|
54
|
+
duration: 45,
|
|
55
|
+
});
|
|
56
|
+
expect(logger_1.default).toBeDefined();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('log levels', () => {
|
|
60
|
+
it('should support error level', () => {
|
|
61
|
+
logger_1.default.error('Critical error');
|
|
62
|
+
expect(logger_1.default).toBeDefined();
|
|
63
|
+
});
|
|
64
|
+
it('should support warn level', () => {
|
|
65
|
+
logger_1.default.warn('Warning message');
|
|
66
|
+
expect(logger_1.default).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
it('should support info level', () => {
|
|
69
|
+
logger_1.default.info('Info message');
|
|
70
|
+
expect(logger_1.default).toBeDefined();
|
|
71
|
+
});
|
|
72
|
+
it('should support debug level', () => {
|
|
73
|
+
logger_1.default.debug('Debug message');
|
|
74
|
+
expect(logger_1.default).toBeDefined();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe('logger methods', () => {
|
|
78
|
+
it('should have all required logging methods', () => {
|
|
79
|
+
expect(typeof logger_1.default.info).toBe('function');
|
|
80
|
+
expect(typeof logger_1.default.error).toBe('function');
|
|
81
|
+
expect(typeof logger_1.default.warn).toBe('function');
|
|
82
|
+
expect(typeof logger_1.default.debug).toBe('function');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe('various message formats', () => {
|
|
86
|
+
it('should log string messages', () => {
|
|
87
|
+
logger_1.default.info('Simple string message');
|
|
88
|
+
expect(logger_1.default).toBeDefined();
|
|
89
|
+
});
|
|
90
|
+
it('should log with empty metadata', () => {
|
|
91
|
+
logger_1.default.info('Message', {});
|
|
92
|
+
expect(logger_1.default).toBeDefined();
|
|
93
|
+
});
|
|
94
|
+
it('should log with nested objects', () => {
|
|
95
|
+
logger_1.default.info('Complex data', {
|
|
96
|
+
user: {
|
|
97
|
+
id: 1,
|
|
98
|
+
name: 'Test User',
|
|
99
|
+
roles: ['admin', 'user'],
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
expect(logger_1.default).toBeDefined();
|
|
103
|
+
});
|
|
104
|
+
it('should log with arrays', () => {
|
|
105
|
+
logger_1.default.info('Array data', {
|
|
106
|
+
items: [1, 2, 3, 4, 5],
|
|
107
|
+
});
|
|
108
|
+
expect(logger_1.default).toBeDefined();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe('error scenarios', () => {
|
|
112
|
+
it('should handle logging errors gracefully', () => {
|
|
113
|
+
const circularObj = { name: 'test' };
|
|
114
|
+
circularObj.self = circularObj;
|
|
115
|
+
// Should not throw even with circular reference
|
|
116
|
+
expect(() => {
|
|
117
|
+
logger_1.default.info('Circular object', { data: 'safe' });
|
|
118
|
+
}).not.toThrow();
|
|
119
|
+
});
|
|
120
|
+
it('should handle undefined metadata', () => {
|
|
121
|
+
logger_1.default.info('Message', undefined);
|
|
122
|
+
expect(logger_1.default).toBeDefined();
|
|
123
|
+
});
|
|
124
|
+
it('should handle null metadata', () => {
|
|
125
|
+
logger_1.default.info('Message', null);
|
|
126
|
+
expect(logger_1.default).toBeDefined();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe('logger instance', () => {
|
|
130
|
+
it('should be defined', () => {
|
|
131
|
+
expect(logger_1.default).toBeDefined();
|
|
132
|
+
});
|
|
133
|
+
it('should have all required methods', () => {
|
|
134
|
+
expect(typeof logger_1.default.info).toBe('function');
|
|
135
|
+
expect(typeof logger_1.default.error).toBe('function');
|
|
136
|
+
expect(typeof logger_1.default.warn).toBe('function');
|
|
137
|
+
expect(typeof logger_1.default.debug).toBe('function');
|
|
138
|
+
expect(typeof logger_1.default.http).toBe('function');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cors.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/middleware/cors.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const cors_middleware_1 = require("../../middleware/cors.middleware");
|
|
4
|
+
describe('CorsMiddleware', () => {
|
|
5
|
+
let mockReq;
|
|
6
|
+
let mockRes;
|
|
7
|
+
let nextFn;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
mockReq = {
|
|
10
|
+
method: 'GET',
|
|
11
|
+
headers: {
|
|
12
|
+
origin: 'https://example.com',
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
mockRes = {
|
|
16
|
+
setHeader: jest.fn(),
|
|
17
|
+
status: jest.fn().mockReturnThis(),
|
|
18
|
+
end: jest.fn(),
|
|
19
|
+
};
|
|
20
|
+
nextFn = jest.fn();
|
|
21
|
+
});
|
|
22
|
+
describe('wildcard origin', () => {
|
|
23
|
+
it('should allow all origins with *', () => {
|
|
24
|
+
const middleware = new cors_middleware_1.CorsMiddleware({ origin: '*' });
|
|
25
|
+
middleware.handle(mockReq, mockRes, nextFn);
|
|
26
|
+
expect(mockRes.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Origin', '*');
|
|
27
|
+
expect(nextFn).toHaveBeenCalled();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('specific origin', () => {
|
|
31
|
+
it('should allow specific origin', () => {
|
|
32
|
+
const middleware = new cors_middleware_1.CorsMiddleware({ origin: 'https://example.com' });
|
|
33
|
+
middleware.handle(mockReq, mockRes, nextFn);
|
|
34
|
+
expect(mockRes.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Origin', 'https://example.com');
|
|
35
|
+
expect(mockRes.setHeader).toHaveBeenCalledWith('Vary', 'Origin');
|
|
36
|
+
expect(nextFn).toHaveBeenCalled();
|
|
37
|
+
});
|
|
38
|
+
it('should not set origin header for disallowed origin', () => {
|
|
39
|
+
const middleware = new cors_middleware_1.CorsMiddleware({ origin: 'https://allowed.com' });
|
|
40
|
+
middleware.handle(mockReq, mockRes, nextFn);
|
|
41
|
+
expect(mockRes.setHeader).not.toHaveBeenCalledWith('Access-Control-Allow-Origin', expect.anything());
|
|
42
|
+
expect(nextFn).toHaveBeenCalled();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe('array of origins', () => {
|
|
46
|
+
it('should allow origins in array', () => {
|
|
47
|
+
const middleware = new cors_middleware_1.CorsMiddleware({
|
|
48
|
+
origin: ['https://example.com', 'https://test.com'],
|
|
49
|
+
});
|
|
50
|
+
middleware.handle(mockReq, mockRes, nextFn);
|
|
51
|
+
expect(mockRes.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Origin', 'https://example.com');
|
|
52
|
+
expect(nextFn).toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('function origin', () => {
|
|
56
|
+
it('should use function to validate origin', () => {
|
|
57
|
+
const middleware = new cors_middleware_1.CorsMiddleware({
|
|
58
|
+
origin: (origin) => origin.endsWith('.example.com'),
|
|
59
|
+
});
|
|
60
|
+
mockReq.headers = { origin: 'https://app.example.com' };
|
|
61
|
+
middleware.handle(mockReq, mockRes, nextFn);
|
|
62
|
+
expect(mockRes.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Origin', 'https://app.example.com');
|
|
63
|
+
expect(nextFn).toHaveBeenCalled();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe('credentials', () => {
|
|
67
|
+
it('should set credentials header when enabled', () => {
|
|
68
|
+
const middleware = new cors_middleware_1.CorsMiddleware({ credentials: true });
|
|
69
|
+
middleware.handle(mockReq, mockRes, nextFn);
|
|
70
|
+
expect(mockRes.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Credentials', 'true');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe('exposed headers', () => {
|
|
74
|
+
it('should set exposed headers', () => {
|
|
75
|
+
const middleware = new cors_middleware_1.CorsMiddleware({
|
|
76
|
+
exposedHeaders: ['X-Custom-Header', 'X-Another-Header'],
|
|
77
|
+
});
|
|
78
|
+
middleware.handle(mockReq, mockRes, nextFn);
|
|
79
|
+
expect(mockRes.setHeader).toHaveBeenCalledWith('Access-Control-Expose-Headers', 'X-Custom-Header, X-Another-Header');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe('preflight requests', () => {
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
mockReq.method = 'OPTIONS';
|
|
85
|
+
});
|
|
86
|
+
it('should handle preflight request', () => {
|
|
87
|
+
const middleware = new cors_middleware_1.CorsMiddleware();
|
|
88
|
+
middleware.handle(mockReq, mockRes, nextFn);
|
|
89
|
+
expect(mockRes.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Methods', expect.any(String));
|
|
90
|
+
expect(mockRes.status).toHaveBeenCalledWith(204);
|
|
91
|
+
expect(mockRes.end).toHaveBeenCalled();
|
|
92
|
+
expect(nextFn).not.toHaveBeenCalled();
|
|
93
|
+
});
|
|
94
|
+
it('should set allowed methods', () => {
|
|
95
|
+
const middleware = new cors_middleware_1.CorsMiddleware({
|
|
96
|
+
methods: ['GET', 'POST'],
|
|
97
|
+
});
|
|
98
|
+
middleware.handle(mockReq, mockRes, nextFn);
|
|
99
|
+
expect(mockRes.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Methods', 'GET, POST');
|
|
100
|
+
});
|
|
101
|
+
it('should set allowed headers from request', () => {
|
|
102
|
+
mockReq.headers = {
|
|
103
|
+
...mockReq.headers,
|
|
104
|
+
'access-control-request-headers': 'Content-Type, Authorization',
|
|
105
|
+
};
|
|
106
|
+
const middleware = new cors_middleware_1.CorsMiddleware();
|
|
107
|
+
middleware.handle(mockReq, mockRes, nextFn);
|
|
108
|
+
expect(mockRes.setHeader).toHaveBeenCalledWith('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
109
|
+
});
|
|
110
|
+
it('should set max age', () => {
|
|
111
|
+
const middleware = new cors_middleware_1.CorsMiddleware({ maxAge: 3600 });
|
|
112
|
+
middleware.handle(mockReq, mockRes, nextFn);
|
|
113
|
+
expect(mockRes.setHeader).toHaveBeenCalledWith('Access-Control-Max-Age', '3600');
|
|
114
|
+
});
|
|
115
|
+
it('should continue if preflightContinue is true', () => {
|
|
116
|
+
const middleware = new cors_middleware_1.CorsMiddleware({ preflightContinue: true });
|
|
117
|
+
middleware.handle(mockReq, mockRes, nextFn);
|
|
118
|
+
expect(nextFn).toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('static create method', () => {
|
|
122
|
+
it('should create middleware function', () => {
|
|
123
|
+
const middlewareFn = cors_middleware_1.CorsMiddleware.create({ origin: '*' });
|
|
124
|
+
expect(typeof middlewareFn).toBe('function');
|
|
125
|
+
middlewareFn(mockReq, mockRes, nextFn);
|
|
126
|
+
expect(nextFn).toHaveBeenCalled();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csrf.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/middleware/csrf.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const csrf_middleware_1 = require("../../middleware/csrf.middleware");
|
|
4
|
+
const http_error_1 = require("../../errors/http.error");
|
|
5
|
+
// Mock logger
|
|
6
|
+
jest.mock('../../logger', () => ({
|
|
7
|
+
warn: jest.fn(),
|
|
8
|
+
error: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
describe('CsrfMiddleware', () => {
|
|
11
|
+
let mockReq;
|
|
12
|
+
let mockRes;
|
|
13
|
+
let nextFn;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.useFakeTimers();
|
|
16
|
+
mockReq = {
|
|
17
|
+
method: 'GET',
|
|
18
|
+
url: '/test',
|
|
19
|
+
headers: {},
|
|
20
|
+
};
|
|
21
|
+
mockReq.socket = { remoteAddress: '127.0.0.1' };
|
|
22
|
+
mockRes = {
|
|
23
|
+
setHeader: jest.fn(),
|
|
24
|
+
status: jest.fn().mockReturnThis(),
|
|
25
|
+
json: jest.fn(),
|
|
26
|
+
};
|
|
27
|
+
nextFn = jest.fn();
|
|
28
|
+
});
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
jest.useRealTimers();
|
|
31
|
+
});
|
|
32
|
+
describe('constructor', () => {
|
|
33
|
+
it('should create middleware with default options', () => {
|
|
34
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware();
|
|
35
|
+
expect(middleware).toBeDefined();
|
|
36
|
+
});
|
|
37
|
+
it('should accept custom options', () => {
|
|
38
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware({
|
|
39
|
+
cookieName: 'custom-csrf',
|
|
40
|
+
headerName: 'x-custom-csrf',
|
|
41
|
+
secret: 'test-secret',
|
|
42
|
+
});
|
|
43
|
+
expect(middleware).toBeDefined();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe('use - GET requests', () => {
|
|
47
|
+
it('should allow GET requests without token', () => {
|
|
48
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware();
|
|
49
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
50
|
+
expect(nextFn).toHaveBeenCalled();
|
|
51
|
+
expect(mockRes.setHeader).toHaveBeenCalledWith('X-CSRF-Token', expect.any(String));
|
|
52
|
+
});
|
|
53
|
+
it('should set CSRF cookie', () => {
|
|
54
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware();
|
|
55
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
56
|
+
expect(mockRes.setHeader).toHaveBeenCalledWith('Set-Cookie', expect.stringContaining('_csrf='));
|
|
57
|
+
});
|
|
58
|
+
it('should generate token with signature', () => {
|
|
59
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware();
|
|
60
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
61
|
+
const setHeaderCalls = mockRes.setHeader.mock.calls;
|
|
62
|
+
const csrfTokenCall = setHeaderCalls.find((call) => call[0] === 'X-CSRF-Token');
|
|
63
|
+
const token = csrfTokenCall[1];
|
|
64
|
+
expect(token).toContain('.');
|
|
65
|
+
expect(token.split('.').length).toBe(2);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe('use - POST requests', () => {
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
mockReq.method = 'POST';
|
|
71
|
+
});
|
|
72
|
+
it('should reject POST without token', () => {
|
|
73
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware();
|
|
74
|
+
expect(() => {
|
|
75
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
76
|
+
}).toThrow(http_error_1.HttpError);
|
|
77
|
+
});
|
|
78
|
+
it('should accept POST with valid token in header', () => {
|
|
79
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware();
|
|
80
|
+
// First request to get token
|
|
81
|
+
mockReq.method = 'GET';
|
|
82
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
83
|
+
const setHeaderCalls = mockRes.setHeader.mock.calls;
|
|
84
|
+
const csrfTokenCall = setHeaderCalls.find((call) => call[0] === 'X-CSRF-Token');
|
|
85
|
+
const token = csrfTokenCall[1];
|
|
86
|
+
// Second request with token
|
|
87
|
+
jest.clearAllMocks();
|
|
88
|
+
mockReq.method = 'POST';
|
|
89
|
+
mockReq.headers = {
|
|
90
|
+
'x-csrf-token': token,
|
|
91
|
+
};
|
|
92
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
93
|
+
expect(nextFn).toHaveBeenCalled();
|
|
94
|
+
});
|
|
95
|
+
it('should accept POST with valid token in body', () => {
|
|
96
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware();
|
|
97
|
+
// Get token
|
|
98
|
+
mockReq.method = 'GET';
|
|
99
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
100
|
+
const setHeaderCalls = mockRes.setHeader.mock.calls;
|
|
101
|
+
const csrfTokenCall = setHeaderCalls.find((call) => call[0] === 'X-CSRF-Token');
|
|
102
|
+
const token = csrfTokenCall[1];
|
|
103
|
+
// POST with token in body
|
|
104
|
+
jest.clearAllMocks();
|
|
105
|
+
mockReq.method = 'POST';
|
|
106
|
+
mockReq.body = { _csrf: token };
|
|
107
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
108
|
+
expect(nextFn).toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
it('should accept POST with valid token in query', () => {
|
|
111
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware();
|
|
112
|
+
// Get token
|
|
113
|
+
mockReq.method = 'GET';
|
|
114
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
115
|
+
const setHeaderCalls = mockRes.setHeader.mock.calls;
|
|
116
|
+
const csrfTokenCall = setHeaderCalls.find((call) => call[0] === 'X-CSRF-Token');
|
|
117
|
+
const token = csrfTokenCall[1];
|
|
118
|
+
// POST with token in query
|
|
119
|
+
jest.clearAllMocks();
|
|
120
|
+
mockReq.method = 'POST';
|
|
121
|
+
mockReq.query = { _csrf: token };
|
|
122
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
123
|
+
expect(nextFn).toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
it('should reject POST with invalid token', () => {
|
|
126
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware();
|
|
127
|
+
mockReq.headers = {
|
|
128
|
+
'x-csrf-token': 'invalid-token',
|
|
129
|
+
};
|
|
130
|
+
expect(() => {
|
|
131
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
132
|
+
}).toThrow(http_error_1.HttpError);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe('excluded paths', () => {
|
|
136
|
+
it('should skip CSRF check for excluded paths', () => {
|
|
137
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware({
|
|
138
|
+
excludePaths: ['/api/webhook'],
|
|
139
|
+
});
|
|
140
|
+
mockReq.method = 'POST';
|
|
141
|
+
mockReq.url = '/api/webhook';
|
|
142
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
143
|
+
expect(nextFn).toHaveBeenCalled();
|
|
144
|
+
});
|
|
145
|
+
it('should check CSRF for non-excluded paths', () => {
|
|
146
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware({
|
|
147
|
+
excludePaths: ['/api/webhook'],
|
|
148
|
+
});
|
|
149
|
+
mockReq.method = 'POST';
|
|
150
|
+
mockReq.url = '/api/users';
|
|
151
|
+
expect(() => {
|
|
152
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
153
|
+
}).toThrow(http_error_1.HttpError);
|
|
154
|
+
});
|
|
155
|
+
it('should handle path with query string', () => {
|
|
156
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware({
|
|
157
|
+
excludePaths: ['/api/webhook'],
|
|
158
|
+
});
|
|
159
|
+
mockReq.method = 'POST';
|
|
160
|
+
mockReq.url = '/api/webhook?param=value';
|
|
161
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
162
|
+
expect(nextFn).toHaveBeenCalled();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
describe('custom methods', () => {
|
|
166
|
+
it('should protect custom methods', () => {
|
|
167
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware({
|
|
168
|
+
methods: ['POST', 'DELETE', 'CUSTOM'],
|
|
169
|
+
});
|
|
170
|
+
mockReq.method = 'CUSTOM';
|
|
171
|
+
expect(() => {
|
|
172
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
173
|
+
}).toThrow(http_error_1.HttpError);
|
|
174
|
+
});
|
|
175
|
+
it('should not protect methods not in list', () => {
|
|
176
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware({
|
|
177
|
+
methods: ['POST'],
|
|
178
|
+
});
|
|
179
|
+
mockReq.method = 'PUT';
|
|
180
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
181
|
+
expect(nextFn).toHaveBeenCalled();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
describe('cookie options', () => {
|
|
185
|
+
it('should set cookie with custom options', () => {
|
|
186
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware({
|
|
187
|
+
cookieOptions: {
|
|
188
|
+
httpOnly: true,
|
|
189
|
+
secure: true,
|
|
190
|
+
sameSite: 'strict',
|
|
191
|
+
path: '/api',
|
|
192
|
+
maxAge: 7200,
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
196
|
+
const setHeaderCalls = mockRes.setHeader.mock.calls;
|
|
197
|
+
const cookieCall = setHeaderCalls.find((call) => call[0] === 'Set-Cookie');
|
|
198
|
+
const cookie = cookieCall[1];
|
|
199
|
+
expect(cookie).toContain('HttpOnly');
|
|
200
|
+
expect(cookie).toContain('Secure');
|
|
201
|
+
expect(cookie).toContain('SameSite=strict');
|
|
202
|
+
expect(cookie).toContain('Path=/api');
|
|
203
|
+
expect(cookie).toContain('Max-Age=7200');
|
|
204
|
+
});
|
|
205
|
+
it('should use custom cookie name', () => {
|
|
206
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware({
|
|
207
|
+
cookieName: 'custom-csrf-token',
|
|
208
|
+
});
|
|
209
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
210
|
+
const setHeaderCalls = mockRes.setHeader.mock.calls;
|
|
211
|
+
const cookieCall = setHeaderCalls.find((call) => call[0] === 'Set-Cookie');
|
|
212
|
+
const cookie = cookieCall[1];
|
|
213
|
+
expect(cookie).toContain('custom-csrf-token=');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
describe('session handling', () => {
|
|
217
|
+
it('should use session ID from cookie', () => {
|
|
218
|
+
mockReq.headers = {
|
|
219
|
+
cookie: 'sessionId=abc123; other=value',
|
|
220
|
+
};
|
|
221
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware();
|
|
222
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
223
|
+
expect(nextFn).toHaveBeenCalled();
|
|
224
|
+
});
|
|
225
|
+
it('should fallback to IP+UA hash when no session', () => {
|
|
226
|
+
mockReq.headers = {
|
|
227
|
+
'user-agent': 'Mozilla/5.0',
|
|
228
|
+
};
|
|
229
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware();
|
|
230
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
231
|
+
expect(nextFn).toHaveBeenCalled();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
describe('token reuse', () => {
|
|
235
|
+
it('should reuse token for same session', () => {
|
|
236
|
+
const middleware = new csrf_middleware_1.CsrfMiddleware();
|
|
237
|
+
// First request
|
|
238
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
239
|
+
const token1 = mockRes.setHeader.mock.calls.find((call) => call[0] === 'X-CSRF-Token')[1];
|
|
240
|
+
// Second request
|
|
241
|
+
jest.clearAllMocks();
|
|
242
|
+
middleware.use(mockReq, mockRes, nextFn);
|
|
243
|
+
const token2 = mockRes.setHeader.mock.calls.find((call) => call[0] === 'X-CSRF-Token')[1];
|
|
244
|
+
expect(token1).toBe(token2);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global-middleware.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/middleware/global-middleware.test.ts"],"names":[],"mappings":""}
|