236 lines
7.0 KiB
TypeScript
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);
|
|
}
|
|
});
|
|
});
|