Skip to content

Testing Guide

Comprehensive testing strategies, frameworks, and best practices for TrojanHorse.js development and quality assurance.

Overview

TrojanHorse.js uses a multi-layered testing approach including unit tests, integration tests, end-to-end tests, and performance testing to ensure reliability and security in production environments.

graph TB
    A[Testing Strategy] --> B[Unit Tests]
    A --> C[Integration Tests]
    A --> D[E2E Tests]
    A --> E[Performance Tests]
    A --> F[Security Tests]

    B --> B1[Jest Framework]
    B --> B2[Mock Services]
    B --> B3[Coverage Reports]

    C --> C1[API Testing]
    C --> C2[Database Testing]
    C --> C3[Feed Integration]

    D --> D1[User Workflows]
    D --> D2[System Integration]
    D --> D3[Browser Testing]

    E --> E1[Load Testing]
    E --> E2[Stress Testing]
    E --> E3[Benchmarking]

    F --> F1[Penetration Testing]
    F --> F2[Vulnerability Scanning]
    F --> F3[Security Audits]

Testing Framework Setup

Jest Configuration

// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',

  // Test file patterns
  testMatch: [
    '**/src/**/__tests__/**/*.test.ts',
    '**/tests/**/*.test.ts'
  ],

  // Coverage configuration
  collectCoverage: true,
  coverageDirectory: 'coverage',
  coverageReporters: ['text', 'lcov', 'html'],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  },

  // Setup files
  setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],

  // Module path mapping
  moduleNameMapping: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '^@tests/(.*)$': '<rootDir>/tests/$1'
  },

  // Test timeout
  testTimeout: 10000,

  // Parallel testing
  maxWorkers: '50%'
};

Unit Testing

Core Component Testing

// tests/core/TrojanHorse.test.ts
import { TrojanHorse } from '@/core/TrojanHorse';
import { MockFeedRegistry } from '@tests/mocks/MockFeedRegistry';
import { MockKeyVault } from '@tests/mocks/MockKeyVault';

describe('TrojanHorse Core', () => {
  let trojan: TrojanHorse;
  let mockFeedRegistry: MockFeedRegistry;
  let mockKeyVault: MockKeyVault;

  beforeEach(() => {
    mockFeedRegistry = new MockFeedRegistry();
    mockKeyVault = new MockKeyVault();

    trojan = new TrojanHorse({
      sources: ['urlhaus', 'virustotal'],
      testing: true,
      feedRegistry: mockFeedRegistry,
      keyVault: mockKeyVault
    });
  });

  describe('scout method', () => {
    it('should detect malicious domains', async () => {
      // Arrange
      const maliciousDomain = 'malicious-site.com';
      mockFeedRegistry.mockResponse('urlhaus', {
        indicator: maliciousDomain,
        threat: true,
        confidence: 95,
        sources: ['urlhaus']
      });

      // Act
      const results = await trojan.scout(maliciousDomain);

      // Assert
      expect(results).toHaveLength(1);
      expect(results[0].threat).toBe(true);
      expect(results[0].confidence).toBeGreaterThan(90);
      expect(results[0].sources).toContain('urlhaus');
    });

    it('should handle feed timeouts gracefully', async () => {
      // Arrange
      const indicator = 'timeout-test.com';
      mockFeedRegistry.mockTimeout('virustotal', 5000);

      // Act & Assert
      await expect(trojan.scout(indicator, { timeout: 1000 }))
        .resolves.not.toThrow();
    });

    it('should validate input parameters', async () => {
      // Test invalid indicators
      await expect(trojan.scout('')).rejects.toThrow('Invalid indicator');
      await expect(trojan.scout(null as any)).rejects.toThrow('Invalid indicator');
      await expect(trojan.scout('x'.repeat(3000))).rejects.toThrow('Indicator too long');
    });

    it('should emit threatDetected events', async () => {
      // Arrange
      const threatHandler = jest.fn();
      trojan.on('threatDetected', threatHandler);

      mockFeedRegistry.mockResponse('urlhaus', {
        indicator: 'evil.com',
        threat: true,
        confidence: 90
      });

      // Act
      await trojan.scout('evil.com');

      // Assert
      expect(threatHandler).toHaveBeenCalledWith(
        expect.objectContaining({
          indicator: 'evil.com',
          threat: true,
          confidence: 90
        })
      );
    });
  });

  describe('correlation', () => {
    it('should correlate related threats', async () => {
      // Arrange
      const indicators = ['domain1.com', 'domain2.com', 'related-ip.com'];

      indicators.forEach(indicator => {
        mockFeedRegistry.mockResponse('urlhaus', {
          indicator,
          threat: true,
          confidence: 85,
          metadata: { campaign: 'test-campaign' }
        });
      });

      // Act
      const correlation = await trojan.correlate(indicators[0]);

      // Assert
      expect(correlation.relatedThreats).toHaveLength(2);
      expect(correlation.correlationScore).toBeGreaterThan(70);
    });
  });
});

