platformservice/test/test.reputationmonitor.ts

243 lines
9.1 KiB
TypeScript
Raw Normal View History

2025-05-07 20:20:17 +00:00
import { tap, expect } from '@push.rocks/tapbundle';
import * as plugins from '../ts/plugins.js';
import * as paths from '../ts/paths.js';
import { SenderReputationMonitor } from '../ts/deliverability/classes.senderreputationmonitor.js';
// Cleanup any temporary test data
const cleanupTestData = () => {
const reputationDataPath = plugins.path.join(paths.dataDir, 'reputation');
if (plugins.fs.existsSync(reputationDataPath)) {
// Remove the directory recursively using fs instead of smartfile
plugins.fs.rmSync(reputationDataPath, { recursive: true, force: true });
2025-05-07 20:20:17 +00:00
}
};
// Helper to reset the singleton instance between tests
const resetSingleton = () => {
// @ts-ignore - accessing private static field for testing
SenderReputationMonitor.instance = null;
2025-05-07 20:20:17 +00:00
};
// Before running any tests
tap.test('setup', async () => {
cleanupTestData();
});
// Test initialization of SenderReputationMonitor
tap.test('should initialize SenderReputationMonitor with default settings', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance();
expect(reputationMonitor).toBeTruthy();
2025-05-07 20:20:17 +00:00
// Check if the object has the expected methods
expect(typeof reputationMonitor.recordSendEvent).toEqual('function');
expect(typeof reputationMonitor.getReputationData).toEqual('function');
expect(typeof reputationMonitor.getReputationSummary).toEqual('function');
2025-05-07 20:20:17 +00:00
});
// Test initialization with custom settings
tap.test('should initialize SenderReputationMonitor with custom settings', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: true,
domains: ['example.com', 'test.com'],
updateFrequency: 12 * 60 * 60 * 1000, // 12 hours
alertThresholds: {
minReputationScore: 80,
maxComplaintRate: 0.05
}
});
// Test adding domains
reputationMonitor.addDomain('newdomain.com');
// Test retrieving reputation data
const data = reputationMonitor.getReputationData('example.com');
expect(data).toBeTruthy();
expect(data.domain).toEqual('example.com');
2025-05-07 20:20:17 +00:00
});
// Test recording and tracking send events
tap.test('should record send events and update metrics', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: true,
domains: ['example.com']
});
// Record a series of events
reputationMonitor.recordSendEvent('example.com', { type: 'sent', count: 100 });
reputationMonitor.recordSendEvent('example.com', { type: 'delivered', count: 95 });
reputationMonitor.recordSendEvent('example.com', { type: 'bounce', hardBounce: true, count: 3 });
reputationMonitor.recordSendEvent('example.com', { type: 'bounce', hardBounce: false, count: 2 });
reputationMonitor.recordSendEvent('example.com', { type: 'complaint', count: 1 });
// Check metrics
const metrics = reputationMonitor.getReputationData('example.com');
expect(metrics).toBeTruthy();
expect(metrics.volume.sent).toEqual(100);
expect(metrics.volume.delivered).toEqual(95);
expect(metrics.volume.hardBounces).toEqual(3);
expect(metrics.volume.softBounces).toEqual(2);
expect(metrics.complaints.total).toEqual(1);
2025-05-07 20:20:17 +00:00
});
// Test reputation score calculation
tap.test('should calculate reputation scores correctly', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: true,
domains: ['high.com', 'medium.com', 'low.com']
});
// Record events for different domains
reputationMonitor.recordSendEvent('high.com', { type: 'sent', count: 1000 });
reputationMonitor.recordSendEvent('high.com', { type: 'delivered', count: 990 });
reputationMonitor.recordSendEvent('high.com', { type: 'open', count: 500 });
reputationMonitor.recordSendEvent('medium.com', { type: 'sent', count: 1000 });
reputationMonitor.recordSendEvent('medium.com', { type: 'delivered', count: 950 });
reputationMonitor.recordSendEvent('medium.com', { type: 'open', count: 300 });
reputationMonitor.recordSendEvent('low.com', { type: 'sent', count: 1000 });
reputationMonitor.recordSendEvent('low.com', { type: 'delivered', count: 850 });
reputationMonitor.recordSendEvent('low.com', { type: 'open', count: 100 });
// Get reputation summary
const summary = reputationMonitor.getReputationSummary();
expect(Array.isArray(summary)).toBeTrue();
expect(summary.length >= 3).toBeTrue();
2025-05-07 20:20:17 +00:00
// Check that domains are included in the summary
const domains = summary.map(item => item.domain);
expect(domains.includes('high.com')).toBeTrue();
expect(domains.includes('medium.com')).toBeTrue();
expect(domains.includes('low.com')).toBeTrue();
2025-05-07 20:20:17 +00:00
});
// Test adding and removing domains
tap.test('should add and remove domains for monitoring', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: true,
domains: ['example.com']
});
// Add a new domain
reputationMonitor.addDomain('newdomain.com');
// Record data for the new domain
reputationMonitor.recordSendEvent('newdomain.com', { type: 'sent', count: 50 });
// Check that data was recorded for the new domain
const metrics = reputationMonitor.getReputationData('newdomain.com');
expect(metrics).toBeTruthy();
expect(metrics.volume.sent).toEqual(50);
2025-05-07 20:20:17 +00:00
// Remove a domain
reputationMonitor.removeDomain('newdomain.com');
// Check that data is no longer available
const removedMetrics = reputationMonitor.getReputationData('newdomain.com');
expect(removedMetrics === null).toBeTrue();
2025-05-07 20:20:17 +00:00
});
// Test handling open and click events
tap.test('should track engagement metrics correctly', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: true,
domains: ['example.com']
});
// Record basic sending metrics
reputationMonitor.recordSendEvent('example.com', { type: 'sent', count: 1000 });
reputationMonitor.recordSendEvent('example.com', { type: 'delivered', count: 950 });
// Record engagement events
reputationMonitor.recordSendEvent('example.com', { type: 'open', count: 500 });
reputationMonitor.recordSendEvent('example.com', { type: 'click', count: 250 });
// Check engagement metrics
const metrics = reputationMonitor.getReputationData('example.com');
expect(metrics).toBeTruthy();
expect(metrics.engagement.opens).toEqual(500);
expect(metrics.engagement.clicks).toEqual(250);
expect(typeof metrics.engagement.openRate).toEqual('number');
expect(typeof metrics.engagement.clickRate).toEqual('number');
2025-05-07 20:20:17 +00:00
});
// Test historical data tracking
tap.test('should store historical reputation data', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: true,
domains: ['example.com']
});
// Record events over multiple days
const today = new Date();
// Record data
reputationMonitor.recordSendEvent('example.com', { type: 'sent', count: 1000 });
reputationMonitor.recordSendEvent('example.com', { type: 'delivered', count: 950 });
// Get metrics data
const metrics = reputationMonitor.getReputationData('example.com');
// Check that historical data exists
expect(metrics.historical).toBeTruthy();
expect(metrics.historical.reputationScores).toBeTruthy();
2025-05-07 20:20:17 +00:00
// Check that daily send volume is tracked
expect(metrics.volume.dailySendVolume).toBeTruthy();
2025-05-07 20:20:17 +00:00
const todayStr = today.toISOString().split('T')[0];
expect(metrics.volume.dailySendVolume[todayStr]).toEqual(1000);
2025-05-07 20:20:17 +00:00
});
// Test event recording for different event types
tap.test('should correctly handle different event types', async () => {
resetSingleton();
const reputationMonitor = SenderReputationMonitor.getInstance({
enabled: true,
domains: ['example.com']
});
// Record different types of events
reputationMonitor.recordSendEvent('example.com', { type: 'sent', count: 100 });
reputationMonitor.recordSendEvent('example.com', { type: 'delivered', count: 95 });
reputationMonitor.recordSendEvent('example.com', { type: 'bounce', hardBounce: true, count: 3 });
reputationMonitor.recordSendEvent('example.com', { type: 'bounce', hardBounce: false, count: 2 });
reputationMonitor.recordSendEvent('example.com', { type: 'complaint', receivingDomain: 'gmail.com', count: 1 });
reputationMonitor.recordSendEvent('example.com', { type: 'open', count: 50 });
reputationMonitor.recordSendEvent('example.com', { type: 'click', count: 25 });
// Check metrics for different event types
const metrics = reputationMonitor.getReputationData('example.com');
// Check volume metrics
expect(metrics.volume.sent).toEqual(100);
expect(metrics.volume.delivered).toEqual(95);
expect(metrics.volume.hardBounces).toEqual(3);
expect(metrics.volume.softBounces).toEqual(2);
2025-05-07 20:20:17 +00:00
// Check complaint metrics
expect(metrics.complaints.total).toEqual(1);
expect(metrics.complaints.topDomains[0].domain).toEqual('gmail.com');
2025-05-07 20:20:17 +00:00
// Check engagement metrics
expect(metrics.engagement.opens).toEqual(50);
expect(metrics.engagement.clicks).toEqual(25);
2025-05-07 20:20:17 +00:00
});
// After all tests, clean up
tap.test('cleanup', async () => {
cleanupTestData();
});
tap.test('stop', async () => {
await tap.stopForcefully();
});
2025-05-07 20:20:17 +00:00
export default tap.start();