From 96101238afb82d200cf9d5005ffc2fc0391f23e4 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 2 Feb 2023 20:20:58 +0100 Subject: harness,wallet-cli: notification-based testing with RPC wallet --- packages/taler-wallet-cli/src/index.ts | 163 ++++----------------------------- 1 file changed, 20 insertions(+), 143 deletions(-) (limited to 'packages/taler-wallet-cli') diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts index 67d0e3784..cce982dfb 100644 --- a/packages/taler-wallet-cli/src/index.ts +++ b/packages/taler-wallet-cli/src/index.ts @@ -60,13 +60,15 @@ import { WalletCoreApiClient, walletCoreDebugFlags, } from "@gnu-taler/taler-wallet-core"; + +import { + createRemoteWallet, + getClientFromRemoteWallet, + makeNotificationWaiter, +} from "@gnu-taler/taler-wallet-core/remote"; import fs from "fs"; import os from "os"; -import { - connectRpc, - JsonMessage, - runRpcServer, -} from "@gnu-taler/taler-util/twrpc"; +import { JsonMessage, runRpcServer } from "@gnu-taler/taler-util/twrpc"; // This module also serves as the entry point for the crypto // thread worker, and thus must expose these two handlers. @@ -280,162 +282,33 @@ async function createLocalWallet( } } -export interface RemoteWallet { - /** - * Low-level interface for making API requests to wallet-core. - */ - makeCoreApiRequest( - operation: string, - payload: unknown, - ): Promise; - - /** - * Close the connection to the remote wallet. - */ - close(): void; -} - -async function createRemoteWallet( - notificationHandler?: (n: WalletNotification) => void, -): Promise { - let nextRequestId = 1; - let requestMap: Map< - string, - { - promiseCapability: OpenedPromise; - } - > = new Map(); - - const ctx = await connectRpc({ - socketFilename: "wallet-core.sock", - onEstablished(connection) { - const ctx: RemoteWallet = { - makeCoreApiRequest(operation, payload) { - const id = `req-${nextRequestId}`; - const req: CoreApiRequestEnvelope = { - operation, - id, - args: payload, - }; - const promiseCap = openPromise(); - requestMap.set(id, { - promiseCapability: promiseCap, - }); - connection.sendMessage(req as unknown as JsonMessage); - return promiseCap.promise; - }, - close() { - connection.close(); - }, - }; - return { - result: ctx, - onDisconnect() { - logger.info("remote wallet disconnected"); - }, - onMessage(m) { - // FIXME: use a codec for parsing the response envelope! - - logger.info(`got message from remote wallet: ${j2s(m)}`); - if (typeof m !== "object" || m == null) { - logger.warn("message from wallet not understood (wrong type)"); - return; - } - const type = (m as any).type; - if (type === "response" || type === "error") { - const id = (m as any).id; - if (typeof id !== "string") { - logger.warn( - "message from wallet not understood (no id in response)", - ); - return; - } - const h = requestMap.get(id); - if (!h) { - logger.warn(`no handler registered for response id ${id}`); - return; - } - h.promiseCapability.resolve(m as any); - } else if (type === "notification") { - logger.info("got notification"); - if (notificationHandler) { - notificationHandler((m as any).payload); - } - } else { - logger.warn("message from wallet not understood"); - } - }, - }; - }, - }); - return ctx; -} - -/** - * Get a high-level API client from a remove wallet. - */ -function getClientFromRemoteWallet(w: RemoteWallet): WalletCoreApiClient { - const client: WalletCoreApiClient = { - async call(op, payload): Promise { - const res = await w.makeCoreApiRequest(op, payload); - switch (res.type) { - case "error": - throw TalerError.fromUncheckedDetail(res.error); - case "response": - return res.result; - } - }, - }; - return client; -} - async function withWallet( walletCliArgs: WalletCliArgsType, f: (ctx: WalletContext) => Promise, ): Promise { - // Bookkeeping for waiting on notification conditions - let nextCondIndex = 1; - const condMap: Map< - number, - { - condition: (n: WalletNotification) => boolean; - promiseCapability: OpenedPromise; - } - > = new Map(); - function onNotification(n: WalletNotification) { - condMap.forEach((cond, condKey) => { - if (cond.condition(n)) { - cond.promiseCapability.resolve(); - } - }); - } - function waitForNotificationCond(cond: (n: WalletNotification) => boolean) { - const promCap = openPromise(); - condMap.set(nextCondIndex++, { - condition: cond, - promiseCapability: promCap, - }); - return promCap.promise; - } + const waiter = makeNotificationWaiter(); if (walletCliArgs.wallet.walletConnection) { logger.info("creating remote wallet"); - const w = await createRemoteWallet(onNotification); + const w = await createRemoteWallet({ + notificationHandler: waiter.notify, + socketFilename: walletCliArgs.wallet.walletConnection, + }); const ctx: WalletContext = { makeCoreApiRequest(operation, payload) { return w.makeCoreApiRequest(operation, payload); }, client: getClientFromRemoteWallet(w), - waitForNotificationCond, + waitForNotificationCond: waiter.waitForNotificationCond, }; const res = await f(ctx); w.close(); return res; } else { - const w = await createLocalWallet(walletCliArgs, onNotification); + const w = await createLocalWallet(walletCliArgs, waiter.notify); const ctx: WalletContext = { client: w.client, - waitForNotificationCond, + waitForNotificationCond: waiter.waitForNotificationCond, makeCoreApiRequest(operation, payload) { return w.handleCoreApiRequest(operation, "my-req", payload); }, @@ -1053,7 +926,11 @@ advancedCli .subcommand("serve", "serve", { help: "Serve the wallet API via a unix domain socket.", }) + .requiredOption("unixPath", ["--unix-path"], clk.STRING, { + default: "wallet-core.sock", + }) .action(async (args) => { + logger.info(`serving at ${args.serve.unixPath}`); const w = await createLocalWallet(args); w.runTaskLoop() .then((res) => { @@ -1070,7 +947,7 @@ advancedCli }); }); await runRpcServer({ - socketFilename: "wallet-core.sock", + socketFilename: args.serve.unixPath, onConnect(client) { logger.info("connected"); const clientId = nextClientId++; -- cgit v1.2.3