saas-backend-kit 1.0.1 → 1.0.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.
- package/README.md +38 -6
- package/dist/auth/index.js +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/index.mjs +1 -1
- package/dist/auth/index.mjs.map +1 -1
- package/dist/index.js +49 -41
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +49 -41
- package/dist/index.mjs.map +1 -1
- package/dist/rate-limit/index.js +1 -0
- package/dist/rate-limit/index.js.map +1 -1
- package/dist/rate-limit/index.mjs +1 -0
- package/dist/rate-limit/index.mjs.map +1 -1
- package/dist/response/index.js +51 -40
- package/dist/response/index.js.map +1 -1
- package/dist/response/index.mjs +51 -40
- package/dist/response/index.mjs.map +1 -1
- package/jest-output.json +72 -0
- package/jest.config.js +19 -0
- package/package.json +9 -7
- package/src/auth/jwt.ts +1 -1
- package/src/rate-limit/express.ts +1 -0
- package/src/response/index.ts +49 -40
- package/tests/auth.test.ts +134 -0
- package/tests/config.test.ts +36 -0
- package/tests/logger.test.ts +47 -0
- package/tests/notifications.test.ts +19 -0
- package/tests/rate-limit.test.ts +50 -0
- package/tests/upload.test.ts +33 -0
- package/tsconfig.test.json +14 -0
package/jest.config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: 'ts-jest',
|
|
3
|
+
testEnvironment: 'node',
|
|
4
|
+
roots: ['<rootDir>/tests'],
|
|
5
|
+
testMatch: ['**/*.test.ts'],
|
|
6
|
+
collectCoverageFrom: [
|
|
7
|
+
'src/**/*.ts',
|
|
8
|
+
'!src/**/*.d.ts'
|
|
9
|
+
],
|
|
10
|
+
coverageDirectory: 'coverage',
|
|
11
|
+
verbose: true,
|
|
12
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
|
13
|
+
transform: {
|
|
14
|
+
'^.+\\.tsx?$': ['ts-jest', {
|
|
15
|
+
tsconfig: 'tsconfig.test.json',
|
|
16
|
+
diagnostics: false
|
|
17
|
+
}]
|
|
18
|
+
}
|
|
19
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saas-backend-kit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Production-grade modular backend toolkit for building scalable SaaS applications",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"scripts": {
|
|
56
56
|
"build": "tsup && node copy-dts.js",
|
|
57
57
|
"dev": "tsup --watch",
|
|
58
|
-
"test": "
|
|
59
|
-
"test:watch": "
|
|
58
|
+
"test": "jest",
|
|
59
|
+
"test:watch": "jest --watch",
|
|
60
60
|
"lint": "eslint src --ext .ts",
|
|
61
61
|
"typecheck": "tsc --noEmit",
|
|
62
62
|
"prepublishOnly": "npm run build"
|
|
@@ -84,10 +84,10 @@
|
|
|
84
84
|
"bcryptjs": "^2.4.3",
|
|
85
85
|
"bullmq": "^5.1.0",
|
|
86
86
|
"express": "^4.18.2",
|
|
87
|
-
"fastify": "^
|
|
87
|
+
"fastify": "^5.0.0",
|
|
88
88
|
"ioredis": "^5.3.2",
|
|
89
89
|
"jsonwebtoken": "^9.0.2",
|
|
90
|
-
"nodemailer": "^
|
|
90
|
+
"nodemailer": "^7.0.0",
|
|
91
91
|
"pino": "^8.17.2",
|
|
92
92
|
"pino-pretty": "^10.3.1",
|
|
93
93
|
"twilio": "^4.20.1",
|
|
@@ -96,13 +96,15 @@
|
|
|
96
96
|
"devDependencies": {
|
|
97
97
|
"@types/bcryptjs": "^2.4.6",
|
|
98
98
|
"@types/express": "^4.17.21",
|
|
99
|
+
"@types/jest": "^29.5.11",
|
|
99
100
|
"@types/jsonwebtoken": "^9.0.5",
|
|
100
101
|
"@types/node": "^20.10.6",
|
|
101
102
|
"@types/nodemailer": "^6.4.14",
|
|
102
103
|
"eslint": "^8.56.0",
|
|
104
|
+
"jest": "^29.7.0",
|
|
105
|
+
"ts-jest": "^29.1.1",
|
|
103
106
|
"tsup": "^8.0.1",
|
|
104
|
-
"typescript": "^5.3.3"
|
|
105
|
-
"vitest": "^1.1.3"
|
|
107
|
+
"typescript": "^5.3.3"
|
|
106
108
|
},
|
|
107
109
|
"peerDependencies": {
|
|
108
110
|
"express": "^4.0.0",
|
package/src/auth/jwt.ts
CHANGED
|
@@ -39,7 +39,7 @@ export class JWTService {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
refreshTokens(refreshToken: string): TokenPair {
|
|
42
|
-
const payload = this.verifyRefreshToken(refreshToken);
|
|
42
|
+
const { iat, exp, nbf, ...payload } = this.verifyRefreshToken(refreshToken) as JWTPayload & { iat?: number; exp?: number; nbf?: number };
|
|
43
43
|
return this.generateTokenPair(payload);
|
|
44
44
|
}
|
|
45
45
|
}
|
package/src/response/index.ts
CHANGED
|
@@ -13,7 +13,8 @@ export interface ApiResponse<T = unknown> {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export interface PaginatedResponse<T> extends ApiResponse<T> {
|
|
16
|
+
export interface PaginatedResponse<T> extends Omit<ApiResponse<T[]>, 'data'> {
|
|
17
|
+
data: T[];
|
|
17
18
|
meta: {
|
|
18
19
|
page: number;
|
|
19
20
|
limit: number;
|
|
@@ -141,57 +142,65 @@ declare global {
|
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
try {
|
|
146
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
147
|
+
const proto = require('express').response;
|
|
148
|
+
if (proto) {
|
|
149
|
+
proto.success = function <T>(this: Response, data?: T, message?: string, statusCode: number = 200) {
|
|
150
|
+
return ResponseHelper.success(this, data, message, statusCode);
|
|
151
|
+
};
|
|
147
152
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
};
|
|
153
|
+
proto.created = function <T>(this: Response, data?: T, message?: string) {
|
|
154
|
+
return ResponseHelper.created(this, data, message);
|
|
155
|
+
};
|
|
151
156
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
};
|
|
157
|
+
proto.updated = function <T>(this: Response, data?: T, message?: string) {
|
|
158
|
+
return ResponseHelper.updated(this, data, message);
|
|
159
|
+
};
|
|
155
160
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
};
|
|
161
|
+
proto.deleted = function (this: Response, message?: string) {
|
|
162
|
+
return ResponseHelper.deleted(this, message);
|
|
163
|
+
};
|
|
159
164
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
};
|
|
165
|
+
proto.error = function (this: Response, error: string, statusCode: number = 400, code?: string, details?: Record<string, unknown>) {
|
|
166
|
+
return ResponseHelper.error(this, error, statusCode, code, details);
|
|
167
|
+
};
|
|
163
168
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
};
|
|
169
|
+
proto.badRequest = function (this: Response, error?: string, code?: string) {
|
|
170
|
+
return ResponseHelper.badRequest(this, error, code);
|
|
171
|
+
};
|
|
167
172
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
};
|
|
173
|
+
proto.unauthorized = function (this: Response, error?: string, code?: string) {
|
|
174
|
+
return ResponseHelper.unauthorized(this, error, code);
|
|
175
|
+
};
|
|
171
176
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
};
|
|
177
|
+
proto.forbidden = function (this: Response, error?: string, code?: string) {
|
|
178
|
+
return ResponseHelper.forbidden(this, error, code);
|
|
179
|
+
};
|
|
175
180
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
};
|
|
181
|
+
proto.notFound = function (this: Response, error?: string, code?: string) {
|
|
182
|
+
return ResponseHelper.notFound(this, error, code);
|
|
183
|
+
};
|
|
179
184
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
};
|
|
185
|
+
proto.conflict = function (this: Response, error?: string, code?: string) {
|
|
186
|
+
return ResponseHelper.conflict(this, error, code);
|
|
187
|
+
};
|
|
183
188
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
};
|
|
189
|
+
proto.validationError = function (this: Response, error: string, details?: Record<string, unknown>) {
|
|
190
|
+
return ResponseHelper.validationError(this, error, details);
|
|
191
|
+
};
|
|
187
192
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
};
|
|
193
|
+
proto.internalError = function (this: Response, error?: string) {
|
|
194
|
+
return ResponseHelper.internalError(this, error);
|
|
195
|
+
};
|
|
191
196
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
};
|
|
197
|
+
proto.paginated = function <T>(this: Response, data: T[], page: number, limit: number, total: number) {
|
|
198
|
+
return ResponseHelper.paginated(this, data, page, limit, total);
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
// express not available at runtime
|
|
203
|
+
}
|
|
195
204
|
|
|
196
205
|
export const response = ResponseHelper;
|
|
197
206
|
export default ResponseHelper;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
describe('Auth Module', () => {
|
|
2
|
+
const mockUserStore = {
|
|
3
|
+
users: new Map(),
|
|
4
|
+
|
|
5
|
+
async findByEmail(email: string) {
|
|
6
|
+
for (const user of this.users.values()) {
|
|
7
|
+
if (user.email === email) return user;
|
|
8
|
+
}
|
|
9
|
+
return null;
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
async findById(id: string) {
|
|
13
|
+
return this.users.get(id) || null;
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
async create(data: any) {
|
|
17
|
+
const bcrypt = require('bcryptjs');
|
|
18
|
+
const id = Math.random().toString(36).substr(2, 9);
|
|
19
|
+
const hashedPassword = await bcrypt.hash(data.password, 10);
|
|
20
|
+
const user = { id, email: data.email, role: data.role || 'user', ...data, password: hashedPassword };
|
|
21
|
+
this.users.set(id, user);
|
|
22
|
+
return user;
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
async update(id: string, data: any) {
|
|
26
|
+
const user = this.users.get(id);
|
|
27
|
+
if (!user) throw new Error('User not found');
|
|
28
|
+
const updated = { ...user, ...data };
|
|
29
|
+
this.users.set(id, updated);
|
|
30
|
+
return updated;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
test('should create auth service', () => {
|
|
35
|
+
const { createAuth } = require('../dist/auth');
|
|
36
|
+
const auth = createAuth({ jwtSecret: 'test-secret-key-that-is-at-least-32-chars' }, mockUserStore);
|
|
37
|
+
|
|
38
|
+
expect(auth).toBeDefined();
|
|
39
|
+
expect(auth.initialize).toBeDefined();
|
|
40
|
+
expect(auth.register).toBeDefined();
|
|
41
|
+
expect(auth.login).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('should register a new user', async () => {
|
|
45
|
+
const { createAuth } = require('../dist/auth');
|
|
46
|
+
const auth = createAuth({ jwtSecret: 'test-secret-key-that-is-at-least-32-chars' }, mockUserStore);
|
|
47
|
+
|
|
48
|
+
const result = await auth.register({
|
|
49
|
+
email: 'test@example.com',
|
|
50
|
+
password: 'password123',
|
|
51
|
+
name: 'Test User'
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(result.user).toBeDefined();
|
|
55
|
+
expect(result.user.email).toBe('test@example.com');
|
|
56
|
+
expect(result.tokens).toHaveProperty('accessToken');
|
|
57
|
+
expect(result.tokens).toHaveProperty('refreshToken');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('should login with valid credentials', async () => {
|
|
61
|
+
const { createAuth } = require('../dist/auth');
|
|
62
|
+
const auth = createAuth({ jwtSecret: 'test-secret-key-that-is-at-least-32-chars' }, mockUserStore);
|
|
63
|
+
|
|
64
|
+
await auth.register({
|
|
65
|
+
email: 'login@test.com',
|
|
66
|
+
password: 'password123'
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const result = await auth.login({
|
|
70
|
+
email: 'login@test.com',
|
|
71
|
+
password: 'password123'
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(result.user).toBeDefined();
|
|
75
|
+
expect(result.tokens).toHaveProperty('accessToken');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('should fail login with invalid credentials', async () => {
|
|
79
|
+
const { createAuth } = require('../dist/auth');
|
|
80
|
+
const auth = createAuth({ jwtSecret: 'test-secret-key-that-is-at-least-32-chars' }, mockUserStore);
|
|
81
|
+
|
|
82
|
+
await auth.register({
|
|
83
|
+
email: 'fail@test.com',
|
|
84
|
+
password: 'password123'
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await expect(auth.login({
|
|
88
|
+
email: 'fail@test.com',
|
|
89
|
+
password: 'wrongpassword'
|
|
90
|
+
})).rejects.toThrow('Invalid credentials');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('should throw error for duplicate registration', async () => {
|
|
94
|
+
const { createAuth } = require('../dist/auth');
|
|
95
|
+
const auth = createAuth({ jwtSecret: 'test-secret-key-that-is-at-least-32-chars' }, mockUserStore);
|
|
96
|
+
|
|
97
|
+
await auth.register({
|
|
98
|
+
email: 'duplicate@test.com',
|
|
99
|
+
password: 'password123'
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await expect(auth.register({
|
|
103
|
+
email: 'duplicate@test.com',
|
|
104
|
+
password: 'password123'
|
|
105
|
+
})).rejects.toThrow('User already exists');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('should generate middleware', () => {
|
|
109
|
+
const { createAuth } = require('../dist/auth');
|
|
110
|
+
const auth = createAuth({ jwtSecret: 'test-secret-key-that-is-at-least-32-chars' }, mockUserStore);
|
|
111
|
+
|
|
112
|
+
const middleware = auth.getMiddleware();
|
|
113
|
+
expect(middleware).toBeDefined();
|
|
114
|
+
expect(typeof middleware).toBe('function');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('should create requireUser middleware', () => {
|
|
118
|
+
const { createAuth } = require('../dist/auth');
|
|
119
|
+
const auth = createAuth({ jwtSecret: 'test-secret-key-that-is-at-least-32-chars' }, mockUserStore);
|
|
120
|
+
|
|
121
|
+
const middleware = auth.requireUser();
|
|
122
|
+
expect(middleware).toBeDefined();
|
|
123
|
+
expect(typeof middleware).toBe('function');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('should create requireRole middleware', () => {
|
|
127
|
+
const { createAuth } = require('../dist/auth');
|
|
128
|
+
const auth = createAuth({ jwtSecret: 'test-secret-key-that-is-at-least-32-chars' }, mockUserStore);
|
|
129
|
+
|
|
130
|
+
const middleware = auth.requireRole('admin');
|
|
131
|
+
expect(middleware).toBeDefined();
|
|
132
|
+
expect(typeof middleware).toBe('function');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
describe('Config Module', () => {
|
|
2
|
+
test('should load config with defaults', async () => {
|
|
3
|
+
const { config } = require('../dist/config');
|
|
4
|
+
const cfg = config.load();
|
|
5
|
+
|
|
6
|
+
expect(cfg.NODE_ENV).toBe('test');
|
|
7
|
+
expect(cfg.PORT).toBe('3000');
|
|
8
|
+
expect(cfg.REDIS_URL).toBe('redis://localhost:6379');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('should get config value', async () => {
|
|
12
|
+
const { config } = require('../dist/config');
|
|
13
|
+
config.load();
|
|
14
|
+
|
|
15
|
+
const port = config.int('PORT');
|
|
16
|
+
expect(port).toBe(3000);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('should check environment', async () => {
|
|
20
|
+
const { config } = require('../dist/config');
|
|
21
|
+
config.load();
|
|
22
|
+
|
|
23
|
+
expect(config.isTest()).toBe(true);
|
|
24
|
+
expect(config.isProduction()).toBe(false);
|
|
25
|
+
expect(config.isDevelopment()).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('should get all config', async () => {
|
|
29
|
+
const { config } = require('../dist/config');
|
|
30
|
+
const cfg = config.getAll();
|
|
31
|
+
|
|
32
|
+
expect(cfg).toHaveProperty('NODE_ENV');
|
|
33
|
+
expect(cfg).toHaveProperty('PORT');
|
|
34
|
+
expect(cfg).toHaveProperty('AWS_REGION');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
describe('Logger Module', () => {
|
|
2
|
+
test('should create logger', () => {
|
|
3
|
+
const { logger } = require('../dist/logger');
|
|
4
|
+
|
|
5
|
+
expect(logger).toBeDefined();
|
|
6
|
+
expect(logger.info).toBeDefined();
|
|
7
|
+
expect(logger.error).toBeDefined();
|
|
8
|
+
expect(logger.warn).toBeDefined();
|
|
9
|
+
expect(logger.debug).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('should log info message', () => {
|
|
13
|
+
const { logger } = require('../dist/logger');
|
|
14
|
+
|
|
15
|
+
expect(() => logger.info('test message')).not.toThrow();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('should log error with metadata', () => {
|
|
19
|
+
const { logger } = require('../dist/logger');
|
|
20
|
+
|
|
21
|
+
expect(() => logger.error('error occurred', { code: 500 })).not.toThrow();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('should create child logger', () => {
|
|
25
|
+
const { logger } = require('../dist/logger');
|
|
26
|
+
|
|
27
|
+
const child = logger.child({ module: 'auth' });
|
|
28
|
+
expect(child).toBeDefined();
|
|
29
|
+
expect(child.info).toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('should create named logger', () => {
|
|
33
|
+
const { logger } = require('../dist/logger');
|
|
34
|
+
|
|
35
|
+
const named = logger.create({ name: 'test-logger' });
|
|
36
|
+
expect(named).toBeDefined();
|
|
37
|
+
expect(named.info).toBeDefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('should get logger by name', () => {
|
|
41
|
+
const { logger } = require('../dist/logger');
|
|
42
|
+
|
|
43
|
+
logger.create({ name: 'custom-logger' });
|
|
44
|
+
const custom = logger.get('custom-logger');
|
|
45
|
+
expect(custom).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
describe('Notifications Module', () => {
|
|
2
|
+
test('should have notify object defined', () => {
|
|
3
|
+
const { notify } = require('../dist/notifications');
|
|
4
|
+
|
|
5
|
+
expect(notify).toBeDefined();
|
|
6
|
+
expect(notify.email).toBeDefined();
|
|
7
|
+
expect(notify.sms).toBeDefined();
|
|
8
|
+
expect(notify.webhook).toBeDefined();
|
|
9
|
+
expect(notify.slack).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('should have notification export', () => {
|
|
13
|
+
const { notification } = require('../dist/notifications');
|
|
14
|
+
|
|
15
|
+
expect(notification).toBeDefined();
|
|
16
|
+
expect(notification.email).toBeDefined();
|
|
17
|
+
expect(notification.sms).toBeDefined();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
describe('Rate Limit Module', () => {
|
|
2
|
+
test('should create rate limiter middleware', () => {
|
|
3
|
+
const { rateLimit } = require('../src/rate-limit');
|
|
4
|
+
|
|
5
|
+
const middleware = rateLimit({ window: '1m', limit: 100 });
|
|
6
|
+
expect(middleware).toBeDefined();
|
|
7
|
+
expect(typeof middleware).toBe('function');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('should create rate limiter with defaults', () => {
|
|
11
|
+
const { rateLimit } = require('../src/rate-limit');
|
|
12
|
+
|
|
13
|
+
const middleware = rateLimit();
|
|
14
|
+
expect(middleware).toBeDefined();
|
|
15
|
+
expect(typeof middleware).toBe('function');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('should create custom rate limiter', () => {
|
|
19
|
+
const { createRateLimiter } = require('../src/rate-limit');
|
|
20
|
+
|
|
21
|
+
const limiter = createRateLimiter({ window: '1m', limit: 10 });
|
|
22
|
+
expect(limiter).toBeDefined();
|
|
23
|
+
expect(limiter.middleware).toBeDefined();
|
|
24
|
+
expect(limiter.destroy).toBeDefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should use custom key generator', () => {
|
|
28
|
+
const { rateLimit } = require('../src/rate-limit');
|
|
29
|
+
|
|
30
|
+
const middleware = rateLimit({
|
|
31
|
+
window: '1m',
|
|
32
|
+
limit: 100,
|
|
33
|
+
keyGenerator: (req: any) => req.userId || 'anonymous'
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(middleware).toBeDefined();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('should use custom skip function', () => {
|
|
40
|
+
const { rateLimit } = require('../src/rate-limit');
|
|
41
|
+
|
|
42
|
+
const middleware = rateLimit({
|
|
43
|
+
window: '1m',
|
|
44
|
+
limit: 100,
|
|
45
|
+
skip: (req: any) => req.skipRateLimit === true
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(middleware).toBeDefined();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
describe('Upload Module (S3)', () => {
|
|
2
|
+
test('should have upload functions defined', () => {
|
|
3
|
+
const { upload } = require('../dist/upload');
|
|
4
|
+
|
|
5
|
+
expect(upload).toBeDefined();
|
|
6
|
+
expect(upload.initialize).toBeDefined();
|
|
7
|
+
expect(upload.file).toBeDefined();
|
|
8
|
+
expect(upload.image).toBeDefined();
|
|
9
|
+
expect(upload.video).toBeDefined();
|
|
10
|
+
expect(upload.delete).toBeDefined();
|
|
11
|
+
expect(upload.getSignedUrl).toBeDefined();
|
|
12
|
+
expect(upload.getPublicUrl).toBeDefined();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('should initialize S3 service', () => {
|
|
16
|
+
const { upload } = require('../dist/upload');
|
|
17
|
+
|
|
18
|
+
expect(() => {
|
|
19
|
+
upload.initialize({
|
|
20
|
+
region: 'us-east-1',
|
|
21
|
+
accessKeyId: 'test-key',
|
|
22
|
+
secretAccessKey: 'test-secret',
|
|
23
|
+
bucket: 'test-bucket'
|
|
24
|
+
});
|
|
25
|
+
}).not.toThrow();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('should have s3Service defined', () => {
|
|
29
|
+
const { s3Service } = require('../dist/upload');
|
|
30
|
+
|
|
31
|
+
expect(s3Service).toBeDefined();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": ".",
|
|
5
|
+
"types": ["jest", "node"],
|
|
6
|
+
"composite": false,
|
|
7
|
+
"declaration": false,
|
|
8
|
+
"declarationMap": false,
|
|
9
|
+
"sourceMap": false,
|
|
10
|
+
"noUnusedLocals": false,
|
|
11
|
+
"noUnusedParameters": false
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*", "tests/**/*"]
|
|
14
|
+
}
|