Skip to main content
Codex Multi-Auth maintains a comprehensive test suite with 2,071 tests across 87 test files, enforcing an 80% coverage threshold for statements, branches, functions, and lines.

Test Suite Overview

Technology Stack

  • Test Framework: Vitest with globals enabled
  • Coverage Provider: V8 (via @vitest/coverage-v8)
  • Property Testing: fast-check and @fast-check/vitest
  • Mocking: Vitest’s built-in vi.mock() and vi.useFakeTimers()

Coverage Thresholds

// vitest.config.ts
coverage: {
  provider: 'v8',
  reporter: ['text', 'json', 'html'],
  thresholds: {
    statements: 80,
    branches: 80,
    functions: 80,
    lines: 80,
  },
}

Running Tests

Basic Test Commands

# Run all tests once
npm test

# Watch mode (re-run on file changes)
npm run test:watch

# Coverage report with thresholds
npm run test:coverage

# Interactive UI
npm run test:ui

Specialized Test Suites

# Model matrix testing
npm run test:model-matrix
npm run test:model-matrix:smoke

# Edit format benchmarks
npm run bench:edit-formats
npm run bench:edit-formats:smoke

Test Architecture

Core Test Categories

CategoryFilesFocus
OAuth Flowauth.test.ts, oauth-server.integration.test.tsPKCE, JWT decoding, callback server (port 1455)
Account Rotationrotation.test.ts, rotation-integration.test.tsHealth scoring, account selection, cooldown
Storagestorage.test.ts, storage-async.test.tsV3 format, worktree migration, concurrent access
Request Pipelinerequest-transformer.test.ts, response-handler.test.tsModel normalization, SSE parsing
CLI Managementcodex-manager-cli.test.ts, cli.test.tsSettings UI, Q cancel, hotkeys
Property Testsproperty/*.test.tsFast-check randomized testing
Chaos Testschaos/fault-injection.test.tsFault injection scenarios

Example Test Patterns

OAuth Flow Testing

// test/auth.test.ts
import { describe, it, expect } from 'vitest';
import { parseAuthorizationInput, createState } from '../lib/auth/auth.js';

describe('Auth Module', () => {
  describe('parseAuthorizationInput', () => {
    it('should parse full OAuth callback URL', () => {
      const input = 'http://127.0.0.1:1455/auth/callback?code=abc123&state=xyz789';
      const result = parseAuthorizationInput(input);
      expect(result).toEqual({ code: 'abc123', state: 'xyz789' });
    });

    it('should parse code#state format', () => {
      const input = 'abc123#xyz789';
      const result = parseAuthorizationInput(input);
      expect(result).toEqual({ code: 'abc123', state: 'xyz789' });
    });
  });

  describe('createState', () => {
    it('should generate a random 32-character hex string', () => {
      const state = createState();
      expect(state).toMatch(/^[a-f0-9]{32}$/);
    });

    it('should generate unique states', () => {
      const state1 = createState();
      const state2 = createState();
      expect(state1).not.toBe(state2);
    });
  });
});

Account Rotation Testing

// test/rotation.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { HealthScoreTracker, DEFAULT_HEALTH_SCORE_CONFIG } from '../lib/rotation.js';

describe('HealthScoreTracker', () => {
  let tracker: HealthScoreTracker;

  beforeEach(() => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2026-01-30T12:00:00Z'));
    tracker = new HealthScoreTracker();
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  describe('recordRateLimit', () => {
    it('decreases score by rateLimitDelta', () => {
      tracker.recordRateLimit(0);
      const expected = 
        DEFAULT_HEALTH_SCORE_CONFIG.maxScore + 
        DEFAULT_HEALTH_SCORE_CONFIG.rateLimitDelta;
      expect(tracker.getScore(0)).toBe(expected);
    });

    it('increments consecutive failures', () => {
      tracker.recordRateLimit(0);
      expect(tracker.getConsecutiveFailures(0)).toBe(1);

      tracker.recordRateLimit(0);
      expect(tracker.getConsecutiveFailures(0)).toBe(2);
    });
  });
});

Storage Testing with Deduplication

// test/storage.test.ts
import { describe, it, expect } from 'vitest';
import { normalizeAccountStorage, deduplicateAccounts } from '../lib/storage.js';

describe('storage', () => {
  describe('deduplication', () => {
    it('deduplicates accounts by keeping the most recently used record', () => {
      const now = Date.now();
      const accounts = [
        {
          accountId: 'acctA',
          refreshToken: 'tokenA',
          addedAt: now - 2000,
          lastUsed: now - 1000,
        },
        {
          accountId: 'acctA',
          refreshToken: 'tokenA',
          addedAt: now - 1500,
          lastUsed: now,  // Most recent
        },
      ];

      const deduped = deduplicateAccounts(accounts);
      expect(deduped).toHaveLength(1);
      expect(deduped[0]?.lastUsed).toBe(now);
    });

    it('remaps activeIndex after deduplication', () => {
      const raw = {
        version: 1,
        activeIndex: 1,
        accounts: [
          { accountId: 'acctA', /* ... */ },
          { accountId: 'acctA', /* duplicate */ },
          { accountId: 'acctB', /* ... */ },
        ],
      };

      const normalized = normalizeAccountStorage(raw);
      expect(normalized?.accounts).toHaveLength(2);
      expect(normalized?.activeIndex).toBe(0);
    });
  });
});

