Files
xo-cli/tests/cli/commands/template.test.ts

236 lines
7.0 KiB
TypeScript

import { expect, test, describe, beforeEach, afterEach } from "vitest";
import { mkdtempSync, rmSync, writeFileSync } 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, p2pkhTemplateIdentifier } from "../mocks/template-p2pkh";
import { AppService } from "../../../src/services/app";
import { handleTemplateCommand } from "../../../src/cli/commands/template";
import { CommandError } from "../../../src/cli/commands/types";
import { createCommandDeps, createMockIO, expectLogs, type LogExpectation } from "../mocks/command";
type TestCase = {
name: string;
inputs: string[];
options?: Record<string, string>;
shouldThrow: boolean;
expectedEvent?: string;
expectedData?: Record<string, unknown>;
logs?: LogExpectation[];
};
const testCases: TestCase[] = [
// List command
{
name: "list returns count of imported templates",
inputs: ["list"],
shouldThrow: false,
expectedData: {
count: 1,
},
logs: [{ out: "Wallet (P2PKH)" }],
},
// List by category
{
name: "list action returns actions for template",
inputs: ["list", "action", p2pkhTemplateIdentifier],
shouldThrow: false,
expectedData: {},
logs: [{ out: "receive" }],
},
{
name: "list output returns outputs for template",
inputs: ["list", "output", p2pkhTemplateIdentifier],
shouldThrow: false,
expectedData: {},
logs: [{ out: "receiveOutput" }],
},
{
name: "list variable returns variables for template",
inputs: ["list", "variable", p2pkhTemplateIdentifier],
shouldThrow: false,
expectedData: {},
logs: [{ out: "ownerKey" }],
},
{
name: "list transaction returns transactions for template",
inputs: ["list", "transaction", p2pkhTemplateIdentifier],
shouldThrow: false,
expectedData: {},
},
{
name: "list lockingscript returns locking scripts for template",
inputs: ["list", "lockingscript", p2pkhTemplateIdentifier],
shouldThrow: false,
expectedData: {},
},
// Inspect command
{
name: "inspect action returns action details",
inputs: ["inspect", "action", p2pkhTemplateIdentifier, "receive"],
shouldThrow: false,
expectedData: {},
},
{
name: "inspect output returns output details",
inputs: ["inspect", "output", p2pkhTemplateIdentifier, "receiveOutput"],
shouldThrow: false,
expectedData: {},
},
{
name: "inspect variable returns variable details",
inputs: ["inspect", "variable", p2pkhTemplateIdentifier, "ownerKey"],
shouldThrow: false,
expectedData: {},
},
// Error cases - subcommand
{
name: "throws when no subcommand provided",
inputs: [],
shouldThrow: true,
expectedEvent: "template.subcommand.missing",
},
{
name: "throws when unknown subcommand provided",
inputs: ["unknown-subcommand"],
shouldThrow: true,
expectedEvent: "template.subcommand.unknown",
},
// Error cases - import
{
name: "throws when import called without file",
inputs: ["import"],
shouldThrow: true,
expectedEvent: "template.import.file_missing",
},
{
name: "throws when import called with non-existent file",
inputs: ["import", "non-existent-file.json"],
shouldThrow: true,
expectedEvent: "template.import.file_not_found",
},
// Error cases - list category
{
name: "throws when list category called without template identifier",
inputs: ["list", "action"],
shouldThrow: true,
expectedEvent: "template.list.identifier_missing",
},
{
name: "throws when list category called with unknown template",
inputs: ["list", "action", "unknown-template"],
shouldThrow: true,
expectedEvent: "template.list.not_found",
},
{
name: "throws when list called with unknown category",
inputs: ["list", "unknown-category", p2pkhTemplateIdentifier],
shouldThrow: true,
expectedEvent: "template.list.category_unknown",
},
// Error cases - inspect
{
name: "throws when inspect called without all arguments",
inputs: ["inspect", "action"],
shouldThrow: true,
expectedEvent: "template.inspect.arguments_missing",
},
{
name: "throws when inspect called with unknown template",
inputs: ["inspect", "action", "unknown-template", "receive"],
shouldThrow: true,
expectedEvent: "template.resolve.not_found",
},
{
name: "throws when inspect called with unknown action",
inputs: ["inspect", "action", p2pkhTemplateIdentifier, "unknown-action"],
shouldThrow: true,
expectedEvent: "template.inspect.action_missing",
},
{
name: "throws when inspect called with unknown category",
inputs: ["inspect", "unknown-category", p2pkhTemplateIdentifier, "field"],
shouldThrow: true,
expectedEvent: "template.inspect.category_unknown",
},
// Error cases - set-default
{
name: "throws when set-default called without all arguments",
inputs: ["set-default", "template"],
shouldThrow: true,
expectedEvent: "template.default.arguments_missing",
},
];
describe("template 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-template-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 handleTemplateCommand(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 handleTemplateCommand(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);
}
});
test("import imports template from file", async () => {
const templatePath = path.join(tempDir, "test-template.json");
writeFileSync(templatePath, JSON.stringify(p2pkhTemplate));
const { io } = createMockIO();
const originalCwd = process.cwd();
process.chdir(tempDir);
try {
const result = await handleTemplateCommand(
createCommandDeps(app, io),
["import", "test-template.json"],
{},
);
expect(result.templateFile).toBe("test-template.json");
} finally {
process.chdir(originalCwd);
}
});
});