Advertisement

← vibesdk

container/monitor-cli.test.ts

90.4 KB Download file
/**
 * Monitor-CLI Comprehensive Test Suite
 */
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'bun:test';
import { spawn, ChildProcess } from 'child_process';
import { promises as fs } from 'fs';
import { join } from 'path';
import { randomUUID } from 'crypto';
import { EventEmitter } from 'events';

import { ProcessMonitor } from './process-monitor.js';
import { StorageManager } from './storage.js';
import {
  ProcessInfo,
  ProcessState,
  MonitoringOptions,
  MonitoringEvent,
  SimpleError,
  DEFAULT_MONITORING_OPTIONS,
  getDataDirectory
} from './types.js';

// ============================================================================
// TEST CONFIGURATION
// ============================================================================

const TEST_TIMEOUT = 10000; // 10 seconds for process lifecycle operations
const SHORT_TIMEOUT = 3000;
const STRESS_TIMEOUT = 15000;

// Temp directory for test artifacts
let testDataDir: string;
let testInstanceCounter = 0;

// ============================================================================
// MOCK VITE SERVER SCRIPTS
// ============================================================================

/**
 * Creates mock process scripts for various scenarios.
 * These simulate realistic Vite dev server behavior.
 */
const MOCK_SCRIPTS = {
  // Normal startup with ready message
  startup: `
    const pinoLog = (level, msg, extra = {}) => console.log(JSON.stringify({
      level, msg, time: Date.now(), ...extra
    }));
    pinoLog(30, 'VITE v5.4.0  ready in 234 ms');
    pinoLog(30, 'Local:   http://localhost:5173/');
    pinoLog(30, 'Network: use --host to expose');
    // Keep alive
    setInterval(() => {
      pinoLog(30, 'HMR update detected');
    }, 500);
  `,

  // Emit error then continue running
  error: `
    const pinoLog = (level, msg, extra = {}) => console.log(JSON.stringify({
      level, msg, time: Date.now(), ...extra
    }));
    pinoLog(30, 'VITE v5.4.0  ready in 234 ms');
    setTimeout(() => {
      pinoLog(50, 'React error: Cannot read property of undefined');
    }, 100);
    setInterval(() => {}, 1000);
  `,

  // Emit fatal error and crash
  crash: `
    const pinoLog = (level, msg, extra = {}) => console.log(JSON.stringify({
      level, msg, time: Date.now(), ...extra
    }));
    pinoLog(30, 'VITE v5.4.0  ready in 150 ms');
    setTimeout(() => {
      pinoLog(60, 'fatal error: Maximum call stack size exceeded');
      process.exit(1);
    }, 200);
  `,

  // Clean exit
  cleanExit: `
    const pinoLog = (level, msg, extra = {}) => console.log(JSON.stringify({
      level, msg, time: Date.now(), ...extra
    }));
    pinoLog(30, 'VITE v5.4.0  ready in 100 ms');
    setTimeout(() => {
      pinoLog(30, 'Shutting down...');
      process.exit(0);
    }, 300);
  `,

  // Hang (no output)
  hang: `
    // Start but produce no output - simulates hung process
    setInterval(() => {}, 1000);
  `,

  // High-frequency log output
  flood: `
    const pinoLog = (level, msg, extra = {}) => console.log(JSON.stringify({
      level, msg, time: Date.now(), ...extra
    }));
    pinoLog(30, 'VITE v5.4.0  ready in 50 ms');
    let counter = 0;
    const interval = setInterval(() => {
      for (let i = 0; i < 100; i++) {
        pinoLog(30, 'HMR update ' + (counter++));
      }
      if (counter >= 1000) {
        clearInterval(interval);
        pinoLog(30, 'Flood complete');
      }
    }, 10);
  `,

  // EADDRINUSE error
  portConflict: `
    const pinoLog = (level, msg, extra = {}) => console.log(JSON.stringify({
      level, msg, time: Date.now(), ...extra
    }));
    pinoLog(60, 'Error: listen EADDRINUSE: address already in use :::5173');
    process.exit(1);
  `,

  // Module not found error
  moduleNotFound: `
    const pinoLog = (level, msg, extra = {}) => console.log(JSON.stringify({
      level, msg, time: Date.now(), ...extra
    }));
    pinoLog(60, 'Error: Cannot find module \\'./missing-component\\'');
    process.exit(1);
  `,

  // Compilation error
  compilationError: `
    const pinoLog = (level, msg, extra = {}) => console.log(JSON.stringify({
      level, msg, time: Date.now(), ...extra
    }));
    pinoLog(30, 'VITE v5.4.0  ready in 200 ms');
    setTimeout(() => {
      pinoLog(50, 'Failed to compile: SyntaxError: Unexpected token in App.tsx:42:15');
      pinoLog(50, 'Error in ./src/App.tsx');
    }, 150);
    setInterval(() => {}, 1000);
  `,

  // React error boundary crash
  reactCrash: `
    const pinoLog = (level, msg, extra = {}) => console.log(JSON.stringify({
      level, msg, time: Date.now(), ...extra
    }));
    pinoLog(30, 'VITE v5.4.0  ready in 180 ms');
    setTimeout(() => {
      pinoLog(50, 'Uncaught TypeError: Cannot read properties of null (reading \\'map\\')');
      pinoLog(50, 'The above error occurred in the <UserList> component');
      pinoLog(50, 'React will try to recreate this component tree from scratch');
    }, 200);
    setInterval(() => {}, 1000);
  `,

  // Delayed startup (for health check tests)
  slowStartup: `
    const pinoLog = (level, msg, extra = {}) => console.log(JSON.stringify({
      level, msg, time: Date.now(), ...extra
    }));
    // Wait 2 seconds before ready message
    setTimeout(() => {
      pinoLog(30, 'VITE v5.4.0  ready in 2000 ms');
    }, 2000);
    setInterval(() => {}, 1000);
  `,

  // Multiple crashes for restart testing
  crashAfterRun: `
    const pinoLog = (level, msg, extra = {}) => console.log(JSON.stringify({
      level, msg, time: Date.now(), ...extra
    }));
    const runId = process.env.RUN_ID || Date.now();
    pinoLog(30, 'Starting run ' + runId);
    pinoLog(30, 'VITE v5.4.0  ready in 100 ms');
    setTimeout(() => {
      pinoLog(60, 'Crash ' + runId);
      process.exit(1);
    }, 500);
  `,

  // Echo mode - echoes stdin lines (for testing input)
  echo: `
    const readline = require('readline');
    const rl = readline.createInterface({ input: process.stdin });
    console.log(JSON.stringify({ level: 30, msg: 'Echo server ready', time: Date.now() }));
    rl.on('line', (line) => {
      console.log(JSON.stringify({ level: 30, msg: 'Echo: ' + line, time: Date.now() }));
    });
  `,

  // Plain text output (non-JSON)
  plainText: `
    console.log('Plain text log line 1');
    console.log('Plain text log line 2');
    console.error('Error output line');
    setTimeout(() => {
      console.log('Done');
      process.exit(0);
    }, 200);
  `,

  // Mixed JSON and plain text
  mixedOutput: `
    const pinoLog = (level, msg) => console.log(JSON.stringify({ level, msg, time: Date.now() }));
    pinoLog(30, 'JSON message 1');
    console.log('Plain text message');
    pinoLog(50, 'JSON error message');
    console.error('Plain stderr message');
    pinoLog(30, 'JSON message 2');
    setTimeout(() => process.exit(0), 300);
  `
};

// ============================================================================
// TEST HELPERS
// ============================================================================

/**
 * Generate unique instance ID for test isolation
 */
function getTestInstanceId(): string {
  return `test-${Date.now()}-${++testInstanceCounter}`;
}

/**
 * Create a temporary data directory for tests
 */
async function createTempDataDir(): Promise<string> {
  const dir = join(process.cwd(), '.test-data', randomUUID());
  await fs.mkdir(dir, { recursive: true });
  return dir;
}

/**
 * Clean up temporary directory
 */