Property-Based Testing

// test/property/rotation.property.test.ts
import { test } from '@fast-check/vitest';
import { fc } from 'fast-check';
import { selectHybridAccount } from '../../lib/rotation.js';

test.prop([
  fc.array(fc.record({
    accountId: fc.string(),
    email: fc.emailAddress(),
    health: fc.integer({ min: 0, max: 100 }),
  }), { minLength: 1, maxLength: 20 }),
])('selectHybridAccount always returns a valid account index', (accounts) => {
  const result = selectHybridAccount(accounts);
  expect(result).toBeGreaterThanOrEqual(0);
  expect(result).toBeLessThan(accounts.length);
});

Testing Best Practices

1. Fake Timers for Deterministic Tests

beforeEach(() => {
  vi.useFakeTimers();
  vi.setSystemTime(new Date('2026-01-30T12:00:00Z'));
});

afterEach(() => {
  vi.useRealTimers();
});

it('schedules retry after exponential backoff', () => {
  const backoff = exponentialBackoff(3); // attempt 3
  vi.advanceTimersByTime(backoff);
  // Assert retry happened
});

2. Windows Filesystem Safety

Problem: Windows antivirus can lock files during cleanup, causing EBUSY/EPERM errors. Solution: Always use removeWithRetry in test cleanup:
import { removeWithRetry } from './test-helpers.js';

afterEach(async () => {
  // WRONG: Direct fs.rm (fails on Windows)
  // await fs.rm(tmpDir, { recursive: true });

  // CORRECT: Retry with backoff
  await removeWithRetry(tmpDir, { recursive: true });
});

3. Mocking Network Requests

import { vi } from 'vitest';

it('handles 5xx server errors with account rotation', async () => {
  const mockFetch = vi.fn().mockResolvedValueOnce({
    ok: false,
    status: 503,
    statusText: 'Service Unavailable',
  });

  global.fetch = mockFetch;

  // Test rotation logic triggers
  await makeRequest();
  
  expect(mockFetch).toHaveBeenCalledTimes(1);
  // Assert account health penalty applied
});

4. Testing OAuth Server Integration

// test/oauth-server.integration.test.ts
import { startOAuthServer } from '../lib/auth/server.js';

it('binds to port 1455 and handles callback', async () => {
  const { server, promise } = await startOAuthServer({
    state: 'test-state',
    codeVerifier: 'test-verifier',
  });

  // Simulate OAuth callback
  const response = await fetch(
    'http://127.0.0.1:1455/auth/callback?code=abc123&state=test-state'
  );

  expect(response.status).toBe(200);
  
  const result = await promise;
  expect(result.code).toBe('abc123');
  
  server.close();
});

Coverage Exclusions

Certain files are excluded from coverage requirements:
// vitest.config.ts
coverage: {
  exclude: [
    'node_modules/',
    'dist/',
    'test/',
    'eslint.config.js',
    'index.ts',              // Plugin entry point (integration tested)
    'lib/codex-manager.ts',  // CLI dispatcher
    'lib/ui/**',             // Terminal UI (hard to test)
    'lib/tools/**',          // Tool helpers
    'scripts/**',            // Build/hygiene scripts
  ],
}

Continuous Integration

CI Test Gate

# Run in CI (GitHub Actions)
npm run typecheck
npm run lint
npm test
npm run build
npm run clean:repo:check  # Validate no committed artifacts
npm run audit:ci          # Security audit

Security Audits

# Production dependencies only (high severity+)
npm run audit:prod

# All dependencies including dev (high severity+)
npm run audit:all

# Dev dependencies with allowlist
npm run audit:dev:allowlist

Chaos and Fault Injection

// test/chaos/fault-injection.test.ts
import { injectFault, FaultType } from '../lib/testing/fault-injector.js';

describe('Chaos Testing', () => {
  it('recovers from network timeout during token refresh', async () => {
    injectFault(FaultType.NETWORK_TIMEOUT, { duration: 5000 });
    
    const result = await refreshAccessToken('refresh-token');
    
    // Should fail gracefully and trigger account rotation
    expect(result.success).toBe(false);
    expect(result.error).toContain('timeout');
  });
});

Test File Organization

test/
├── *.test.ts              # Unit tests mirroring lib/ structure
├── property/              # Property-based tests (fast-check)
│   ├── setup.ts           # Property test configuration
│   └── *.property.test.ts # Randomized invariant tests
├── chaos/                 # Fault injection tests
│   └── fault-injection.test.ts
└── fixtures/              # Test data
    └── v3-storage.json    # Storage format fixtures

Debugging Tests

Run Single Test File

vitest run test/auth.test.ts

Run Specific Test

vitest run -t "should parse full OAuth callback URL"

Enable Debug Logging

ENABLE_PLUGIN_REQUEST_LOGGING=1 npm test
CODEX_PLUGIN_LOG_BODIES=1 npm test  # Include request/response bodies
CODEX_PLUGIN_LOG_BODIES=1 may log sensitive data. Only use during local troubleshooting.

Build docs developers (and LLMs) love