import { expect, test, describe, beforeEach, afterEach } from "vitest"; import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { createMockAppService, createMockEngine, DEFAULT_SEED, } from "../mocks/engine"; import { type Engine } from "@xo-cash/engine"; import { p2pkhTemplate } from "../mocks/template-p2pkh"; import { AppService } from "../../../src/services/app"; import { handleReceiveCommand } from "../../../src/cli/commands/receive"; import { CommandError } from "../../../src/cli/commands/types"; import { createCommandDeps, createMockIO, expectLogs, type LogExpectation, } from "../mocks/command"; type TestCase = { name: string; inputs: string[]; options?: Record; shouldThrow: boolean; expectedEvent?: string; expectedData?: Record; logs?: LogExpectation[]; }; const testCases: TestCase[] = [ // Successful address generation with template name and output identifier { name: "generates address with template name and output", inputs: ["Wallet (P2PKH)", "receiveOutput"], shouldThrow: false, expectedData: { address: expect.stringMatching(/^bitcoincash:q[a-z0-9]+$/), }, logs: [{ out: "bitcoincash:q" }], }, // Successful address generation with role specified { name: "generates address with template name, output, and role", inputs: ["Wallet (P2PKH)", "receiveOutput", "receiver"], shouldThrow: false, expectedData: { address: expect.stringMatching(/^bitcoincash:q[a-z0-9]+$/), }, logs: [{ out: "bitcoincash:q" }], }, // Missing all required arguments { name: "throws when no arguments provided", inputs: [], shouldThrow: true, expectedEvent: "receive.arguments.missing", }, // Missing output identifier { name: "throws when output identifier missing", inputs: ["Wallet (P2PKH)"], shouldThrow: true, expectedEvent: "receive.arguments.missing", }, // Unknown template { name: "throws when template not found", inputs: ["unknown-template", "receiveOutput"], shouldThrow: true, expectedEvent: "template.resolve.not_found", }, ]; describe("receive command", () => { let engine: Engine; let app: AppService; let tempDir: string; beforeEach(async () => { engine = await createMockEngine(DEFAULT_SEED); await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); tempDir = mkdtempSync(path.join(tmpdir(), "xo-cli-receive-tests-")); }); afterEach(async () => { await engine.stop(); rmSync(tempDir, { recursive: true, force: true }); }); test.each(testCases)( "$name", async ({ inputs, options, shouldThrow, expectedEvent, expectedData, logs, }) => { const { io, spies } = createMockIO(); if (shouldThrow) { try { await handleReceiveCommand( createCommandDeps(app, io), inputs, options ?? {}, ); expect.fail("Expected command to throw"); } catch (error) { if (expectedEvent) { expect(error).toBeInstanceOf(CommandError); expect((error as CommandError).event).toBe(expectedEvent); } } } else { const result = await handleReceiveCommand( createCommandDeps(app, io), inputs, options ?? {}, ); if (expectedData) { Object.entries(expectedData).forEach(([key, value]) => { expect(result[key as keyof typeof result]).toEqual(value); }); } } if (logs) { expectLogs(spies, logs); } }, ); });