async function cleanupTempDir(dir: string): Promise<void> {
  try {
    await fs.rm(dir, { recursive: true, force: true });
  } catch {
    // Ignore cleanup errors
  }
}

/**
 * Wait for ProcessMonitor to reach a specific state
 */
function waitForState(
  monitor: ProcessMonitor,
  targetState: ProcessState,
  timeout: number = TEST_TIMEOUT
): Promise<void> {
  return new Promise((resolve, reject) => {
    const timeoutId = setTimeout(() => {
      reject(new Error(`Timeout waiting for state: ${targetState} (current: ${monitor.getState()})`));
    }, timeout);

    const checkState = () => {
      if (monitor.getState() === targetState) {
        clearTimeout(timeoutId);
        resolve();
        return true;
      }
      return false;
    };

    if (checkState()) return;

    const handler = (event: MonitoringEvent) => {
      if (event.type === 'state_changed' && event.newState === targetState) {
        clearTimeout(timeoutId);
        monitor.removeListener('state_changed', handler);
        resolve();
      }
    };

    monitor.on('state_changed', handler);
  });
}

/**
 * Wait for a specific event from ProcessMonitor
 */
function waitForEvent(
  monitor: ProcessMonitor,
  eventType: MonitoringEvent['type'],
  timeout: number = TEST_TIMEOUT
): Promise<MonitoringEvent> {
  return new Promise((resolve, reject) => {
    const timeoutId = setTimeout(() => {
      reject(new Error(`Timeout waiting for event: ${eventType}`));
    }, timeout);

    const handler = (event: MonitoringEvent) => {
      if (event.type === eventType) {
        clearTimeout(timeoutId);
        monitor.removeListener(eventType, handler);
        resolve(event);
      }
    };

    monitor.on(eventType, handler);
  });
}

/**
 * Spawn a mock process with the given script
 */
function spawnMockProcess(scriptKey: keyof typeof MOCK_SCRIPTS, env?: Record<string, string>): ChildProcess {
  const script = MOCK_SCRIPTS[scriptKey];
  return spawn('bun', ['-e', script], {
    env: { ...process.env, ...env },
    stdio: ['pipe', 'pipe', 'pipe']
  });
}

/**
 * Create ProcessInfo for testing
 */
function createTestProcessInfo(instanceId: string, scriptKey: keyof typeof MOCK_SCRIPTS = 'startup'): ProcessInfo {
  return {
    id: `proc-${instanceId}-${Date.now()}`,
    instanceId,
    command: 'bun',
    args: ['-e', MOCK_SCRIPTS[scriptKey]],
    cwd: process.cwd(),
    restartCount: 0
  };
}

/**
 * Create ProcessMonitor with test defaults
 */
function createTestMonitor(
  storage: StorageManager,
  instanceId: string,
  scriptKey: keyof typeof MOCK_SCRIPTS = 'startup',
  options: MonitoringOptions = {}
): ProcessMonitor {
  const processInfo = createTestProcessInfo(instanceId, scriptKey);
  return new ProcessMonitor(processInfo, storage, {
    autoRestart: false,
    maxRestarts: 3,
    restartDelay: 100,
    healthCheckInterval: 5000,
    errorBufferSize: 100,
    ...options
  });
}

/**
 * Sleep helper
 */
function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// ============================================================================
// CIRCULAR BUFFER TESTS
// ============================================================================

describe('Unit: CircularBuffer', () => {
  // Since CircularBuffer is private, we test its behavior through ProcessMonitor's getRecentLogs

  it('should handle empty buffer', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      const logs = monitor.getRecentLogs(50);
      expect(logs).toEqual([]);
    } finally {
      storage.close();
    }
  });

  it('should store and retrieve logs in order', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      const result = await monitor.start();
      expect(result.success).toBe(true);

      // Wait for some logs to accumulate
      await sleep(500);

      const logs = monitor.getRecentLogs(50);
      expect(logs.length).toBeGreaterThan(0);

      // Verify logs are in chronological order
      for (let i = 1; i < logs.length; i++) {
        expect(logs[i].timestamp.getTime()).toBeGreaterThanOrEqual(logs[i - 1].timestamp.getTime());
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should respect buffer capacity', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    const bufferSize = 50;
    try {
      const monitor = createTestMonitor(storage, instanceId, 'flood', {
        errorBufferSize: bufferSize
      });
      const result = await monitor.start();
      expect(result.success).toBe(true);

      // Wait for flood to complete
      await sleep(2000);

      const logs = monitor.getRecentLogs(1000);
      // Buffer should not exceed configured size
      expect(logs.length).toBeLessThanOrEqual(bufferSize);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should handle slice operations correctly', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      const result = await monitor.start();
      expect(result.success).toBe(true);

      await sleep(1000);

      const allLogs = monitor.getRecentLogs(100);
      const lastFive = monitor.getRecentLogs(5);

      expect(lastFive.length).toBeLessThanOrEqual(5);
      if (allLogs.length >= 5) {
        expect(lastFive).toEqual(allLogs.slice(-5));
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);
});

// ============================================================================
// FILE LOCK TESTS
// ============================================================================

describe('Unit: FileLock', () => {
  it('should create lock file on acquire', async () => {
    const instanceId = getTestInstanceId();
    const logFilePath = join(testDataDir, `${instanceId}-test.log`);
    const lockFilePath = `${logFilePath}.lock`;

    // Write initial file
    await fs.writeFile(logFilePath, 'test content');

    // Acquire lock by simulating what FileLock does
    await fs.writeFile(lockFilePath, `${process.pid}:${Date.now()}`, { flag: 'wx' });

    // Verify lock file exists
    const lockExists = await fs.access(lockFilePath).then(() => true).catch(() => false);
    expect(lockExists).toBe(true);

    // Clean up
    await fs.unlink(lockFilePath).catch(() => {});
    await fs.unlink(logFilePath).catch(() => {});
  });

  it('should detect stale locks', async () => {
    const instanceId = getTestInstanceId();
    const logFilePath = join(testDataDir, `${instanceId}-stale.log`);
    const lockFilePath = `${logFilePath}.lock`;

    // Create stale lock (timestamp from 60 seconds ago)
    const staleTime = Date.now() - 60000;
    await fs.writeFile(lockFilePath, `99999:${staleTime}`);

    // Verify we can read the stale timestamp
    const content = await fs.readFile(lockFilePath, 'utf8');
    const [, timestamp] = content.split(':');
    const lockTime = parseInt(timestamp, 10);

    // Lock is older than 30 seconds, should be considered stale
    expect(Date.now() - lockTime).toBeGreaterThan(30000);

    // Clean up
    await fs.unlink(lockFilePath).catch(() => {});
  });

  it('should block concurrent lock attempts', async () => {
    const instanceId = getTestInstanceId();
    const logFilePath = join(testDataDir, `${instanceId}-concurrent.log`);
    const lockFilePath = `${logFilePath}.lock`;

    // First lock
    await fs.writeFile(lockFilePath, `${process.pid}:${Date.now()}`, { flag: 'wx' });

    // Second lock should fail with EEXIST
    let error: Error | null = null;
    try {
      await fs.writeFile(lockFilePath, `${process.pid}:${Date.now()}`, { flag: 'wx' });
    } catch (e) {
      error = e as Error;
    }

    expect(error).not.toBeNull();
    expect((error as NodeJS.ErrnoException).code).toBe('EEXIST');

    // Clean up
    await fs.unlink(lockFilePath).catch(() => {});
  });
});

// ============================================================================
// SIMPLE LOG MANAGER TESTS (via ProcessMonitor)
// ============================================================================

describe('Unit: SimpleLogManager', () => {
  it('should append and retrieve logs', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      const result = await monitor.start();
      expect(result.success).toBe(true);

      await sleep(500);

      const logs = await monitor.getAllLogsAndReset();
      expect(logs.length).toBeGreaterThan(0);
      expect(logs).toContain('VITE');

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should reset log file after retrieval', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      const result = await monitor.start();
      expect(result.success).toBe(true);

      await sleep(500);

      // First retrieval should have content
      const logs1 = await monitor.getAllLogsAndReset();
      expect(logs1.length).toBeGreaterThan(0);

      // Immediately after reset, logs should be empty or minimal
      const logs2 = await monitor.getAllLogsAndReset();
      expect(logs2.length).toBeLessThan(logs1.length);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should handle concurrent read/write operations', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'flood');
      const result = await monitor.start();
      expect(result.success).toBe(true);

      // Concurrent reads while flooding
      const reads = await Promise.all([
        sleep(100).then(() => monitor.getAllLogsAndReset()),
        sleep(200).then(() => monitor.getAllLogsAndReset()),
        sleep(300).then(() => monitor.getAllLogsAndReset())
      ]);

      // All reads should complete without error
      for (const logs of reads) {
        expect(typeof logs).toBe('string');
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);
});

// ============================================================================
// PROCESS MONITOR STATE MACHINE TESTS
// ============================================================================

describe('Unit: ProcessMonitor State Machine', () => {
  it('should start in stopped state', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      expect(monitor.getState()).toBe('stopped');
    } finally {
      storage.close();
    }
  });

  it('should transition stopped -> starting -> running', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      const states: ProcessState[] = [];

      monitor.on('state_changed', (event: MonitoringEvent) => {
        if (event.type === 'state_changed') {
          states.push(event.newState);
        }
      });

      const result = await monitor.start();
      expect(result.success).toBe(true);
      expect(monitor.getState()).toBe('running');
      expect(states).toContain('starting');
      expect(states).toContain('running');

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should transition running -> stopping -> stopped on graceful stop', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      await monitor.start();
      expect(monitor.getState()).toBe('running');

      const states: ProcessState[] = [];
      monitor.on('state_changed', (event: MonitoringEvent) => {
        if (event.type === 'state_changed') {
          states.push(event.newState);
        }
      });

      await monitor.stop();
      expect(monitor.getState()).toBe('stopped');
      expect(states).toContain('stopping');

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should transition running -> crashed on unexpected exit', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'crash', {
        autoRestart: false
      });

      const crashPromise = waitForEvent(monitor, 'process_crashed');
      await monitor.start();
      await crashPromise;

      expect(monitor.getState()).toBe('crashed');

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should prevent starting when already running', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      await monitor.start();
      expect(monitor.getState()).toBe('running');

      const result = await monitor.start();
      expect(result.success).toBe(false);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should prevent stopping when already stopped', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      expect(monitor.getState()).toBe('stopped');

      const result = await monitor.stop();
      expect(result.success).toBe(true); // Idempotent

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  });
});

