@nauth-toolkit/core 0.1.0 → 0.1.5

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 (184) hide show
  1. package/LICENSE +90 -0
  2. package/README.md +9 -0
  3. package/package.json +8 -3
  4. package/jest.config.js +0 -15
  5. package/jest.setup.ts +0 -6
  6. package/src/adapters/database-columns.ts +0 -165
  7. package/src/adapters/express.adapter.ts +0 -385
  8. package/src/adapters/fastify.adapter.ts +0 -416
  9. package/src/adapters/index.ts +0 -16
  10. package/src/adapters/storage.factory.ts +0 -143
  11. package/src/bootstrap.ts +0 -374
  12. package/src/dto/auth-challenge.dto.ts +0 -231
  13. package/src/dto/auth-response.dto.ts +0 -253
  14. package/src/dto/challenge-response.dto.ts +0 -234
  15. package/src/dto/change-password-request.dto.ts +0 -50
  16. package/src/dto/change-password-response.dto.ts +0 -29
  17. package/src/dto/change-password.dto.ts +0 -57
  18. package/src/dto/error-response.dto.ts +0 -136
  19. package/src/dto/get-available-methods.dto.ts +0 -55
  20. package/src/dto/get-challenge-data-response.dto.ts +0 -28
  21. package/src/dto/get-challenge-data.dto.ts +0 -69
  22. package/src/dto/get-client-info.dto.ts +0 -104
  23. package/src/dto/get-device-token-response.dto.ts +0 -25
  24. package/src/dto/get-events-by-type.dto.ts +0 -76
  25. package/src/dto/get-ip-address-response.dto.ts +0 -24
  26. package/src/dto/get-mfa-status.dto.ts +0 -94
  27. package/src/dto/get-risk-assessment-history.dto.ts +0 -39
  28. package/src/dto/get-session-id-response.dto.ts +0 -25
  29. package/src/dto/get-setup-data-response.dto.ts +0 -31
  30. package/src/dto/get-setup-data.dto.ts +0 -75
  31. package/src/dto/get-suspicious-activity.dto.ts +0 -42
  32. package/src/dto/get-user-agent-response.dto.ts +0 -23
  33. package/src/dto/get-user-auth-history.dto.ts +0 -95
  34. package/src/dto/get-user-by-email.dto.ts +0 -61
  35. package/src/dto/get-user-by-id.dto.ts +0 -46
  36. package/src/dto/get-user-devices.dto.ts +0 -53
  37. package/src/dto/get-user-response.dto.ts +0 -17
  38. package/src/dto/has-provider.dto.ts +0 -56
  39. package/src/dto/index.ts +0 -57
  40. package/src/dto/is-trusted-device-response.dto.ts +0 -34
  41. package/src/dto/list-providers-response.dto.ts +0 -23
  42. package/src/dto/login.dto.ts +0 -95
  43. package/src/dto/logout-all-response.dto.ts +0 -24
  44. package/src/dto/logout-all.dto.ts +0 -65
  45. package/src/dto/logout-response.dto.ts +0 -25
  46. package/src/dto/logout.dto.ts +0 -64
  47. package/src/dto/refresh-token.dto.ts +0 -36
  48. package/src/dto/remove-devices.dto.ts +0 -85
  49. package/src/dto/resend-code-response.dto.ts +0 -32
  50. package/src/dto/resend-code.dto.ts +0 -51
  51. package/src/dto/reset-password.dto.ts +0 -115
  52. package/src/dto/respond-challenge.dto.ts +0 -272
  53. package/src/dto/set-mfa-exemption.dto.ts +0 -112
  54. package/src/dto/set-must-change-password-response.dto.ts +0 -27
  55. package/src/dto/set-must-change-password.dto.ts +0 -46
  56. package/src/dto/set-preferred-method.dto.ts +0 -80
  57. package/src/dto/setup-mfa.dto.ts +0 -98
  58. package/src/dto/signup.dto.ts +0 -174
  59. package/src/dto/social-auth.dto.ts +0 -422
  60. package/src/dto/trust-device-response.dto.ts +0 -30
  61. package/src/dto/trust-device.dto.ts +0 -9
  62. package/src/dto/update-user-attributes-request.dto.ts +0 -51
  63. package/src/dto/user-response.dto.ts +0 -138
  64. package/src/dto/user-update.dto.ts +0 -222
  65. package/src/dto/verify-email.dto.ts +0 -313
  66. package/src/dto/verify-mfa-code.dto.ts +0 -103
  67. package/src/dto/verify-phone-by-sub.dto.ts +0 -78
  68. package/src/dto/verify-phone.dto.ts +0 -245
  69. package/src/entities/auth-audit.entity.ts +0 -232
  70. package/src/entities/challenge-session.entity.ts +0 -116
  71. package/src/entities/index.ts +0 -29
  72. package/src/entities/login-attempt.entity.ts +0 -64
  73. package/src/entities/mfa-device.entity.ts +0 -151
  74. package/src/entities/rate-limit.entity.ts +0 -44
  75. package/src/entities/session.entity.ts +0 -180
  76. package/src/entities/social-account.entity.ts +0 -96
  77. package/src/entities/storage-lock.entity.ts +0 -39
  78. package/src/entities/trusted-device.entity.ts +0 -112
  79. package/src/entities/user.entity.ts +0 -243
  80. package/src/entities/verification-token.entity.ts +0 -141
  81. package/src/enums/auth-audit-event-type.enum.ts +0 -360
  82. package/src/enums/error-codes.enum.ts +0 -420
  83. package/src/enums/mfa-method.enum.ts +0 -97
  84. package/src/enums/risk-factor.enum.ts +0 -111
  85. package/src/exceptions/nauth.exception.ts +0 -231
  86. package/src/handlers/auth.handler.ts +0 -260
  87. package/src/handlers/client-info.handler.ts +0 -101
  88. package/src/handlers/csrf.handler.ts +0 -156
  89. package/src/handlers/token-delivery.handler.ts +0 -118
  90. package/src/index.ts +0 -118
  91. package/src/interfaces/client-info.interface.ts +0 -85
  92. package/src/interfaces/config.interface.ts +0 -2135
  93. package/src/interfaces/entities.interface.ts +0 -226
  94. package/src/interfaces/index.ts +0 -15
  95. package/src/interfaces/logger.interface.ts +0 -283
  96. package/src/interfaces/mfa-provider.interface.ts +0 -154
  97. package/src/interfaces/oauth.interface.ts +0 -148
  98. package/src/interfaces/provider.interface.ts +0 -47
  99. package/src/interfaces/social-auth-provider.interface.ts +0 -131
  100. package/src/interfaces/storage-adapter.interface.ts +0 -82
  101. package/src/interfaces/template.interface.ts +0 -510
  102. package/src/interfaces/token-verifier.interface.ts +0 -110
  103. package/src/internal.ts +0 -178
  104. package/src/platform/interfaces.ts +0 -299
  105. package/src/schemas/auth-config.schema.ts +0 -646
  106. package/src/services/adaptive-mfa-decision.service.spec.ts +0 -1058
  107. package/src/services/adaptive-mfa-decision.service.ts +0 -457
  108. package/src/services/auth-audit.service.spec.ts +0 -675
  109. package/src/services/auth-audit.service.ts +0 -558
  110. package/src/services/auth-challenge-helper.service.spec.ts +0 -3227
  111. package/src/services/auth-challenge-helper.service.ts +0 -825
  112. package/src/services/auth-flow-context-builder.service.ts +0 -520
  113. package/src/services/auth-flow-rules.ts +0 -202
  114. package/src/services/auth-flow-state-definitions.ts +0 -190
  115. package/src/services/auth-flow-state-machine.service.ts +0 -207
  116. package/src/services/auth-flow-state-machine.types.ts +0 -316
  117. package/src/services/auth.service.spec.ts +0 -4195
  118. package/src/services/auth.service.ts +0 -3727
  119. package/src/services/challenge.service.spec.ts +0 -1363
  120. package/src/services/challenge.service.ts +0 -696
  121. package/src/services/client-info.service.spec.ts +0 -572
  122. package/src/services/client-info.service.ts +0 -374
  123. package/src/services/csrf.service.ts +0 -54
  124. package/src/services/email-verification.service.spec.ts +0 -1229
  125. package/src/services/email-verification.service.ts +0 -578
  126. package/src/services/geo-location.service.spec.ts +0 -603
  127. package/src/services/geo-location.service.ts +0 -599
  128. package/src/services/index.ts +0 -13
  129. package/src/services/jwt.service.spec.ts +0 -882
  130. package/src/services/jwt.service.ts +0 -621
  131. package/src/services/mfa-base.service.spec.ts +0 -246
  132. package/src/services/mfa-base.service.ts +0 -611
  133. package/src/services/mfa.service.spec.ts +0 -693
  134. package/src/services/mfa.service.ts +0 -960
  135. package/src/services/password.service.spec.ts +0 -166
  136. package/src/services/password.service.ts +0 -309
  137. package/src/services/phone-verification.service.spec.ts +0 -1120
  138. package/src/services/phone-verification.service.ts +0 -751
  139. package/src/services/risk-detection.service.spec.ts +0 -1292
  140. package/src/services/risk-detection.service.ts +0 -1012
  141. package/src/services/risk-scoring.service.spec.ts +0 -204
  142. package/src/services/risk-scoring.service.ts +0 -131
  143. package/src/services/session.service.spec.ts +0 -1293
  144. package/src/services/session.service.ts +0 -803
  145. package/src/services/social-account.service.spec.ts +0 -725
  146. package/src/services/social-auth-base.service.spec.ts +0 -418
  147. package/src/services/social-auth-base.service.ts +0 -581
  148. package/src/services/social-auth.service.spec.ts +0 -238
  149. package/src/services/social-auth.service.ts +0 -436
  150. package/src/services/social-provider-registry.service.spec.ts +0 -238
  151. package/src/services/social-provider-registry.service.ts +0 -122
  152. package/src/services/trusted-device.service.spec.ts +0 -505
  153. package/src/services/trusted-device.service.ts +0 -339
  154. package/src/storage/account-lockout-storage.service.spec.ts +0 -310
  155. package/src/storage/account-lockout-storage.service.ts +0 -89
  156. package/src/storage/index.ts +0 -3
  157. package/src/storage/memory-storage.adapter.ts +0 -443
  158. package/src/storage/rate-limit-storage.service.spec.ts +0 -247
  159. package/src/storage/rate-limit-storage.service.ts +0 -38
  160. package/src/templates/html-template.engine.spec.ts +0 -161
  161. package/src/templates/html-template.engine.ts +0 -688
  162. package/src/templates/index.ts +0 -7
  163. package/src/utils/common-passwords.spec.ts +0 -230
  164. package/src/utils/common-passwords.ts +0 -170
  165. package/src/utils/context-storage.ts +0 -188
  166. package/src/utils/cookie-names.util.ts +0 -67
  167. package/src/utils/cookies.util.ts +0 -94
  168. package/src/utils/index.ts +0 -12
  169. package/src/utils/ip-extractor.spec.ts +0 -330
  170. package/src/utils/ip-extractor.ts +0 -220
  171. package/src/utils/nauth-logger.spec.ts +0 -388
  172. package/src/utils/nauth-logger.ts +0 -215
  173. package/src/utils/pii-redactor.spec.ts +0 -130
  174. package/src/utils/pii-redactor.ts +0 -288
  175. package/src/utils/setup/get-repositories.ts +0 -140
  176. package/src/utils/setup/init-services.ts +0 -422
  177. package/src/utils/setup/init-social.ts +0 -189
  178. package/src/utils/setup/init-storage.ts +0 -94
  179. package/src/utils/setup/register-mfa.ts +0 -165
  180. package/src/utils/setup/run-nauth-migrations.ts +0 -61
  181. package/src/utils/token-delivery-policy.ts +0 -38
  182. package/src/validators/template.validator.ts +0 -219
  183. package/tsconfig.json +0 -37
  184. package/tsconfig.lint.json +0 -6
