From bd1ae909b54b7f22aaaf8018207ad2bf94de8306 Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Mon, 27 Apr 2026 09:45:38 +0000 Subject: [PATCH] Fix tests --- src/services/rates.ts | 2 +- tests/cli/commands/invitation.test.ts | 38 +++++++++++++++-------- tests/cli/commands/receive.test.ts | 3 +- tests/cli/commands/resource.test.ts | 43 +++++++++++++++------------ tests/cli/commands/template.test.ts | 3 +- tests/cli/mocks/engine.ts | 24 ++++++++------- tests/cli/mocks/rates-service.ts | 23 ++++++++++++++ 7 files changed, 92 insertions(+), 44 deletions(-) create mode 100644 tests/cli/mocks/rates-service.ts diff --git a/src/services/rates.ts b/src/services/rates.ts index 62b436b..eb5d45f 100644 --- a/src/services/rates.ts +++ b/src/services/rates.ts @@ -56,7 +56,7 @@ export class RatesService extends EventEmitter { private unsubscribeFromAdapter: (() => void) | null = null; private started = false; - private constructor(adapter: RatesAdapter) { + constructor(adapter: RatesAdapter) { super(); this.adapter = adapter; } diff --git a/tests/cli/commands/invitation.test.ts b/tests/cli/commands/invitation.test.ts index 46da818..833d302 100644 --- a/tests/cli/commands/invitation.test.ts +++ b/tests/cli/commands/invitation.test.ts @@ -45,6 +45,7 @@ import { expectLogs, type LogExpectation, } from "../mocks/command"; +import { State } from "@xo-cash/state"; // ============================================================================ // Error Cases - Validate argument parsing and error handling @@ -156,7 +157,8 @@ describe("invitation command - error cases", () => { let paths: CommandPaths; beforeEach(async () => { - engine = await createMockEngine(DEFAULT_SEED); + const mockEngine = await createMockEngine(DEFAULT_SEED); + engine = mockEngine.engine; await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); tempDir = mkdtempSync(path.join(tmpdir(), "xo-cli-invitation-errors-")); @@ -196,7 +198,8 @@ describe("invitation command - receive flow", () => { let paths: CommandPaths; beforeEach(async () => { - engine = await createMockEngine(DEFAULT_SEED); + const mockEngine = await createMockEngine(DEFAULT_SEED); + engine = mockEngine.engine; await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); tempDir = mkdtempSync(path.join(tmpdir(), "xo-cli-invitation-receive-")); @@ -308,7 +311,8 @@ describe("invitation command - request satoshis flow", () => { let paths: CommandPaths; beforeEach(async () => { - engine = await createMockEngine(DEFAULT_SEED); + const mockEngine = await createMockEngine(DEFAULT_SEED); + engine = mockEngine.engine; await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); tempDir = mkdtempSync(path.join(tmpdir(), "xo-cli-invitation-request-")); @@ -396,12 +400,15 @@ describe("invitation command - request satoshis flow", () => { describe("invitation command - send flow with resources", () => { let engine: Engine; + let state: State; let app: AppService; let tempDir: string; let paths: CommandPaths; beforeEach(async () => { - engine = await createMockEngine(DEFAULT_SEED); + const mockEngine = await createMockEngine(DEFAULT_SEED); + engine = mockEngine.engine; + state = mockEngine.state; await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); tempDir = mkdtempSync(path.join(tmpdir(), "xo-cli-invitation-send-")); @@ -497,7 +504,7 @@ describe("invitation command - send flow with resources", () => { * This validates our test infrastructure works correctly. */ test("fake resources are accessible via engine", async () => { - const resource = await addFakeResource(engine, { + const resource = await addFakeResource(state!, { valueSatoshis: 50000, templateIdentifier: p2pkhTemplateIdentifier, outputIdentifier: "receiveOutput", @@ -526,7 +533,8 @@ describe("invitation command - multi-step append", () => { let paths: CommandPaths; beforeEach(async () => { - engine = await createMockEngine(DEFAULT_SEED); + const mockEngine = await createMockEngine(DEFAULT_SEED); + engine = mockEngine.engine; await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); tempDir = mkdtempSync(path.join(tmpdir(), "xo-cli-invitation-append-")); @@ -673,7 +681,8 @@ describe("invitation command - list and inspect", () => { let paths: CommandPaths; beforeEach(async () => { - engine = await createMockEngine(DEFAULT_SEED); + const mockEngine = await createMockEngine(DEFAULT_SEED); + engine = mockEngine.engine; await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); tempDir = mkdtempSync(path.join(tmpdir(), "xo-cli-invitation-list-")); @@ -841,7 +850,8 @@ describe("invitation command - sign flow", () => { let paths: CommandPaths; beforeEach(async () => { - engine = await createMockEngine(DEFAULT_SEED); + const mockEngine = await createMockEngine(DEFAULT_SEED); + engine = mockEngine.engine; await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); tempDir = mkdtempSync(path.join(tmpdir(), "xo-cli-invitation-sign-")); @@ -961,7 +971,8 @@ describe("invitation command - import flow", () => { let paths: CommandPaths; beforeEach(async () => { - engine = await createMockEngine(DEFAULT_SEED); + const mockEngine = await createMockEngine(DEFAULT_SEED); + engine = mockEngine.engine; await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); tempDir = mkdtempSync(path.join(tmpdir(), "xo-cli-invitation-import-")); @@ -1092,7 +1103,8 @@ describe("invitation command - auto-inputs flow", () => { let paths: CommandPaths; beforeEach(async () => { - engine = await createMockEngine(DEFAULT_SEED); + const mockEngine = await createMockEngine(DEFAULT_SEED); + engine = mockEngine.engine; await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); tempDir = mkdtempSync(path.join(tmpdir(), "xo-cli-invitation-autoinputs-")); @@ -1167,7 +1179,8 @@ describe("invitation command - broadcast flow", () => { let paths: CommandPaths; beforeEach(async () => { - engine = await createMockEngine(DEFAULT_SEED); + const mockEngine = await createMockEngine(DEFAULT_SEED); + engine = mockEngine.engine; await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); tempDir = mkdtempSync(path.join(tmpdir(), "xo-cli-invitation-broadcast-")); @@ -1231,7 +1244,8 @@ describe("invitation command - full lifecycle", () => { let paths: CommandPaths; beforeEach(async () => { - engine = await createMockEngine(DEFAULT_SEED); + const mockEngine = await createMockEngine(DEFAULT_SEED); + engine = mockEngine.engine; await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); tempDir = mkdtempSync(path.join(tmpdir(), "xo-cli-invitation-lifecycle-")); diff --git a/tests/cli/commands/receive.test.ts b/tests/cli/commands/receive.test.ts index 0b3356c..c50cb26 100644 --- a/tests/cli/commands/receive.test.ts +++ b/tests/cli/commands/receive.test.ts @@ -81,7 +81,8 @@ describe("receive command", () => { let tempDir: string; beforeEach(async () => { - engine = await createMockEngine(DEFAULT_SEED); + const mockEngine = await createMockEngine(DEFAULT_SEED); + engine = mockEngine.engine; await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); diff --git a/tests/cli/commands/resource.test.ts b/tests/cli/commands/resource.test.ts index a6fd36b..7668b84 100644 --- a/tests/cli/commands/resource.test.ts +++ b/tests/cli/commands/resource.test.ts @@ -22,6 +22,7 @@ import { expectLogs, type LogExpectation, } from "../mocks/command"; +import { State } from "@xo-cash/state"; type TestCase = { name: string; @@ -120,7 +121,8 @@ describe("resource command", () => { let tempDir: string; beforeEach(async () => { - engine = await createMockEngine(DEFAULT_SEED); + const mockEngine = await createMockEngine(DEFAULT_SEED); + engine = mockEngine.engine; await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); @@ -181,11 +183,14 @@ describe("resource command", () => { describe("resource command with populated data", () => { let engine: Engine; + let state: State; let app: AppService; let tempDir: string; beforeEach(async () => { - engine = await createMockEngine(DEFAULT_SEED); + const mockEngine = await createMockEngine(DEFAULT_SEED); + engine = mockEngine.engine; + state = mockEngine.state; await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); tempDir = mkdtempSync(path.join(tmpdir(), "xo-cli-resource-tests-")); @@ -197,8 +202,8 @@ describe("resource command with populated data", () => { }); test("list returns count when resources exist", async () => { - await addFakeResource(engine, { valueSatoshis: 50000 }); - await addFakeResource(engine, { valueSatoshis: 25000 }); + await addFakeResource(state, { valueSatoshis: 50000 }); + await addFakeResource(state, { valueSatoshis: 25000 }); const { io, spies } = createMockIO(); const result = await handleResourceCommand( @@ -212,8 +217,8 @@ describe("resource command with populated data", () => { }); test("list shows total satoshis", async () => { - await addFakeResource(engine, { valueSatoshis: 50000 }); - await addFakeResource(engine, { valueSatoshis: 25000 }); + await addFakeResource(state, { valueSatoshis: 50000 }); + await addFakeResource(state, { valueSatoshis: 25000 }); const { io, spies } = createMockIO(); await handleResourceCommand(createCommandDeps(app, io), ["list"], {}); @@ -222,8 +227,8 @@ describe("resource command with populated data", () => { }); test("list excludes reserved resources by default", async () => { - await addFakeResource(engine, { valueSatoshis: 50000 }); - await addFakeResource(engine, { + await addFakeResource(state, { valueSatoshis: 50000 }); + await addFakeResource(state, { valueSatoshis: 25000, reservedBy: "inv-123", }); @@ -239,12 +244,12 @@ describe("resource command with populated data", () => { }); test("list reserved shows only reserved resources", async () => { - await addFakeResource(engine, { valueSatoshis: 50000 }); - await addFakeResource(engine, { + await addFakeResource(state, { valueSatoshis: 50000 }); + await addFakeResource(state, { valueSatoshis: 25000, reservedBy: "inv-123", }); - await addFakeResource(engine, { + await addFakeResource(state, { valueSatoshis: 10000, reservedBy: "inv-456", }); @@ -261,8 +266,8 @@ describe("resource command with populated data", () => { }); test("list all shows both reserved and unreserved", async () => { - await addFakeResource(engine, { valueSatoshis: 50000 }); - await addFakeResource(engine, { + await addFakeResource(state, { valueSatoshis: 50000 }); + await addFakeResource(state, { valueSatoshis: 25000, reservedBy: "inv-123", }); @@ -278,7 +283,7 @@ describe("resource command with populated data", () => { }); test("unreserve releases a reserved UTXO", async () => { - const resource = await addFakeResource(engine, { + const resource = await addFakeResource(state, { valueSatoshis: 25000, reservedBy: "inv-123", }); @@ -306,7 +311,7 @@ describe("resource command with populated data", () => { }); test("unreserve reports when UTXO is not reserved", async () => { - const resource = await addFakeResource(engine, { valueSatoshis: 25000 }); + const resource = await addFakeResource(state, { valueSatoshis: 25000 }); const { io, spies } = createMockIO(); await handleResourceCommand( @@ -322,12 +327,12 @@ describe("resource command with populated data", () => { }); test("unreserve-all releases all reserved UTXOs", async () => { - await addFakeResource(engine, { valueSatoshis: 50000 }); - await addFakeResource(engine, { + await addFakeResource(state, { valueSatoshis: 50000 }); + await addFakeResource(state, { valueSatoshis: 25000, reservedBy: "inv-123", }); - await addFakeResource(engine, { + await addFakeResource(state, { valueSatoshis: 10000, reservedBy: "inv-456", }); @@ -348,7 +353,7 @@ describe("resource command with populated data", () => { }); test("list displays outpoint information", async () => { - const resource = await addFakeResource(engine, { valueSatoshis: 12345 }); + const resource = await addFakeResource(state, { valueSatoshis: 12345 }); const { io, spies } = createMockIO(); await handleResourceCommand(createCommandDeps(app, io), ["list"], {}); diff --git a/tests/cli/commands/template.test.ts b/tests/cli/commands/template.test.ts index 72dba1e..4301809 100644 --- a/tests/cli/commands/template.test.ts +++ b/tests/cli/commands/template.test.ts @@ -183,7 +183,8 @@ describe("template command", () => { let tempDir: string; beforeEach(async () => { - engine = await createMockEngine(DEFAULT_SEED); + const mockEngine = await createMockEngine(DEFAULT_SEED); + engine = mockEngine.engine; await engine.importTemplate(p2pkhTemplate); app = await createMockAppService(engine); diff --git a/tests/cli/mocks/engine.ts b/tests/cli/mocks/engine.ts index 54c28a0..e904916 100644 --- a/tests/cli/mocks/engine.ts +++ b/tests/cli/mocks/engine.ts @@ -4,6 +4,7 @@ import { createStorageAdapter, State, StorageType, + UnspentOutputStatus, type UnspentOutputData, } from "@xo-cash/state"; import { InMemoryBlockchainProvider } from "@xo-cash/engine"; @@ -13,6 +14,8 @@ import { binToHex, sha256 } from "@bitauth/libauth"; import { AppService } from "../../../src/services/app"; import { InMemoryStorage } from "../../../src/services/storage"; import { MockElectrumService } from "./electrum-service"; +import { MockRatesService } from "./rates-service"; +import { RatesService } from "../../../src/services/rates"; export const DEFAULT_SEED = "page pencil stock planet limb cluster assault speak off joke private pioneer"; @@ -57,11 +60,11 @@ export const randomTxHash = (): string => { * @returns The created UnspentOutputData object. */ export const addFakeResource = async ( - engine: Engine, + state: State, options: FakeResourceOptions = {}, ): Promise => { const resource: UnspentOutputData = { - status: "confirmed", + status: UnspentOutputStatus.CONFIRMED, selectable: true, privacy: false, templateIdentifier: options.templateIdentifier ?? "test-template", @@ -76,7 +79,7 @@ export const addFakeResource = async ( reservedBy: options.reservedBy, }; - await engine.state.storeUnspentOutputData(resource); + await state.storeUnspentOutputData(resource); return resource; }; @@ -88,12 +91,12 @@ export const addFakeResource = async ( * @param invitationIdentifier - The invitation identifier to reserve for. */ export const reserveResource = async ( - engine: Engine, + state: State, outpointTransactionHash: string, outpointIndex: number, invitationIdentifier: string, ): Promise => { - await engine.state.executeBulkUnspentOutputReservation( + await state.executeBulkUnspentOutputReservation( [{ outpointTransactionHash, outpointIndex }], true, invitationIdentifier, @@ -108,12 +111,12 @@ export const reserveResource = async ( * @param invitationIdentifier - The invitation identifier to unreserve from. */ export const unreserveResource = async ( - engine: Engine, + state: State, outpointTransactionHash: string, outpointIndex: number, invitationIdentifier: string, ): Promise => { - await engine.state.executeBulkUnspentOutputReservation( + await state.executeBulkUnspentOutputReservation( [{ outpointTransactionHash, outpointIndex }], false, invitationIdentifier, @@ -153,13 +156,14 @@ export const createMockEngine = async (seed: string) => { const engine = new Engine(seed, state, blockchainMonitor, blockchainProvider); await engine.initializeStateSync(); - return engine; + return { engine, state, blockchainMonitor, blockchainProvider }; }; export const createMockAppService = async (engine: Engine) => { const storage = await InMemoryStorage.create(); - const electrum = new MockElectrumService(); + const mockRates = new MockRatesService(); + const rates = new RatesService(mockRates); const config = { syncServerUrl: "http://localhost:3000", @@ -170,5 +174,5 @@ export const createMockAppService = async (engine: Engine) => { invitationStoragePath: "test-invitations.db", }; - return new AppService(engine, storage, config, electrum); + return new AppService(engine, storage, config, rates); }; diff --git a/tests/cli/mocks/rates-service.ts b/tests/cli/mocks/rates-service.ts new file mode 100644 index 0000000..1237d71 --- /dev/null +++ b/tests/cli/mocks/rates-service.ts @@ -0,0 +1,23 @@ +import { BaseRates } from "../../../src/utils/rates/base-rates"; + +export class MockRatesService extends BaseRates { + constructor() { + super(); + } + + async getRate(numeratorUnitCode: string, denominatorUnitCode: string): Promise { + return 1; + } + + async start(): Promise { + return; + } + + async stop(): Promise { + return; + } + + async listPairs(): Promise> { + return new Set(); + } +} \ No newline at end of file