SPB Git

Repository Public

alerts-helpers.ts 6.8 KB 78 lines typescript

1// Shared fixtures of the alerts and monitoring tests: an own database, a scripted clock and dependencies, row builders.2import { randomUUID } from 'node:crypto';3import { mkdtempSync } from 'node:fs';4import { tmpdir } from 'node:os';5import { join } from 'node:path';6import type { AlertDispatcher, DispatcherDeps } from '../src/alerts/dispatcher.ts';7import type { SignerStatus } from '../src/signer-client.ts';8import { useDatabase } from './derivatives-helpers.ts';910export interface World {11  sql: any; db: typeof import('../src/db.ts'); store: typeof import('../src/alerts/store.ts'); mod: typeof import('../src/alerts/dispatcher.ts');12  clock: { t: number; advance(ms: number): number };13  /** scripted environment read by the condition rules */14  env: { signer: SignerStatus | null; rpc: any[]; loops: any[]; backup: { dir: string; lastAt: number | null; files: number } | null; disk: { path: string; freePct: number; freeGb: number; totalGb: number } | null;15    fetch: typeof fetch; fault: DispatcherDeps['fault'];16    /** v8: the strategy sandbox as the condition rules see it (scripted: the tests do not depend on this machine's sandbox) */17    lab: { mode: 'sandbox-exec' | 'off' | 'unavailable'; ok: boolean; reason: string | null; python: string; checkedAt: number | null; lastFailureAt: number | null; lastFailure: string | null; calls: number; refused: number } };18  dispatcher(o?: Partial<DispatcherDeps>): AlertDispatcher;19  alerts(where?: string): Promise<any[]>;20}2122export const SIGNER_OK = { keyId: 'key-1', addresses: {}, halted: false, enrollmentOpen: false, credentials: [], policy: { version: 3, perTx: {}, daily: {} }, spentToday: {}, journalHead: null, journalLength: 0 } as unknown as SignerStatus;2324export async function world(dbName: string): Promise<World> {25  await useDatabase(dbName);26  process.env.SPW_DATA_DIR = mkdtempSync(join(tmpdir(), 'spw-alerts-'));27  const db = await import('../src/db.ts');28  await db.sql.unsafe('drop schema public cascade; create schema public;');29  await db.migrate();30  const store = await import('../src/alerts/store.ts'), mod = await import('../src/alerts/dispatcher.ts');31  await store.migrateAlerts(true);32  const clock = { t: Date.now(), advance(ms: number) { this.t += ms; return this.t; } };33  const env: World['env'] = { signer: SIGNER_OK, rpc: [], loops: [], backup: null, disk: null, fetch: (async () => new Response(null, { status: 201 })) as typeof fetch, fault: undefined,34    lab: { mode: 'sandbox-exec', ok: true, reason: null, python: '/x/python', checkedAt: 0, lastFailureAt: null, lastFailure: null, calls: 0, refused: 0 } };35  const dispatcher = (o: Partial<DispatcherDeps> = {}) => new mod.AlertDispatcher({36    now: () => clock.t, fetch: ((...a: Parameters<typeof fetch>) => env.fetch(...a)) as typeof fetch, origin: 'https://wallet.test', settleMs: -1e12 /* the scripted clock is behind the audit rows' real timestamps */, allowHttpWebhook: true,37    signerStatus: async () => env.signer, rpcHealth: () => env.rpc, loops: () => env.loops, backup: () => env.backup, disk: async () => env.disk, lab: async () => env.lab, fault: (s, src) => env.fault?.(s, src), ...o });38  const alerts = (where = 'true') => db.sql.unsafe(`select * from alerts where ${where} order by id`) as Promise<any[]>;39  return { sql: db.sql, db, store, mod, clock, env, dispatcher, alerts };40}4142export async function addAgent(sql: any, o: { name?: string; state?: string; interval?: string; lastBar?: Date | null; mandate?: unknown; mandateId?: string | null; capital?: string; cash?: string; universe?: string[] } = {}): Promise<string> {43  const id = randomUUID();44  const [n] = await sql`select coalesce(max(vault_index), 0) + 1 as n from agents`;45  await sql`insert into agents (id, name, state, interval, universe, risk, vault_index, vault_address, last_bar, mandate, mandate_id, capital_usdc, cash)46    values (${id}, ${o.name ?? 'Momentum'}, ${o.state ?? 'PAPER_TRADING'}, ${o.interval ?? '1h'}, ${sql.json(o.universe ?? ['BTC', 'ETH'])}, ${sql.json({})}, ${n.n}, ${'0x' + id.replace(/-/g, '').padEnd(40, '0').slice(0, 40)},47      ${o.lastBar ?? null}, ${o.mandate ? sql.json(o.mandate) : null}, ${o.mandateId ?? null}, ${o.capital ?? '1000000000'}, ${o.cash ?? 'USDC'})`;48  return id;49}50export async function addProposal(sql: any, o: { kind?: string; state?: string; network?: string; symbol?: string; decimals?: number; amount?: string; createdAt?: Date; swap?: unknown; bridge?: unknown; error?: string | null } = {}): Promise<string> {51  const id = randomUUID();52  await sql`insert into proposals (id, idempotency_key, state, network, asset, symbol, decimals, amount, from_addr, to_addr, kind, swap, bridge, error, created_at)53    values (${id}, ${id}, ${o.state ?? 'finalized'}, ${o.network ?? 'base'}, 'native', ${o.symbol ?? 'USDC'}, ${o.decimals ?? 6}, ${o.amount ?? '25000000'}, '0xfrom', '0xto', ${o.kind ?? 'transfer'},54      ${o.swap ? sql.json(o.swap) : null}, ${o.bridge ? sql.json(o.bridge) : null}, ${o.error ?? null}, ${o.createdAt ?? new Date()})`;55  return id;56}57export async function addPaperAccount(sql: any, o: { label?: string; positions?: unknown[]; collateral?: string } = {}): Promise<string> {58  const id = randomUUID();59  await sql`insert into deriv_paper_accounts (id, venue, network, label, initial_collateral, state, policy) values (${id}, 'hyperliquid', 'mainnet', ${o.label ?? 'Paper HL'}, 10000,60    ${sql.json({ positions: o.positions ?? [], collateral: o.collateral ?? '10000' })}, ${sql.json({ halt: null })})`;61  return id;62}63export async function addVenueAccount(sql: any, o: { label?: string; network?: string; mode?: string; meta?: unknown } = {}): Promise<string> {64  const id = randomUUID();65  await sql`insert into deriv_accounts (id, venue, network, account, agent_address, label, mode, policy, meta) values (${id}, 'hyperliquid', ${o.network ?? 'mainnet'}, ${'0x' + id.replace(/-/g, '').slice(0, 40).padEnd(40, '0')}, '0xagent',66    ${o.label ?? 'HL main'}, ${o.mode ?? 'MANUAL_LIVE'}, ${sql.json({})}, ${sql.json(o.meta ?? {})})`;67  return id;68}69export const riskEvent = (sql: any, account: string, kind: string, code: string | null, message: string, ts = new Date(), dedupe: string | null = null) =>70  sql`insert into deriv_risk_events (account_id, ts, kind, code, message, data, dedupe) values (${account}, ${ts}, ${kind}, ${code}, ${message}, '{}', ${dedupe}) returning id`;71/** a session row and the cookie that carries it: the alerts endpoints are tested as the signed-in owner without the passkey ceremony */72export async function ownerCookie(sql: any, ua = 'Mozilla/5.0 (iPhone) Safari/605'): Promise<string> {73  const { createHash, randomBytes } = await import('node:crypto');74  const token = randomBytes(32).toString('base64url');75  await sql`insert into sessions (id_hash, expires_at, ua, ip) values (${createHash('sha256').update(token).digest('hex')}, now() + interval '1 hour', ${ua}, '203.0.113.7')`;76  return `__Host-spw=${token}`;77}78
Files main
Menu

Theme