// ============================================================================
// PROCESS MONITOR STREAM PROCESSING TESTS
// ============================================================================

describe('Unit: ProcessMonitor Stream Processing', () => {
  it('should handle JSON log lines', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      await monitor.start();
      await sleep(500);

      const logs = monitor.getRecentLogs(50);
      expect(logs.some(l => l.content.includes('VITE'))).toBe(true);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should handle plain text output', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'plainText');
      await monitor.start();
      await waitForState(monitor, 'stopped', SHORT_TIMEOUT);

      const allLogs = await monitor.getAllLogsAndReset();
      expect(allLogs).toContain('Plain text');

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should handle mixed JSON and plain text', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'mixedOutput');
      await monitor.start();
      await waitForState(monitor, 'stopped', SHORT_TIMEOUT);

      const allLogs = await monitor.getAllLogsAndReset();
      expect(allLogs).toContain('JSON message');
      expect(allLogs).toContain('Plain text');

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should separate stdout and stderr', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'plainText');
      await monitor.start();
      await waitForState(monitor, 'stopped', SHORT_TIMEOUT);

      const logs = monitor.getRecentLogs(50);
      const stdoutLogs = logs.filter(l => l.stream === 'stdout');
      const stderrLogs = logs.filter(l => l.stream === 'stderr');

      expect(stdoutLogs.length).toBeGreaterThan(0);
      expect(stderrLogs.length).toBeGreaterThan(0);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should handle high-frequency output', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'flood', {
        errorBufferSize: 200
      });
      await monitor.start();

      // Wait for flood to complete
      await sleep(3000);

      const logs = monitor.getRecentLogs(200);
      expect(logs.length).toBeGreaterThan(50);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, STRESS_TIMEOUT);
});

// ============================================================================
// PROCESS MONITOR ERROR DETECTION TESTS
// ============================================================================

describe('Unit: ProcessMonitor Error Detection', () => {
  it('should detect level 50 errors', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'error');
      const errorPromise = waitForEvent(monitor, 'error_detected');
      await monitor.start();

      const event = await errorPromise;
      expect(event.type).toBe('error_detected');
      if (event.type === 'error_detected') {
        expect(event.error.level).toBe(50);
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should detect level 60 fatal errors', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'crash', {
        autoRestart: false
      });
      const errorPromise = waitForEvent(monitor, 'error_detected');
      await monitor.start();

      const event = await errorPromise;
      expect(event.type).toBe('error_detected');
      if (event.type === 'error_detected') {
        expect(event.error.level).toBe(60);
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should store errors in storage', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'error');
      // Set up event listener BEFORE starting to avoid race condition
      const errorPromise = waitForEvent(monitor, 'error_detected');
      await monitor.start();
      await errorPromise;
      await sleep(200);

      const result = storage.getErrors(instanceId);
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.length).toBeGreaterThan(0);
        expect(result.data[0].level).toBe(50);
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should detect EADDRINUSE as fatal', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'portConflict', {
        autoRestart: false
      });
      const errorPromise = waitForEvent(monitor, 'error_detected');
      await monitor.start();

      const event = await errorPromise;
      if (event.type === 'error_detected') {
        expect(event.error.message).toContain('EADDRINUSE');
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should detect module not found as fatal', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'moduleNotFound', {
        autoRestart: false
      });
      const errorPromise = waitForEvent(monitor, 'error_detected');
      await monitor.start();

      const event = await errorPromise;
      if (event.type === 'error_detected') {
        expect(event.error.message).toContain('Cannot find module');
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should capture compilation errors', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'compilationError');
      const errorPromise = waitForEvent(monitor, 'error_detected');
      await monitor.start();

      const event = await errorPromise;
      if (event.type === 'error_detected') {
        expect(event.error.message).toContain('SyntaxError');
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should capture React errors', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'reactCrash');
      const errorPromise = waitForEvent(monitor, 'error_detected');
      await monitor.start();

      const event = await errorPromise;
      if (event.type === 'error_detected') {
        expect(event.error.message).toContain('TypeError');
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);
});

// ============================================================================
// PROCESS MONITOR RESTART LOGIC TESTS
// ============================================================================

describe('Unit: ProcessMonitor Restart Logic', () => {
  it('should auto-restart on crash when enabled', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'crash', {
        autoRestart: true,
        maxRestarts: 2,
        restartDelay: 100
      });

      let startCount = 0;
      monitor.on('process_started', () => {
        startCount++;
      });

      await monitor.start();

      // Wait for restart attempts
      await sleep(2000);

      // Should have started multiple times (initial + restarts)
      expect(startCount).toBeGreaterThan(1);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should respect maxRestarts limit', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const maxRestarts = 2;
      const monitor = createTestMonitor(storage, instanceId, 'crash', {
        autoRestart: true,
        maxRestarts,
        restartDelay: 100
      });

      let startCount = 0;
      monitor.on('process_started', () => {
        startCount++;
      });

      await monitor.start();

      // Wait for all restart attempts
      await sleep(3000);

      // Initial start + maxRestarts
      expect(startCount).toBeLessThanOrEqual(maxRestarts + 1);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should not restart on clean exit', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'cleanExit', {
        autoRestart: true,
        maxRestarts: 3,
        restartDelay: 100
      });

      let startCount = 0;
      monitor.on('process_started', () => {
        startCount++;
      });

      await monitor.start();
      await waitForState(monitor, 'stopped', SHORT_TIMEOUT);

      // Wait to ensure no restart
      await sleep(500);

      // Should only start once (clean exit = no restart)
      expect(startCount).toBe(1);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should respect restartDelay', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const restartDelay = 500;
      const monitor = createTestMonitor(storage, instanceId, 'crash', {
        autoRestart: true,
        maxRestarts: 1,
        restartDelay
      });

      const startTimes: number[] = [];
      monitor.on('process_started', () => {
        startTimes.push(Date.now());
      });

      await monitor.start();

      // Wait for restart
      await sleep(2000);

      if (startTimes.length >= 2) {
        const delay = startTimes[1] - startTimes[0];
        // Delay should be at least restartDelay (with some tolerance for crash time)
        expect(delay).toBeGreaterThanOrEqual(restartDelay - 100);
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);
});

