2025-05-07 20:20:17 +00:00
|
|
|
import { tap, expect } from '@push.rocks/tapbundle';
|
|
|
|
import { IPReputationChecker, ReputationThreshold, IPType } from '../ts/security/classes.ipreputationchecker.js';
|
|
|
|
import * as plugins from '../ts/plugins.js';
|
|
|
|
|
|
|
|
// Mock for dns lookup
|
|
|
|
const originalDnsResolve = plugins.dns.promises.resolve;
|
|
|
|
let mockDnsResolveImpl: (hostname: string) => Promise<string[]> = async () => ['127.0.0.1'];
|
|
|
|
|
|
|
|
// Setup mock DNS resolver
|
|
|
|
plugins.dns.promises.resolve = async (hostname: string) => {
|
|
|
|
return mockDnsResolveImpl(hostname);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Test instantiation
|
|
|
|
tap.test('IPReputationChecker - should be instantiable', async () => {
|
|
|
|
const checker = IPReputationChecker.getInstance({
|
|
|
|
enableDNSBL: false,
|
|
|
|
enableIPInfo: false,
|
|
|
|
enableLocalCache: false
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(checker).toBeTruthy();
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test singleton pattern
|
|
|
|
tap.test('IPReputationChecker - should use singleton pattern', async () => {
|
|
|
|
const checker1 = IPReputationChecker.getInstance();
|
|
|
|
const checker2 = IPReputationChecker.getInstance();
|
|
|
|
|
|
|
|
// Both instances should be the same object
|
|
|
|
expect(checker1 === checker2).toEqual(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test IP validation
|
|
|
|
tap.test('IPReputationChecker - should validate IP address format', async () => {
|
|
|
|
const checker = IPReputationChecker.getInstance({
|
|
|
|
enableDNSBL: false,
|
|
|
|
enableIPInfo: false,
|
|
|
|
enableLocalCache: false
|
|
|
|
});
|
|
|
|
|
|
|
|
// Valid IP should work
|
|
|
|
const result = await checker.checkReputation('192.168.1.1');
|
|
|
|
expect(result.score).toBeGreaterThan(0);
|
|
|
|
expect(result.error).toBeUndefined();
|
|
|
|
|
|
|
|
// Invalid IP should fail with error
|
|
|
|
const invalidResult = await checker.checkReputation('invalid.ip');
|
|
|
|
expect(invalidResult.error).toBeTruthy();
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test DNSBL lookups
|
|
|
|
tap.test('IPReputationChecker - should check IP against DNSBL', async () => {
|
|
|
|
try {
|
|
|
|
// Setup mock implementation for DNSBL
|
|
|
|
mockDnsResolveImpl = async (hostname: string) => {
|
|
|
|
// Listed in DNSBL if IP contains 2
|
|
|
|
if (hostname.includes('2.1.168.192') && hostname.includes('zen.spamhaus.org')) {
|
|
|
|
return ['127.0.0.2'];
|
|
|
|
}
|
|
|
|
throw { code: 'ENOTFOUND' };
|
|
|
|
};
|
|
|
|
|
|
|
|
// Create a new instance with specific settings for this test
|
|
|
|
const testInstance = new IPReputationChecker({
|
|
|
|
dnsblServers: ['zen.spamhaus.org'],
|
|
|
|
enableIPInfo: false,
|
|
|
|
enableLocalCache: false,
|
|
|
|
maxCacheSize: 1 // Small cache for testing
|
|
|
|
});
|
|
|
|
|
|
|
|
// Clean IP should have good score
|
|
|
|
const cleanResult = await testInstance.checkReputation('192.168.1.1');
|
|
|
|
expect(cleanResult.isSpam).toEqual(false);
|
|
|
|
expect(cleanResult.score).toEqual(100);
|
|
|
|
|
|
|
|
// Blacklisted IP should have reduced score
|
|
|
|
const blacklistedResult = await testInstance.checkReputation('192.168.1.2');
|
|
|
|
expect(blacklistedResult.isSpam).toEqual(true);
|
|
|
|
expect(blacklistedResult.score < 100).toEqual(true); // Less than 100
|
|
|
|
expect(blacklistedResult.blacklists).toBeTruthy();
|
|
|
|
expect((blacklistedResult.blacklists || []).length > 0).toEqual(true);
|
|
|
|
} catch (err) {
|
|
|
|
console.error('Test error:', err);
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test caching behavior
|
|
|
|
tap.test('IPReputationChecker - should cache reputation results', async () => {
|
|
|
|
// Create a fresh instance for this test
|
|
|
|
const testInstance = new IPReputationChecker({
|
|
|
|
enableIPInfo: false,
|
|
|
|
enableLocalCache: false,
|
|
|
|
maxCacheSize: 10 // Small cache for testing
|
|
|
|
});
|
|
|
|
|
|
|
|
// Check that first look performs a lookup and second uses cache
|
|
|
|
const ip = '192.168.1.10';
|
|
|
|
|
|
|
|
// First check should add to cache
|
|
|
|
const result1 = await testInstance.checkReputation(ip);
|
|
|
|
expect(result1).toBeTruthy();
|
|
|
|
|
|
|
|
// Manually verify it's in cache - access private member for testing
|
|
|
|
const hasInCache = (testInstance as any).reputationCache.has(ip);
|
|
|
|
expect(hasInCache).toEqual(true);
|
|
|
|
|
|
|
|
// Call again, should use cache
|
|
|
|
const result2 = await testInstance.checkReputation(ip);
|
|
|
|
expect(result2).toBeTruthy();
|
|
|
|
|
|
|
|
// Results should be identical
|
|
|
|
expect(result1.score).toEqual(result2.score);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test risk level classification
|
|
|
|
tap.test('IPReputationChecker - should classify risk levels correctly', async () => {
|
|
|
|
expect(IPReputationChecker.getRiskLevel(10)).toEqual('high');
|
|
|
|
expect(IPReputationChecker.getRiskLevel(30)).toEqual('medium');
|
|
|
|
expect(IPReputationChecker.getRiskLevel(60)).toEqual('low');
|
|
|
|
expect(IPReputationChecker.getRiskLevel(90)).toEqual('trusted');
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test IP type detection
|
|
|
|
tap.test('IPReputationChecker - should detect special IP types', async () => {
|
|
|
|
const testInstance = new IPReputationChecker({
|
|
|
|
enableDNSBL: false,
|
|
|
|
enableIPInfo: true,
|
|
|
|
enableLocalCache: false,
|
|
|
|
maxCacheSize: 5 // Small cache for testing
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test Tor exit node detection
|
|
|
|
const torResult = await testInstance.checkReputation('171.25.1.1');
|
|
|
|
expect(torResult.isTor).toEqual(true);
|
|
|
|
expect(torResult.score < 90).toEqual(true);
|
|
|
|
|
|
|
|
// Test VPN detection
|
|
|
|
const vpnResult = await testInstance.checkReputation('185.156.1.1');
|
|
|
|
expect(vpnResult.isVPN).toEqual(true);
|
|
|
|
expect(vpnResult.score < 90).toEqual(true);
|
|
|
|
|
|
|
|
// Test proxy detection
|
|
|
|
const proxyResult = await testInstance.checkReputation('34.92.1.1');
|
|
|
|
expect(proxyResult.isProxy).toEqual(true);
|
|
|
|
expect(proxyResult.score < 90).toEqual(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test error handling
|
|
|
|
tap.test('IPReputationChecker - should handle DNS lookup errors gracefully', async () => {
|
|
|
|
// Setup mock implementation to simulate error
|
|
|
|
mockDnsResolveImpl = async () => {
|
|
|
|
throw new Error('DNS server error');
|
|
|
|
};
|
|
|
|
|
|
|
|
const checker = IPReputationChecker.getInstance({
|
|
|
|
dnsblServers: ['zen.spamhaus.org'],
|
|
|
|
enableIPInfo: false,
|
|
|
|
enableLocalCache: false,
|
|
|
|
maxCacheSize: 300 // Force new instance
|
|
|
|
});
|
|
|
|
|
|
|
|
// Should return a result despite errors
|
|
|
|
const result = await checker.checkReputation('192.168.1.1');
|
|
|
|
expect(result.score).toEqual(100); // No blacklist hits found due to error
|
|
|
|
expect(result.isSpam).toEqual(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Restore original implementation at the end
|
|
|
|
tap.test('Cleanup - restore mocks', async () => {
|
|
|
|
plugins.dns.promises.resolve = originalDnsResolve;
|
|
|
|
});
|
|
|
|
|
2025-05-07 22:06:55 +00:00
|
|
|
tap.test('stop', async () => {
|
|
|
|
await tap.stopForcefully();
|
|
|
|
});
|
|
|
|
|
2025-05-07 20:20:17 +00:00
|
|
|
export default tap.start();
|