Feed Testing

// tests/feeds/URLhausFeed.test.ts
import { URLhausFeed } from '@/feeds/URLhausFeed';
import { MockHTTPClient } from '@tests/mocks/MockHTTPClient';

describe('URLhaus Feed', () => {
  let feed: URLhausFeed;
  let mockClient: MockHTTPClient;

  beforeEach(() => {
    mockClient = new MockHTTPClient();
    feed = new URLhausFeed({
      baseUrl: 'https://urlhaus-api.abuse.ch',
      httpClient: mockClient
    });
  });

  describe('fetchThreatData', () => {
    it('should fetch threat data for domains', async () => {
      // Arrange
      const domain = 'malicious-domain.com';
      mockClient.mockResponse('/api/host/', {
        query_status: 'ok',
        urlhaus_reference: 'https://urlhaus.abuse.ch/host/malicious-domain.com/',
        host: domain,
        firstseen: '2025-01-15',
        url_count: 5,
        blacklists: {
          spamhaus_dbl: 'not listed',
          surbl: 'listed'
        }
      });

      // Act
      const result = await feed.fetchThreatData(domain);

      // Assert
      expect(result.indicator).toBe(domain);
      expect(result.type).toBe('domain');
      expect(result.threat).toBe(true);
      expect(result.sources).toContain('URLhaus');
      expect(result.metadata.url_count).toBe(5);
    });

    it('should handle API errors gracefully', async () => {
      // Arrange
      mockClient.mockError('/api/host/', new Error('API rate limit exceeded'));

      // Act & Assert
      const result = await feed.fetchThreatData('test-domain.com');
      expect(result.threat).toBe(false);
      expect(result.confidence).toBe(0);
      expect(result.metadata.error).toBeDefined();
    });

    it('should cache responses', async () => {
      // Arrange
      const domain = 'cached-domain.com';
      mockClient.mockResponse('/api/host/', { query_status: 'ok' });

      // Act
      await feed.fetchThreatData(domain);
      await feed.fetchThreatData(domain); // Second call should use cache

      // Assert
      expect(mockClient.getCallCount('/api/host/')).toBe(1);
    });
  });

  describe('batchQuery', () => {
    it('should handle batch queries efficiently', async () => {
      // Arrange
      const domains = Array.from({ length: 100 }, (_, i) => `domain${i}.com`);
      mockClient.mockBatchResponse('/api/host/', domains.map(domain => ({
        query_status: 'ok',
        host: domain
      })));

      // Act
      const startTime = Date.now();
      const results = await feed.batchQuery(domains);
      const duration = Date.now() - startTime;

      // Assert
      expect(results).toHaveLength(100);
      expect(duration).toBeLessThan(5000); // Should complete within 5 seconds
    });
  });
});

Security Component Testing

// tests/security/CryptoEngine.test.ts
import { CryptoEngine } from '@/security/CryptoEngine';