// ============================================================================
// STORAGE MANAGER TESTS
// ============================================================================

describe('Unit: StorageManager', () => {
  it('should initialize databases', async () => {
    const instanceId = getTestInstanceId();
    const errorDbPath = join(testDataDir, `${instanceId}-errors.db`);
    const logDbPath = join(testDataDir, `${instanceId}-logs.db`);

    const storage = new StorageManager(errorDbPath, logDbPath);

    try {
      const errorDbExists = await fs.access(errorDbPath).then(() => true).catch(() => false);
      const logDbExists = await fs.access(logDbPath).then(() => true).catch(() => false);

      expect(errorDbExists).toBe(true);
      expect(logDbExists).toBe(true);
    } finally {
      storage.close();
    }
  });

  it('should store and retrieve errors', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const error: SimpleError = {
        timestamp: new Date().toISOString(),
        level: 50,
        message: 'Test error message',
        rawOutput: '{"level":50,"msg":"Test error message"}'
      };

      const storeResult = storage.storeError(instanceId, 'proc-1', error);
      expect(storeResult.success).toBe(true);

      const getResult = storage.getErrors(instanceId);
      expect(getResult.success).toBe(true);
      if (getResult.success) {
        expect(getResult.data.length).toBe(1);
        expect(getResult.data[0].message).toBe('Test error message');
      }
    } finally {
      storage.close();
    }
  });

  it('should deduplicate errors by hash', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const error: SimpleError = {
        timestamp: new Date().toISOString(),
        level: 50,
        message: 'Duplicate error',
        rawOutput: '{"level":50,"msg":"Duplicate error"}'
      };

      // Store same error multiple times
      storage.storeError(instanceId, 'proc-1', error);
      storage.storeError(instanceId, 'proc-1', { ...error, timestamp: new Date().toISOString() });
      storage.storeError(instanceId, 'proc-1', { ...error, timestamp: new Date().toISOString() });

      const getResult = storage.getErrors(instanceId);
      expect(getResult.success).toBe(true);
      if (getResult.success) {
        // Should be deduplicated
        expect(getResult.data.length).toBe(1);
        // Occurrence count should be incremented
        expect(getResult.data[0].occurrenceCount).toBe(3);
      }
    } finally {
      storage.close();
    }
  });

  it('should clear errors', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const error: SimpleError = {
        timestamp: new Date().toISOString(),
        level: 50,
        message: 'Error to clear',
        rawOutput: '{}'
      };

      storage.storeError(instanceId, 'proc-1', error);

      const clearResult = storage.clearErrors(instanceId);
      expect(clearResult.success).toBe(true);
      if (clearResult.success) {
        expect(clearResult.data.clearedCount).toBe(1);
      }

      const getResult = storage.getErrors(instanceId);
      if (getResult.success) {
        expect(getResult.data.length).toBe(0);
      }
    } finally {
      storage.close();
    }
  });

  it('should get error summary', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      storage.storeError(instanceId, 'proc-1', {
        timestamp: new Date().toISOString(),
        level: 50,
        message: 'Error 1',
        rawOutput: '{}'
      });
      storage.storeError(instanceId, 'proc-1', {
        timestamp: new Date().toISOString(),
        level: 60,
        message: 'Fatal 1',
        rawOutput: '{}'
      });

      const summaryResult = storage.getErrorSummary(instanceId);
      expect(summaryResult.success).toBe(true);
      if (summaryResult.success) {
        expect(summaryResult.data.totalErrors).toBe(2);
        expect(summaryResult.data.uniqueErrors).toBe(2);
        expect(summaryResult.data.errorsByLevel[50]).toBe(1);
        expect(summaryResult.data.errorsByLevel[60]).toBe(1);
      }
    } finally {
      storage.close();
    }
  });

  it('should store and retrieve logs', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const storeResult = storage.storeLogs([
        { instanceId, processId: 'proc-1', level: 'info', message: 'Log 1', stream: 'stdout' },
        { instanceId, processId: 'proc-1', level: 'error', message: 'Log 2', stream: 'stderr' }
      ]);
      expect(storeResult.success).toBe(true);

      const getResult = storage.getLogs({ instanceId });
      expect(getResult.success).toBe(true);
      if (getResult.success) {
        expect(getResult.data.logs.length).toBe(2);
      }
    } finally {
      storage.close();
    }
  });

  it('should clear logs', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      storage.storeLogs([
        { instanceId, processId: 'proc-1', level: 'info', message: 'Log', stream: 'stdout' }
      ]);

      const clearResult = storage.clearLogs(instanceId);
      expect(clearResult.success).toBe(true);

      const getResult = storage.getLogs({ instanceId });
      if (getResult.success) {
        expect(getResult.data.logs.length).toBe(0);
      }
    } finally {
      storage.close();
    }
  });

  it('should handle transaction correctly', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      storage.transaction(() => {
        storage.storeError(instanceId, 'proc-1', {
          timestamp: new Date().toISOString(),
          level: 50,
          message: 'Transaction error',
          rawOutput: '{}'
        });
      });

      const getResult = storage.getErrors(instanceId);
      expect(getResult.success).toBe(true);
      if (getResult.success) {
        expect(getResult.data.length).toBe(1);
      }
    } finally {
      storage.close();
    }
  });
});

// ============================================================================
// CLI VALIDATION TESTS
// ============================================================================

describe('Unit: CLI Validation', () => {
  // Import validation functions (we test via process spawn to avoid import issues)

  it('should accept valid instance IDs', async () => {
    const validIds = [
      'my-app',
      'myApp123',
      'test_instance',
      'a',
      'A123-test_app'
    ];

    for (const id of validIds) {
      // Validate pattern directly
      const pattern = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
      expect(pattern.test(id)).toBe(true);
    }
  });

  it('should reject invalid instance IDs', async () => {
    const invalidIds = [
      '',
      '-starts-with-dash',
      '_starts_with_underscore',
      '../path/traversal',
      'has spaces',
      'has@special',
      'a'.repeat(65) // Too long
    ];

    const pattern = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
    const maxLen = 64;

    for (const id of invalidIds) {
      const isValid = id.length > 0 && id.length <= maxLen && pattern.test(id);
      expect(isValid).toBe(false);
    }
  });

  it('should parse integer arguments correctly', () => {
    const parseIntArg = (value: unknown): number | undefined => {
      if (value === undefined || value === null) return undefined;
      const parsed = parseInt(String(value), 10);
      return isNaN(parsed) ? undefined : parsed;
    };

    expect(parseIntArg('123')).toBe(123);
    expect(parseIntArg(456)).toBe(456);
    expect(parseIntArg('0')).toBe(0);
    expect(parseIntArg(undefined)).toBe(undefined);
    expect(parseIntArg('invalid')).toBe(undefined);
  });

  it('should handle SafeJSON with circular references', () => {
    // Simulate SafeJSON.stringify
    const safeStringify = (data: unknown): string => {
      try {
        const seen = new WeakSet();
        return JSON.stringify(data, (_key, value) => {
          if (typeof value === 'object' && value !== null) {
            if (seen.has(value)) {
              return '[Circular Reference]';
            }
            seen.add(value);
          }
          return value;
        });
      } catch {
        return '{"error":"Failed to serialize"}';
      }
    };

    // Normal object
    expect(safeStringify({ a: 1 })).toBe('{"a":1}');

    // Circular reference
    const circular: Record<string, unknown> = { a: 1 };
    circular.self = circular;
    const result = safeStringify(circular);
    expect(result).toContain('[Circular Reference]');
  });
});