@@ -1,247 +0,0 @@
1
- import { RateLimitStorageService } from './rate-limit-storage.service';
2
- import { StorageAdapter } from '../interfaces/storage-adapter.interface';
3
-
4
- /**
5
- * Rate Limit Storage Service Unit Tests
6
- *
7
- * Tests rate limiting storage operations.
8
- * Covers counter increment, window management, and expiration handling.
9
- *
10
- * Platform-agnostic: Uses direct instantiation, no NestJS dependencies.
11
- */
12
- describe('RateLimitStorageService', () => {
13
- let service: RateLimitStorageService;
14
- let mockStorageAdapter: jest.Mocked<StorageAdapter>;
15
-
16
- beforeEach(() => {
17
- // Create mock storage adapter
18
- mockStorageAdapter = {
19
- initialize: jest.fn(),
20
- isHealthy: jest.fn(),
21
- get: jest.fn(),
22
- set: jest.fn(),
23
- del: jest.fn(),
24
- exists: jest.fn(),
25
- incr: jest.fn(),
26
- decr: jest.fn(),
27
- expire: jest.fn(),
28
- ttl: jest.fn(),
29
- hget: jest.fn(),
30
- hset: jest.fn(),
31
- hgetall: jest.fn(),
32
- hdel: jest.fn(),
33
- lpush: jest.fn(),
34
- lrange: jest.fn(),
35
- llen: jest.fn(),
36
- keys: jest.fn(),
37
- scan: jest.fn(),
38
- cleanup: jest.fn(),
39
- disconnect: jest.fn(),
40
- } as any;
41
-
42
- // Instantiate service directly
43
- service = new RateLimitStorageService(mockStorageAdapter);
44
- });
45
-
46
- afterEach(() => {
47
- jest.clearAllMocks();
48
- });
49
-
50
- // ============================================================================
51
- // Service Initialization
52
- // ============================================================================
53
-
54
- it('should be defined', () => {
55
- expect(service).toBeDefined();
56
- });
57
-
58
- // ============================================================================
59
- // incrementRateLimit() Method
60
- // ============================================================================
61
-
62
- describe('incrementRateLimit', () => {
63
- it('should increment counter and return count', async () => {
64
- mockStorageAdapter.incr.mockResolvedValue(1);
65
- mockStorageAdapter.expire.mockResolvedValue();
66
-
67
- const result = await service.incrementRateLimit('user-123', '/api/login', 60000);
68
-
69
- expect(result).toBe(1);
70
- expect(mockStorageAdapter.incr).toHaveBeenCalledWith('nauth:ratelimit:user-123:/api/login');
71
- });
72
-
73
- it('should set expiry on first request in window', async () => {
74
- mockStorageAdapter.incr.mockResolvedValue(1);
75
- mockStorageAdapter.expire.mockResolvedValue();
76
-
77
- await service.incrementRateLimit('user-123', '/api/login', 60000);
78
-
79
- expect(mockStorageAdapter.expire).toHaveBeenCalledWith(
80
- 'nauth:ratelimit:user-123:/api/login',
81
- 60, // 60000ms = 60 seconds
82
- );
83
- });
84
-
85
- it('should not set expiry on subsequent requests', async () => {
86
- mockStorageAdapter.incr.mockResolvedValue(2);
87
- mockStorageAdapter.expire.mockResolvedValue();
88
-
89
- await service.incrementRateLimit('user-123', '/api/login', 60000);
90
-
91
- expect(mockStorageAdapter.expire).not.toHaveBeenCalled();
92
- });
93
-
94
- it('should convert windowMs to seconds for TTL', async () => {
95
- mockStorageAdapter.incr.mockResolvedValue(1);
96
- mockStorageAdapter.expire.mockResolvedValue();
97
-
98
- await service.incrementRateLimit('user-123', '/api/login', 120000); // 2 minutes
99
-
100
- expect(mockStorageAdapter.expire).toHaveBeenCalledWith(
101
- 'nauth:ratelimit:user-123:/api/login',
102
- 120, // 120000ms = 120 seconds
103
- );
104
- });
105
-
106
- it('should round up windowMs to seconds', async () => {
107
- mockStorageAdapter.incr.mockResolvedValue(1);
108
- mockStorageAdapter.expire.mockResolvedValue();
109
-
110
- await service.incrementRateLimit('user-123', '/api/login', 1500); // 1.5 seconds
111
-
112
- expect(mockStorageAdapter.expire).toHaveBeenCalledWith(
113
- 'nauth:ratelimit:user-123:/api/login',
114
- 2, // Math.ceil(1500 / 1000) = 2
115
- );
116
- });
117
-
118
- it('should use correct key format', async () => {
119
- mockStorageAdapter.incr.mockResolvedValue(1);
120
- mockStorageAdapter.expire.mockResolvedValue();
121
-
122
- await service.incrementRateLimit('ip-192.168.1.1', '/api/signup', 30000);
123
-
124
- expect(mockStorageAdapter.incr).toHaveBeenCalledWith('nauth:ratelimit:ip-192.168.1.1:/api/signup');
125
- });
126
- });
127
-
128
- // ============================================================================
129
- // getRateLimit() Method
130
- // ============================================================================
131
-
132
- describe('getRateLimit', () => {
133
- it('should return current rate limit count', async () => {
134
- mockStorageAdapter.get.mockResolvedValue('5');
135
-
136
- const result = await service.getRateLimit('user-123', '/api/login');
137
-
138
- expect(result).toBe(5);
139
- expect(mockStorageAdapter.get).toHaveBeenCalledWith('nauth:ratelimit:user-123:/api/login');
140
- });
141
-
142
- it('should return 0 when no limit recorded', async () => {
143
- mockStorageAdapter.get.mockResolvedValue(null);
144
-
145
- const result = await service.getRateLimit('user-123', '/api/login');
146
-
147
- expect(result).toBe(0);
148
- });
149
-
150
- it('should parse string value to number', async () => {
151
- mockStorageAdapter.get.mockResolvedValue('10');
152
-
153
- const result = await service.getRateLimit('user-123', '/api/login');
154
-
155
- expect(result).toBe(10);
156
- });
157
-
158
- it('should handle empty string', async () => {
159
- mockStorageAdapter.get.mockResolvedValue('');
160
-
161
- const result = await service.getRateLimit('user-123', '/api/login');
162
-
163
- expect(result).toBe(0);
164
- });
165
-
166
- it('should use correct key format', async () => {
167
- mockStorageAdapter.get.mockResolvedValue('3');
168
-
169
- await service.getRateLimit('ip-10.0.0.1', '/api/reset-password');
170
-
171
- expect(mockStorageAdapter.get).toHaveBeenCalledWith('nauth:ratelimit:ip-10.0.0.1:/api/reset-password');
172
- });
173
- });
174
-
175
- // ============================================================================
176
- // resetRateLimit() Method
177
- // ============================================================================
178
-
179
- describe('resetRateLimit', () => {
180
- it('should delete rate limit counter', async () => {
181
- mockStorageAdapter.del.mockResolvedValue();
182
-
183
- await service.resetRateLimit('user-123', '/api/login');
184
-
185
- expect(mockStorageAdapter.del).toHaveBeenCalledWith('nauth:ratelimit:user-123:/api/login');
186
- });
187
-
188
- it('should reset limit for correct identifier and endpoint', async () => {
189
- mockStorageAdapter.del.mockResolvedValue();
190
-
191
- await service.resetRateLimit('ip-192.168.1.1', '/api/signup');
192
-
193
- expect(mockStorageAdapter.del).toHaveBeenCalledWith('nauth:ratelimit:ip-192.168.1.1:/api/signup');
194
- });
195
- });
196
-
197
- // ============================================================================
198
- // Integration Tests
199
- // ============================================================================
200
-
201
- describe('Integration', () => {
202
- it('should track rate limit across multiple requests', async () => {
203
- mockStorageAdapter.incr.mockResolvedValueOnce(1).mockResolvedValueOnce(2).mockResolvedValueOnce(3);
204
- mockStorageAdapter.expire.mockResolvedValue();
205
- mockStorageAdapter.get.mockResolvedValue('3');
206
-
207
- // Make 3 requests
208
- await service.incrementRateLimit('user-123', '/api/login', 60000);
209
- await service.incrementRateLimit('user-123', '/api/login', 60000);
210
- await service.incrementRateLimit('user-123', '/api/login', 60000);
211
-
212
- const count = await service.getRateLimit('user-123', '/api/login');
213
-
214
- expect(count).toBe(3);
215
- expect(mockStorageAdapter.expire).toHaveBeenCalledTimes(1); // Only on first request
216
- });
217
-
218
- it('should reset rate limit correctly', async () => {
219
- mockStorageAdapter.incr.mockResolvedValue(5);
220
- mockStorageAdapter.expire.mockResolvedValue();
221
- mockStorageAdapter.del.mockResolvedValue();
222
- mockStorageAdapter.get.mockResolvedValue(null);
223
-
224
- await service.incrementRateLimit('user-123', '/api/login', 60000);
225
- await service.resetRateLimit('user-123', '/api/login');
226
-
227
- const count = await service.getRateLimit('user-123', '/api/login');
228
-
229
- expect(count).toBe(0);
230
- });
231
-
232
- it('should handle different endpoints independently', async () => {
233
- mockStorageAdapter.incr.mockResolvedValueOnce(1).mockResolvedValueOnce(1);
234
- mockStorageAdapter.expire.mockResolvedValue();
235
- mockStorageAdapter.get.mockResolvedValueOnce('1').mockResolvedValueOnce('1');
236
-
237
- await service.incrementRateLimit('user-123', '/api/login', 60000);
238
- await service.incrementRateLimit('user-123', '/api/signup', 60000);
239
-
240
- const loginCount = await service.getRateLimit('user-123', '/api/login');
241
- const signupCount = await service.getRateLimit('user-123', '/api/signup');
242
-
243
- expect(loginCount).toBe(1);
244
- expect(signupCount).toBe(1);
245
- });
246
- });
247
- });
@@ -1,38 +0,0 @@
1
- import { RateLimitStorage, StorageAdapter } from '../interfaces/storage-adapter.interface';
2
-
3
- /**
4
- * Rate limit storage implementation using StorageAdapter (Platform-Agnostic)
5
- */
6
- export class RateLimitStorageService implements RateLimitStorage {
7
- private readonly keyPrefix = 'nauth:ratelimit:';
8
-
9
- constructor(private readonly storageAdapter: StorageAdapter) {}
10
-
11
- async incrementRateLimit(identifier: string, endpoint: string, windowMs: number): Promise<number> {
12
- const key = this.getKey(identifier, endpoint);
13
- const count = await this.storageAdapter.incr(key);
14
-
15
- if (count === 1) {
16
- // First request in this window, set expiry
17
- const ttl = Math.ceil(windowMs / 1000);
18
- await this.storageAdapter.expire(key, ttl);
19
- }
20
-
21
- return count;
22
- }
23
-
24
- async getRateLimit(identifier: string, endpoint: string): Promise<number> {
25
- const key = this.getKey(identifier, endpoint);
26
- const value = await this.storageAdapter.get(key);
27
- return value ? parseInt(value, 10) : 0;
28
- }
29
-
30
- async resetRateLimit(identifier: string, endpoint: string): Promise<void> {
31
- const key = this.getKey(identifier, endpoint);
32
- await this.storageAdapter.del(key);
33
- }
34
-
35
- private getKey(identifier: string, endpoint: string): string {
36
- return `${this.keyPrefix}${identifier}:${endpoint}`;
37
- }
38
- }
@@ -1,161 +0,0 @@
1
- import { HtmlTemplateEngine } from './html-template.engine';
2
- import { TemplateType } from '../interfaces/template.interface';
3
-
4
- describe('HtmlTemplateEngine', () => {
5
- let engine: HtmlTemplateEngine;
6
-
7
- beforeEach(() => {
8
- engine = new HtmlTemplateEngine();
9
- });
10
-
11
- it('should be defined', () => {
12
- expect(engine).toBeDefined();
13
- });
14
-
15
- describe('render', () => {
16
- it('should render verification email template', async () => {
17
- const variables = {
18
- appName: 'Test App',
19
- userName: 'John Doe',
20
- code: '123456',
21
- link: 'https://example.com/verify',
22
- expiryMinutes: 60,
23
- companyName: 'Test Company',
24
- };
25
-
26
- const email = await engine.render(TemplateType.VERIFICATION, variables);
27
-
28
- expect(email.subject).toContain('Test App');
29
- expect(email.html).toContain('John Doe');
30
- expect(email.html).toContain('123456');
31
- expect(email.html).toContain('https://example.com/verify');
32
- expect(email.text).toContain('123456');
33
- });
34
-
35
- it('should render password reset template', async () => {
36
- const variables = {
37
- appName: 'Test App',
38
- userName: 'Jane Doe',
39
- link: 'https://example.com/reset',
40
- expiryMinutes: 30,
41
- companyName: 'Test Company',
42
- };
43
-
44
- const email = await engine.render(TemplateType.PASSWORD_RESET, variables);
45
-
46
- expect(email.subject).toContain('Reset'); // Capitalized in actual template
47
- expect(email.html).toContain('Jane Doe');
48
- expect(email.html).toContain('https://example.com/reset');
49
- });
50
-
51
- it('should render welcome email template', async () => {
52
- const variables = {
53
- appName: 'Test App',
54
- userName: 'Bob Smith',
55
- dashboardUrl: 'https://example.com/dashboard',
56
- supportEmail: 'support@example.com',
57
- companyName: 'Test Company',
58
- };
59
-
60
- const email = await engine.render(TemplateType.WELCOME, variables);
61
-
62
- expect(email.subject).toContain('Welcome');
63
- expect(email.html).toContain('Bob Smith');
64
- expect(email.html).toContain('https://example.com/dashboard');
65
- });
66
-
67
- it('should render account lockout template', async () => {
68
- const variables = {
69
- appName: 'Test App',
70
- userName: 'Alice Johnson',
71
- reason: 'Too many failed login attempts',
72
- durationMinutes: 15,
73
- supportEmail: 'support@example.com',
74
- companyName: 'Test Company',
75
- };
76
-
77
- const email = await engine.render(TemplateType.ACCOUNT_LOCKOUT, variables);
78
-
79
- expect(email.subject).toContain('Account Locked');
80
- expect(email.html).toContain('Alice Johnson');
81
- expect(email.html).toContain('Too many failed login attempts');
82
- expect(email.html).toContain('15');
83
- });
84
-
85
- it('should render new device login template', async () => {
86
- const variables = {
87
- appName: 'Test App',
88
- userName: 'Charlie Brown',
89
- deviceName: 'iPhone 13',
90
- deviceType: 'mobile',
91
- ipAddress: '192.168.1.100',
92
- location: 'New York, US',
93
- timestamp: '2025-10-22T12:00:00Z',
94
- supportEmail: 'support@example.com',
95
- companyName: 'Test Company',
96
- };
97
-
98
- const email = await engine.render(TemplateType.NEW_DEVICE, variables);
99
-
100
- expect(email.subject).toContain('New Device Login');
101
- expect(email.html).toContain('Charlie Brown');
102
- expect(email.html).toContain('iPhone 13');
103
- expect(email.html).toContain('192.168.1.100');
104
- });
105
-
106
- it('should throw error for unknown template type', async () => {
107
- let error: Error | undefined;
108
- try {
109
- await engine.render('unknown' as any, {});
110
- } catch (e) {
111
- error = e as Error;
112
- }
113
- expect(error).toBeDefined();
114
- expect(error?.message).toContain('Template "unknown" not found');
115
- });
116
-
117
- it('should handle missing variables gracefully', async () => {
118
- const variables = {
119
- appName: 'Test App',
120
- // Missing other required variables
121
- };
122
-
123
- const email = await engine.render(TemplateType.VERIFICATION, variables);
124
-
125
- expect(email.subject).toContain('Test App');
126
- expect(email.html).toBeDefined();
127
- expect(email.text).toBeDefined();
128
- });
129
-
130
- it('should replace all occurrences of a variable', async () => {
131
- const variables = {
132
- appName: 'Test App',
133
- userName: 'Test User',
134
- companyName: 'Test Company',
135
- };
136
-
137
- const email = await engine.render(TemplateType.WELCOME, variables);
138
-
139
- // userName should appear in the greeting
140
- expect(email.html).toContain('Test User');
141
- expect(email.text).toContain('Test User');
142
- });
143
-
144
- it('should handle special characters in variables', async () => {
145
- const variables = {
146
- appName: 'Test & Company <Special>',
147
- userName: 'John "Doe"',
148
- code: '123456',
149
- link: 'https://example.com/verify',
150
- expiryMinutes: 60,
151
- companyName: 'Test & Co.',
152
- };
153
-
154
- const email = await engine.render(TemplateType.VERIFICATION, variables);
155
-
156
- // HTML escapes special characters in userName
157
- expect(email.html).toContain('John &quot;Doe&quot;');
158
- expect(email.text).toContain('John "Doe"');
159
- });
160
- });
161
- });