@bernierllc/sender-identity-verification 1.0.0

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 (51) hide show
  1. package/.env.test +8 -0
  2. package/.eslintrc.js +30 -0
  3. package/README.md +376 -0
  4. package/__tests__/SenderIdentityVerification.test.ts +461 -0
  5. package/__tests__/__mocks__/fetch-mock.ts +156 -0
  6. package/__tests__/additional-coverage.test.ts +129 -0
  7. package/__tests__/additional-error-coverage.test.ts +483 -0
  8. package/__tests__/branch-coverage.test.ts +509 -0
  9. package/__tests__/config.test.ts +119 -0
  10. package/__tests__/error-handling.test.ts +321 -0
  11. package/__tests__/final-branch-coverage.test.ts +372 -0
  12. package/__tests__/integration.real-api.test.ts +295 -0
  13. package/__tests__/providers.test.ts +331 -0
  14. package/__tests__/service-coverage.test.ts +412 -0
  15. package/dist/SenderIdentityVerification.d.ts +72 -0
  16. package/dist/SenderIdentityVerification.js +643 -0
  17. package/dist/config.d.ts +31 -0
  18. package/dist/config.js +38 -0
  19. package/dist/errors.d.ts +27 -0
  20. package/dist/errors.js +61 -0
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.js +21 -0
  23. package/dist/providers/MailgunProvider.d.ts +13 -0
  24. package/dist/providers/MailgunProvider.js +35 -0
  25. package/dist/providers/SESProvider.d.ts +12 -0
  26. package/dist/providers/SESProvider.js +47 -0
  27. package/dist/providers/SMTPProvider.d.ts +12 -0
  28. package/dist/providers/SMTPProvider.js +30 -0
  29. package/dist/providers/SendGridProvider.d.ts +19 -0
  30. package/dist/providers/SendGridProvider.js +98 -0
  31. package/dist/templates/verification-email.d.ts +9 -0
  32. package/dist/templates/verification-email.js +67 -0
  33. package/dist/types.d.ts +139 -0
  34. package/dist/types.js +33 -0
  35. package/dist/utils/domain-extractor.d.ts +4 -0
  36. package/dist/utils/domain-extractor.js +20 -0
  37. package/jest.config.cjs +33 -0
  38. package/package.json +60 -0
  39. package/src/SenderIdentityVerification.ts +796 -0
  40. package/src/config.ts +81 -0
  41. package/src/errors.ts +64 -0
  42. package/src/global.d.ts +24 -0
  43. package/src/index.ts +24 -0
  44. package/src/providers/MailgunProvider.ts +35 -0
  45. package/src/providers/SESProvider.ts +51 -0
  46. package/src/providers/SMTPProvider.ts +29 -0
  47. package/src/providers/SendGridProvider.ts +108 -0
  48. package/src/templates/verification-email.ts +67 -0
  49. package/src/types.ts +163 -0
  50. package/src/utils/domain-extractor.ts +18 -0
  51. package/tsconfig.json +22 -0