// ============================================================================
// INTEGRATION TESTS
// ============================================================================

describe('Integration: Full Process Lifecycle', () => {
  it('should complete start -> monitor -> stop cycle', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');

      // Start
      const startResult = await monitor.start();
      expect(startResult.success).toBe(true);
      expect(monitor.getState()).toBe('running');

      // Monitor for a bit
      await sleep(500);

      // Verify logs are being collected
      const logs = monitor.getRecentLogs(50);
      expect(logs.length).toBeGreaterThan(0);

      // Stop
      const stopResult = await monitor.stop();
      expect(stopResult.success).toBe(true);
      expect(monitor.getState()).toBe('stopped');

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should capture errors during lifecycle', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'error');

      await monitor.start();
      await sleep(500);

      // Verify errors were captured
      const errorResult = storage.getErrors(instanceId);
      expect(errorResult.success).toBe(true);
      if (errorResult.success) {
        expect(errorResult.data.length).toBeGreaterThan(0);
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should handle process info correctly', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');

      await monitor.start();

      const info = monitor.getProcessInfo();
      expect(info.instanceId).toBe(instanceId);
      expect(info.command).toBe('bun');
      expect(info.pid).toBeGreaterThan(0);
      expect(info.status).toBe('running');
      expect(info.startTime).toBeInstanceOf(Date);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should emit all expected events', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'error');
      const events: string[] = [];

      monitor.on('process_started', () => events.push('process_started'));
      monitor.on('error_detected', () => events.push('error_detected'));
      monitor.on('process_stopped', () => events.push('process_stopped'));
      monitor.on('state_changed', () => events.push('state_changed'));

      await monitor.start();
      await sleep(500);
      await monitor.stop();

      expect(events).toContain('process_started');
      expect(events).toContain('error_detected');
      expect(events).toContain('process_stopped');
      expect(events).toContain('state_changed');

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);
});

describe('Integration: Error Management', () => {
  it('should accumulate multiple unique errors', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      // Store different errors
      storage.storeError(instanceId, 'proc-1', {
        timestamp: new Date().toISOString(),
        level: 50,
        message: 'Error type A',
        rawOutput: '{}'
      });
      storage.storeError(instanceId, 'proc-1', {
        timestamp: new Date().toISOString(),
        level: 50,
        message: 'Error type B',
        rawOutput: '{}'
      });
      storage.storeError(instanceId, 'proc-1', {
        timestamp: new Date().toISOString(),
        level: 60,
        message: 'Error type C',
        rawOutput: '{}'
      });

      const result = storage.getErrors(instanceId);
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.length).toBe(3);
      }
    } finally {
      storage.close();
    }
  });

  it('should track error occurrence counts', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const error = {
        timestamp: new Date().toISOString(),
        level: 50,
        message: 'Repeated error',
        rawOutput: '{}'
      };

      for (let i = 0; i < 5; i++) {
        storage.storeError(instanceId, 'proc-1', {
          ...error,
          timestamp: new Date().toISOString()
        });
      }

      const result = storage.getErrors(instanceId);
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.length).toBe(1); // Deduplicated
        expect(result.data[0].occurrenceCount).toBe(5);
      }
    } finally {
      storage.close();
    }
  });
});

describe('Integration: Log Management', () => {
  it('should retrieve logs with pagination', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      // Store many logs
      const logs = Array.from({ length: 50 }, (_, i) => ({
        instanceId,
        processId: 'proc-1',
        level: 'info' as const,
        message: `Log ${i}`,
        stream: 'stdout' as const
      }));
      storage.storeLogs(logs);

      // Get first page
      const page1 = storage.getLogs({ instanceId, limit: 10, offset: 0 });
      expect(page1.success).toBe(true);
      if (page1.success) {
        expect(page1.data.logs.length).toBe(10);
        expect(page1.data.hasMore).toBe(true);
      }

      // Get second page
      const page2 = storage.getLogs({ instanceId, limit: 10, offset: 10 });
      expect(page2.success).toBe(true);
      if (page2.success) {
        expect(page2.data.logs.length).toBe(10);
      }
    } finally {
      storage.close();
    }
  });

  it('should return log statistics', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      storage.storeLogs([
        { instanceId, processId: 'proc-1', level: 'info', message: 'Info 1', stream: 'stdout' },
        { instanceId, processId: 'proc-1', level: 'info', message: 'Info 2', stream: 'stdout' },
        { instanceId, processId: 'proc-1', level: 'error', message: 'Error 1', stream: 'stderr' }
      ]);

      const stats = storage.getLogStats(instanceId);
      expect(stats.success).toBe(true);
      if (stats.success) {
        expect(stats.data.totalLogs).toBe(3);
        expect(stats.data.logsByLevel.info).toBe(2);
        expect(stats.data.logsByLevel.error).toBe(1);
        expect(stats.data.logsByStream.stdout).toBe(2);
        expect(stats.data.logsByStream.stderr).toBe(1);
      }
    } finally {
      storage.close();
    }
  });
});

// ============================================================================
// STRESS TESTS
// ============================================================================

describe('Stress: High Frequency Logs', () => {
  it('should handle 1000+ log lines without data loss', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'flood', {
        errorBufferSize: 500
      });

      await monitor.start();

      // Wait for flood to complete
      await sleep(4000);

      // Get all logs
      const logs = await monitor.getAllLogsAndReset();
      const lineCount = logs.split('\n').filter(l => l.trim()).length;

      // Should have captured many logs (some may be trimmed)
      expect(lineCount).toBeGreaterThan(100);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, STRESS_TIMEOUT);

  it('should maintain responsiveness under load', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'flood');
      await monitor.start();

      // Measure response time during flood
      const startTime = Date.now();
      const state = monitor.getState();
      const elapsed = Date.now() - startTime;

      expect(state).toBe('running');
      expect(elapsed).toBeLessThan(100); // Should respond quickly

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);
});

describe('Stress: Rapid Restarts', () => {
  it('should handle rapid restart cycles', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'crash', {
        autoRestart: true,
        maxRestarts: 5,
        restartDelay: 50
      });

      let restartCount = 0;
      monitor.on('process_started', () => {
        restartCount++;
      });

      await monitor.start();

      // Wait for restart cycles
      await sleep(3000);

      // Should have restarted multiple times
      expect(restartCount).toBeGreaterThan(1);
      expect(restartCount).toBeLessThanOrEqual(6); // Initial + 5 restarts

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, STRESS_TIMEOUT);

  it('should not leak resources on rapid start/stop', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      for (let i = 0; i < 5; i++) {
        const monitor = createTestMonitor(storage, `${instanceId}-${i}`, 'startup');
        await monitor.start();
        await sleep(100);
        await monitor.stop();
        await monitor.cleanup();
      }

      // If we got here without errors, resources are being cleaned up
      expect(true).toBe(true);
    } finally {
      storage.close();
    }
  }, STRESS_TIMEOUT);
});

describe('Stress: Concurrent Access', () => {
  it('should handle concurrent log reads', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      await monitor.start();

      // Concurrent reads
      const reads = await Promise.all([
        monitor.getAllLogsAndReset(),
        monitor.getAllLogsAndReset(),
        monitor.getAllLogsAndReset(),
        monitor.getRecentLogs(50),
        monitor.getRecentLogs(50)
      ]);

      // All reads should complete without error
      for (const result of reads) {
        expect(result).toBeDefined();
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should handle concurrent error storage', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      // Concurrent error writes
      const writes = await Promise.all(
        Array.from({ length: 10 }, (_, i) =>
          Promise.resolve(storage.storeError(instanceId, 'proc-1', {
            timestamp: new Date().toISOString(),
            level: 50,
            message: `Concurrent error ${i}`,
            rawOutput: '{}'
          }))
        )
      );

      // All writes should succeed
      for (const result of writes) {
        expect(result.success).toBe(true);
      }

      // Verify all errors stored
      const getResult = storage.getErrors(instanceId);
      if (getResult.success) {
        expect(getResult.data.length).toBe(10);
      }
    } finally {
      storage.close();
    }
  });
});