describe('CryptoEngine', () => {
  let crypto: CryptoEngine;

  beforeEach(() => {
    crypto = new CryptoEngine({
      algorithm: 'aes-256-gcm',
      keyDerivation: 'argon2id'
    });
  });

  describe('encryption/decryption', () => {
    it('should encrypt and decrypt data correctly', async () => {
      // Arrange
      const plaintext = 'sensitive data';
      const password = 'strong-password-123';

      // Act
      const encrypted = await crypto.encrypt(plaintext, { password });
      const decrypted = await crypto.decrypt(encrypted, { password });

      // Assert
      expect(decrypted).toBe(plaintext);
      expect(encrypted.data).not.toBe(plaintext);
      expect(encrypted.iv).toBeDefined();
      expect(encrypted.tag).toBeDefined();
      expect(encrypted.salt).toBeDefined();
    });

    it('should fail with wrong password', async () => {
      // Arrange
      const plaintext = 'secret data';
      const correctPassword = 'correct-password';
      const wrongPassword = 'wrong-password';

      const encrypted = await crypto.encrypt(plaintext, { password: correctPassword });

      // Act & Assert
      await expect(crypto.decrypt(encrypted, { password: wrongPassword }))
        .rejects.toThrow('Decryption failed');
    });

    it('should use different salts for same password', async () => {
      // Arrange
      const plaintext = 'data';
      const password = 'password';

      // Act
      const encrypted1 = await crypto.encrypt(plaintext, { password });
      const encrypted2 = await crypto.encrypt(plaintext, { password });

      // Assert
      expect(encrypted1.salt).not.toBe(encrypted2.salt);
      expect(encrypted1.data).not.toBe(encrypted2.data);
    });
  });

  describe('key derivation', () => {
    it('should derive consistent keys from same password and salt', async () => {
      // Arrange
      const password = 'test-password';
      const salt = 'fixed-salt-for-testing';

      // Act
      const key1 = await crypto.deriveKey(password, { salt });
      const key2 = await crypto.deriveKey(password, { salt });

      // Assert
      expect(key1.key).toBe(key2.key);
    });

    it('should derive different keys with different salts', async () => {
      // Arrange
      const password = 'test-password';

      // Act
      const key1 = await crypto.deriveKey(password);
      const key2 = await crypto.deriveKey(password);

      // Assert
      expect(key1.key).not.toBe(key2.key);
      expect(key1.salt).not.toBe(key2.salt);
    });
  });
});

Integration Testing

API Integration Tests

// tests/integration/api.test.ts
import request from 'supertest';
import { createTestApp } from '@tests/helpers/testApp';
import { TestDatabase } from '@tests/helpers/TestDatabase';

describe('API Integration Tests', () => {
  let app: Express.Application;
  let database: TestDatabase;

  beforeAll(async () => {
    database = new TestDatabase();
    await database.setup();

    app = createTestApp({
      database: database.connection,
      testing: true
    });
  });

  afterAll(async () => {
    await database.teardown();
  });

  describe('POST /api/threats/scan', () => {
    it('should scan a single indicator', async () => {
      const response = await request(app)
        .post('/api/threats/scan')
        .send({
          indicator: 'malicious-domain.com',
          sources: ['urlhaus', 'virustotal']
        })
        .expect(200);

      expect(response.body).toMatchObject({
        indicator: 'malicious-domain.com',
        results: expect.any(Array),
        timestamp: expect.any(String)
      });
    });

    it('should require authentication', async () => {
      await request(app)
        .post('/api/threats/scan')
        .send({ indicator: 'test.com' })
        .expect(401);
    });

    it('should validate input parameters', async () => {
      const response = await request(app)
        .post('/api/threats/scan')
        .set('Authorization', 'Bearer valid-token')
        .send({ indicator: '' })
        .expect(400);

      expect(response.body.error).toContain('Invalid indicator');
    });
  });

  describe('GET /api/threats/history', () => {
    it('should return threat history', async () => {
      // First, create some threat history
      await request(app)
        .post('/api/threats/scan')
        .set('Authorization', 'Bearer valid-token')
        .send({ indicator: 'history-test.com' });

      const response = await request(app)
        .get('/api/threats/history')
        .set('Authorization', 'Bearer valid-token')
        .expect(200);

      expect(response.body.threats).toBeDefined();
      expect(Array.isArray(response.body.threats)).toBe(true);
    });
  });
});

