Skip to content

Testing

How to run automated tests, what's covered, and the non-optional manual validation procedure before going live.

Last updated: 2026-05-13 Current status: 100 tests, ~87% branch coverage. Keystore: 9/9 passing.


1. Running tests

Command Mode Use case
npm test Watch mode (vitest) Local dev while changing code
npm run test:run Single run CI, pre-commit verification
npx vitest run tests/<file>.test.js Single file Targeted debugging
npx vitest run --coverage With coverage report Periodic; before releases

Vitest config: 📁 vitest.config.mjs.


2. What's covered

Suite File Covers
Config tests/config.test.js zod parsing, NODE_ENV gating, position schema
Rate limit tests/rateLimit.test.js Token-bucket refill, minute/hour caps
Trigger engine tests/triggerEngine.test.js Predicate eval, firing guard, completed terminal, manualFire
Price monitor tests/priceMonitor.test.js V2 reserves, V3 sqrtPriceX96, rolling volume buffer
Event monitor tests/eventMonitor.test.js Custom log filter, emergency wallet detection
Executor dispatch tests/executorDispatch.test.js DEX routing, pRetry, rate-limit acquisition
V2 executor tests/executorV2.test.js Honeypot pre-flight, removeLiquidity, log decode, revoke
V3 executor tests/executorV3.test.js NFT ownership, staticCall slippage, multicall
Raydium executor tests/executorRaydium.test.js AMM v4 / CPMM / CLMM dispatch, slippage
Approval mgr tests/approvalManager.test.js Allowance check, USDT-style reset, revoke
Keystore tests/keystore.test.js Priority chain (Ledger > keystore > raw), prod gating
Gas tests/gas.test.js EIP-1559 fee build, cap enforcement
Slippage tests/slippage.test.js bigint math, edge cases (0 bps, 10_000 bps)
PnL tests/pnl.test.js USD value calc from reserves

3. Known gaps (queued for future work)

In rough priority order:

# Area Why it matters
1 Keystore loader full integration (Ledger mock) High-value path; current tests cover priority chain but not full Ledger transport
2 V3 price decoder utility sqrtPriceX96 → human price; isolated test would catch decimal-handling regressions
3 WS reconnect (makeWsProvider) Currently untested; mock socket disconnects
4 Alerter fan-out Telegram + Discord retry on transient HTTP failures
5 Full suite execution check Periodic green-light gate before release

4. Adding a test

The repo uses vitest with ESM. Mock pattern (see tests/keystore.test.js for a complete example):

import { describe, it, expect, vi, beforeEach } from 'vitest';

const mockConfig = { /* fields */ };
vi.mock('../src/config.js', () => ({ default: mockConfig }));
vi.mock('../src/logger.js', () => ({
  default: { info: () => {}, warn: () => {}, debug: () => {}, error: () => {} },
}));

const { moduleUnderTest } = await import('../src/path/to/module.js');

describe('feature', () => {
  beforeEach(() => { /* reset mockConfig */ });

  it('does the thing', async () => {
    expect(await moduleUnderTest()).toBe(/* … */);
  });
});

Notes: - ESM mocks must be declared before the dynamic await import(). - Always reset mockConfig in beforeEach — mutations leak between tests otherwise. - For tests that spin up an encrypted keystore, use encryptKeystoreJsonSync with scrypt: { N: 2 } to keep tests fast (default N=131072 is ~3s per call).


5. Manual validation — REQUIRED before going live

Unit tests do not verify market behavior, RPC reliability, or trigger sensitivity. Every config change that affects executors, triggers, or chains must pass this checklist before flipping DRY_RUN=false.

5.1 Pre-flight checklist

  • npm run test:run — all tests pass
  • npm run lint — clean
  • .env reviewed — DRY_RUN=true, NODE_ENV=development
  • POSITIONS_JSON reviewed — each entry has the right chain, dex, address/tokenId, and for V3 a poolAddress
  • WebSocket RPC URLs are valid for every chain referenced
  • Telegram bot token + chat ID set and tested (you should get a startup ping)

5.2 Dry-run procedure

npm run dryrun 2>&1 | tee dryrun-$(date +%Y%m%d-%H%M%S).log

Run for ≥24 hours. During this window:

  1. First 5 min — verify all chains connect, all positions register, no schema warnings.
  2. First 1 h — verify price updates flow on every position (LOG_LEVEL=debug for first hour helps).
  3. Sensitivity probe — temporarily lower TRIGGER_PRICE_DROP_PCT to ~0.5 to force a simulated trigger; confirm:
  4. Executor logs intended tx (router address, amounts, slippage, gas)
  5. Telegram alert fires
  6. firing guard prevents repeat trigger for same position
  7. Reset trigger to production value after probe
  8. Manual command test — send /status and /remove <pair> in Telegram; confirm simulated tx logged.
  9. 24h soak — leave running unattended. Look for: WS reconnects (should be rare and recover cleanly), unhandled errors, memory growth.

5.3 Going live

After ≥24h clean dry run:

# In .env:
DRY_RUN=false
NODE_ENV=production
# Confirm signer is keystore or Ledger (raw key is refused in production)

Restart:

npm start | npx pino-pretty
# OR
docker compose down && docker compose up -d && docker compose logs -f

⚠️ First live run: keep one finger on Ctrl+C for the first hour. Verify on a block explorer that the first real removal (if any fires) has correct amounts and slippage. Past mistakes cost real money.


6. Continuous integration (not yet wired)

No CI is configured. When you wire one (GitHub Actions recommended):

# .github/workflows/test.yml
name: test
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npm run lint
      - run: npm run test:run
      - run: npm run audit:evm-only

Add a coverage badge once CI is up.