describe('Stress: Process Killing', () => {
  it('should handle SIGTERM during running state', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      await monitor.start();

      // Immediate stop
      const stopResult = await monitor.stop();
      expect(stopResult.success).toBe(true);
      expect(monitor.getState()).toBe('stopped');

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should handle stop during starting state gracefully', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'slowStartup');

      // Start but don't wait
      const startPromise = monitor.start();

      // Small delay then stop
      await sleep(50);
      const stopResult = await monitor.stop();

      // Should either succeed or return error (not crash)
      expect(stopResult).toBeDefined();

      await startPromise.catch(() => {}); // Handle any start errors
      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should terminate subprocess tree on stop', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    const spawnChildScript = `
      const { spawn } = require('child_process');
      const child = spawn('bun', ['-e', 'setInterval(() => {}, 1000)'], { stdio: 'ignore' });
      console.log(JSON.stringify({ level: 30, msg: 'CHILD_PID:' + child.pid, time: Date.now() }));
      setInterval(() => {}, 1000);
    `;

    try {
      const monitor = new ProcessMonitor(
        {
          id: `proc-${instanceId}`,
          instanceId,
          command: 'bun',
          args: ['-e', spawnChildScript],
          cwd: testDataDir,
          restartCount: 0
        },
        storage,
        {
          ...DEFAULT_MONITORING_OPTIONS,
          healthCheckInterval: 1000,
          autoRestart: false
        }
      );

      await monitor.start();
      await sleep(300);

      const logs = monitor.getRecentLogs(200);
      const childLine = logs.find((line) => line.content.includes('CHILD_PID:'));
      expect(childLine).toBeDefined();

      const match = childLine?.content.match(/CHILD_PID:(\d+)/);
      expect(match).toBeTruthy();
      const childPid = Number(match?.[1]);
      expect(Number.isFinite(childPid)).toBe(true);

      await monitor.stop();

      let childAlive = true;
      try {
        process.kill(childPid, 0);
      } catch {
        childAlive = false;
      }

      expect(childAlive).toBe(false);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);
});

// ============================================================================
// REAL-WORLD SCENARIO TESTS
// ============================================================================

describe('Scenarios: Vite Dev Server', () => {
  it('should detect successful startup', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      await monitor.start();
      await sleep(500);

      const logs = await monitor.getAllLogsAndReset();
      expect(logs).toContain('VITE');
      expect(logs).toContain('ready');

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should capture compilation errors', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'compilationError');
      // Set up event listener BEFORE starting to avoid race condition
      const errorPromise = waitForEvent(monitor, 'error_detected');
      await monitor.start();
      await errorPromise;

      const errors = storage.getErrors(instanceId);
      if (errors.success && errors.data.length > 0) {
        expect(errors.data.some(e => e.message.includes('SyntaxError'))).toBe(true);
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should handle EADDRINUSE gracefully', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'portConflict', {
        autoRestart: false
      });

      const crashPromise = waitForEvent(monitor, 'process_crashed', SHORT_TIMEOUT);
      await monitor.start();
      await crashPromise;

      expect(monitor.getState()).toBe('crashed');

      const errors = storage.getErrors(instanceId);
      if (errors.success && errors.data.length > 0) {
        expect(errors.data.some(e => e.message.includes('EADDRINUSE'))).toBe(true);
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should restart on crash up to maxRestarts', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const maxRestarts = 2;
      const monitor = createTestMonitor(storage, instanceId, 'crash', {
        autoRestart: true,
        maxRestarts,
        restartDelay: 100
      });

      let startCount = 0;
      let crashCount = 0;

      monitor.on('process_started', () => startCount++);
      monitor.on('process_crashed', () => crashCount++);

      await monitor.start();

      // Wait for all restart attempts
      await sleep(3000);

      // Should have exhausted all restart attempts
      expect(crashCount).toBe(maxRestarts + 1);
      expect(startCount).toBe(maxRestarts + 1);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, STRESS_TIMEOUT);

  it('should emit health_check_failed for unresponsive process', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'hang', {
        healthCheckInterval: 500, // Very short for testing
        autoRestart: false
      });

      let healthFailed = false;
      monitor.on('health_check_failed', () => {
        healthFailed = true;
      });

      await monitor.start();

      // Wait for health check to fail (need 2x interval with no activity)
      await sleep(2000);

      expect(healthFailed).toBe(true);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should handle React error boundary crashes', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'reactCrash');

      const errorPromise = waitForEvent(monitor, 'error_detected');
      await monitor.start();
      await errorPromise;

      const errors = storage.getErrors(instanceId);
      if (errors.success) {
        expect(errors.data.some(e =>
          e.message.includes('TypeError') || e.message.includes('React')
        )).toBe(true);
      }

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should distinguish between graceful and forced shutdown', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      // Test graceful shutdown (clean exit)
      const monitor1 = createTestMonitor(storage, `${instanceId}-1`, 'cleanExit', {
        autoRestart: false
      });
      await monitor1.start();
      await waitForState(monitor1, 'stopped', SHORT_TIMEOUT);

      // Should be stopped, not crashed
      expect(monitor1.getState()).toBe('stopped');
      await monitor1.cleanup();

      // Test forced shutdown (crash)
      const monitor2 = createTestMonitor(storage, `${instanceId}-2`, 'crash', {
        autoRestart: false
      });
      await monitor2.start();
      await waitForState(monitor2, 'crashed', SHORT_TIMEOUT);

      // Should be crashed
      expect(monitor2.getState()).toBe('crashed');
      await monitor2.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);
});

// ============================================================================
// MONITOR LOG COHERENCE TESTS
// ============================================================================

describe('Monitor Logs: Coherent Log Streams', () => {
  it('should include monitor logs for process lifecycle events', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      await monitor.start();
      await sleep(500);
      await monitor.stop();

      const logs = await monitor.getAllLogsAndReset();

      // Should have monitor logs for process creation and start
      expect(logs).toContain('[MONITOR]');
      expect(logs).toContain('Starting process');
      expect(logs).toContain('Process started successfully');
      expect(logs).toContain('PID=');

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should log crash events and restart attempts coherently', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'crash', {
        autoRestart: true,
        maxRestarts: 2,
        restartDelay: 100
      });

      await monitor.start();

      // Wait for restart cycles to complete
      await sleep(2500);

      const logs = await monitor.getAllLogsAndReset();

      // Should have multiple lifecycle events documented
      expect(logs).toContain('[MONITOR]');
      expect(logs).toContain('Process crashed');
      expect(logs).toContain('Scheduling restart');
      expect(logs).toContain('attempt=1');
      expect(logs).toContain('attempt=2');

      // Should document max restarts reached
      expect(logs).toContain('maxRestarts');

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, STRESS_TIMEOUT);

  it('should maintain coherent log stream across multiple restarts', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'crash', {
        autoRestart: true,
        maxRestarts: 3,
        restartDelay: 50
      });

      let startCount = 0;
      monitor.on('process_started', () => startCount++);

      await monitor.start();

      // Wait for all restart cycles
      await sleep(3000);

      const logs = await monitor.getAllLogsAndReset();
      const logLines = logs.split('\n');

      // Find all monitor log lines
      const monitorLogs = logLines.filter(line => line.includes('[MONITOR]'));

      // Should have at least: start, crash, restart schedule, start, crash... for each cycle
      expect(monitorLogs.length).toBeGreaterThanOrEqual(8); // At least 2 events per restart cycle

      // Verify chronological order - each "Starting process" should be followed by "Process started" or error
      const startingLogs = monitorLogs.filter(l => l.includes('Starting process'));
      const startedLogs = monitorLogs.filter(l => l.includes('Process started successfully'));
      const crashedLogs = monitorLogs.filter(l => l.includes('Process crashed'));

      // Should have starts + crashes = total runs
      expect(startingLogs.length).toBe(startCount);
      expect(crashedLogs.length).toBeGreaterThanOrEqual(startCount - 1);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, STRESS_TIMEOUT);

  it('should include runtime duration in exit logs', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'cleanExit', {
        autoRestart: false
      });

      await monitor.start();
      await waitForState(monitor, 'stopped', SHORT_TIMEOUT);

      const logs = await monitor.getAllLogsAndReset();

      // Should log runtime information on exit
      expect(logs).toContain('[MONITOR]');
      expect(logs).toContain('runtime=');
      expect(logs).toContain('exitCode=0');

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should log stop request and graceful shutdown', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = createTestMonitor(storage, instanceId, 'startup');
      await monitor.start();
      await sleep(300);
      await monitor.stop();

      const logs = await monitor.getAllLogsAndReset();

      // Should log stop request and clean exit
      expect(logs).toContain('[MONITOR]');
      expect(logs).toContain('Stop requested');
      // Process exits cleanly via SIGTERM (exit event comes first, marking it as clean exit)
      expect(logs).toContain('exited cleanly');

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);
});

// ============================================================================
// PORT HEALTH CHECK TESTS
// ============================================================================

describe('Health Check: Port Monitoring', () => {
  // Mock script that binds to a port (using Bun's built-in server)
  const createPortBindScript = (port: number) => `
    const pinoLog = (level, msg) => console.log(JSON.stringify({
      level, msg, time: Date.now()
    }));

    pinoLog(30, 'Starting server...');

    Bun.serve({
      port: ${port},
      fetch(req) {
        return new Response('OK');
      }
    });

    pinoLog(30, 'Server listening on port ${port}');
    // Keep alive
    setInterval(() => {}, 1000);
  `;

  // Script that doesn't bind to any port (simulates misconfigured server that hangs)
  const noPortBindScript = `
    const pinoLog = (level, msg) => console.log(JSON.stringify({
      level, msg, time: Date.now()
    }));

    pinoLog(30, 'Starting but NOT binding to port...');

    // Just stay alive without producing output (simulates a hung startup)
    setInterval(() => {}, 1000);
  `;

  it('should detect when expected port is bound', async () => {
    const instanceId = getTestInstanceId();
    const testPort = 19000 + Math.floor(Math.random() * 1000); // Random port to avoid conflicts
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = new ProcessMonitor(
        {
          id: `proc-${instanceId}`,
          instanceId,
          command: 'bun',
          args: ['-e', createPortBindScript(testPort)],
          cwd: testDataDir,
          restartCount: 0
        },
        storage,
        {
          ...DEFAULT_MONITORING_OPTIONS,
          expectedPort: testPort,
          healthCheckInterval: 500,
          autoRestart: false
        }
      );

      await monitor.start();

      // Wait for port to bind and health check to run
      await sleep(1500);

      const logs = await monitor.getAllLogsAndReset();

      // Should log when port starts accepting connections
      expect(logs).toContain('[MONITOR]');
      expect(logs).toContain(`Port ${testPort}`);
      expect(logs).toContain('accepting connections');

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should report health issue when expected port is not bound', async () => {
    const instanceId = getTestInstanceId();
    const testPort = 19000 + Math.floor(Math.random() * 1000);
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      const monitor = new ProcessMonitor(
        {
          id: `proc-${instanceId}`,
          instanceId,
          command: 'bun',
          args: ['-e', noPortBindScript],
          cwd: testDataDir,
          restartCount: 0
        },
        storage,
        {
          ...DEFAULT_MONITORING_OPTIONS,
          expectedPort: testPort,
          healthCheckInterval: 500,
          autoRestart: false
        }
      );

      let healthCheckFailed = false;
      monitor.on('health_check_failed', () => {
        healthCheckFailed = true;
      });

      await monitor.start();

      // Wait for health check to detect missing port (needs 2x healthCheckInterval with no port)
      await sleep(2000);

      // Should have detected the missing port
      expect(healthCheckFailed).toBe(true);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should include expectedPort in startup log', async () => {
    const instanceId = getTestInstanceId();
    const testPort = 19000 + Math.floor(Math.random() * 1000);
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      // Create monitor with expectedPort option
      const monitor = new ProcessMonitor(
        {
          id: `proc-${instanceId}`,
          instanceId,
          command: 'bun',
          args: ['-e', MOCK_SCRIPTS.startup],
          cwd: testDataDir,
          restartCount: 0
        },
        storage,
        {
          ...DEFAULT_MONITORING_OPTIONS,
          expectedPort: testPort,
          healthCheckInterval: 1000,
          autoRestart: false
        }
      );

      await monitor.start();
      await sleep(500);

      const logs = await monitor.getAllLogsAndReset();

      // Should include port info in startup log
      expect(logs).toContain('[MONITOR]');
      expect(logs).toContain(`expectedPort=${testPort}`);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);

  it('should check PID liveness during health check', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    try {
      // Use hang script which produces no output - this will trigger health check
      const monitor = createTestMonitor(storage, instanceId, 'hang', {
        healthCheckInterval: 300,
        autoRestart: false
      });

      let healthCheckEvents: MonitoringEvent[] = [];
      monitor.on('health_check_failed', (event) => {
        healthCheckEvents.push(event);
      });

      await monitor.start();

      // Wait for health checks to run
      await sleep(1500);

      // Health check should have run (due to inactivity) but PID should still be alive
      // So we shouldn't get "PID not responding" - just inactivity warnings
      const hasInactivityWarning = healthCheckEvents.length > 0;
      expect(hasInactivityWarning).toBe(true);

      await monitor.cleanup();
    } finally {
      storage.close();
    }
  }, TEST_TIMEOUT);
});

// ============================================================================
// PORT LIVENESS RESTART TESTS
// ============================================================================

describe('Health Check: Port Liveness Restart', () => {
  it('should restart when previously responsive port becomes unresponsive', async () => {
    const instanceId = getTestInstanceId();
    const storage = new StorageManager(
      join(testDataDir, `${instanceId}-errors.db`),
      join(testDataDir, `${instanceId}-logs.db`)
    );

    const originalFetch = globalThis.fetch;
    let callCount = 0;

    try {
      // First probe succeeds (marks port confirmed), next two fail (triggers restart), then succeed again.
      globalThis.fetch = (async () => {
        callCount += 1;
        if (callCount === 1 || callCount >= 4) {
          return new Response('OK', { status: 200 });
        }
        throw new Error('ECONNREFUSED');
      }) as typeof fetch;

      const testPort = 19000 + Math.floor(Math.random() * 1000);

      const monitor = new ProcessMonitor(
        {
          id: `proc-${instanceId}`,
          instanceId,
          command: 'bun',
          args: ['-e', MOCK_SCRIPTS.hang],
          cwd: testDataDir,
          restartCount: 0
        },
        storage,
        {
          ...DEFAULT_MONITORING_OPTIONS,
          expectedPort: testPort,
          healthCheckInterval: 50,
          restartDelay: 10,
          maxRestarts: 2,
          autoRestart: true
        }
      );

      let startedCount = 0;
      const startedTwice = new Promise<void>((resolve, reject) => {
        const timeout = setTimeout(() => {
          reject(new Error('Timeout waiting for second process start'));
        }, TEST_TIMEOUT);

        monitor.on('process_started', () => {
          startedCount += 1;
          if (startedCount >= 2) {
            clearTimeout(timeout);
            resolve();
          }
        });
      });

      await monitor.start();
      await startedTwice;

      const logs = await monitor.getAllLogsAndReset();
      expect(logs).toContain('Health-triggered restart');

      await monitor.cleanup();
    } finally {
      globalThis.fetch = originalFetch;
      storage.close();
    }
  }, TEST_TIMEOUT);
});

// ============================================================================
// CLI FLAG WIRING TESTS
// ============================================================================

describe('CLI: Monitoring Flag Wiring', () => {
  it('should apply port-based monitoring defaults when only --port is provided', async () => {
    const instanceId = getTestInstanceId();
    const port = 20000 + Math.floor(Math.random() * 1000);

    const child = spawn(
      'bun',
      [
        'run',
        'cli-tools.ts',
        'process',
        'start',
        '-i',
        instanceId,
        '--port',
        String(port),
        '--',
        'bun',
        '-e',
        'setInterval(() => {}, 1000)'
      ],
      {
        cwd: process.cwd(),
        env: { ...process.env },
        stdio: ['ignore', 'pipe', 'pipe']
      }
    );

    let stdout = '';

    const waitForConfiguredOutput = new Promise<void>((resolve, reject) => {
      const timeout = setTimeout(() => {
        reject(new Error('Timeout waiting for CLI configuration output'));
      }, SHORT_TIMEOUT);

      child.stdout?.on('data', (chunk: Buffer) => {
        stdout += chunk.toString('utf8');
        if (
          stdout.includes('Health Check Interval: 10000ms') &&
          stdout.includes('Restart Delay: 1000ms') &&
          stdout.includes('Max Restarts: 120') &&
          stdout.includes(`Expected Port: ${port}`)
        ) {
          clearTimeout(timeout);
          resolve();
        }
      });

      child.stderr?.on('data', (chunk: Buffer) => {
        stdout += chunk.toString('utf8');
      });

      child.on('error', reject);
    });

    try {
      await waitForConfiguredOutput;
      expect(stdout).toContain('Starting Process Monitor');
      expect(stdout).toContain('Health Check Interval: 10000ms');
      expect(stdout).toContain('Restart Delay: 1000ms');
      expect(stdout).toContain('Max Restarts: 120');
      expect(stdout).toContain(`Expected Port: ${port}`);
    } finally {
      child.kill('SIGINT');
      await new Promise<void>((resolve) => {
        child.on('exit', () => resolve());
        setTimeout(() => resolve(), SHORT_TIMEOUT);
      });
    }
  }, TEST_TIMEOUT);

  it('should allow overriding port-based defaults via explicit flags', async () => {
    const instanceId = getTestInstanceId();
    const port = 20000 + Math.floor(Math.random() * 1000);

    const child = spawn(
      'bun',
      [
        'run',
        'cli-tools.ts',
        'process',
        'start',
        '-i',
        instanceId,
        '--port',
        String(port),
        '--health-check-interval',
        '12345',
        '--max-restarts',
        '5',
        '--restart-delay',
        '2345',
        '--',
        'bun',
        '-e',
        'setInterval(() => {}, 1000)'
      ],
      {
        cwd: process.cwd(),
        env: { ...process.env },
        stdio: ['ignore', 'pipe', 'pipe']
      }
    );

    let stdout = '';

    const waitForConfiguredOutput = new Promise<void>((resolve, reject) => {
      const timeout = setTimeout(() => {
        reject(new Error('Timeout waiting for CLI configuration output'));
      }, SHORT_TIMEOUT);

      child.stdout?.on('data', (chunk: Buffer) => {
        stdout += chunk.toString('utf8');
        if (
          stdout.includes('Health Check Interval: 12345ms') &&
          stdout.includes('Restart Delay: 2345ms') &&
          stdout.includes('Max Restarts: 5') &&
          stdout.includes(`Expected Port: ${port}`)
        ) {
          clearTimeout(timeout);
          resolve();
        }
      });

      child.stderr?.on('data', (chunk: Buffer) => {
        stdout += chunk.toString('utf8');
      });

      child.on('error', reject);
    });

    try {
      await waitForConfiguredOutput;
      expect(stdout).toContain('Health Check Interval: 12345ms');
      expect(stdout).toContain('Restart Delay: 2345ms');
      expect(stdout).toContain('Max Restarts: 5');
      expect(stdout).toContain(`Expected Port: ${port}`);
    } finally {
      child.kill('SIGINT');
      await new Promise<void>((resolve) => {
        child.on('exit', () => resolve());
        setTimeout(() => resolve(), SHORT_TIMEOUT);
      });
    }
  }, TEST_TIMEOUT);
});

// ============================================================================
// INTEGRATION: CLI LOGS GET
// ============================================================================

describe('Integration: CLI Logs Get', () => {
  it('should include health restart events in `logs get` output', async () => {
    const instanceId = getTestInstanceId();
    const port = 21000 + Math.floor(Math.random() * 1000);

    const serverStopsListeningScript = `
      const port = Number.parseInt(process.env.PORT || '0', 10);
      const pinoLog = (level, msg) => console.log(JSON.stringify({ level, msg, time: Date.now() }));

      const server = Bun.serve({
        port,
        fetch() {
          return new Response('OK');
        }
      });

      pinoLog(30, 'Server started on ' + port);

      setTimeout(() => {
        server.stop(true);
        pinoLog(30, 'Server stopped listening on ' + port);
      }, 200);

      setInterval(() => {}, 1000);
    `;

    const startProc = spawn(
      'bun',
      [
        'run',
        'cli-tools.ts',
        'process',
        'start',
        '-i',
        instanceId,
        '--port',
        String(port),
        '--health-check-interval',
        '100',
        '--max-restarts',
        '1',
        '--restart-delay',
        '10',
        '--',
        'bun',
        '-e',
        serverStopsListeningScript
      ],
      {
        cwd: process.cwd(),
        env: { ...process.env },
        stdio: ['ignore', 'pipe', 'pipe']
      }
    );

    let combinedOutput = '';

    const waitForRestart = new Promise<void>((resolve, reject) => {
      const timeout = setTimeout(() => {
        reject(new Error('Timeout waiting for CLI to restart process'));
      }, STRESS_TIMEOUT);

      const onChunk = (chunk: Buffer) => {
        combinedOutput += chunk.toString('utf8');
        if (combinedOutput.includes('Scheduling restart') || combinedOutput.includes('Restarting process')) {
          clearTimeout(timeout);
          resolve();
        }
      };

      startProc.stdout?.on('data', onChunk);
      startProc.stderr?.on('data', onChunk);
      startProc.on('error', reject);
    });

    try {
      await waitForRestart;

      // Give log file a moment to flush.
      await sleep(200);

      const getLogsProc = spawn(
        'bun',
        ['run', 'cli-tools.ts', 'logs', 'get', '-i', instanceId, '--format', 'raw', '--reset'],
        { cwd: process.cwd(), env: { ...process.env }, stdio: ['ignore', 'pipe', 'pipe'] }
      );

      let logsOut = '';
      await new Promise<void>((resolve, reject) => {
        const timeout = setTimeout(() => reject(new Error('Timeout reading logs get output')), SHORT_TIMEOUT);

        getLogsProc.stdout?.on('data', (chunk: Buffer) => {
          logsOut += chunk.toString('utf8');
        });
        getLogsProc.stderr?.on('data', (chunk: Buffer) => {
          logsOut += chunk.toString('utf8');
        });

        getLogsProc.on('error', reject);
        getLogsProc.on('exit', (code) => {
          clearTimeout(timeout);
          if (code === 0) resolve();
          else reject(new Error(`logs get exited with code ${code}`));
        });
      });

      expect(logsOut).toContain('[monitor]');
      expect(logsOut).toContain('[MONITOR] Starting process:');
      expect(logsOut).toContain(`Port ${port} is now accepting connections`);
      expect(logsOut).toContain('Health-triggered restart: port');
    } finally {
      startProc.kill('SIGINT');
      await new Promise<void>((resolve) => {
        startProc.on('exit', () => resolve());
        setTimeout(() => resolve(), SHORT_TIMEOUT);
      });
    }
  }, STRESS_TIMEOUT);
});

// ============================================================================
// SETUP AND TEARDOWN
// ============================================================================

beforeAll(async () => {
  testDataDir = await createTempDataDir();
  // Set environment for tests
  process.env.CLI_DATA_DIR = testDataDir;
});

afterAll(async () => {
  await cleanupTempDir(testDataDir);
});

afterEach(async () => {
  // Small delay between tests for cleanup
  await sleep(100);
});