@@ -0,0 +1,295 @@
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
+
9
+ /**
10
+ * Real API Integration Tests
11
+ *
12
+ * These tests use REAL API credentials and make REAL API calls to:
13
+ * - SendGrid for sender verification
14
+ * - Name.com for DNS validation
15
+ *
16
+ * Run with: npm run test:integration
17
+ *
18
+ * Prerequisites:
19
+ * - .env.test file with valid API keys
20
+ * - Access to bernierllc.com domain
21
+ */
22
+
23
+ import * as dotenv from 'dotenv';
24
+ import * as path from 'path';
25
+ import { SendGridProvider } from '../src/providers/SendGridProvider';
26
+ import { SenderIdentity, EmailProvider, SenderStatus } from '../src/types';
27
+
28
+ // Load test environment variables
29
+ dotenv.config({ path: path.join(__dirname, '..', '.env.test') });
30
+
31
+ // Skip these tests if API keys are not configured OR if not explicitly enabled
32
+ // Set RUN_INTEGRATION_TESTS=true to run these tests
33
+ const SKIP_INTEGRATION_TESTS = !process.env.RUN_INTEGRATION_TESTS || !process.env.SENDGRID_API_KEY || !process.env.TESTING_DOMAIN;
34
+
35
+ describe('Real API Integration Tests', () => {
36
+ if (SKIP_INTEGRATION_TESTS) {
37
+ it.skip('Skipping integration tests - API keys not configured', () => {});
38
+ return;
39
+ }
40
+
41
+ const testDomain = process.env.TESTING_DOMAIN!;
42
+ const testEmail = `test-${Date.now()}@${testDomain}`;
43
+
44
+ const mockSender: SenderIdentity = {
45
+ id: `test-${Date.now()}`,
46
+ email: testEmail,
47
+ name: 'Test Sender',
48
+ replyToEmail: testEmail,
49
+ replyToName: 'Test Reply',
50
+ domain: testDomain,
51
+ provider: EmailProvider.SENDGRID,
52
+ status: SenderStatus.PENDING,
53
+ isDefault: false,
54
+ isActive: true,
55
+ verificationAttempts: 0,
56
+ createdAt: new Date(),
57
+ updatedAt: new Date()
58
+ };
59
+
60
+ // Move provider to outer scope so it's accessible to all test suites
61
+ let provider: SendGridProvider;
62
+ let createdSenderId: string | null = null;
63
+
64
+ beforeAll(() => {
65
+ provider = new SendGridProvider(process.env.SENDGRID_API_KEY!);
66
+ });
67
+
68
+ describe('SendGrid Real API', () => {
69
+ afterAll(async () => {
70
+ // Cleanup: Delete the test sender from SendGrid
71
+ if (createdSenderId) {
72
+ try {
73
+ await fetch(`https://api.sendgrid.com/v3/verified_senders/${createdSenderId}`, {
74
+ method: 'DELETE',
75
+ headers: {
76
+ 'Authorization': `Bearer ${process.env.SENDGRID_API_KEY}`,
77
+ 'Content-Type': 'application/json'
78
+ }
79
+ });
80
+ console.log(`✅ Cleaned up SendGrid sender: ${createdSenderId}`);
81
+ } catch (error) {
82
+ console.warn(`⚠️ Failed to cleanup SendGrid sender: ${error}`);
83
+ }
84
+ }
85
+ });
86
+
87
+ it('should successfully create sender with real SendGrid API', async () => {
88
+ const result = await provider.verifySender(mockSender);
89
+
90
+ console.log('📤 SendGrid Response:', JSON.stringify(result, null, 2));
91
+
92
+ expect(result.success).toBe(true);
93
+ expect(result.data).toBeDefined();
94
+ expect(result.data?.providerId).toBeDefined();
95
+
96
+ // Store for cleanup
97
+ if (result.data?.providerId) {
98
+ createdSenderId = result.data.providerId;
99
+ console.log(`📝 Created SendGrid sender ID: ${createdSenderId}`);
100
+ }
101
+ }, 30000); // 30 second timeout for real API call
102
+
103
+ it('should handle invalid API key with real SendGrid API', async () => {
104
+ const invalidProvider = new SendGridProvider('invalid-key-12345');
105
+ const result = await invalidProvider.verifySender(mockSender);
106
+
107
+ console.log('🔒 Invalid Key Response:', JSON.stringify(result, null, 2));
108
+
109
+ expect(result.success).toBe(false);
110
+ expect(result.error).toBeDefined();
111
+ // SendGrid returns specific error messages for auth failures
112
+ expect(
113
+ result.error?.includes('authorization') ||
114
+ result.error?.includes('Unauthorized') ||
115
+ result.error?.includes('API')
116
+ ).toBe(true);
117
+ }, 30000);
118
+
119
+ it('should return actual SendGrid metadata', async () => {
120
+ const result = await provider.verifySender({
121
+ ...mockSender,
122
+ email: `test-metadata-${Date.now()}@${testDomain}`
123
+ });
124
+
125
+ if (result.success && result.data) {
126
+ console.log('📊 SendGrid Metadata:', JSON.stringify(result.data.metadata, null, 2));
127
+
128
+ expect(result.data.metadata).toBeDefined();
129
+ expect(typeof result.data.metadata).toBe('object');
130
+
131
+ // Store for cleanup if created
132
+ if (result.data.providerId && result.data.providerId !== createdSenderId) {
133
+ // Cleanup this one too
134
+ setTimeout(async () => {
135
+ try {
136
+ await fetch(`https://api.sendgrid.com/v3/verified_senders/${result.data!.providerId}`, {
137
+ method: 'DELETE',
138
+ headers: {
139
+ 'Authorization': `Bearer ${process.env.SENDGRID_API_KEY}`,
140
+ 'Content-Type': 'application/json'
141
+ }
142
+ });
143
+ } catch (e) {
144
+ // Ignore cleanup errors
145
+ }
146
+ }, 1000);
147
+ }
148
+ }
149
+ }, 30000);
150
+ });
151
+
152
+ describe('Domain Verification with Real DNS', () => {
153
+ it('should extract correct domain from real email', () => {
154
+ const email = `sender@${testDomain}`;
155
+ const parts = email.split('@');
156
+
157
+ expect(parts.length).toBe(2);
158
+ expect(parts[1]).toBe(testDomain);
159
+ });
160
+
161
+ it('should validate bernardllc.com is a real domain', async () => {
162
+ // Test DNS resolution using native DNS lookup
163
+ const response = await fetch(`https://dns.google/resolve?name=${testDomain}&type=MX`);
164
+ const data = await response.json();
165
+
166
+ console.log(`🌐 DNS Records for ${testDomain}:`, JSON.stringify(data, null, 2));
167
+
168
+ expect(response.ok).toBe(true);
169
+ expect(data).toBeDefined();
170
+ // Real domain should have some DNS records
171
+ expect(data.Status).toBeDefined();
172
+ }, 30000);
173
+ });
174
+
175
+ describe('Error Handling with Real APIs', () => {
176
+ it('should handle empty API key', async () => {
177
+ const emptyProvider = new SendGridProvider('');
178
+ const result = await emptyProvider.verifySender(mockSender);
179
+
180
+ expect(result.success).toBe(false);
181
+ expect(result.error).toContain('not configured');
182
+ });
183
+
184
+ it('should handle malformed email address', async () => {
185
+ const result = await provider.verifySender({
186
+ ...mockSender,
187
+ email: 'not-an-email'
188
+ });
189
+
190
+ // SendGrid should reject this
191
+ expect(result.success).toBe(false);
192
+ if (!result.success) {
193
+ console.log('❌ Malformed Email Error:', result.error);
194
+ }
195
+ }, 30000);
196
+
197
+ it('should handle network timeout gracefully', async () => {
198
+ // Create a provider with a short timeout (simulated by using invalid endpoint)
199
+ const result = await provider.verifySender({
200
+ ...mockSender,
201
+ email: `timeout-test-${Date.now()}@${testDomain}`
202
+ });
203
+
204
+ // Should eventually complete (success or fail, but not hang)
205
+ expect(result).toBeDefined();
206
+ expect(typeof result.success).toBe('boolean');
207
+ }, 10000);
208
+ });
209
+
210
+ describe('Real-World Scenarios', () => {
211
+ it('should verify sender with real domain ownership check', async () => {
212
+ // This test demonstrates the full workflow:
213
+ // 1. Create sender in SendGrid
214
+ // 2. SendGrid sends verification email
215
+ // 3. Domain must be real and accessible
216
+
217
+ const uniqueEmail = `verification-${Date.now()}@${testDomain}`;
218
+ const result = await provider.verifySender({
219
+ ...mockSender,
220
+ email: uniqueEmail
221
+ });
222
+
223
+ console.log('🔍 Verification Flow Result:', JSON.stringify(result, null, 2));
224
+
225
+ // Should succeed in creating the verification request
226
+ expect(result).toBeDefined();
227
+
228
+ // If successful, cleanup
229
+ if (result.success && result.data?.providerId) {
230
+ setTimeout(async () => {
231
+ try {
232
+ await fetch(`https://api.sendgrid.com/v3/verified_senders/${result.data!.providerId}`, {
233
+ method: 'DELETE',
234
+ headers: {
235
+ 'Authorization': `Bearer ${process.env.SENDGRID_API_KEY}`,
236
+ 'Content-Type': 'application/json'
237
+ }
238
+ });
239
+ } catch (e) {
240
+ // Ignore cleanup errors
241
+ }
242
+ }, 1000);
243
+ }
244
+ }, 30000);
245
+
246
+ it('should handle multiple concurrent verification requests', async () => {
247
+ const senders = [
248
+ `concurrent-1-${Date.now()}@${testDomain}`,
249
+ `concurrent-2-${Date.now()}@${testDomain}`,
250
+ `concurrent-3-${Date.now()}@${testDomain}`
251
+ ];
252
+
253
+ const results = await Promise.all(
254
+ senders.map(email =>
255
+ provider.verifySender({
256
+ ...mockSender,
257
+ email
258
+ })
259
+ )
260
+ );
261
+
262
+ console.log('🔄 Concurrent Results:', results.map((r: typeof results[0]) => ({
263
+ success: r.success,
264
+ providerId: r.success ? r.data?.providerId : undefined,
265
+ error: !r.success ? r.error : undefined
266
+ })));
267
+
268
+ // All should complete (success or failure)
269
+ expect(results.length).toBe(3);
270
+ results.forEach((result: typeof results[0]) => {
271
+ expect(result).toBeDefined();
272
+ expect(typeof result.success).toBe('boolean');
273
+ });
274
+
275
+ // Cleanup any successful creations
276
+ setTimeout(async () => {
277
+ for (const result of results) {
278
+ if (result.success && result.data?.providerId) {
279
+ try {
280
+ await fetch(`https://api.sendgrid.com/v3/verified_senders/${result.data.providerId}`, {
281
+ method: 'DELETE',
282
+ headers: {
283
+ 'Authorization': `Bearer ${process.env.SENDGRID_API_KEY}`,
284
+ 'Content-Type': 'application/json'
285
+ }
286
+ });
287
+ } catch (e) {
288
+ // Ignore cleanup errors
289
+ }
290
+ }
291
+ }
292
+ }, 2000);
293
+ }, 60000); // 60 seconds for concurrent operations
294
+ });
295
+ });
@@ -0,0 +1,331 @@
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
+
9
+ import { SendGridProvider } from '../src/providers/SendGridProvider';
10
+ import { MailgunProvider } from '../src/providers/MailgunProvider';
11
+ import { SESProvider } from '../src/providers/SESProvider';
12
+ import { SMTPProvider } from '../src/providers/SMTPProvider';
13
+ import { SenderIdentity, EmailProvider, SenderStatus } from '../src/types';
14
+ import { createFetchMock } from './__mocks__/fetch-mock';
15
+
16
+ describe('Provider Tests with HTTP Mocking', () => {
17
+ const mockSender: SenderIdentity = {
18
+ id: 'test-123',
19
+ email: 'test@example.com',
20
+ name: 'Test User',
21
+ replyToEmail: 'reply@example.com',
22
+ replyToName: 'Reply User',
23
+ domain: 'example.com',
24
+ provider: EmailProvider.SENDGRID,
25
+ status: SenderStatus.PENDING,
26
+ isDefault: false,
27
+ isActive: true,
28
+ verificationAttempts: 0,
29
+ createdAt: new Date(),
30
+ updatedAt: new Date()
31
+ };
32
+
33
+ let fetchMock: ReturnType<typeof createFetchMock>;
34
+ let originalFetch: typeof fetch;
35
+
36
+ beforeEach(() => {
37
+ fetchMock = createFetchMock();
38
+ originalFetch = global.fetch;
39
+ global.fetch = fetchMock.getFetchMock() as typeof fetch;
40
+ });
41
+
42
+ afterEach(() => {
43
+ global.fetch = originalFetch;
44
+ fetchMock.clear();
45
+ });
46
+
47
+ describe('SendGridProvider', () => {
48
+ describe('Success Cases', () => {
49
+ it('should successfully verify sender with valid API key', async () => {
50
+ const provider = new SendGridProvider('test-api-key');
51
+
52
+ fetchMock.mockSuccess(
53
+ 'https://api.sendgrid.com/v3/verified_senders',
54
+ {
55
+ id: 'sendgrid-123',
56
+ from_email: 'test@example.com',
57
+ verified: false,
58
+ nickname: 'Test User'
59
+ }
60
+ );
61
+
62
+ const result = await provider.verifySender(mockSender);
63
+
64
+ expect(result.success).toBe(true);
65
+ expect(result.data).toBeDefined();
66
+ expect(result.data?.providerId).toBe('sendgrid-123');
67
+ expect(result.data?.metadata).toBeDefined();
68
+
69
+ const history = fetchMock.getCallHistory();
70
+ expect(history).toHaveLength(1);
71
+ expect(history[0].url).toBe('https://api.sendgrid.com/v3/verified_senders');
72
+ expect(history[0].method).toBe('POST');
73
+ });
74
+
75
+ it('should include sender details in request', async () => {
76
+ const provider = new SendGridProvider('test-api-key');
77
+
78
+ fetchMock.mockSuccess(
79
+ 'https://api.sendgrid.com/v3/verified_senders',
80
+ { id: 'sendgrid-456', verified: false }
81
+ );
82
+
83
+ await provider.verifySender(mockSender);
84
+
85
+ const history = fetchMock.getCallHistory();
86
+ const requestBody = JSON.parse(history[0].body || '{}');
87
+
88
+ expect(requestBody.nickname).toBe('Test User');
89
+ expect(requestBody.from_email).toBe('test@example.com');
90
+ expect(requestBody.from_name).toBe('Test User');
91
+ expect(requestBody.reply_to).toBe('reply@example.com');
92
+ expect(requestBody.reply_to_name).toBe('Reply User');
93
+ });
94
+ });
95
+
96
+ describe('Error Cases', () => {
97
+ it('should fail without API key', async () => {
98
+ const provider = new SendGridProvider('');
99
+ const result = await provider.verifySender(mockSender);
100
+
101
+ expect(result.success).toBe(false);
102
+ expect(result.error).toContain('API key');
103
+ });
104
+
105
+ it('should handle 401 Unauthorized error', async () => {
106
+ const provider = new SendGridProvider('invalid-key');
107
+
108
+ fetchMock.mockError(
109
+ 'https://api.sendgrid.com/v3/verified_senders',
110
+ 401,
111
+ {
112
+ errors: [{ message: 'Unauthorized - Invalid API key' }]
113
+ }
114
+ );
115
+
116
+ const result = await provider.verifySender(mockSender);
117
+
118
+ expect(result.success).toBe(false);
119
+ expect(result.error).toBe('SendGrid verification failed');
120
+ expect(result.errors).toContain('Unauthorized - Invalid API key');
121
+ });
122
+
123
+ it('should handle 403 Forbidden error', async () => {
124
+ const provider = new SendGridProvider('test-key');
125
+
126
+ fetchMock.mockError(
127
+ 'https://api.sendgrid.com/v3/verified_senders',
128
+ 403,
129
+ {
130
+ errors: [{ message: 'Forbidden - Insufficient permissions' }]
131
+ }
132
+ );
133
+
134
+ const result = await provider.verifySender(mockSender);
135
+
136
+ expect(result.success).toBe(false);
137
+ expect(result.error).toBe('SendGrid verification failed');
138
+ expect(result.errors).toContain('Forbidden - Insufficient permissions');
139
+ });
140
+
141
+ it('should handle 404 Not Found error', async () => {
142
+ const provider = new SendGridProvider('test-key');
143
+
144
+ fetchMock.mockError(
145
+ 'https://api.sendgrid.com/v3/verified_senders',
146
+ 404,
147
+ {
148
+ errors: [{ message: 'Resource not found' }]
149
+ }
150
+ );
151
+
152
+ const result = await provider.verifySender(mockSender);
153
+
154
+ expect(result.success).toBe(false);
155
+ expect(result.error).toBe('SendGrid verification failed');
156
+ expect(result.errors).toContain('Resource not found');
157
+ });
158
+
159
+ it('should handle 429 Rate Limit error', async () => {
160
+ const provider = new SendGridProvider('test-key');
161
+
162
+ fetchMock.mockError(
163
+ 'https://api.sendgrid.com/v3/verified_senders',
164
+ 429,
165
+ {
166
+ errors: [{ message: 'Rate limit exceeded' }]
167
+ }
168
+ );
169
+
170
+ const result = await provider.verifySender(mockSender);
171
+
172
+ expect(result.success).toBe(false);
173
+ expect(result.error).toBe('SendGrid verification failed');
174
+ expect(result.errors).toContain('Rate limit exceeded');
175
+ });
176
+
177
+ it('should handle 500 Internal Server Error', async () => {
178
+ const provider = new SendGridProvider('test-key');
179
+
180
+ fetchMock.mockError(
181
+ 'https://api.sendgrid.com/v3/verified_senders',
182
+ 500,
183
+ {
184
+ errors: [{ message: 'Internal server error' }]
185
+ }
186
+ );
187
+
188
+ const result = await provider.verifySender(mockSender);
189
+
190
+ expect(result.success).toBe(false);
191
+ expect(result.error).toBe('SendGrid verification failed');
192
+ expect(result.errors).toContain('Internal server error');
193
+ });
194
+
195
+ it('should handle network errors', async () => {
196
+ const provider = new SendGridProvider('test-key');
197
+
198
+ fetchMock.mockNetworkError(
199
+ 'https://api.sendgrid.com/v3/verified_senders',
200
+ 'Connection timeout'
201
+ );
202
+
203
+ const result = await provider.verifySender(mockSender);
204
+
205
+ expect(result.success).toBe(false);
206
+ expect(result.error).toBe('SendGrid verification failed');
207
+ expect(result.errors).toContain('Connection timeout');
208
+ });
209
+
210
+ it('should handle connection refused', async () => {
211
+ const provider = new SendGridProvider('test-key');
212
+
213
+ fetchMock.mockNetworkError(
214
+ 'https://api.sendgrid.com/v3/verified_senders',
215
+ 'Connection refused'
216
+ );
217
+
218
+ const result = await provider.verifySender(mockSender);
219
+
220
+ expect(result.success).toBe(false);
221
+ expect(result.error).toBe('SendGrid verification failed');
222
+ expect(result.errors).toContain('Connection refused');
223
+ });
224
+
225
+ it('should handle malformed response', async () => {
226
+ const provider = new SendGridProvider('test-key');
227
+
228
+ fetchMock.mockError(
229
+ 'https://api.sendgrid.com/v3/verified_senders',
230
+ 400,
231
+ {} // Missing errors array
232
+ );
233
+
234
+ const result = await provider.verifySender(mockSender);
235
+
236
+ expect(result.success).toBe(false);
237
+ expect(result.error).toBe('SendGrid verification failed');
238
+ expect(result.errors).toContain('SendGrid API error');
239
+ });
240
+
241
+ it('should handle response without ID', async () => {
242
+ const provider = new SendGridProvider('test-key');
243
+
244
+ fetchMock.mockSuccess(
245
+ 'https://api.sendgrid.com/v3/verified_senders',
246
+ {
247
+ // Missing id field
248
+ verified: false
249
+ }
250
+ );
251
+
252
+ const result = await provider.verifySender(mockSender);
253
+
254
+ expect(result.success).toBe(true);
255
+ expect(result.data?.providerId).toBe('');
256
+ });
257
+ });
258
+ });
259
+
260
+ describe('MailgunProvider', () => {
261
+ it('should succeed without API key (domain-only verification)', async () => {
262
+ const provider = new MailgunProvider('');
263
+ const result = await provider.verifySender(mockSender);
264
+
265
+ expect(result.success).toBe(true);
266
+ expect(result.data).toBeDefined();
267
+ expect(result.data?.note).toContain('Mailgun does not require');
268
+ });
269
+
270
+ it('should succeed with API key', async () => {
271
+ const provider = new MailgunProvider('test-key');
272
+ const result = await provider.verifySender(mockSender);
273
+
274
+ expect(result.success).toBe(true);
275
+ expect(result.data).toBeDefined();
276
+ });
277
+
278
+ it('should note domain-level verification', async () => {
279
+ const provider = new MailgunProvider('mg-api-key');
280
+ const result = await provider.verifySender(mockSender);
281
+
282
+ expect(result.success).toBe(true);
283
+ expect(result.data?.note).toContain('individual sender verification');
284
+ });
285
+ });
286
+
287
+ describe('SESProvider', () => {
288
+ it('should succeed without credentials (stub implementation)', async () => {
289
+ const provider = new SESProvider('', '', '');
290
+ const result = await provider.verifySender(mockSender);
291
+
292
+ expect(result.success).toBe(true);
293
+ expect(result.data).toBeDefined();
294
+ expect(result.data?.note).toContain('SES verification stubbed');
295
+ });
296
+
297
+ it('should succeed with credentials', async () => {
298
+ const provider = new SESProvider('AKIAIOSFODNN7EXAMPLE', 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', 'us-east-1');
299
+ const result = await provider.verifySender(mockSender);
300
+
301
+ expect(result.success).toBe(true);
302
+ expect(result.data).toBeDefined();
303
+ });
304
+
305
+ it('should note pending AWS SDK integration', async () => {
306
+ const provider = new SESProvider('key', 'secret', 'us-west-2');
307
+ const result = await provider.verifySender(mockSender);
308
+
309
+ expect(result.success).toBe(true);
310
+ expect(result.data?.note).toContain('AWS SDK integration pending');
311
+ });
312
+ });
313
+
314
+ describe('SMTPProvider', () => {
315
+ it('should always succeed (no verification required)', async () => {
316
+ const provider = new SMTPProvider();
317
+ const result = await provider.verifySender(mockSender);
318
+
319
+ expect(result.success).toBe(true);
320
+ expect(result.data).toBeDefined();
321
+ });
322
+
323
+ it('should note SMTP does not require verification', async () => {
324
+ const provider = new SMTPProvider();
325
+ const result = await provider.verifySender(mockSender);
326
+
327
+ expect(result.success).toBe(true);
328
+ expect(result.data?.note).toContain('SMTP does not require');
329
+ });
330
+ });
331
+ });