208 lines
5.9 KiB
TypeScript
Executable File
208 lines
5.9 KiB
TypeScript
Executable File
#!/usr/bin/env tsx
|
||
/**
|
||
* Test script for email tracking system
|
||
* Tests:
|
||
* 1. Webhook endpoint
|
||
* 2. Email logs API
|
||
* 3. Email status checking
|
||
*/
|
||
|
||
import { ENV } from '../src/lib/env';
|
||
|
||
const BASE_URL = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3001';
|
||
const ADMIN_EMAIL = process.env.TEST_ADMIN_EMAIL || 'admin@themoyos.co.za';
|
||
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'TSD107AS';
|
||
|
||
interface TestResult {
|
||
name: string;
|
||
passed: boolean;
|
||
message: string;
|
||
data?: any;
|
||
}
|
||
|
||
const results: TestResult[] = [];
|
||
|
||
async function test(name: string, fn: () => Promise<any>): Promise<void> {
|
||
try {
|
||
console.log(`\n🧪 Testing: ${name}`);
|
||
const result = await fn();
|
||
results.push({ name, passed: true, message: 'Passed', data: result });
|
||
console.log(`✅ ${name}: PASSED`);
|
||
if (result && typeof result === 'object') {
|
||
console.log(JSON.stringify(result, null, 2));
|
||
}
|
||
} catch (error) {
|
||
const message = error instanceof Error ? error.message : String(error);
|
||
results.push({ name, passed: false, message });
|
||
console.log(`❌ ${name}: FAILED - ${message}`);
|
||
}
|
||
}
|
||
|
||
async function login(): Promise<string> {
|
||
const response = await fetch(`${BASE_URL}/api/admin/auth`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
username: 'admin',
|
||
password: ADMIN_PASSWORD,
|
||
}),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`Login failed: ${response.status} ${response.statusText}`);
|
||
}
|
||
|
||
const cookies = response.headers.get('set-cookie');
|
||
if (!cookies) {
|
||
throw new Error('No session cookie received');
|
||
}
|
||
|
||
// Extract session cookie
|
||
const sessionMatch = cookies.match(/session=([^;]+)/);
|
||
if (!sessionMatch) {
|
||
throw new Error('Could not extract session cookie');
|
||
}
|
||
|
||
return sessionMatch[1];
|
||
}
|
||
|
||
async function main() {
|
||
console.log('📧 Email Tracking System Test');
|
||
console.log('='.repeat(50));
|
||
console.log(`Base URL: ${BASE_URL}`);
|
||
console.log(`Webhook URL: ${ENV.APP_URL}/api/webhooks/resend`);
|
||
|
||
// Test 1: Webhook endpoint (GET)
|
||
await test('Webhook Endpoint (GET)', async () => {
|
||
const response = await fetch(`${BASE_URL}/api/webhooks/resend`);
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
const data = await response.json();
|
||
if (!data.webhookUrl || !data.status) {
|
||
throw new Error('Invalid webhook response');
|
||
}
|
||
return data;
|
||
});
|
||
|
||
// Test 2: Login to get session
|
||
let sessionCookie = '';
|
||
await test('Admin Login', async () => {
|
||
sessionCookie = await login();
|
||
return { message: 'Logged in successfully' };
|
||
});
|
||
|
||
if (!sessionCookie) {
|
||
console.log('\n⚠️ Cannot continue tests without admin session');
|
||
return;
|
||
}
|
||
|
||
// Test 3: Get email logs
|
||
await test('Get Email Logs', async () => {
|
||
const response = await fetch(`${BASE_URL}/api/admin/emails?limit=10`, {
|
||
headers: {
|
||
'Cookie': `session=${sessionCookie}`,
|
||
},
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
if (!data.success || !Array.isArray(data.emails)) {
|
||
throw new Error('Invalid email logs response');
|
||
}
|
||
|
||
return {
|
||
total: data.pagination?.total || 0,
|
||
emails: data.emails.length,
|
||
sample: data.emails[0] || null,
|
||
};
|
||
});
|
||
|
||
// Test 4: Webhook signature verification (if secret is set)
|
||
if (process.env.RESEND_WEBHOOK_SECRET) {
|
||
await test('Webhook Signature Verification', async () => {
|
||
const crypto = await import('crypto');
|
||
const secret = process.env.RESEND_WEBHOOK_SECRET!;
|
||
const testPayload = JSON.stringify({ type: 'email.sent', data: { id: 'test-123' } });
|
||
const signature = crypto
|
||
.createHmac('sha256', secret)
|
||
.update(testPayload)
|
||
.digest('hex');
|
||
|
||
const response = await fetch(`${BASE_URL}/api/webhooks/resend`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'resend-signature': signature,
|
||
},
|
||
body: testPayload,
|
||
});
|
||
|
||
// Should accept valid signature (or return 200/400, not 401)
|
||
if (response.status === 401) {
|
||
throw new Error('Webhook rejected valid signature');
|
||
}
|
||
|
||
return { message: 'Signature verification working' };
|
||
});
|
||
} else {
|
||
console.log('\n⚠️ RESEND_WEBHOOK_SECRET not set, skipping signature test');
|
||
}
|
||
|
||
// Test 5: Simulate webhook event (without signature for testing)
|
||
await test('Simulate Webhook Event (email.sent)', async () => {
|
||
const testPayload = {
|
||
type: 'email.sent',
|
||
data: {
|
||
email_id: 'test-webhook-' + Date.now(),
|
||
created_at: new Date().toISOString(),
|
||
},
|
||
};
|
||
|
||
const response = await fetch(`${BASE_URL}/api/webhooks/resend`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify(testPayload),
|
||
});
|
||
|
||
// Should accept webhook (might return 200 or 400 if email not found, but not 500)
|
||
if (response.status >= 500) {
|
||
throw new Error(`Server error: ${response.status}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
return { message: 'Webhook processed', status: response.status, data };
|
||
});
|
||
|
||
// Summary
|
||
console.log('\n' + '='.repeat(50));
|
||
console.log('📊 Test Summary');
|
||
console.log('='.repeat(50));
|
||
|
||
const passed = results.filter(r => r.passed).length;
|
||
const failed = results.filter(r => !r.passed).length;
|
||
|
||
results.forEach(result => {
|
||
const icon = result.passed ? '✅' : '❌';
|
||
console.log(`${icon} ${result.name}: ${result.message}`);
|
||
});
|
||
|
||
console.log('\n' + '='.repeat(50));
|
||
console.log(`Total: ${results.length} | Passed: ${passed} | Failed: ${failed}`);
|
||
console.log('='.repeat(50));
|
||
|
||
if (failed > 0) {
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
main().catch(error => {
|
||
console.error('Fatal error:', error);
|
||
process.exit(1);
|
||
});
|