Formatting
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect } from "vitest"
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
import { convertArgsToObject } from "../../src/cli/arguments";
|
||||
|
||||
@@ -11,45 +11,59 @@ const testCases = [
|
||||
},
|
||||
},
|
||||
{
|
||||
input: ['-var-requested-satohis', '1000', '-role', 'receiver'],
|
||||
input: ["-var-requested-satohis", "1000", "-role", "receiver"],
|
||||
expected: {
|
||||
args: [],
|
||||
options: { "varRequestedSatohis": "1000", role: "receiver" },
|
||||
options: { varRequestedSatohis: "1000", role: "receiver" },
|
||||
},
|
||||
},
|
||||
{
|
||||
input: ['-o', 'output.json', '-var-requested-satohis', '1000', '-role', 'receiver'],
|
||||
input: [
|
||||
"-o",
|
||||
"output.json",
|
||||
"-var-requested-satohis",
|
||||
"1000",
|
||||
"-role",
|
||||
"receiver",
|
||||
],
|
||||
expected: {
|
||||
args: [],
|
||||
options: { output: "output.json", "varRequestedSatohis": "1000", role: "receiver" },
|
||||
options: {
|
||||
output: "output.json",
|
||||
varRequestedSatohis: "1000",
|
||||
role: "receiver",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: ['mnemonic', 'create', 'page', 'pencil', '-v', '-o', 'mnemonic.txt'],
|
||||
input: ["mnemonic", "create", "page", "pencil", "-v", "-o", "mnemonic.txt"],
|
||||
expected: {
|
||||
args: ['mnemonic', 'create', 'page', 'pencil'],
|
||||
args: ["mnemonic", "create", "page", "pencil"],
|
||||
options: { verbose: "true", output: "mnemonic.txt" },
|
||||
},
|
||||
},
|
||||
{
|
||||
input: ['-v', 'invitation', 'list', '-m', 'mnemonicFile'],
|
||||
input: ["-v", "invitation", "list", "-m", "mnemonicFile"],
|
||||
expected: {
|
||||
args: ['invitation', 'list'],
|
||||
args: ["invitation", "list"],
|
||||
options: { verbose: "true", mnemonicFile: "mnemonicFile" },
|
||||
},
|
||||
},
|
||||
{
|
||||
input: ['--help', 'template', 'import', 'template.json'],
|
||||
input: ["--help", "template", "import", "template.json"],
|
||||
expected: {
|
||||
args: ['template', 'import', 'template.json'],
|
||||
args: ["template", "import", "template.json"],
|
||||
options: { help: "true" },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe("convertArgsToObject", () => {
|
||||
it.each(testCases)("should split positional args from options", ({ input, expected }) => {
|
||||
const result = convertArgsToObject(input);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
it.each(testCases)(
|
||||
"should split positional args from options",
|
||||
({ input, expected }) => {
|
||||
const result = convertArgsToObject(input);
|
||||
expect(result).toEqual(expected);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -73,11 +73,19 @@ describe("command handler contracts", () => {
|
||||
const { io } = createMockIO();
|
||||
|
||||
await expect(
|
||||
handleResourceCommand(createCommandDeps(fakeApp, io), ["does-not-exist"], {}),
|
||||
handleResourceCommand(
|
||||
createCommandDeps(fakeApp, io),
|
||||
["does-not-exist"],
|
||||
{},
|
||||
),
|
||||
).rejects.toThrow(CommandError);
|
||||
|
||||
try {
|
||||
await handleResourceCommand(createCommandDeps(fakeApp, io), ["does-not-exist"], {});
|
||||
await handleResourceCommand(
|
||||
createCommandDeps(fakeApp, io),
|
||||
["does-not-exist"],
|
||||
{},
|
||||
);
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(CommandError);
|
||||
expect((error as CommandError).event).toBe("resource.subcommand.unknown");
|
||||
|
||||
@@ -11,18 +11,40 @@
|
||||
*/
|
||||
|
||||
import { expect, test, describe, beforeEach, afterEach } from "vitest";
|
||||
import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import {
|
||||
existsSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
readdirSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { addFakeResource, createMockAppService, createMockEngine, DEFAULT_SEED, randomTxHash } from "../mocks/engine";
|
||||
import {
|
||||
addFakeResource,
|
||||
createMockAppService,
|
||||
createMockEngine,
|
||||
DEFAULT_SEED,
|
||||
randomTxHash,
|
||||
} from "../mocks/engine";
|
||||
import { type Engine } from "@xo-cash/engine";
|
||||
import { p2pkhTemplate, p2pkhTemplateIdentifier } from "../mocks/template-p2pkh";
|
||||
import {
|
||||
p2pkhTemplate,
|
||||
p2pkhTemplateIdentifier,
|
||||
} from "../mocks/template-p2pkh";
|
||||
import { AppService } from "../../../src/services/app";
|
||||
|
||||
import { handleInvitationCommand } from "../../../src/cli/commands/invitation";
|
||||
import { CommandError, CommandPaths } from "../../../src/cli/commands/types";
|
||||
import { createCommandDeps, createMockIO, createMockPaths, expectLogs, type LogExpectation } from "../mocks/command";
|
||||
import {
|
||||
createCommandDeps,
|
||||
createMockIO,
|
||||
createMockPaths,
|
||||
expectLogs,
|
||||
type LogExpectation,
|
||||
} from "../mocks/command";
|
||||
|
||||
// ============================================================================
|
||||
// Error Cases - Validate argument parsing and error handling
|
||||
@@ -150,7 +172,11 @@ describe("invitation command - error cases", () => {
|
||||
const { io } = createMockIO();
|
||||
|
||||
try {
|
||||
await handleInvitationCommand(createCommandDeps(app, io, paths), inputs, {});
|
||||
await handleInvitationCommand(
|
||||
createCommandDeps(app, io, paths),
|
||||
inputs,
|
||||
{},
|
||||
);
|
||||
expect.fail("Expected command to throw");
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(CommandError);
|
||||
@@ -211,7 +237,10 @@ describe("invitation command - receive flow", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
const expectedFile = path.join(tempDir, `inv-${result.invitationIdentifier}.json`);
|
||||
const expectedFile = path.join(
|
||||
tempDir,
|
||||
`inv-${result.invitationIdentifier}.json`,
|
||||
);
|
||||
expect(existsSync(expectedFile)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -325,8 +354,12 @@ describe("invitation command - request satoshis flow", () => {
|
||||
);
|
||||
expect(invitation).toBeDefined();
|
||||
|
||||
const variables = invitation?.data.commits.flatMap((c) => c.data.variables ?? []);
|
||||
const requestedSatoshis = variables?.find((v) => v.variableIdentifier === "requestedSatoshis");
|
||||
const variables = invitation?.data.commits.flatMap(
|
||||
(c) => c.data.variables ?? [],
|
||||
);
|
||||
const requestedSatoshis = variables?.find(
|
||||
(v) => v.variableIdentifier === "requestedSatoshis",
|
||||
);
|
||||
expect(requestedSatoshis).toBeDefined();
|
||||
expect(requestedSatoshis?.value).toBe("10000");
|
||||
});
|
||||
@@ -347,8 +380,12 @@ describe("invitation command - request satoshis flow", () => {
|
||||
(inv) => inv.data.invitationIdentifier === result.invitationIdentifier,
|
||||
);
|
||||
|
||||
const variables = invitation?.data.commits.flatMap((c) => c.data.variables ?? []);
|
||||
const requestedSatoshis = variables?.find((v) => v.variableIdentifier === "requestedSatoshis");
|
||||
const variables = invitation?.data.commits.flatMap(
|
||||
(c) => c.data.variables ?? [],
|
||||
);
|
||||
const requestedSatoshis = variables?.find(
|
||||
(v) => v.variableIdentifier === "requestedSatoshis",
|
||||
);
|
||||
expect(requestedSatoshis?.roleIdentifier).toBe("receiver");
|
||||
});
|
||||
});
|
||||
@@ -388,7 +425,8 @@ describe("invitation command - send flow with resources", () => {
|
||||
["create", "Wallet (P2PKH)", "sendSatoshis"],
|
||||
{
|
||||
varTransferredSatoshis: "10000",
|
||||
varRecipientLockingscript: "76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac",
|
||||
varRecipientLockingscript:
|
||||
"76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac",
|
||||
role: "sender",
|
||||
},
|
||||
);
|
||||
@@ -408,7 +446,8 @@ describe("invitation command - send flow with resources", () => {
|
||||
["create", "Wallet (P2PKH)", "sendSatoshis"],
|
||||
{
|
||||
varTransferredSatoshis: "10000",
|
||||
varRecipientLockingscript: "76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac",
|
||||
varRecipientLockingscript:
|
||||
"76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac",
|
||||
role: "sender",
|
||||
},
|
||||
);
|
||||
@@ -418,8 +457,12 @@ describe("invitation command - send flow with resources", () => {
|
||||
);
|
||||
expect(invitation).toBeDefined();
|
||||
|
||||
const variables = invitation?.data.commits.flatMap((c) => c.data.variables ?? []);
|
||||
const transferredSatoshis = variables?.find((v) => v.variableIdentifier === "transferredSatoshis");
|
||||
const variables = invitation?.data.commits.flatMap(
|
||||
(c) => c.data.variables ?? [],
|
||||
);
|
||||
const transferredSatoshis = variables?.find(
|
||||
(v) => v.variableIdentifier === "transferredSatoshis",
|
||||
);
|
||||
expect(transferredSatoshis).toBeDefined();
|
||||
expect(transferredSatoshis?.value).toBe("10000");
|
||||
});
|
||||
@@ -436,8 +479,10 @@ describe("invitation command - send flow with resources", () => {
|
||||
["create", "Wallet (P2PKH)", "sendSatoshis"],
|
||||
{
|
||||
varTransferredSatoshis: "10000",
|
||||
varRecipientLockingscript: "76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac",
|
||||
addInput: "0000000000000000000000000000000000000000000000000000000000000000:0",
|
||||
varRecipientLockingscript:
|
||||
"76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac",
|
||||
addInput:
|
||||
"0000000000000000000000000000000000000000000000000000000000000000:0",
|
||||
role: "sender",
|
||||
},
|
||||
);
|
||||
@@ -516,10 +561,15 @@ describe("invitation command - multi-step append", () => {
|
||||
expectLogs(spies, [{ out: "Invitation appended" }]);
|
||||
|
||||
const invitation = app.invitations.find(
|
||||
(inv) => inv.data.invitationIdentifier === createResult.invitationIdentifier,
|
||||
(inv) =>
|
||||
inv.data.invitationIdentifier === createResult.invitationIdentifier,
|
||||
);
|
||||
const variables = invitation?.data.commits.flatMap(
|
||||
(c) => c.data.variables ?? [],
|
||||
);
|
||||
const requestedSatoshis = variables?.find(
|
||||
(v) => v.variableIdentifier === "requestedSatoshis",
|
||||
);
|
||||
const variables = invitation?.data.commits.flatMap((c) => c.data.variables ?? []);
|
||||
const requestedSatoshis = variables?.find((v) => v.variableIdentifier === "requestedSatoshis");
|
||||
expect(requestedSatoshis?.value).toBe("25000");
|
||||
});
|
||||
|
||||
@@ -569,7 +619,10 @@ describe("invitation command - multi-step append", () => {
|
||||
|
||||
expectLogs(spies, [{ out: "Invitation updated" }]);
|
||||
|
||||
const expectedFile = path.join(tempDir, `inv-${createResult.invitationIdentifier}.json`);
|
||||
const expectedFile = path.join(
|
||||
tempDir,
|
||||
`inv-${createResult.invitationIdentifier}.json`,
|
||||
);
|
||||
expect(existsSync(expectedFile)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -594,12 +647,17 @@ describe("invitation command - multi-step append", () => {
|
||||
);
|
||||
|
||||
const invitation = app.invitations.find(
|
||||
(inv) => inv.data.invitationIdentifier === createResult.invitationIdentifier,
|
||||
(inv) =>
|
||||
inv.data.invitationIdentifier === createResult.invitationIdentifier,
|
||||
);
|
||||
expect(invitation?.data.commits.length).toBeGreaterThan(1);
|
||||
|
||||
const variables = invitation?.data.commits.flatMap((c) => c.data.variables ?? []);
|
||||
const requestedSatoshis = variables?.find((v) => v.variableIdentifier === "requestedSatoshis");
|
||||
const variables = invitation?.data.commits.flatMap(
|
||||
(c) => c.data.variables ?? [],
|
||||
);
|
||||
const requestedSatoshis = variables?.find(
|
||||
(v) => v.variableIdentifier === "requestedSatoshis",
|
||||
);
|
||||
expect(requestedSatoshis?.value).toBe("10000");
|
||||
});
|
||||
});
|
||||
@@ -724,7 +782,10 @@ describe("invitation command - list and inspect", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
const invitationFilePath = path.join(tempDir, `inv-${createResult.invitationIdentifier}.json`);
|
||||
const invitationFilePath = path.join(
|
||||
tempDir,
|
||||
`inv-${createResult.invitationIdentifier}.json`,
|
||||
);
|
||||
|
||||
const { io: inspectIO } = createMockIO();
|
||||
|
||||
@@ -813,7 +874,9 @@ describe("invitation command - sign flow", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
expect(signResult.invitationIdentifier).toBe(createResult.invitationIdentifier);
|
||||
expect(signResult.invitationIdentifier).toBe(
|
||||
createResult.invitationIdentifier,
|
||||
);
|
||||
expectLogs(spies, [{ out: "Invitation signed" }]);
|
||||
});
|
||||
|
||||
@@ -840,7 +903,8 @@ describe("invitation command - sign flow", () => {
|
||||
);
|
||||
|
||||
const invitation = app.invitations.find(
|
||||
(inv) => inv.data.invitationIdentifier === createResult.invitationIdentifier,
|
||||
(inv) =>
|
||||
inv.data.invitationIdentifier === createResult.invitationIdentifier,
|
||||
);
|
||||
|
||||
expect(invitation).toBeDefined();
|
||||
@@ -921,7 +985,10 @@ describe("invitation command - import flow", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
const invitationFilePath = path.join(tempDir, `inv-${createResult.invitationIdentifier}.json`);
|
||||
const invitationFilePath = path.join(
|
||||
tempDir,
|
||||
`inv-${createResult.invitationIdentifier}.json`,
|
||||
);
|
||||
|
||||
const secondApp = await createMockAppService(engine);
|
||||
const { io: importIO } = createMockIO();
|
||||
@@ -965,7 +1032,10 @@ describe("invitation command - import flow", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
const invitationFilePath = path.join(tempDir, `inv-${createResult.invitationIdentifier}.json`);
|
||||
const invitationFilePath = path.join(
|
||||
tempDir,
|
||||
`inv-${createResult.invitationIdentifier}.json`,
|
||||
);
|
||||
|
||||
const secondApp = await createMockAppService(engine);
|
||||
const { io: importIO } = createMockIO();
|
||||
@@ -991,7 +1061,10 @@ describe("invitation command - import flow", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
const invitationFilePath = path.join(tempDir, `inv-${createResult.invitationIdentifier}.json`);
|
||||
const invitationFilePath = path.join(
|
||||
tempDir,
|
||||
`inv-${createResult.invitationIdentifier}.json`,
|
||||
);
|
||||
|
||||
const secondApp = await createMockAppService(engine);
|
||||
const { io: importIO } = createMockIO();
|
||||
@@ -1044,7 +1117,8 @@ describe("invitation command - auto-inputs flow", () => {
|
||||
["create", "Wallet (P2PKH)", "sendSatoshis"],
|
||||
{
|
||||
varTransferredSatoshis: "10000",
|
||||
varRecipientLockingscript: "76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac",
|
||||
varRecipientLockingscript:
|
||||
"76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac",
|
||||
role: "sender",
|
||||
autoInputs: "true",
|
||||
},
|
||||
@@ -1052,7 +1126,9 @@ describe("invitation command - auto-inputs flow", () => {
|
||||
expect.fail("Expected command to throw");
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(CommandError);
|
||||
expect((error as CommandError).event).toBe("invitation.create.append_params_failed");
|
||||
expect((error as CommandError).event).toBe(
|
||||
"invitation.create.append_params_failed",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1068,7 +1144,8 @@ describe("invitation command - auto-inputs flow", () => {
|
||||
["create", "Wallet (P2PKH)", "sendSatoshis"],
|
||||
{
|
||||
varTransferredSatoshis: "10000",
|
||||
varRecipientLockingscript: "76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac",
|
||||
varRecipientLockingscript:
|
||||
"76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac",
|
||||
role: "sender",
|
||||
autoInputs: "true",
|
||||
},
|
||||
@@ -1117,7 +1194,9 @@ describe("invitation command - broadcast flow", () => {
|
||||
expect.fail("Expected command to throw");
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(CommandError);
|
||||
expect((error as CommandError).event).toBe("invitation.broadcast.not_found");
|
||||
expect((error as CommandError).event).toBe(
|
||||
"invitation.broadcast.not_found",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1188,11 +1267,14 @@ describe("invitation command - full lifecycle", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
expect(signResult.invitationIdentifier).toBe(createResult.invitationIdentifier);
|
||||
expect(signResult.invitationIdentifier).toBe(
|
||||
createResult.invitationIdentifier,
|
||||
);
|
||||
expectLogs(signSpies, [{ out: "Invitation signed" }]);
|
||||
|
||||
const invitation = app.invitations.find(
|
||||
(inv) => inv.data.invitationIdentifier === createResult.invitationIdentifier,
|
||||
(inv) =>
|
||||
inv.data.invitationIdentifier === createResult.invitationIdentifier,
|
||||
);
|
||||
expect(invitation).toBeDefined();
|
||||
expect(invitation?.data.commits.length).toBeGreaterThan(0);
|
||||
@@ -1210,7 +1292,10 @@ describe("invitation command - full lifecycle", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
const invitationFilePath = path.join(tempDir, `inv-${createResult.invitationIdentifier}.json`);
|
||||
const invitationFilePath = path.join(
|
||||
tempDir,
|
||||
`inv-${createResult.invitationIdentifier}.json`,
|
||||
);
|
||||
expect(existsSync(invitationFilePath)).toBe(true);
|
||||
|
||||
const { io: inspectIO } = createMockIO();
|
||||
@@ -1254,7 +1339,9 @@ describe("invitation command - full lifecycle", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
expect(signResult.invitationIdentifier).toBe(createResult.invitationIdentifier);
|
||||
expect(signResult.invitationIdentifier).toBe(
|
||||
createResult.invitationIdentifier,
|
||||
);
|
||||
expectLogs(signSpies, [{ out: "Invitation signed" }]);
|
||||
});
|
||||
|
||||
@@ -1270,7 +1357,10 @@ describe("invitation command - full lifecycle", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
const invitationFilePath = path.join(tempDir, `inv-${createResult.invitationIdentifier}.json`);
|
||||
const invitationFilePath = path.join(
|
||||
tempDir,
|
||||
`inv-${createResult.invitationIdentifier}.json`,
|
||||
);
|
||||
|
||||
const afterCreate = JSON.parse(readFileSync(invitationFilePath, "utf-8"));
|
||||
const createCommitCount = afterCreate.commits?.length ?? 0;
|
||||
@@ -1335,7 +1425,9 @@ describe("invitation command - full lifecycle", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
expect(reqResult.invitationIdentifier).toBe(createResult.invitationIdentifier);
|
||||
expect(reqResult.invitationIdentifier).toBe(
|
||||
createResult.invitationIdentifier,
|
||||
);
|
||||
expect(spies.out).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,12 @@ import { DEFAULT_SEED } from "../mocks/engine";
|
||||
|
||||
import { handleMnemonicCommand } from "../../../src/cli/commands/mnemonic";
|
||||
import { CommandError } from "../../../src/cli/commands/types";
|
||||
import { createMockIO, createMockPaths, expectLogs, type LogExpectation } from "../mocks/command";
|
||||
import {
|
||||
createMockIO,
|
||||
createMockPaths,
|
||||
expectLogs,
|
||||
type LogExpectation,
|
||||
} from "../mocks/command";
|
||||
import { BCHMnemonicURL } from "../../../src/utils/bch-mnemonic-url";
|
||||
|
||||
type TestCase = {
|
||||
@@ -103,7 +108,7 @@ describe("mnemonic commands", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
tempDir = mkdtempSync(path.join(tmpdir(), "xo-cli-mnemonic-tests-"));
|
||||
|
||||
|
||||
// Write a single test mnemonic file to the temp directory
|
||||
writeFileSync(
|
||||
path.join(tempDir, "mnemonic-test"),
|
||||
@@ -116,31 +121,45 @@ describe("mnemonic commands", () => {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test.each(testCases)("mnemonic command: $inputs", async ({ inputs, options, shouldThrow, expectedEvent, expectedData, logs }) => {
|
||||
const { io, spies } = createMockIO();
|
||||
const paths = createMockPaths(tempDir);
|
||||
test.each(testCases)(
|
||||
"mnemonic command: $inputs",
|
||||
async ({
|
||||
inputs,
|
||||
options,
|
||||
shouldThrow,
|
||||
expectedEvent,
|
||||
expectedData,
|
||||
logs,
|
||||
}) => {
|
||||
const { io, spies } = createMockIO();
|
||||
const paths = createMockPaths(tempDir);
|
||||
|
||||
if (shouldThrow) {
|
||||
try {
|
||||
await handleMnemonicCommand({ io, paths }, inputs, options ?? {});
|
||||
expect.fail("Expected command to throw");
|
||||
} catch (error) {
|
||||
if (expectedEvent) {
|
||||
expect(error).toBeInstanceOf(CommandError);
|
||||
expect((error as CommandError).event).toBe(expectedEvent);
|
||||
if (shouldThrow) {
|
||||
try {
|
||||
await handleMnemonicCommand({ io, paths }, 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 handleMnemonicCommand(
|
||||
{ io, paths },
|
||||
inputs,
|
||||
options ?? {},
|
||||
);
|
||||
if (expectedData) {
|
||||
Object.entries(expectedData).forEach(([key, value]) => {
|
||||
expect(result[key as keyof typeof result]).toEqual(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const result = await handleMnemonicCommand({ io, paths }, inputs, options ?? {});
|
||||
if (expectedData) {
|
||||
Object.entries(expectedData).forEach(([key, value]) => {
|
||||
expect(result[key as keyof typeof result]).toEqual(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (logs) {
|
||||
expectLogs(spies, logs);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (logs) {
|
||||
expectLogs(spies, logs);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,14 +3,23 @@ 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 {
|
||||
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";
|
||||
import {
|
||||
createCommandDeps,
|
||||
createMockIO,
|
||||
expectLogs,
|
||||
type LogExpectation,
|
||||
} from "../mocks/command";
|
||||
|
||||
type TestCase = {
|
||||
name: string;
|
||||
@@ -85,30 +94,48 @@ describe("receive command", () => {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test.each(testCases)("$name", async ({ inputs, options, shouldThrow, expectedEvent, expectedData, logs }) => {
|
||||
const { io, spies } = createMockIO();
|
||||
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);
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
});
|
||||
if (logs) {
|
||||
expectLogs(spies, logs);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,14 +3,25 @@ import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { addFakeResource, createMockAppService, createMockEngine, DEFAULT_SEED, reserveResource } from "../mocks/engine";
|
||||
import {
|
||||
addFakeResource,
|
||||
createMockAppService,
|
||||
createMockEngine,
|
||||
DEFAULT_SEED,
|
||||
reserveResource,
|
||||
} from "../mocks/engine";
|
||||
import { type Engine } from "@xo-cash/engine";
|
||||
import { p2pkhTemplate } from "../mocks/template-p2pkh";
|
||||
import { AppService } from "../../../src/services/app";
|
||||
|
||||
import { handleResourceCommand } from "../../../src/cli/commands/resource";
|
||||
import { CommandError } from "../../../src/cli/commands/types";
|
||||
import { createCommandDeps, createMockIO, expectLogs, type LogExpectation } from "../mocks/command";
|
||||
import {
|
||||
createCommandDeps,
|
||||
createMockIO,
|
||||
expectLogs,
|
||||
type LogExpectation,
|
||||
} from "../mocks/command";
|
||||
|
||||
type TestCase = {
|
||||
name: string;
|
||||
@@ -94,7 +105,10 @@ const testCases: TestCase[] = [
|
||||
},
|
||||
{
|
||||
name: "throws when unreserve called with non-existent UTXO",
|
||||
inputs: ["unreserve", "0000000000000000000000000000000000000000000000000000000000000000:0"],
|
||||
inputs: [
|
||||
"unreserve",
|
||||
"0000000000000000000000000000000000000000000000000000000000000000:0",
|
||||
],
|
||||
shouldThrow: true,
|
||||
expectedEvent: "resource.unreserve.utxo_missing",
|
||||
},
|
||||
@@ -119,32 +133,50 @@ describe("resource command", () => {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test.each(testCases)("$name", async ({ inputs, options, shouldThrow, expectedEvent, expectedData, logs }) => {
|
||||
const { io, spies } = createMockIO();
|
||||
test.each(testCases)(
|
||||
"$name",
|
||||
async ({
|
||||
inputs,
|
||||
options,
|
||||
shouldThrow,
|
||||
expectedEvent,
|
||||
expectedData,
|
||||
logs,
|
||||
}) => {
|
||||
const { io, spies } = createMockIO();
|
||||
|
||||
if (shouldThrow) {
|
||||
try {
|
||||
await handleResourceCommand(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);
|
||||
if (shouldThrow) {
|
||||
try {
|
||||
await handleResourceCommand(
|
||||
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 handleResourceCommand(
|
||||
createCommandDeps(app, io),
|
||||
inputs,
|
||||
options ?? {},
|
||||
);
|
||||
if (expectedData) {
|
||||
Object.entries(expectedData).forEach(([key, value]) => {
|
||||
expect(result[key as keyof typeof result]).toEqual(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const result = await handleResourceCommand(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);
|
||||
}
|
||||
});
|
||||
if (logs) {
|
||||
expectLogs(spies, logs);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("resource command with populated data", () => {
|
||||
@@ -169,7 +201,11 @@ describe("resource command with populated data", () => {
|
||||
await addFakeResource(engine, { valueSatoshis: 25000 });
|
||||
|
||||
const { io, spies } = createMockIO();
|
||||
const result = await handleResourceCommand(createCommandDeps(app, io), ["list"], {});
|
||||
const result = await handleResourceCommand(
|
||||
createCommandDeps(app, io),
|
||||
["list"],
|
||||
{},
|
||||
);
|
||||
|
||||
expect(result.count).toBe(2);
|
||||
expectLogs(spies, [{ out: "Total resources: 2" }]);
|
||||
@@ -187,21 +223,38 @@ describe("resource command with populated data", () => {
|
||||
|
||||
test("list excludes reserved resources by default", async () => {
|
||||
await addFakeResource(engine, { valueSatoshis: 50000 });
|
||||
await addFakeResource(engine, { valueSatoshis: 25000, reservedBy: "inv-123" });
|
||||
await addFakeResource(engine, {
|
||||
valueSatoshis: 25000,
|
||||
reservedBy: "inv-123",
|
||||
});
|
||||
|
||||
const { io } = createMockIO();
|
||||
const result = await handleResourceCommand(createCommandDeps(app, io), ["list"], {});
|
||||
const result = await handleResourceCommand(
|
||||
createCommandDeps(app, io),
|
||||
["list"],
|
||||
{},
|
||||
);
|
||||
|
||||
expect(result.count).toBe(1);
|
||||
});
|
||||
|
||||
test("list reserved shows only reserved resources", async () => {
|
||||
await addFakeResource(engine, { valueSatoshis: 50000 });
|
||||
await addFakeResource(engine, { valueSatoshis: 25000, reservedBy: "inv-123" });
|
||||
await addFakeResource(engine, { valueSatoshis: 10000, reservedBy: "inv-456" });
|
||||
await addFakeResource(engine, {
|
||||
valueSatoshis: 25000,
|
||||
reservedBy: "inv-123",
|
||||
});
|
||||
await addFakeResource(engine, {
|
||||
valueSatoshis: 10000,
|
||||
reservedBy: "inv-456",
|
||||
});
|
||||
|
||||
const { io, spies } = createMockIO();
|
||||
const result = await handleResourceCommand(createCommandDeps(app, io), ["list", "reserved"], {});
|
||||
const result = await handleResourceCommand(
|
||||
createCommandDeps(app, io),
|
||||
["list", "reserved"],
|
||||
{},
|
||||
);
|
||||
|
||||
expect(result.count).toBe(2);
|
||||
expectLogs(spies, [{ out: "reserved for inv-123" }]);
|
||||
@@ -209,29 +262,45 @@ describe("resource command with populated data", () => {
|
||||
|
||||
test("list all shows both reserved and unreserved", async () => {
|
||||
await addFakeResource(engine, { valueSatoshis: 50000 });
|
||||
await addFakeResource(engine, { valueSatoshis: 25000, reservedBy: "inv-123" });
|
||||
await addFakeResource(engine, {
|
||||
valueSatoshis: 25000,
|
||||
reservedBy: "inv-123",
|
||||
});
|
||||
|
||||
const { io } = createMockIO();
|
||||
const result = await handleResourceCommand(createCommandDeps(app, io), ["list", "all"], {});
|
||||
const result = await handleResourceCommand(
|
||||
createCommandDeps(app, io),
|
||||
["list", "all"],
|
||||
{},
|
||||
);
|
||||
|
||||
expect(result.count).toBe(2);
|
||||
});
|
||||
|
||||
test("unreserve releases a reserved UTXO", async () => {
|
||||
const resource = await addFakeResource(engine, { valueSatoshis: 25000, reservedBy: "inv-123" });
|
||||
const resource = await addFakeResource(engine, {
|
||||
valueSatoshis: 25000,
|
||||
reservedBy: "inv-123",
|
||||
});
|
||||
|
||||
const { io, spies } = createMockIO();
|
||||
await handleResourceCommand(
|
||||
createCommandDeps(app, io),
|
||||
["unreserve", `${resource.outpointTransactionHash}:${resource.outpointIndex}`],
|
||||
[
|
||||
"unreserve",
|
||||
`${resource.outpointTransactionHash}:${resource.outpointIndex}`,
|
||||
],
|
||||
{},
|
||||
);
|
||||
|
||||
expectLogs(spies, [{ out: "Unreserved" }, { out: "was reserved for inv-123" }]);
|
||||
expectLogs(spies, [
|
||||
{ out: "Unreserved" },
|
||||
{ out: "was reserved for inv-123" },
|
||||
]);
|
||||
|
||||
const resources = await engine.listUnspentOutputsData();
|
||||
const target = resources.find(
|
||||
r => r.outpointTransactionHash === resource.outpointTransactionHash,
|
||||
(r) => r.outpointTransactionHash === resource.outpointTransactionHash,
|
||||
);
|
||||
expect(target?.reservedBy).toBeUndefined();
|
||||
});
|
||||
@@ -242,7 +311,10 @@ describe("resource command with populated data", () => {
|
||||
const { io, spies } = createMockIO();
|
||||
await handleResourceCommand(
|
||||
createCommandDeps(app, io),
|
||||
["unreserve", `${resource.outpointTransactionHash}:${resource.outpointIndex}`],
|
||||
[
|
||||
"unreserve",
|
||||
`${resource.outpointTransactionHash}:${resource.outpointIndex}`,
|
||||
],
|
||||
{},
|
||||
);
|
||||
|
||||
@@ -251,17 +323,27 @@ describe("resource command with populated data", () => {
|
||||
|
||||
test("unreserve-all releases all reserved UTXOs", async () => {
|
||||
await addFakeResource(engine, { valueSatoshis: 50000 });
|
||||
await addFakeResource(engine, { valueSatoshis: 25000, reservedBy: "inv-123" });
|
||||
await addFakeResource(engine, { valueSatoshis: 10000, reservedBy: "inv-456" });
|
||||
await addFakeResource(engine, {
|
||||
valueSatoshis: 25000,
|
||||
reservedBy: "inv-123",
|
||||
});
|
||||
await addFakeResource(engine, {
|
||||
valueSatoshis: 10000,
|
||||
reservedBy: "inv-456",
|
||||
});
|
||||
|
||||
const { io, spies } = createMockIO();
|
||||
const result = await handleResourceCommand(createCommandDeps(app, io), ["unreserve-all"], {});
|
||||
const result = await handleResourceCommand(
|
||||
createCommandDeps(app, io),
|
||||
["unreserve-all"],
|
||||
{},
|
||||
);
|
||||
|
||||
expect(result.count).toBe(2);
|
||||
expectLogs(spies, [{ out: "Unreserved" }, { out: "2" }]);
|
||||
|
||||
const resources = await engine.listUnspentOutputsData();
|
||||
const reserved = resources.filter(r => r.reservedBy);
|
||||
const reserved = resources.filter((r) => r.reservedBy);
|
||||
expect(reserved).toHaveLength(0);
|
||||
});
|
||||
|
||||
|
||||
@@ -3,14 +3,26 @@ 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 {
|
||||
createMockAppService,
|
||||
createMockEngine,
|
||||
DEFAULT_SEED,
|
||||
} from "../mocks/engine";
|
||||
import { type Engine } from "@xo-cash/engine";
|
||||
import { p2pkhTemplate, p2pkhTemplateIdentifier } from "../mocks/template-p2pkh";
|
||||
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";
|
||||
import {
|
||||
createCommandDeps,
|
||||
createMockIO,
|
||||
expectLogs,
|
||||
type LogExpectation,
|
||||
} from "../mocks/command";
|
||||
|
||||
type TestCase = {
|
||||
name: string;
|
||||
@@ -184,42 +196,60 @@ describe("template command", () => {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test.each(testCases)("$name", async ({ inputs, options, shouldThrow, expectedEvent, expectedData, logs }) => {
|
||||
const { io, spies } = createMockIO();
|
||||
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);
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
});
|
||||
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),
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { expect, test, describe, beforeEach, afterEach } from "vitest";
|
||||
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
@@ -12,7 +18,8 @@ import {
|
||||
} from "../../src/cli/mnemonic";
|
||||
import { BCHMnemonicURL } from "../../src/utils/bch-mnemonic-url";
|
||||
|
||||
const TEST_SEED = "page pencil stock planet limb cluster assault speak off joke private pioneer";
|
||||
const TEST_SEED =
|
||||
"page pencil stock planet limb cluster assault speak off joke private pioneer";
|
||||
|
||||
describe("mnemonic utilities", () => {
|
||||
let tempDir: string;
|
||||
@@ -68,7 +75,11 @@ describe("mnemonic utilities", () => {
|
||||
});
|
||||
|
||||
test("sanitizes filename to basename only", () => {
|
||||
const filename = createMnemonicFile(tempDir, TEST_SEED, "../../../evil-path");
|
||||
const filename = createMnemonicFile(
|
||||
tempDir,
|
||||
TEST_SEED,
|
||||
"../../../evil-path",
|
||||
);
|
||||
|
||||
expect(filename).toBe("evil-path");
|
||||
expect(existsSync(path.join(tempDir, "evil-path"))).toBe(true);
|
||||
@@ -95,7 +106,10 @@ describe("mnemonic utilities", () => {
|
||||
|
||||
try {
|
||||
writeFileSync(path.join(tempDir, "mnemonic-relative"), "test");
|
||||
const resolved = resolveMnemonicFilePath("/nonexistent", "mnemonic-relative");
|
||||
const resolved = resolveMnemonicFilePath(
|
||||
"/nonexistent",
|
||||
"mnemonic-relative",
|
||||
);
|
||||
expect(resolved).toBe(path.join(tempDir, "mnemonic-relative"));
|
||||
} finally {
|
||||
process.chdir(originalCwd);
|
||||
@@ -110,15 +124,18 @@ describe("mnemonic utilities", () => {
|
||||
});
|
||||
|
||||
test("throws when file not found anywhere", () => {
|
||||
expect(() => resolveMnemonicFilePath(tempDir, "nonexistent-file")).toThrow(
|
||||
/Mnemonic file not found/,
|
||||
);
|
||||
expect(() =>
|
||||
resolveMnemonicFilePath(tempDir, "nonexistent-file"),
|
||||
).toThrow(/Mnemonic file not found/);
|
||||
});
|
||||
|
||||
test("strips path components and looks up basename in mnemonicsDir", () => {
|
||||
writeFileSync(path.join(tempDir, "mnemonic-basename"), "test");
|
||||
|
||||
const resolved = resolveMnemonicFilePath(tempDir, "some/path/mnemonic-basename");
|
||||
const resolved = resolveMnemonicFilePath(
|
||||
tempDir,
|
||||
"some/path/mnemonic-basename",
|
||||
);
|
||||
expect(resolved).toBe(path.join(tempDir, "mnemonic-basename"));
|
||||
});
|
||||
});
|
||||
@@ -140,11 +157,16 @@ describe("mnemonic utilities", () => {
|
||||
});
|
||||
|
||||
test("throws when file not found", () => {
|
||||
expect(() => loadMnemonic(tempDir, "nonexistent")).toThrow(/Mnemonic file not found/);
|
||||
expect(() => loadMnemonic(tempDir, "nonexistent")).toThrow(
|
||||
/Mnemonic file not found/,
|
||||
);
|
||||
});
|
||||
|
||||
test("throws when file contains invalid data", () => {
|
||||
writeFileSync(path.join(tempDir, "mnemonic-invalid"), "not a valid mnemonic url");
|
||||
writeFileSync(
|
||||
path.join(tempDir, "mnemonic-invalid"),
|
||||
"not a valid mnemonic url",
|
||||
);
|
||||
|
||||
expect(() => loadMnemonic(tempDir, "mnemonic-invalid")).toThrow();
|
||||
});
|
||||
|
||||
@@ -92,27 +92,36 @@ export const createMockIO = (): MockIO => {
|
||||
* @param spies - The mock IO spies from createMockIO
|
||||
* @param logs - Array of log expectations to validate
|
||||
*/
|
||||
export const expectLogs = (spies: MockIOSpies, logs: LogExpectation[]): void => {
|
||||
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));
|
||||
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));
|
||||
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));
|
||||
expect(spies.verbose).toHaveBeenCalledWith(
|
||||
expect.stringContaining(log.verbose),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
/**
|
||||
* Mock Electrum service for testing.
|
||||
* NOTE & TODO: Do we even need this in the actual app? I forget why we had this, but it seems like its just overly complicating things
|
||||
* And we end up in stupid situations where we are creating a mock for a single function class.
|
||||
*/
|
||||
export class MockElectrumService {
|
||||
constructor() {}
|
||||
|
||||
async hasSeenTransaction(transactionHash: string): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { BlockchainMonitor, Engine } from "@xo-cash/engine";
|
||||
|
||||
import { createStorageAdapter, State, StorageType, type UnspentOutputData } from "@xo-cash/state";
|
||||
import {
|
||||
createStorageAdapter,
|
||||
State,
|
||||
StorageType,
|
||||
type UnspentOutputData,
|
||||
} from "@xo-cash/state";
|
||||
import { InMemoryBlockchainProvider } from "@xo-cash/engine";
|
||||
import { convertMnemonicToSeedBytes } from "@xo-cash/crypto";
|
||||
|
||||
@@ -9,7 +14,8 @@ import { AppService } from "../../../src/services/app";
|
||||
import { InMemoryStorage } from "../../../src/services/storage";
|
||||
import { MockElectrumService } from "./electrum-service";
|
||||
|
||||
export const DEFAULT_SEED = "page pencil stock planet limb cluster assault speak off joke private pioneer";
|
||||
export const DEFAULT_SEED =
|
||||
"page pencil stock planet limb cluster assault speak off joke private pioneer";
|
||||
|
||||
/**
|
||||
* Options for creating a fake resource (UTXO) in tests.
|
||||
@@ -39,7 +45,9 @@ export type FakeResourceOptions = {
|
||||
export const randomTxHash = (): string => {
|
||||
const bytes = new Uint8Array(32);
|
||||
crypto.getRandomValues(bytes);
|
||||
return Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("");
|
||||
return Array.from(bytes)
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -62,7 +70,9 @@ export const addFakeResource = async (
|
||||
outpointTransactionHash: options.outpointTransactionHash ?? randomTxHash(),
|
||||
minedAtHeight: options.minedAtHeight ?? 800000,
|
||||
valueSatoshis: options.valueSatoshis ?? 10000,
|
||||
lockingBytecode: options.lockingBytecode ?? "76a914000000000000000000000000000000000000000088ac",
|
||||
lockingBytecode:
|
||||
options.lockingBytecode ??
|
||||
"76a914000000000000000000000000000000000000000088ac",
|
||||
reservedBy: options.reservedBy,
|
||||
};
|
||||
|
||||
@@ -161,4 +171,4 @@ export const createMockAppService = async (engine: Engine) => {
|
||||
};
|
||||
|
||||
return new AppService(engine, storage, config, electrum);
|
||||
};
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,9 @@ describe("paths utilities", () => {
|
||||
test("returns path under config dir", () => {
|
||||
const mnemonicsDir = getMnemonicsDir();
|
||||
|
||||
expect(mnemonicsDir).toBe(path.join(homedir(), ".config", "xo-cli", "mnemonics"));
|
||||
expect(mnemonicsDir).toBe(
|
||||
path.join(homedir(), ".config", "xo-cli", "mnemonics"),
|
||||
);
|
||||
});
|
||||
|
||||
test("creates the directory if it does not exist", () => {
|
||||
@@ -58,7 +60,9 @@ describe("paths utilities", () => {
|
||||
test("returns .wallet file path under config dir", () => {
|
||||
const walletConfigPath = getWalletConfigPath();
|
||||
|
||||
expect(walletConfigPath).toBe(path.join(homedir(), ".config", "xo-cli", ".wallet"));
|
||||
expect(walletConfigPath).toBe(
|
||||
path.join(homedir(), ".config", "xo-cli", ".wallet"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -111,9 +115,9 @@ describe("paths utilities", () => {
|
||||
});
|
||||
|
||||
test("throws when file not found anywhere", () => {
|
||||
expect(() => resolveMnemonicFilePath("nonexistent-mnemonic-file-xyz")).toThrow(
|
||||
/Mnemonic file not found/,
|
||||
);
|
||||
expect(() =>
|
||||
resolveMnemonicFilePath("nonexistent-mnemonic-file-xyz"),
|
||||
).toThrow(/Mnemonic file not found/);
|
||||
});
|
||||
|
||||
test("does not resolve absolute path if file does not exist", () => {
|
||||
|
||||
Reference in New Issue
Block a user