gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: taler-harness: deployment too


From: gnunet
Subject: [taler-wallet-core] branch master updated: taler-harness: deployment tooling for tipping
Date: Thu, 23 Feb 2023 00:52:23 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new 7985b0a33 taler-harness: deployment tooling for tipping
7985b0a33 is described below

commit 7985b0a33ffc3e258da5d73f4056384c38e626fe
Author: Florian Dold <florian@dold.me>
AuthorDate: Thu Feb 23 00:52:10 2023 +0100

    taler-harness: deployment tooling for tipping
---
 packages/taler-harness/src/harness/harness.ts      |  54 +++++++++
 .../taler-harness/src/harness/libeufin-apis.ts     |   7 +-
 packages/taler-harness/src/index.ts                | 125 ++++++++++++++++++++-
 packages/taler-util/src/http-common.ts             |  18 ++-
 packages/taler-util/src/http-impl.node.ts          |   7 +-
 packages/taler-util/src/http-impl.qtart.ts         |  17 ++-
 packages/taler-wallet-core/src/bank-api-client.ts  |  60 ++++++++++
 7 files changed, 266 insertions(+), 22 deletions(-)

diff --git a/packages/taler-harness/src/harness/harness.ts 
b/packages/taler-harness/src/harness/harness.ts
index 6168ea0b7..5733e776b 100644
--- a/packages/taler-harness/src/harness/harness.ts
+++ b/packages/taler-harness/src/harness/harness.ts
@@ -1436,12 +1436,20 @@ export interface MerchantServiceInterface {
   readonly name: string;
 }
 
