163 lines
4.0 KiB
TypeScript
163 lines
4.0 KiB
TypeScript
import { vi, expect, type Mock } from "vitest";
|
|
import type {
|
|
BaseCommandDependencies,
|
|
CommandDependencies,
|
|
CommandIO,
|
|
CommandPaths,
|
|
} from "../../../src/cli/commands/types";
|
|
import type { AppService } from "../../../src/services/app";
|
|
|
|
/**
|
|
* Captured CLI IO buffers used by tests.
|
|
*/
|
|
export type MockIOCapture = {
|
|
out: string[];
|
|
err: string[];
|
|
verbose: string[];
|
|
};
|
|
|
|
/**
|
|
* Spy functions for each IO channel.
|
|
*/
|
|
export type MockIOSpies = {
|
|
out: Mock;
|
|
err: Mock;
|
|
verbose: Mock;
|
|
};
|
|
|
|
/**
|
|
* Complete mock IO result including the IO adapter, capture buffers, and spies.
|
|
*/
|
|
export type MockIO = {
|
|
io: CommandIO;
|
|
capture: MockIOCapture;
|
|
spies: MockIOSpies;
|
|
};
|
|
|
|
/**
|
|
* Defines an expected log message for assertion.
|
|
* At least one of out, err, or verbose should be specified.
|
|
*/
|
|
export type LogExpectation = {
|
|
/** Expected substring (or exact match if exact=true) in io.out */
|
|
out?: string;
|
|
/** Expected substring (or exact match if exact=true) in io.err */
|
|
err?: string;
|
|
/** Expected substring (or exact match if exact=true) in io.verbose */
|
|
verbose?: string;
|
|
/** If true, match the string exactly instead of using contains (default: false) */
|
|
exact?: boolean;
|
|
};
|
|
|
|
/**
|
|
* Creates a command IO adapter that records every message using vi.fn() spies.
|
|
* This enables vitest's built-in matchers like toHaveBeenCalledWith.
|
|
*/
|
|
export const createMockIO = (): MockIO => {
|
|
const capture: MockIOCapture = {
|
|
out: [],
|
|
err: [],
|
|
verbose: [],
|
|
};
|
|
|
|
const outSpy = vi.fn((message: string) => {
|
|
capture.out.push(message);
|
|
});
|
|
const errSpy = vi.fn((message: string) => {
|
|
capture.err.push(message);
|
|
});
|
|
const verboseSpy = vi.fn((message: string) => {
|
|
capture.verbose.push(message);
|
|
});
|
|
|
|
const io: CommandIO = {
|
|
out: outSpy,
|
|
err: errSpy,
|
|
verbose: verboseSpy,
|
|
};
|
|
|
|
return {
|
|
io,
|
|
capture,
|
|
spies: {
|
|
out: outSpy,
|
|
err: errSpy,
|
|
verbose: verboseSpy,
|
|
},
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Asserts that the expected log messages were printed to the appropriate IO channels.
|
|
* @param spies - The mock IO spies from createMockIO
|
|
* @param logs - Array of log expectations to validate
|
|
*/
|
|
export const expectLogs = (spies: MockIOSpies, logs: LogExpectation[]): void => {
|
|
for (const log of logs) {
|
|
if (log.out !== undefined) {
|
|
if (log.exact) {
|
|
expect(spies.out).toHaveBeenCalledWith(log.out);
|
|
} else {
|
|
expect(spies.out).toHaveBeenCalledWith(expect.stringContaining(log.out));
|
|
}
|
|
}
|
|
if (log.err !== undefined) {
|
|
if (log.exact) {
|
|
expect(spies.err).toHaveBeenCalledWith(log.err);
|
|
} else {
|
|
expect(spies.err).toHaveBeenCalledWith(expect.stringContaining(log.err));
|
|
}
|
|
}
|
|
if (log.verbose !== undefined) {
|
|
if (log.exact) {
|
|
expect(spies.verbose).toHaveBeenCalledWith(log.verbose);
|
|
} else {
|
|
expect(spies.verbose).toHaveBeenCalledWith(expect.stringContaining(log.verbose));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Creates mock paths for testing.
|
|
* @param tempDir - Optional temp directory to use as base for all paths
|
|
*/
|
|
export const createMockPaths = (tempDir?: string): CommandPaths => {
|
|
const base = tempDir ?? "/tmp/xo-cli-test";
|
|
return {
|
|
mnemonicsDir: base,
|
|
dataDir: base,
|
|
walletConfigPath: `${base}/.wallet`,
|
|
workingDir: base,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Creates base command dependencies for commands that do not require the app.
|
|
* @param io - Command IO adapter
|
|
* @param paths - Optional custom paths (defaults to mock paths)
|
|
*/
|
|
export const createBaseCommandDeps = (
|
|
io: CommandIO,
|
|
paths?: CommandPaths,
|
|
): BaseCommandDependencies => ({
|
|
io,
|
|
paths: paths ?? createMockPaths(),
|
|
});
|
|
|
|
/**
|
|
* Creates command dependencies for app-backed command handlers.
|
|
* @param app - App service instance
|
|
* @param io - Command IO adapter
|
|
* @param paths - Optional custom paths (defaults to mock paths)
|
|
*/
|
|
export const createCommandDeps = (
|
|
app: AppService,
|
|
io: CommandIO,
|
|
paths?: CommandPaths,
|
|
): CommandDependencies => ({
|
|
app,
|
|
io,
|
|
paths: paths ?? createMockPaths(),
|
|
});
|