Database Integration Tests

// tests/integration/database.test.ts
import { DatabaseManager } from '@/storage/DatabaseManager';
import { TestDatabase } from '@tests/helpers/TestDatabase';

describe('Database Integration', () => {
  let db: DatabaseManager;
  let testDb: TestDatabase;

  beforeAll(async () => {
    testDb = new TestDatabase();
    await testDb.setup();
    db = new DatabaseManager(testDb.config);
  });

  afterAll(async () => {
    await testDb.teardown();
  });

  describe('threat storage', () => {
    it('should store and retrieve threats', async () => {
      // Arrange
      const threat = {
        indicator: 'test-domain.com',
        type: 'domain',
        threat: true,
        confidence: 85,
        sources: ['test-feed'],
        timestamp: new Date().toISOString()
      };

      // Act
      const id = await db.storeThreat(threat);
      const retrieved = await db.getThreat(id);

      // Assert
      expect(retrieved).toMatchObject(threat);
    });

    it('should handle duplicate threats', async () => {
      // Arrange
      const threat = {
        indicator: 'duplicate-test.com',
        type: 'domain',
        threat: true,
        confidence: 80
      };

      // Act
      const id1 = await db.storeThreat(threat);
      const id2 = await db.storeThreat({ ...threat, confidence: 90 });

      // Assert
      expect(id1).toBe(id2); // Should update existing record
      const retrieved = await db.getThreat(id1);
      expect(retrieved.confidence).toBe(90); // Should have updated confidence
    });
  });

  describe('search functionality', () => {
    it('should search threats by indicator pattern', async () => {
      // Arrange
      await db.storeThreat({
        indicator: 'search-test-1.com',
        type: 'domain',
        threat: true
      });
      await db.storeThreat({
        indicator: 'search-test-2.com',
        type: 'domain',
        threat: true
      });

      // Act
      const results = await db.searchThreats({
        pattern: 'search-test-*.com',
        limit: 10
      });

      // Assert
      expect(results).toHaveLength(2);
      expect(results.every(r => r.indicator.includes('search-test'))).toBe(true);
    });
  });
});

End-to-End Testing

User Workflow Testing

// tests/e2e/user-workflows.test.ts
import { Browser, Page } from 'puppeteer';
import { launchBrowser, createTestUser } from '@tests/helpers/e2e';

describe('User Workflows', () => {
  let browser: Browser;
  let page: Page;

  beforeAll(async () => {
    browser = await launchBrowser();
    page = await browser.newPage();
  });

  afterAll(async () => {
    await browser.close();
  });

  describe('Threat Analysis Workflow', () => {
    it('should allow user to scan threats end-to-end', async () => {
      // Login
      await page.goto('http://localhost:3000/login');
      await page.type('#username', 'test-user@example.com');
      await page.type('#password', 'test-password');
      await page.click('#login-button');
      await page.waitForNavigation();

      // Navigate to threat scanner
      await page.click('[data-testid="threat-scanner-link"]');
      await page.waitForSelector('#threat-input');

      // Input threat indicator
      await page.type('#threat-input', 'malicious-domain.com');
      await page.click('#scan-button');

      // Wait for results
      await page.waitForSelector('[data-testid="scan-results"]', { timeout: 10000 });

      // Verify results are displayed
      const results = await page.$eval('[data-testid="scan-results"]', el => el.textContent);
      expect(results).toContain('malicious-domain.com');

      // Check threat details
      await page.click('[data-testid="threat-details-button"]');
      await page.waitForSelector('[data-testid="threat-metadata"]');

      const metadata = await page.$eval('[data-testid="threat-metadata"]', el => el.textContent);
      expect(metadata).toContain('Confidence');
      expect(metadata).toContain('Sources');
    });

    it('should handle bulk threat scanning', async () => {
      await page.goto('http://localhost:3000/bulk-scan');

      // Upload CSV file
      const fileInput = await page.$('#file-input');
      await fileInput!.uploadFile('./tests/fixtures/threat-indicators.csv');

      await page.click('#upload-button');

      // Wait for processing
      await page.waitForSelector('[data-testid="processing-complete"]', { timeout: 30000 });

      // Verify results
      const resultsCount = await page.$eval('[data-testid="results-count"]', el => 
        parseInt(el.textContent || '0')
      );
      expect(resultsCount).toBeGreaterThan(0);
    });
  });

  describe('Dashboard Workflow', () => {
    it('should display real-time threat metrics', async () => {
      await page.goto('http://localhost:3000/dashboard');

      // Wait for metrics to load
      await page.waitForSelector('[data-testid="threat-metrics"]');

      // Check metric values
      const metricsElements = await page.$$('[data-testid="metric-value"]');
      expect(metricsElements.length).toBeGreaterThan(0);

      // Verify charts are rendered
      const charts = await page.$$('canvas');
      expect(charts.length).toBeGreaterThan(0);
    });
  });
});