+export interface DeleteTippingReserveArgs {
+  reservePub: string;
+  purge?: boolean;
+}
+
 export class MerchantApiClient {
   constructor(
     private baseUrl: string,
     public readonly auth: MerchantAuthConfiguration,
   ) {}
 
+  // FIXME: Migrate everything to this in favor of axios
+  http = createPlatformHttpLib();
+
   async changeAuth(auth: MerchantAuthConfiguration): Promise<void> {
     const url = new URL("private/auth", this.baseUrl);
     await axios.post(url.href, auth, {
@@ -1449,6 +1457,51 @@ export class MerchantApiClient {
     });
   }
 
+  async deleteTippingReserve(req: DeleteTippingReserveArgs): Promise<void> {
+    const url = new URL(`private/reserves/${req.reservePub}`, this.baseUrl);
+    if (req.purge) {
+      url.searchParams.set("purge", "YES");
+    }
+    const resp = await axios.delete(url.href, {
+      headers: this.makeAuthHeader(),
+    });
+    logger.info(`delete status: ${resp.status}`);
+    return;
+  }
+
+  async createTippingReserve(
+    req: CreateMerchantTippingReserveRequest,
+  ): Promise<CreateMerchantTippingReserveConfirmation> {
+    const url = new URL("private/reserves", this.baseUrl);
+    const resp = await axios.post(url.href, req, {
+      headers: this.makeAuthHeader(),
+    });
+    // FIXME: validate
+    return resp.data;
+  }
+
+  async getPrivateInstanceInfo(): Promise<any> {
+    console.log(this.makeAuthHeader());
+    const url = new URL("private", this.baseUrl);
+    logger.info(`request url ${url.href}`);
+    const resp = await this.http.fetch(url.href, {
+      method: "GET",
+      headers: this.makeAuthHeader(),
+    });
+    return await resp.json();
+  }
+
+  async getPrivateTipReserves(): Promise<TippingReserveStatus> {
+    console.log(this.makeAuthHeader());
+    const url = new URL("private/reserves", this.baseUrl);
+    const resp = await this.http.fetch(url.href, {
+      method: "GET",
+      headers: this.makeAuthHeader(),
+    });
+    // FIXME: Validate!
+    return await resp.json();
+  }
+
   async deleteInstance(instanceId: string) {
     const url = new URL(`management/instances/${instanceId}`, this.baseUrl);
     await axios.delete(url.href, {
@@ -1578,6 +1631,7 @@ export namespace MerchantPrivateApi {
       `private/reserves`,
       merchantService.makeInstanceBaseUrl(instance),
     );
+    // FIXME: Don't use axios!
     const resp = await axios.post(reqUrl.href, req);
     // FIXME: validate
     return resp.data;
diff --git a/packages/taler-harness/src/harness/libeufin-apis.ts 
b/packages/taler-harness/src/harness/libeufin-apis.ts
index a6abe3466..4ef588fb5 100644
--- a/packages/taler-harness/src/harness/libeufin-apis.ts
+++ b/packages/taler-harness/src/harness/libeufin-apis.ts
@@ -7,7 +7,8 @@
 
 import axiosImp from "axios";
 const axios = axiosImp.default;
-import { Logger, URL } from "@gnu-taler/taler-util";
+import { AmountString, Logger, URL } from "@gnu-taler/taler-util";
+import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
 
 export interface LibeufinSandboxServiceInterface {
   baseUrl: string;
@@ -163,10 +164,6 @@ export interface LibeufinSandboxAddIncomingRequest {
   direction: string;
 }
 
-function getRandomString(): string {
-  return Math.random().toString(36).substring(2);
-}
-
 /**
  * APIs spread across Legacy and Access, it is therefore
  * the "base URL" relative to which API every call addresses.
diff --git a/packages/taler-harness/src/index.ts 
b/packages/taler-harness/src/index.ts
index 14b8a4302..370550420 100644
--- a/packages/taler-harness/src/index.ts
+++ b/packages/taler-harness/src/index.ts
@@ -22,10 +22,13 @@ import fs from "fs";
 import os from "os";
 import path from "path";
 import {
+  addPaytoQueryParams,
   Amounts,
   Configuration,
   decodeCrock,
+  j2s,
   Logger,
+  parsePaytoUri,
   rsaBlind,
   setGlobalLogLevelFromString,
 } from "@gnu-taler/taler-util";
@@ -33,11 +36,18 @@ import { runBench1 } from "./bench1.js";
 import { runBench2 } from "./bench2.js";
 import { runBench3 } from "./bench3.js";
 import { runEnv1 } from "./env1.js";
-import { GlobalTestState, runTestWithState } from "./harness/harness.js";
+import {
+  GlobalTestState,
+  MerchantApiClient,
+  MerchantPrivateApi,
+  runTestWithState,
+} from "./harness/harness.js";
 import { getTestInfo, runTests } from "./integrationtests/testrunner.js";
 import { lintExchangeDeployment } from "./lint.js";
 import { runEnvFull } from "./env-full.js";
 import { clk } from "@gnu-taler/taler-util/clk";
+import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
+import { BankAccessApiClient } from "@gnu-taler/taler-wallet-core";
 
 const logger = new Logger("taler-harness:index.ts");
 
@@ -152,10 +162,123 @@ advancedCli
     await runTestWithState(testState, runEnv1, "env1", true);
   });
 
+const sandcastleCli = testingCli.subcommand("sandcastleArgs", "sandcastle", {
+  help: "Subcommands for handling GNU Taler sandcastle deployments.",
+});
+
 const deploymentCli = testingCli.subcommand("deploymentArgs", "deployment", {
   help: "Subcommands for handling GNU Taler deployments.",
 });
 
+deploymentCli
+  .subcommand("tipTopup", "tip-topup")
+  .requiredOption("merchantBaseUrl", ["--merchant-url"], clk.STRING)
+  .requiredOption("exchangeBaseUrl", ["--exchange-url"], clk.STRING)
+  .requiredOption("merchantApikey", ["--merchant-apikey"], clk.STRING)
+  .requiredOption("bankAccessUrl", ["--bank-access-url"], clk.STRING)
+  .requiredOption("bankAccount", ["--bank-account"], clk.STRING)
+  .requiredOption("bankPassword", ["--bank-password"], clk.STRING)
+  .requiredOption("wireMethod", ["--wire-method"], clk.STRING)
+  .requiredOption("amount", ["--amount"], clk.STRING)
+  .action(async (args) => {
+    const amount = args.tipTopup.amount;
+
+    const merchantClient = new MerchantApiClient(
+      args.tipTopup.merchantBaseUrl,
+      {
+        method: "token",
+        token: args.tipTopup.merchantApikey,
+      },
+    );
+
+    const res = await merchantClient.getPrivateInstanceInfo();
+    console.log(res);
+
+    const tipReserveResp = await merchantClient.createTippingReserve({
+      exchange_url: args.tipTopup.exchangeBaseUrl,
+      initial_balance: amount,
+      wire_method: args.tipTopup.wireMethod,
+    });
+
+    console.log(tipReserveResp);
+
+    const bankAccessApiClient = new BankAccessApiClient({
+      baseUrl: args.tipTopup.bankAccessUrl,
+      username: args.tipTopup.bankAccount,
+      password: args.tipTopup.bankPassword,
+    });
+
+    const paytoUri = addPaytoQueryParams(tipReserveResp.payto_uri, {
+      message: `tip-reserve ${tipReserveResp.reserve_pub}`,
+    });
+
+    console.log("payto URI:", paytoUri);
+
+    const transactions = await bankAccessApiClient.getTransactions();
+    console.log("transactions:", j2s(transactions));
+
+    await bankAccessApiClient.createTransaction({
+      amount,
+      paytoUri,
+    });
+  });
+
+deploymentCli
+  .subcommand("tipCleanup", "tip-cleanup")
+  .requiredOption("merchantBaseUrl", ["--merchant-url"], clk.STRING)
+  .requiredOption("merchantApikey", ["--merchant-apikey"], clk.STRING)
+  .flag("dryRun", ["--dry-run"])
+  .action(async (args) => {
+    const merchantClient = new MerchantApiClient(
+      args.tipCleanup.merchantBaseUrl,
+      {
+        method: "token",
+        token: args.tipCleanup.merchantApikey,
+      },
+    );
+
+    const res = await merchantClient.getPrivateInstanceInfo();
+    console.log(res);
+
+    const tipRes = await merchantClient.getPrivateTipReserves();
+    console.log(tipRes);
+
+    for (const reserve of tipRes.reserves) {
+      if (Amounts.isZero(reserve.exchange_initial_amount)) {
+        if (args.tipCleanup.dryRun) {
+          logger.info(`dry run, would purge reserve ${reserve}`);
+        } else {
+          await merchantClient.deleteTippingReserve({
+            reservePub: reserve.reserve_pub,
+            purge: true,
+          });
+        }
+      }
+    }
+
+    // FIXME: Now delete reserves that are not filled yet
+  });
+
+deploymentCli
+  .subcommand("tipStatus", "tip-status")
+  .requiredOption("merchantBaseUrl", ["--merchant-url"], clk.STRING)
+  .requiredOption("merchantApikey", ["--merchant-apikey"], clk.STRING)
+  .action(async (args) => {
+    const merchantClient = new MerchantApiClient(
+      args.tipStatus.merchantBaseUrl,
+      {
+        method: "token",
+        token: args.tipStatus.merchantApikey,
+      },
+    );
+
+    const res = await merchantClient.getPrivateInstanceInfo();
+    console.log(res);
+
+    const tipRes = await merchantClient.getPrivateTipReserves();
+    console.log(j2s(tipRes));
+  });
+
 deploymentCli
   .subcommand("lintExchange", "lint-exchange", {
     help: "Run checks on the exchange deployment.",
diff --git a/packages/taler-util/src/http-common.ts 
b/packages/taler-util/src/http-common.ts
index 54f26e615..e5dd92567 100644
--- a/packages/taler-util/src/http-common.ts
+++ b/packages/taler-util/src/http-common.ts
@@ -59,7 +59,7 @@ export interface HttpRequestOptions {
    */
   cancellationToken?: CancellationToken;
 
-  body?: string | ArrayBuffer | Record<string, unknown>;
+  body?: string | ArrayBuffer | object;
 }
 
 /**
@@ -344,9 +344,8 @@ export function getExpiry(
   return t;
 }
 
-
 export interface HttpLibArgs {
-  enableThrottling?: boolean,
+  enableThrottling?: boolean;
 }
 
 export function encodeBody(body: any): ArrayBuffer {
@@ -364,3 +363,16 @@ export function encodeBody(body: any): ArrayBuffer {
   }
   throw new TypeError("unsupported request body type");
 }
+
+export function getDefaultHeaders(method: string): Record<string, string> {
+  const headers: Record<string, string> = {};
+
+  if (method === "POST" || method === "PUT" || method === "PATCH") {
+    // Default to JSON if we have a body
+    headers["Content-Type"] = "application/json";
+  }
+
+  headers["Accept"] = "application/json";
+
+  return headers;
+}
diff --git a/packages/taler-util/src/http-impl.node.ts 
b/packages/taler-util/src/http-impl.node.ts
index 798b81e2d..6dfce934f 100644
--- a/packages/taler-util/src/http-impl.node.ts
+++ b/packages/taler-util/src/http-impl.node.ts
@@ -23,7 +23,7 @@ import * as http from "node:http";
 import * as https from "node:https";
 import { RequestOptions } from "node:http";
 import { TalerError } from "./errors.js";
-import { encodeBody, HttpLibArgs } from "./http-common.js";
+import { encodeBody, getDefaultHeaders, HttpLibArgs } from "./http-common.js";
 import {
   DEFAULT_REQUEST_TIMEOUT_MS,
   Headers,
@@ -85,8 +85,7 @@ export class HttpLibImpl implements HttpRequestLibrary {
       timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS;
     }
 
-    const headers = { ...opt?.headers };
-    headers["Content-Type"] = "application/json";
+    const requestHeadersMap = { ...getDefaultHeaders(method), ...opt?.headers 
};
 
     let reqBody: ArrayBuffer | undefined;
 
@@ -114,7 +113,7 @@ export class HttpLibImpl implements HttpRequestLibrary {
       host: parsedUrl.hostname,
       method: method,
       path,
-      headers: opt?.headers,
+      headers: requestHeadersMap,
     };
 
     const chunks: Uint8Array[] = [];
diff --git a/packages/taler-util/src/http-impl.qtart.ts 
b/packages/taler-util/src/http-impl.qtart.ts
index 954b41802..ee3d1f725 100644
--- a/packages/taler-util/src/http-impl.qtart.ts
+++ b/packages/taler-util/src/http-impl.qtart.ts
@@ -21,7 +21,7 @@
  */
 import { Logger } from "@gnu-taler/taler-util";
 import { TalerError } from "./errors.js";
-import { encodeBody, HttpLibArgs } from "./http-common.js";
+import { encodeBody, getDefaultHeaders, HttpLibArgs } from "./http-common.js";
 import {
   Headers,
   HttpRequestLibrary,
@@ -54,7 +54,7 @@ export class HttpLibImpl implements HttpRequestLibrary {
   }
 
   async fetch(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> {
-    const method = opt?.method ?? "GET";
+    const method = (opt?.method ?? "GET").toUpperCase();
 
     logger.trace(`Requesting ${method} ${url}`);
 
@@ -72,19 +72,18 @@ export class HttpLibImpl implements HttpRequestLibrary {
     }
 
     let data: ArrayBuffer | undefined = undefined;
-    let headers: string[] = [];
-    if (opt?.headers) {
-      for (let headerName of Object.keys(opt.headers)) {
-        headers.push(`${headerName}: ${opt.headers[headerName]}`);
-      }
+    const requestHeadersMap = { ...getDefaultHeaders(method), ...opt?.headers 
};
+    let headersList: string[] = [];
+    for (let headerName of Object.keys(requestHeadersMap)) {
+      headersList.push(`${headerName}: ${requestHeadersMap[headerName]}`);
     }
-    if (method.toUpperCase() === "POST") {
+    if (method === "POST") {
       data = encodeBody(opt?.body);
     }
     const res = await qjsOs.fetchHttp(url, {
       method,
       data,
-      headers,
+      headers: headersList,
     });
     return {
       requestMethod: method,
diff --git a/packages/taler-wallet-core/src/bank-api-client.ts 
b/packages/taler-wallet-core/src/bank-api-client.ts
index f807d2daa..de0d4b852 100644
--- a/packages/taler-wallet-core/src/bank-api-client.ts
+++ b/packages/taler-wallet-core/src/bank-api-client.ts
@@ -37,6 +37,7 @@ import {
   TalerErrorCode,
 } from "@gnu-taler/taler-util";
 import {
+  createPlatformHttpLib,
   HttpRequestLibrary,
   readSuccessResponseJsonOrThrow,
 } from "@gnu-taler/taler-util/http";
@@ -277,3 +278,62 @@ export namespace BankAccessApi {
     );
   }
 }
+
+export interface BankAccessApiClientArgs {
+  baseUrl: string;
+  username: string;
+  password: string;
+}
+
+export interface BankAccessApiCreateTransactionRequest {
+  amount: AmountString;
+  paytoUri: string;
+}
+
+export class BankAccessApiClient {
+  httpLib = createPlatformHttpLib();
+
+  constructor(private args: BankAccessApiClientArgs) {}
+
+  async getTransactions(): Promise<void> {
+    const reqUrl = new URL(
+      `accounts/${this.args.username}/transactions`,
+      this.args.baseUrl,
+    );
+    const authHeaderValue = makeBasicAuthHeader(
+      this.args.username,
+      this.args.password,
+    );
+    const resp = await this.httpLib.fetch(reqUrl.href, {
+      method: "GET",
+      headers: {
+        Authorization: authHeaderValue,
+      },
+    });
+
+    const res = await readSuccessResponseJsonOrThrow(resp, codecForAny());
+    logger.info(`result: ${j2s(res)}`);
+  }
+
+  async createTransaction(
+    req: BankAccessApiCreateTransactionRequest,
+  ): Promise<any> {
+    const reqUrl = new URL(
+      `accounts/${this.args.username}/transactions`,
+      this.args.baseUrl,
+    );
+    const authHeaderValue = makeBasicAuthHeader(
+      this.args.username,
+      this.args.password,
+    );
+    const resp = await this.httpLib.fetch(reqUrl.href, {
+      method: "POST",
+      body: req,
+      headers: {
+        Authorization: authHeaderValue,
+      },
+    });
+
+    return await readSuccessResponseJsonOrThrow(resp, codecForAny());
+  }
+}

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]