@bernierllc/email-sender-manager 1.0.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 (55) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +466 -0
  3. package/dist/__tests__/manager.test.d.ts +2 -0
  4. package/dist/__tests__/manager.test.d.ts.map +1 -0
  5. package/dist/__tests__/manager.test.js +344 -0
  6. package/dist/__tests__/manager.test.js.map +1 -0
  7. package/dist/__tests__/mocks/mock-database.d.ts +15 -0
  8. package/dist/__tests__/mocks/mock-database.d.ts.map +1 -0
  9. package/dist/__tests__/mocks/mock-database.js +219 -0
  10. package/dist/__tests__/mocks/mock-database.js.map +1 -0
  11. package/dist/__tests__/selection/selector.test.d.ts +2 -0
  12. package/dist/__tests__/selection/selector.test.d.ts.map +1 -0
  13. package/dist/__tests__/selection/selector.test.js +351 -0
  14. package/dist/__tests__/selection/selector.test.js.map +1 -0
  15. package/dist/__tests__/utils/domain-utils.test.d.ts +2 -0
  16. package/dist/__tests__/utils/domain-utils.test.d.ts.map +1 -0
  17. package/dist/__tests__/utils/domain-utils.test.js +91 -0
  18. package/dist/__tests__/utils/domain-utils.test.js.map +1 -0
  19. package/dist/__tests__/utils/validation-utils.test.d.ts +2 -0
  20. package/dist/__tests__/utils/validation-utils.test.d.ts.map +1 -0
  21. package/dist/__tests__/utils/validation-utils.test.js +117 -0
  22. package/dist/__tests__/utils/validation-utils.test.js.map +1 -0
  23. package/dist/database/bootstrap.d.ts +35 -0
  24. package/dist/database/bootstrap.d.ts.map +1 -0
  25. package/dist/database/bootstrap.js +183 -0
  26. package/dist/database/bootstrap.js.map +1 -0
  27. package/dist/database/schema.d.ts +13 -0
  28. package/dist/database/schema.d.ts.map +1 -0
  29. package/dist/database/schema.js +72 -0
  30. package/dist/database/schema.js.map +1 -0
  31. package/dist/index.d.ts +8 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +18 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/manager.d.ts +85 -0
  36. package/dist/manager.d.ts.map +1 -0
  37. package/dist/manager.js +469 -0
  38. package/dist/manager.js.map +1 -0
  39. package/dist/selection/selector.d.ts +27 -0
  40. package/dist/selection/selector.d.ts.map +1 -0
  41. package/dist/selection/selector.js +107 -0
  42. package/dist/selection/selector.js.map +1 -0
  43. package/dist/types.d.ts +257 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +9 -0
  46. package/dist/types.js.map +1 -0
  47. package/dist/utils/domain-utils.d.ts +21 -0
  48. package/dist/utils/domain-utils.d.ts.map +1 -0
  49. package/dist/utils/domain-utils.js +65 -0
  50. package/dist/utils/domain-utils.js.map +1 -0
  51. package/dist/utils/validation-utils.d.ts +23 -0
  52. package/dist/utils/validation-utils.d.ts.map +1 -0
  53. package/dist/utils/validation-utils.js +148 -0
  54. package/dist/utils/validation-utils.js.map +1 -0
  55. package/package.json +49 -0