Performance Testing

Load Testing

// tests/performance/load.test.ts
import { performance } from 'perf_hooks';
import { TrojanHorse } from '@/core/TrojanHorse';

describe('Performance Tests', () => {
  let trojan: TrojanHorse;

  beforeAll(async () => {
    trojan = new TrojanHorse({
      sources: ['urlhaus', 'virustotal'],
      performance: {
        workers: 8,
        batchSize: 100
      }
    });
  });

  describe('Single Request Performance', () => {
    it('should complete threat scan within acceptable time', async () => {
      const start = performance.now();

      await trojan.scout('performance-test.com');

      const duration = performance.now() - start;
      expect(duration).toBeLessThan(5000); // 5 second SLA
    });
  });

  describe('Concurrent Request Performance', () => {
    it('should handle concurrent requests efficiently', async () => {
      const concurrentRequests = 50;
      const indicators = Array.from({ length: concurrentRequests }, 
        (_, i) => `concurrent-test-${i}.com`
      );

      const start = performance.now();

      const promises = indicators.map(indicator => trojan.scout(indicator));
      const results = await Promise.all(promises);

      const duration = performance.now() - start;
      const avgDuration = duration / concurrentRequests;

      expect(results).toHaveLength(concurrentRequests);
      expect(avgDuration).toBeLessThan(1000); // 1 second average
    });
  });

  describe('Batch Processing Performance', () => {
    it('should process large batches efficiently', async () => {
      const batchSize = 1000;
      const indicators = Array.from({ length: batchSize }, 
        (_, i) => `batch-test-${i}.com`
      );

      const start = performance.now();

      const results = await trojan.scoutBatch(indicators);

      const duration = performance.now() - start;
      const throughput = batchSize / (duration / 1000); // requests per second

      expect(results).toHaveLength(batchSize);
      expect(throughput).toBeGreaterThan(10); // Minimum 10 RPS
    });
  });

  describe('Memory Usage', () => {
    it('should not have memory leaks during extended operation', async () => {
      const initialMemory = process.memoryUsage().heapUsed;

      // Perform many operations
      for (let i = 0; i < 100; i++) {
        await trojan.scout(`memory-test-${i}.com`);
      }

      // Force garbage collection
      if (global.gc) {
        global.gc();
      }

      const finalMemory = process.memoryUsage().heapUsed;
      const memoryIncrease = (finalMemory - initialMemory) / 1024 / 1024; // MB

      expect(memoryIncrease).toBeLessThan(50); // Less than 50MB increase
    });
  });
});

Benchmark Testing

// tests/performance/benchmark.test.ts
import Benchmark from 'benchmark';
import { TrojanHorse } from '@/core/TrojanHorse';

