Tests. Autocomplete. Few Fixes. Mocks for Electrum Service. Template-to-Json parser. Fix global paths. Use IO Dependency injection for logging from cli. Additional commands in CLI.
This commit is contained in:
192
tests/cli/mnemonic.test.ts
Normal file
192
tests/cli/mnemonic.test.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import { expect, test, describe, beforeEach, afterEach } from "vitest";
|
||||
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import {
|
||||
createMnemonicSeed,
|
||||
createMnemonicFile,
|
||||
resolveMnemonicFilePath,
|
||||
loadMnemonic,
|
||||
listMnemonicFiles,
|
||||
} 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";
|
||||
|
||||
describe("mnemonic utilities", () => {
|
||||
let tempDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = path.join(tmpdir(), `xo-cli-mnemonic-utils-test-${Date.now()}`);
|
||||
mkdirSync(tempDir, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("createMnemonicSeed", () => {
|
||||
test("generates a valid BIP39 mnemonic", () => {
|
||||
const mnemonic = createMnemonicSeed();
|
||||
|
||||
expect(typeof mnemonic).toBe("string");
|
||||
const words = mnemonic.split(" ");
|
||||
expect(words.length).toBe(12);
|
||||
});
|
||||
|
||||
test("generates unique mnemonics on each call", () => {
|
||||
const mnemonic1 = createMnemonicSeed();
|
||||
const mnemonic2 = createMnemonicSeed();
|
||||
|
||||
expect(mnemonic1).not.toBe(mnemonic2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createMnemonicFile", () => {
|
||||
test("creates a mnemonic file with auto-generated name", () => {
|
||||
const filename = createMnemonicFile(tempDir, TEST_SEED);
|
||||
|
||||
expect(filename).toMatch(/^mnemonic-page$/);
|
||||
expect(existsSync(path.join(tempDir, filename))).toBe(true);
|
||||
});
|
||||
|
||||
test("creates a mnemonic file with custom name", () => {
|
||||
const filename = createMnemonicFile(tempDir, TEST_SEED, "my-wallet");
|
||||
|
||||
expect(filename).toBe("my-wallet");
|
||||
expect(existsSync(path.join(tempDir, filename))).toBe(true);
|
||||
});
|
||||
|
||||
test("writes valid BCHMnemonicURL format", () => {
|
||||
const filename = createMnemonicFile(tempDir, TEST_SEED, "test-wallet");
|
||||
const content = readFileSync(path.join(tempDir, filename), "utf8");
|
||||
|
||||
expect(content).toMatch(/^bch-mnemonic:/);
|
||||
const parsed = BCHMnemonicURL.fromURL(content);
|
||||
expect(parsed).toBeDefined();
|
||||
});
|
||||
|
||||
test("sanitizes filename to basename only", () => {
|
||||
const filename = createMnemonicFile(tempDir, TEST_SEED, "../../../evil-path");
|
||||
|
||||
expect(filename).toBe("evil-path");
|
||||
expect(existsSync(path.join(tempDir, "evil-path"))).toBe(true);
|
||||
expect(existsSync(path.join(tempDir, "../../../evil-path"))).toBe(false);
|
||||
});
|
||||
|
||||
test("throws when mnemonic is empty", () => {
|
||||
expect(() => createMnemonicFile(tempDir, "")).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveMnemonicFilePath", () => {
|
||||
test("resolves absolute path when file exists", () => {
|
||||
const filePath = path.join(tempDir, "mnemonic-absolute");
|
||||
writeFileSync(filePath, "test");
|
||||
|
||||
const resolved = resolveMnemonicFilePath(tempDir, filePath);
|
||||
expect(resolved).toBe(filePath);
|
||||
});
|
||||
|
||||
test("resolves path relative to cwd when file exists", () => {
|
||||
const originalCwd = process.cwd();
|
||||
process.chdir(tempDir);
|
||||
|
||||
try {
|
||||
writeFileSync(path.join(tempDir, "mnemonic-relative"), "test");
|
||||
const resolved = resolveMnemonicFilePath("/nonexistent", "mnemonic-relative");
|
||||
expect(resolved).toBe(path.join(tempDir, "mnemonic-relative"));
|
||||
} finally {
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
|
||||
test("resolves from mnemonicsDir when file exists there", () => {
|
||||
writeFileSync(path.join(tempDir, "mnemonic-test"), "test");
|
||||
|
||||
const resolved = resolveMnemonicFilePath(tempDir, "mnemonic-test");
|
||||
expect(resolved).toBe(path.join(tempDir, "mnemonic-test"));
|
||||
});
|
||||
|
||||
test("throws when file not found anywhere", () => {
|
||||
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");
|
||||
expect(resolved).toBe(path.join(tempDir, "mnemonic-basename"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("loadMnemonic", () => {
|
||||
test("loads mnemonic from file", () => {
|
||||
createMnemonicFile(tempDir, TEST_SEED, "test-load");
|
||||
|
||||
const loaded = loadMnemonic(tempDir, "test-load");
|
||||
expect(loaded).toBe(TEST_SEED);
|
||||
});
|
||||
|
||||
test("loads mnemonic from absolute path", () => {
|
||||
const filePath = path.join(tempDir, "mnemonic-absolute-load");
|
||||
createMnemonicFile(tempDir, TEST_SEED, "mnemonic-absolute-load");
|
||||
|
||||
const loaded = loadMnemonic(tempDir, filePath);
|
||||
expect(loaded).toBe(TEST_SEED);
|
||||
});
|
||||
|
||||
test("throws when 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");
|
||||
|
||||
expect(() => loadMnemonic(tempDir, "mnemonic-invalid")).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("listMnemonicFiles", () => {
|
||||
test("returns empty array when no mnemonic files exist", () => {
|
||||
const files = listMnemonicFiles(tempDir);
|
||||
expect(files).toEqual([]);
|
||||
});
|
||||
|
||||
test("lists only files starting with 'mnemonic-'", () => {
|
||||
writeFileSync(path.join(tempDir, "mnemonic-one"), "test");
|
||||
writeFileSync(path.join(tempDir, "mnemonic-two"), "test");
|
||||
writeFileSync(path.join(tempDir, "other-file"), "test");
|
||||
writeFileSync(path.join(tempDir, "wallet.json"), "test");
|
||||
|
||||
const files = listMnemonicFiles(tempDir);
|
||||
expect(files).toHaveLength(2);
|
||||
expect(files).toContain("mnemonic-one");
|
||||
expect(files).toContain("mnemonic-two");
|
||||
expect(files).not.toContain("other-file");
|
||||
expect(files).not.toContain("wallet.json");
|
||||
});
|
||||
|
||||
test("returns sorted or consistent ordering", () => {
|
||||
writeFileSync(path.join(tempDir, "mnemonic-zebra"), "test");
|
||||
writeFileSync(path.join(tempDir, "mnemonic-alpha"), "test");
|
||||
writeFileSync(path.join(tempDir, "mnemonic-beta"), "test");
|
||||
|
||||
const files = listMnemonicFiles(tempDir);
|
||||
expect(files).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("round-trip", () => {
|
||||
test("create and load preserves mnemonic exactly", () => {
|
||||
const original = createMnemonicSeed();
|
||||
createMnemonicFile(tempDir, original, "roundtrip-test");
|
||||
|
||||
const loaded = loadMnemonic(tempDir, "roundtrip-test");
|
||||
expect(loaded).toBe(original);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user