@@ -0,0 +1,351 @@
1
+ /*
2
+ Copyright (c) 2025 Bernier LLC
3
+
4
+ This file is licensed to the client under a limited-use license.
5
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
6
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
7
+ */
8
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
9
+ /* global describe, it, expect, beforeEach */
10
+ import { SenderSelector } from '../../selection/selector.js';
11
+ describe('SenderSelector', () => {
12
+ let selector;
13
+ let testSenders;
14
+ beforeEach(() => {
15
+ const config = {
16
+ strategy: 'domain_match',
17
+ fallbackToDefault: true,
18
+ domainMatchingRules: [],
19
+ };
20
+ selector = new SenderSelector(config);
21
+ // Create test senders
22
+ testSenders = [
23
+ {
24
+ id: 'sender-1',
25
+ name: 'Example Sender',
26
+ fromEmail: 'noreply@example.com',
27
+ fromName: 'Example',
28
+ provider: 'sendgrid',
29
+ isVerified: true,
30
+ verificationStatus: 'verified',
31
+ isDefault: true,
32
+ isActive: true,
33
+ priority: 1,
34
+ domain: 'example.com',
35
+ createdAt: new Date('2025-01-01'),
36
+ updatedAt: new Date('2025-01-01'),
37
+ createdBy: 'admin',
38
+ lastModifiedBy: 'admin',
39
+ lastVerifiedAt: new Date('2025-01-10'),
40
+ },
41
+ {
42
+ id: 'sender-2',
43
+ name: 'App Sender',
44
+ fromEmail: 'noreply@app.example.com',
45
+ fromName: 'App',
46
+ provider: 'sendgrid',
47
+ isVerified: true,
48
+ verificationStatus: 'verified',
49
+ isDefault: false,
50
+ isActive: true,
51
+ priority: 10,
52
+ domain: 'app.example.com',
53
+ createdAt: new Date('2025-01-02'),
54
+ updatedAt: new Date('2025-01-02'),
55
+ createdBy: 'admin',
56
+ lastModifiedBy: 'admin',
57
+ lastVerifiedAt: new Date('2025-01-09'),
58
+ },
59
+ {
60
+ id: 'sender-3',
61
+ name: 'Marketing Sender',
62
+ fromEmail: 'marketing@example.com',
63
+ fromName: 'Marketing',
64
+ provider: 'mailgun',
65
+ isVerified: false,
66
+ verificationStatus: 'pending',
67
+ isDefault: false,
68
+ isActive: true,
69
+ priority: 5,
70
+ domain: 'example.com',
71
+ createdAt: new Date('2025-01-03'),
72
+ updatedAt: new Date('2025-01-03'),
73
+ createdBy: 'admin',
74
+ lastModifiedBy: 'admin',
75
+ allowedDomains: ['*.example.com', 'other.com'],
76
+ },
77
+ {
78
+ id: 'sender-4',
79
+ name: 'Support Sender',
80
+ fromEmail: 'support@example.com',
81
+ fromName: 'Support',
82
+ provider: 'sendgrid',
83
+ isVerified: true,
84
+ verificationStatus: 'verified',
85
+ isDefault: false,
86
+ isActive: true,
87
+ priority: 3,
88
+ domain: 'example.com',
89
+ createdAt: new Date('2025-01-04'),
90
+ updatedAt: new Date('2025-01-04'),
91
+ createdBy: 'admin',
92
+ lastModifiedBy: 'admin',
93
+ lastVerifiedAt: new Date('2025-01-08'),
94
+ },
95
+ ];
96
+ });
97
+ describe('selectBestSender', () => {
98
+ it('should return null for empty candidates', () => {
99
+ const result = selector.selectBestSender([], 'example.com');
100
+ expect(result).toBeNull();
101
+ });
102
+ it('should select by domain match strategy', () => {
103
+ const result = selector.selectBestSender(testSenders, 'example.com', {
104
+ strategy: 'domain_match',
105
+ });
106
+ expect(result).toBeDefined();
107
+ expect(result?.domain).toBe('example.com');
108
+ expect(result?.priority).toBe(1); // Highest priority for exact match
109
+ });
110
+ it('should select by priority strategy', () => {
111
+ const result = selector.selectBestSender(testSenders, 'example.com', {
112
+ strategy: 'priority',
113
+ });
114
+ expect(result).toBeDefined();
115
+ expect(result?.priority).toBe(1);
116
+ expect(result?.id).toBe('sender-1');
117
+ });
118
+ it('should select by round robin strategy', () => {
119
+ const candidates = [testSenders[0], testSenders[1]];
120
+ const result1 = selector.selectBestSender(candidates, 'example.com', {
121
+ strategy: 'round_robin',
122
+ });
123
+ expect(result1?.id).toBe('sender-1');
124
+ const result2 = selector.selectBestSender(candidates, 'example.com', {
125
+ strategy: 'round_robin',
126
+ });
127
+ expect(result2?.id).toBe('sender-2');
128
+ const result3 = selector.selectBestSender(candidates, 'example.com', {
129
+ strategy: 'round_robin',
130
+ });
131
+ expect(result3?.id).toBe('sender-1'); // Wraps around
132
+ });
133
+ it('should use default strategy from config', () => {
134
+ const result = selector.selectBestSender(testSenders, 'example.com');
135
+ expect(result).toBeDefined();
136
+ // Should use domain_match strategy
137
+ expect(result?.domain).toBe('example.com');
138
+ });
139
+ it('should handle unknown strategy gracefully', () => {
140
+ const result = selector.selectBestSender(testSenders, 'example.com', {
141
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
142
+ strategy: 'unknown',
143
+ });
144
+ expect(result).toBeDefined();
145
+ expect(result?.id).toBe('sender-1'); // Returns first candidate
146
+ });
147
+ });
148
+ describe('selectByDomainMatch', () => {
149
+ it('should find exact domain match', () => {
150
+ const result = selector.selectBestSender(testSenders, 'example.com', {
151
+ strategy: 'domain_match',
152
+ });
153
+ expect(result).toBeDefined();
154
+ expect(result?.domain).toBe('example.com');
155
+ expect(result?.priority).toBe(1); // Highest priority among exact matches
156
+ });
157
+ it('should find subdomain match', () => {
158
+ const result = selector.selectBestSender(testSenders, 'api.example.com', {
159
+ strategy: 'domain_match',
160
+ });
161
+ expect(result).toBeDefined();
162
+ // Should match parent domain example.com
163
+ expect(result?.domain).toBe('example.com');
164
+ });
165
+ it('should find parent domain match', () => {
166
+ const result = selector.selectBestSender(testSenders, 'app.example.com', {
167
+ strategy: 'domain_match',
168
+ });
169
+ expect(result).toBeDefined();
170
+ expect(result?.domain).toBe('app.example.com'); // Exact match first
171
+ });
172
+ it('should match allowed domains with wildcards', () => {
173
+ const result = selector.selectBestSender(testSenders, 'other.com', {
174
+ strategy: 'domain_match',
175
+ });
176
+ expect(result).toBeDefined();
177
+ // Should match sender-3 which has 'other.com' in allowedDomains
178
+ expect(result?.id).toBe('sender-3');
179
+ });
180
+ it('should fallback to priority when no domain match', () => {
181
+ const result = selector.selectBestSender(testSenders, 'unrelated.org', {
182
+ strategy: 'domain_match',
183
+ });
184
+ expect(result).toBeDefined();
185
+ expect(result?.priority).toBe(1); // Falls back to highest priority
186
+ });
187
+ });
188
+ describe('selectByPriority', () => {
189
+ it('should select sender with lowest priority number', () => {
190
+ const result = selector.selectBestSender(testSenders, 'example.com', {
191
+ strategy: 'priority',
192
+ });
193
+ expect(result?.priority).toBe(1);
194
+ expect(result?.id).toBe('sender-1');
195
+ });
196
+ it('should prefer verified senders with same priority', () => {
197
+ const candidates = [
198
+ {
199
+ ...testSenders[0],
200
+ id: 'verified',
201
+ priority: 5,
202
+ isVerified: true,
203
+ },
204
+ {
205
+ ...testSenders[1],
206
+ id: 'unverified',
207
+ priority: 5,
208
+ isVerified: false,
209
+ },
210
+ ];
211
+ const result = selector.selectBestSender(candidates, 'example.com', {
212
+ strategy: 'priority',
213
+ });
214
+ expect(result?.id).toBe('verified');
215
+ expect(result?.isVerified).toBe(true);
216
+ });
217
+ it('should prefer more recently verified senders', () => {
218
+ const candidates = [
219
+ {
220
+ ...testSenders[0],
221
+ id: 'newer',
222
+ priority: 5,
223
+ isVerified: true,
224
+ lastVerifiedAt: new Date('2025-01-15'),
225
+ },
226
+ {
227
+ ...testSenders[1],
228
+ id: 'older',
229
+ priority: 5,
230
+ isVerified: true,
231
+ lastVerifiedAt: new Date('2025-01-10'),
232
+ },
233
+ ];
234
+ const result = selector.selectBestSender(candidates, 'example.com', {
235
+ strategy: 'priority',
236
+ });
237
+ expect(result?.id).toBe('newer');
238
+ });
239
+ it('should fallback to creation order', () => {
240
+ const newer = { ...testSenders[0] };
241
+ delete newer.lastVerifiedAt;
242
+ const older = { ...testSenders[1] };
243
+ delete older.lastVerifiedAt;
244
+ const candidates = [
245
+ {
246
+ ...newer,
247
+ id: 'newer',
248
+ priority: 5,
249
+ isVerified: true,
250
+ createdAt: new Date('2025-01-15'),
251
+ },
252
+ {
253
+ ...older,
254
+ id: 'older',
255
+ priority: 5,
256
+ isVerified: true,
257
+ createdAt: new Date('2025-01-10'),
258
+ },
259
+ ];
260
+ const result = selector.selectBestSender(candidates, 'example.com', {
261
+ strategy: 'priority',
262
+ });
263
+ expect(result?.id).toBe('older'); // Earlier creation date
264
+ });
265
+ });
266
+ describe('selectRoundRobin', () => {
267
+ it('should rotate through candidates', () => {
268
+ const candidates = [testSenders[0], testSenders[1], testSenders[2]];
269
+ const results = [
270
+ selector.selectBestSender(candidates, 'test.com', { strategy: 'round_robin' }),
271
+ selector.selectBestSender(candidates, 'test.com', { strategy: 'round_robin' }),
272
+ selector.selectBestSender(candidates, 'test.com', { strategy: 'round_robin' }),
273
+ selector.selectBestSender(candidates, 'test.com', { strategy: 'round_robin' }),
274
+ ];
275
+ expect(results[0]?.id).toBe('sender-1');
276
+ expect(results[1]?.id).toBe('sender-2');
277
+ expect(results[2]?.id).toBe('sender-3');
278
+ expect(results[3]?.id).toBe('sender-1'); // Wraps around
279
+ });
280
+ it('should maintain separate counters per domain', () => {
281
+ const candidates = [testSenders[0], testSenders[1]];
282
+ const result1a = selector.selectBestSender(candidates, 'domain-a.com', {
283
+ strategy: 'round_robin',
284
+ });
285
+ const result2 = selector.selectBestSender(candidates, 'domain-b.com', {
286
+ strategy: 'round_robin',
287
+ });
288
+ const result1b = selector.selectBestSender(candidates, 'domain-a.com', {
289
+ strategy: 'round_robin',
290
+ });
291
+ expect(result1a?.id).toBe('sender-1');
292
+ expect(result2?.id).toBe('sender-1'); // Different domain, starts over
293
+ expect(result1b?.id).toBe('sender-2'); // Same domain, continues
294
+ });
295
+ });
296
+ describe('filterCandidates', () => {
297
+ it('should filter by verification status', () => {
298
+ const filtered = selector.filterCandidates(testSenders, {
299
+ allowUnverified: false,
300
+ });
301
+ expect(filtered.length).toBe(3); // Only verified senders
302
+ expect(filtered.every((s) => s.isVerified)).toBe(true);
303
+ });
304
+ it('should allow unverified when specified', () => {
305
+ const filtered = selector.filterCandidates(testSenders, {
306
+ allowUnverified: true,
307
+ });
308
+ expect(filtered.length).toBe(4); // All senders
309
+ });
310
+ it('should filter by provider', () => {
311
+ const filtered = selector.filterCandidates(testSenders, {
312
+ provider: 'sendgrid',
313
+ });
314
+ expect(filtered.length).toBe(3); // Only SendGrid senders
315
+ expect(filtered.every((s) => s.provider === 'sendgrid')).toBe(true);
316
+ });
317
+ it('should exclude specific sender IDs', () => {
318
+ const filtered = selector.filterCandidates(testSenders, {
319
+ excludeIds: ['sender-1', 'sender-3'],
320
+ });
321
+ expect(filtered.length).toBe(2);
322
+ expect(filtered.find((s) => s.id === 'sender-1')).toBeUndefined();
323
+ expect(filtered.find((s) => s.id === 'sender-3')).toBeUndefined();
324
+ });
325
+ it('should apply multiple filters', () => {
326
+ const filtered = selector.filterCandidates(testSenders, {
327
+ allowUnverified: false,
328
+ provider: 'sendgrid',
329
+ excludeIds: ['sender-1'],
330
+ });
331
+ expect(filtered.length).toBe(2); // Verified, SendGrid, not excluded
332
+ expect(filtered.every((s) => s.isVerified && s.provider === 'sendgrid')).toBe(true);
333
+ expect(filtered.find((s) => s.id === 'sender-1')).toBeUndefined();
334
+ });
335
+ it('should handle empty exclude list', () => {
336
+ const filtered = selector.filterCandidates(testSenders, {
337
+ excludeIds: [],
338
+ allowUnverified: true,
339
+ });
340
+ expect(filtered.length).toBe(4); // No exclusions
341
+ });
342
+ it('should return copy of candidates when no filters', () => {
343
+ const filtered = selector.filterCandidates(testSenders, {
344
+ allowUnverified: true,
345
+ });
346
+ expect(filtered.length).toBe(4);
347
+ expect(filtered).not.toBe(testSenders); // Different array instance
348
+ });
349
+ });
350
+ });
351
+ //# sourceMappingURL=selector.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.test.js","sourceRoot":"","sources":["../../../src/__tests__/selection/selector.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=domain-utils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-utils.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/utils/domain-utils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,91 @@
1
+ /*
2
+ Copyright (c) 2025 Bernier LLC
3
+
4
+ This file is licensed to the client under a limited-use license.
5
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
6
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
7
+ */
8
+ // Test framework functions are globally available via Jest
9
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
10
+ /* global describe, it, expect */
11
+ import { extractDomain, isDomainMatch, isValidEmail, getParentDomain, isSubdomainOf, } from '../../utils/domain-utils.js';
12
+ describe('domain-utils', () => {
13
+ describe('extractDomain', () => {
14
+ it('should extract domain from valid email', () => {
15
+ expect(extractDomain('user@example.com')).toBe('example.com');
16
+ expect(extractDomain('admin@app.example.com')).toBe('app.example.com');
17
+ expect(extractDomain('test@sub.domain.example.org')).toBe('sub.domain.example.org');
18
+ });
19
+ it('should convert domain to lowercase', () => {
20
+ expect(extractDomain('User@Example.COM')).toBe('example.com');
21
+ expect(extractDomain('ADMIN@APP.EXAMPLE.COM')).toBe('app.example.com');
22
+ });
23
+ it('should throw error for invalid email format', () => {
24
+ expect(() => extractDomain('invalid-email')).toThrow('Invalid email format');
25
+ expect(() => extractDomain('no-at-sign')).toThrow('Invalid email format');
26
+ expect(() => extractDomain('@example.com')).toThrow('Invalid email format');
27
+ });
28
+ });
29
+ describe('isDomainMatch', () => {
30
+ it('should match exact domains', () => {
31
+ expect(isDomainMatch('example.com', 'example.com')).toBe(true);
32
+ expect(isDomainMatch('Example.COM', 'example.com')).toBe(true);
33
+ });
34
+ it('should match subdomains', () => {
35
+ expect(isDomainMatch('app.example.com', 'example.com')).toBe(true);
36
+ expect(isDomainMatch('api.app.example.com', 'app.example.com')).toBe(true);
37
+ });
38
+ it('should match parent domains', () => {
39
+ expect(isDomainMatch('example.com', 'app.example.com')).toBe(true);
40
+ });
41
+ it('should support wildcard patterns', () => {
42
+ expect(isDomainMatch('app.example.com', '*.example.com')).toBe(true);
43
+ expect(isDomainMatch('api.example.com', '*.example.com')).toBe(true);
44
+ expect(isDomainMatch('example.com', '*.example.com')).toBe(false);
45
+ });
46
+ it('should not match unrelated domains', () => {
47
+ expect(isDomainMatch('example.com', 'other.com')).toBe(false);
48
+ expect(isDomainMatch('app.example.com', 'other.com')).toBe(false);
49
+ });
50
+ });
51
+ describe('isValidEmail', () => {
52
+ it('should validate correct email addresses', () => {
53
+ expect(isValidEmail('user@example.com')).toBe(true);
54
+ expect(isValidEmail('admin.test@app.example.org')).toBe(true);
55
+ expect(isValidEmail('user+tag@example.co.uk')).toBe(true);
56
+ });
57
+ it('should reject invalid email addresses', () => {
58
+ expect(isValidEmail('invalid')).toBe(false);
59
+ expect(isValidEmail('no-at-sign')).toBe(false);
60
+ expect(isValidEmail('@example.com')).toBe(false);
61
+ expect(isValidEmail('user@')).toBe(false);
62
+ expect(isValidEmail('user @example.com')).toBe(false);
63
+ });
64
+ });
65
+ describe('getParentDomain', () => {
66
+ it('should get parent domain from subdomain', () => {
67
+ expect(getParentDomain('app.example.com')).toBe('example.com');
68
+ expect(getParentDomain('api.app.example.com')).toBe('app.example.com');
69
+ });
70
+ it('should return null for top-level domains', () => {
71
+ expect(getParentDomain('example.com')).toBe(null);
72
+ expect(getParentDomain('example.org')).toBe(null);
73
+ });
74
+ });
75
+ describe('isSubdomainOf', () => {
76
+ it('should identify subdomains correctly', () => {
77
+ expect(isSubdomainOf('app.example.com', 'example.com')).toBe(true);
78
+ expect(isSubdomainOf('api.app.example.com', 'example.com')).toBe(true);
79
+ expect(isSubdomainOf('api.app.example.com', 'app.example.com')).toBe(true);
80
+ });
81
+ it('should be case insensitive', () => {
82
+ expect(isSubdomainOf('APP.Example.COM', 'example.com')).toBe(true);
83
+ });
84
+ it('should return false for non-subdomains', () => {
85
+ expect(isSubdomainOf('example.com', 'example.com')).toBe(false);
86
+ expect(isSubdomainOf('example.com', 'app.example.com')).toBe(false);
87
+ expect(isSubdomainOf('other.com', 'example.com')).toBe(false);
88
+ });
89
+ });
90
+ });
91
+ //# sourceMappingURL=domain-utils.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-utils.test.js","sourceRoot":"","sources":["../../../src/__tests__/utils/domain-utils.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=validation-utils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-utils.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/utils/validation-utils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,117 @@
1
+ /*
2
+ Copyright (c) 2025 Bernier LLC
3
+
4
+ This file is licensed to the client under a limited-use license.
5
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
6
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
7
+ */
8
+ import { validateCreateSenderRequest, validateDomainAllowed, validateSenderConfiguration, } from '../../utils/validation-utils.js';
9
+ describe('validation-utils', () => {
10
+ describe('validateCreateSenderRequest', () => {
11
+ const validRequest = {
12
+ name: 'Test Sender',
13
+ fromEmail: 'test@example.com',
14
+ fromName: 'Test User',
15
+ provider: 'sendgrid',
16
+ createdBy: 'user123',
17
+ };
18
+ it('should validate a correct request', () => {
19
+ const result = validateCreateSenderRequest(validRequest);
20
+ expect(result.errors).toHaveLength(0);
21
+ });
22
+ it('should detect missing required fields', () => {
23
+ const invalidRequest = {
24
+ ...validRequest,
25
+ name: '',
26
+ };
27
+ const result = validateCreateSenderRequest(invalidRequest);
28
+ expect(result.errors.length).toBeGreaterThan(0);
29
+ expect(result.errors.some((e) => e.field === 'name')).toBe(true);
30
+ });
31
+ it('should detect invalid email format', () => {
32
+ const invalidRequest = {
33
+ ...validRequest,
34
+ fromEmail: 'invalid-email',
35
+ };
36
+ const result = validateCreateSenderRequest(invalidRequest);
37
+ expect(result.errors.some((e) => e.field === 'fromEmail')).toBe(true);
38
+ expect(result.errors.some((e) => e.code === 'INVALID_FORMAT')).toBe(true);
39
+ });
40
+ it('should detect invalid reply-to email', () => {
41
+ const invalidRequest = {
42
+ ...validRequest,
43
+ replyToEmail: 'invalid',
44
+ };
45
+ const result = validateCreateSenderRequest(invalidRequest);
46
+ expect(result.errors.some((e) => e.field === 'replyToEmail')).toBe(true);
47
+ });
48
+ it('should detect invalid priority', () => {
49
+ const invalidRequest = {
50
+ ...validRequest,
51
+ priority: -1,
52
+ };
53
+ const result = validateCreateSenderRequest(invalidRequest);
54
+ expect(result.errors.some((e) => e.field === 'priority')).toBe(true);
55
+ });
56
+ it('should warn about empty allowed domains', () => {
57
+ const requestWithEmptyDomains = {
58
+ ...validRequest,
59
+ allowedDomains: [],
60
+ };
61
+ const result = validateCreateSenderRequest(requestWithEmptyDomains);
62
+ expect(result.warnings.some((w) => w.field === 'allowedDomains')).toBe(true);
63
+ });
64
+ it('should warn about missing reply-to email', () => {
65
+ const result = validateCreateSenderRequest(validRequest);
66
+ expect(result.warnings.some((w) => w.field === 'replyToEmail')).toBe(true);
67
+ });
68
+ });
69
+ describe('validateDomainAllowed', () => {
70
+ it('should allow any domain when no restrictions', () => {
71
+ const result = validateDomainAllowed('test@example.com', undefined);
72
+ expect(result.allowed).toBe(true);
73
+ const result2 = validateDomainAllowed('test@example.com', []);
74
+ expect(result2.allowed).toBe(true);
75
+ });
76
+ it('should allow exact domain match', () => {
77
+ const result = validateDomainAllowed('test@example.com', ['example.com']);
78
+ expect(result.allowed).toBe(true);
79
+ });
80
+ it('should allow subdomain match', () => {
81
+ const result = validateDomainAllowed('test@app.example.com', ['example.com']);
82
+ expect(result.allowed).toBe(true);
83
+ });
84
+ it('should reject non-matching domain', () => {
85
+ const result = validateDomainAllowed('test@other.com', ['example.com']);
86
+ expect(result.allowed).toBe(false);
87
+ expect(result.reason).toContain('not in allowed domains');
88
+ });
89
+ it('should handle invalid email format', () => {
90
+ const result = validateDomainAllowed('invalid-email', ['example.com']);
91
+ expect(result.allowed).toBe(false);
92
+ expect(result.reason).toContain('Invalid email format');
93
+ });
94
+ });
95
+ describe('validateSenderConfiguration', () => {
96
+ it('should validate correct configuration', () => {
97
+ const result = validateSenderConfiguration('test@example.com', ['example.com']);
98
+ expect(result.valid).toBe(true);
99
+ expect(result.errors).toHaveLength(0);
100
+ });
101
+ it('should detect invalid email format', () => {
102
+ const result = validateSenderConfiguration('invalid', ['example.com']);
103
+ expect(result.valid).toBe(false);
104
+ expect(result.errors.some((e) => e.code === 'INVALID_FORMAT')).toBe(true);
105
+ });
106
+ it('should detect domain not allowed', () => {
107
+ const result = validateSenderConfiguration('test@other.com', ['example.com']);
108
+ expect(result.valid).toBe(false);
109
+ expect(result.errors.some((e) => e.code === 'DOMAIN_NOT_ALLOWED')).toBe(true);
110
+ });
111
+ it('should allow any domain when no restrictions', () => {
112
+ const result = validateSenderConfiguration('test@anything.com', undefined);
113
+ expect(result.valid).toBe(true);
114
+ });
115
+ });
116
+ });
117
+ //# sourceMappingURL=validation-utils.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-utils.test.js","sourceRoot":"","sources":["../../../src/__tests__/utils/validation-utils.test.ts"],"names":[],"mappings":"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"}