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 -
.envreviewed —DRY_RUN=true,NODE_ENV=development -
POSITIONS_JSONreviewed — each entry has the rightchain,dex,address/tokenId, and for V3 apoolAddress - 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¶
Run for ≥24 hours. During this window:
- First 5 min — verify all chains connect, all positions register, no schema warnings.
- First 1 h — verify price updates flow on every position (
LOG_LEVEL=debugfor first hour helps). - Sensitivity probe — temporarily lower
TRIGGER_PRICE_DROP_PCTto ~0.5 to force a simulated trigger; confirm: - Executor logs intended tx (router address, amounts, slippage, gas)
- Telegram alert fires
firingguard prevents repeat trigger for same position- Reset trigger to production value after probe
- Manual command test — send
/statusand/remove <pair>in Telegram; confirm simulated tx logged. - 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+Cfor 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.