@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.
- package/.env.test +8 -0
- package/.eslintrc.js +30 -0
- package/README.md +376 -0
- package/__tests__/SenderIdentityVerification.test.ts +461 -0
- package/__tests__/__mocks__/fetch-mock.ts +156 -0
- package/__tests__/additional-coverage.test.ts +129 -0
- package/__tests__/additional-error-coverage.test.ts +483 -0
- package/__tests__/branch-coverage.test.ts +509 -0
- package/__tests__/config.test.ts +119 -0
- package/__tests__/error-handling.test.ts +321 -0
- package/__tests__/final-branch-coverage.test.ts +372 -0
- package/__tests__/integration.real-api.test.ts +295 -0
- package/__tests__/providers.test.ts +331 -0
- package/__tests__/service-coverage.test.ts +412 -0
- package/dist/SenderIdentityVerification.d.ts +72 -0
- package/dist/SenderIdentityVerification.js +643 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.js +38 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +61 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +21 -0
- package/dist/providers/MailgunProvider.d.ts +13 -0
- package/dist/providers/MailgunProvider.js +35 -0
- package/dist/providers/SESProvider.d.ts +12 -0
- package/dist/providers/SESProvider.js +47 -0
- package/dist/providers/SMTPProvider.d.ts +12 -0
- package/dist/providers/SMTPProvider.js +30 -0
- package/dist/providers/SendGridProvider.d.ts +19 -0
- package/dist/providers/SendGridProvider.js +98 -0
- package/dist/templates/verification-email.d.ts +9 -0
- package/dist/templates/verification-email.js +67 -0
- package/dist/types.d.ts +139 -0
- package/dist/types.js +33 -0
- package/dist/utils/domain-extractor.d.ts +4 -0
- package/dist/utils/domain-extractor.js +20 -0
- package/jest.config.cjs +33 -0
- package/package.json +60 -0
- package/src/SenderIdentityVerification.ts +796 -0
- package/src/config.ts +81 -0
- package/src/errors.ts +64 -0
- package/src/global.d.ts +24 -0
- package/src/index.ts +24 -0
- package/src/providers/MailgunProvider.ts +35 -0
- package/src/providers/SESProvider.ts +51 -0
- package/src/providers/SMTPProvider.ts +29 -0
- package/src/providers/SendGridProvider.ts +108 -0
- package/src/templates/verification-email.ts +67 -0
- package/src/types.ts +163 -0
- package/src/utils/domain-extractor.ts +18 -0
- 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
|
+
});
|