feat(ClamAvService): Add ClamAV Manager with Docker container management capabilities.
This commit is contained in:
90
test/helpers/clamav.helper.ts
Normal file
90
test/helpers/clamav.helper.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { ClamAVManager } from '../../ts/classes.clamav.manager.js';
|
||||
import { execAsync } from '../../ts/plugins.js';
|
||||
|
||||
let clamManager: ClamAVManager | null = null;
|
||||
let isCleaningUp = false;
|
||||
|
||||
export async function getManager(): Promise<ClamAVManager> {
|
||||
if (!clamManager) {
|
||||
throw new Error('ClamAV manager not initialized');
|
||||
}
|
||||
return clamManager;
|
||||
}
|
||||
|
||||
export async function setupClamAV(): Promise<ClamAVManager> {
|
||||
console.log('[Helper] Setting up ClamAV...');
|
||||
|
||||
// First cleanup any existing containers
|
||||
await forceCleanupContainer();
|
||||
|
||||
if (!clamManager) {
|
||||
console.log('[Helper] Creating new ClamAV manager instance');
|
||||
clamManager = new ClamAVManager();
|
||||
await clamManager.startContainer();
|
||||
console.log('[Helper] ClamAV manager initialized');
|
||||
} else {
|
||||
console.log('[Helper] Using existing ClamAV manager instance');
|
||||
}
|
||||
|
||||
return clamManager;
|
||||
}
|
||||
|
||||
export async function cleanupClamAV(): Promise<void> {
|
||||
if (isCleaningUp) {
|
||||
console.log('[Helper] Cleanup already in progress, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
isCleaningUp = true;
|
||||
console.log('[Helper] Cleaning up ClamAV...');
|
||||
|
||||
try {
|
||||
if (clamManager) {
|
||||
await clamManager.stopContainer();
|
||||
console.log('[Helper] ClamAV container stopped');
|
||||
}
|
||||
await forceCleanupContainer();
|
||||
} catch (error) {
|
||||
console.error('[Helper] Error during cleanup:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
clamManager = null;
|
||||
isCleaningUp = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function forceCleanupContainer(): Promise<void> {
|
||||
try {
|
||||
// Stop any existing container
|
||||
await execAsync('docker stop clamav-daemon').catch(() => {});
|
||||
// Remove any existing container
|
||||
await execAsync('docker rm -f clamav-daemon').catch(() => {});
|
||||
console.log('[Helper] Forced cleanup of existing containers complete');
|
||||
} catch (error) {
|
||||
// Ignore errors as the container might not exist
|
||||
}
|
||||
}
|
||||
|
||||
// Handle interrupts
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('\n[Helper] Received SIGINT. Cleaning up...');
|
||||
try {
|
||||
await cleanupClamAV();
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
console.error('[Helper] Error during cleanup:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure cleanup on process exit
|
||||
process.on('exit', () => {
|
||||
if (clamManager && !isCleaningUp) {
|
||||
console.log('[Helper] Process exit detected, attempting cleanup');
|
||||
// We can't use async functions in exit handler, so we do our best
|
||||
try {
|
||||
execAsync('docker stop clamav-daemon').catch(() => {});
|
||||
execAsync('docker rm -f clamav-daemon').catch(() => {});
|
||||
} catch {}
|
||||
}
|
||||
});
|
55
test/test.clamav.manager.ts
Normal file
55
test/test.clamav.manager.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { expect, tap } from '../ts/plugins.js';
|
||||
import { type ClamAVLogEvent, ClamAVManager } from '../ts/classes.clamav.manager.js';
|
||||
import { setupClamAV, cleanupClamAV, getManager } from './helpers/clamav.helper.js';
|
||||
|
||||
let manager: ClamAVManager;
|
||||
|
||||
tap.test('setup', async () => {
|
||||
manager = await setupClamAV();
|
||||
expect(manager).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('should have initialized container and receive logs', async () => {
|
||||
let logReceived = false;
|
||||
|
||||
// Add event listener for logs
|
||||
manager.on('log', (event: ClamAVLogEvent) => {
|
||||
console.log(`[Test] Received log event: ${event.type} - ${event.message}`);
|
||||
logReceived = true;
|
||||
});
|
||||
|
||||
// Wait for logs
|
||||
const maxWaitTime = 5000;
|
||||
const startTime = Date.now();
|
||||
|
||||
while (!logReceived && Date.now() - startTime < maxWaitTime) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
expect(logReceived).toBeTruthy('No logs received within timeout period');
|
||||
|
||||
// Verify container is running by checking if we can get database info
|
||||
try {
|
||||
const dbInfo = await manager.getDatabaseInfo();
|
||||
expect(dbInfo).toBeTruthy('Container should be running and able to get database info');
|
||||
} catch (error) {
|
||||
console.error('Error getting database info:', error);
|
||||
expect.fail('Failed to get database info - container may not be fully initialized');
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('should get database info', async () => {
|
||||
const dbInfo = await manager.getDatabaseInfo();
|
||||
console.log('Database Info:', dbInfo);
|
||||
expect(dbInfo).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('should update database', async () => {
|
||||
await manager.updateDatabase();
|
||||
});
|
||||
|
||||
tap.test('cleanup', async () => {
|
||||
await cleanupClamAV();
|
||||
});
|
||||
|
||||
tap.start();
|
49
test/test.ts
49
test/test.ts
@ -1,35 +1,40 @@
|
||||
import { expect, expectAsync, tap } from '@push.rocks/tapbundle';
|
||||
import { expect, tap } from '../ts/plugins.js';
|
||||
import * as smartantivirus from '../ts/index.js';
|
||||
import { setupClamAV, cleanupClamAV } from './helpers/clamav.helper.js';
|
||||
|
||||
const EICAR_TEST_STRING = 'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*';
|
||||
let clamService: smartantivirus.ClamAvService;
|
||||
|
||||
tap.test('should create a ClamAvService instance', async () => {
|
||||
clamService = new smartantivirus.ClamAvService();
|
||||
expect(clamService).toBeDefined();
|
||||
tap.test('setup', async () => {
|
||||
await setupClamAV();
|
||||
});
|
||||
|
||||
tap.test('should scan a string', async () => {
|
||||
const scanResult = await clamService.scanString('X5O!P%@AP[4\PZX54(P^)7CC)7}' + '$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*');
|
||||
tap.test('should create a ClamAvService instance and initialize ClamAV', async () => {
|
||||
clamService = new smartantivirus.ClamAvService();
|
||||
expect(clamService).toBeTruthy();
|
||||
// The manager will start the container and wait for initialization
|
||||
await clamService.verifyConnection();
|
||||
});
|
||||
|
||||
tap.test('should detect EICAR test string', async () => {
|
||||
const scanResult = await clamService.scanString(EICAR_TEST_STRING);
|
||||
console.log('Scan Result:', scanResult);
|
||||
// expect(scanResult).toEqual({ isInfected: true, reason: 'FOUND' });
|
||||
expect(scanResult.isInfected).toEqual(true);
|
||||
expect(scanResult.reason).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('should not detect clean string', async () => {
|
||||
const scanResult = await clamService.scanString('This is a clean string with no virus signature');
|
||||
console.log('Clean Scan Result:', scanResult);
|
||||
expect(scanResult.isInfected).toEqual(false);
|
||||
expect(scanResult.reason).toBeUndefined();
|
||||
});
|
||||
|
||||
tap.test('cleanup', async () => {
|
||||
await cleanupClamAV();
|
||||
});
|
||||
|
||||
tap.start();
|
||||
|
||||
|
||||
/* (async () => {
|
||||
|
||||
try {
|
||||
|
||||
await clamService.updateVirusDefinitions(); // Step 2: Update definitions
|
||||
await clamService.startClamDaemon(); // Step 3: Start daemon
|
||||
|
||||
const scanResult = await clamService.scanString('EICAR test string...');
|
||||
console.log('Scan Result:', scanResult);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
})(); */
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user