describe('Benchmark Tests', () => {
  let trojan: TrojanHorse;

  beforeAll(async () => {
    trojan = new TrojanHorse({ sources: ['mock-feed'] });
  });

  it('should benchmark threat scanning performance', (done) => {
    const suite = new Benchmark.Suite();

    suite
      .add('Single threat scan', {
        defer: true,
        fn: async (deferred: any) => {
          await trojan.scout('benchmark-test.com');
          deferred.resolve();
        }
      })
      .add('Batch threat scan (10)', {
        defer: true,
        fn: async (deferred: any) => {
          const indicators = Array.from({ length: 10 }, (_, i) => `batch-${i}.com`);
          await trojan.scoutBatch(indicators);
          deferred.resolve();
        }
      })
      .on('cycle', (event: any) => {
        console.log(String(event.target));
      })
      .on('complete', function(this: any) {
        console.log('Fastest is ' + this.filter('fastest').map('name'));
        done();
      })
      .run({ async: true });
  });
});

Security Testing

Penetration Testing

// tests/security/penetration.test.ts
import request from 'supertest';
import { createTestApp } from '@tests/helpers/testApp';

describe('Security Penetration Tests', () => {
  let app: Express.Application;

  beforeAll(async () => {
    app = createTestApp({ security: true });
  });

  describe('Input Validation', () => {
    it('should prevent SQL injection attacks', async () => {
      const maliciousPayloads = [
        "'; DROP TABLE threats; --",
        "' OR '1'='1",
        "'; SELECT * FROM users; --"
      ];

      for (const payload of maliciousPayloads) {
        const response = await request(app)
          .post('/api/threats/scan')
          .send({ indicator: payload })
          .expect(400);

        expect(response.body.error).toContain('Invalid indicator');
      }
    });

    it('should prevent XSS attacks', async () => {
      const xssPayloads = [
        '<script>alert("xss")</script>',
        'javascript:alert("xss")',
        '<img src="x" onerror="alert(1)">'
      ];

      for (const payload of xssPayloads) {
        const response = await request(app)
          .post('/api/threats/scan')
          .send({ indicator: payload });

        expect(response.body).not.toContain('<script>');
        expect(response.body).not.toContain('javascript:');
      }
    });
  });

  describe('Authentication Security', () => {
    it('should prevent brute force attacks', async () => {
      const invalidCredentials = {
        username: 'test@example.com',
        password: 'wrong-password'
      };

      // Attempt multiple failed logins
      for (let i = 0; i < 6; i++) {
        await request(app)
          .post('/api/auth/login')
          .send(invalidCredentials);
      }

      // Next attempt should be rate limited
      const response = await request(app)
        .post('/api/auth/login')
        .send(invalidCredentials)
        .expect(429);

      expect(response.body.error).toContain('rate limit');
    });
  });

  describe('Authorization Security', () => {
    it('should prevent privilege escalation', async () => {
      // Create user with limited privileges
      const userToken = await createUserToken({ role: 'viewer' });

      // Attempt to access admin endpoint
      const response = await request(app)
        .delete('/api/admin/users/123')
        .set('Authorization', `Bearer ${userToken}`)
        .expect(403);

      expect(response.body.error).toContain('Insufficient permissions');
    });
  });
});

Test Automation

CI/CD Pipeline Testing

# .github/workflows/test.yml
name: Test Suite

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x, 20.x]

    steps:
    - uses: actions/checkout@v4
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'

    - name: Install dependencies
      run: npm ci

    - name: Run unit tests
      run: npm run test:unit

    - name: Generate coverage report
      run: npm run test:coverage

    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage/lcov.info

  integration-tests:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
      redis:
        image: redis:7
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
    - uses: actions/checkout@v4
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '20.x'
        cache: 'npm'

    - name: Install dependencies
      run: npm ci

    - name: Run integration tests
      run: npm run test:integration
      env:
        DATABASE_URL: postgresql://postgres:test@localhost:5432/test
        REDIS_URL: redis://localhost:6379

  security-tests:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Run security scan
      uses: securecodewarrior/github-action-add-sarif@v1
      with:
        sarif-file: security-scan-results.sarif

    - name: Run dependency check
      run: npm audit --audit-level moderate

Next Steps: - Review Architecture Guide for system design - Check Building Guide for build processes
- Explore Contributing Guide for development guidelines