aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-10-26 11:15:45 -0300
committerSebastian <sebasjm@gmail.com>2023-10-26 11:15:45 -0300
commita4c7bc4b284fe7dc4c65ceaad96fc67c40c9a708 (patch)
treef407ad06733a3a82a7c5655d65a81fc3e5248cc1
parente812eae32daddad372c7629867298ca28678a44c (diff)
downloadwallet-core-a4c7bc4b284fe7dc4c65ceaad96fc67c40c9a708.tar.xz
moving cli test to harness
-rw-r--r--packages/taler-harness/src/http-client/bank-core.ts547
-rw-r--r--packages/taler-harness/src/index.ts95
-rw-r--r--packages/taler-util/src/http-client/authentication.ts5
-rw-r--r--packages/taler-util/src/http-client/bank-core.ts580
-rw-r--r--packages/taler-util/src/http-client/bank-integration.ts2
-rw-r--r--packages/taler-util/src/http-client/test.cli.ts77
-rw-r--r--packages/taler-util/src/http-client/types.ts21
-rw-r--r--packages/taler-util/src/http-client/utils.ts35
-rw-r--r--packages/taler-util/src/http-impl.node.ts2
-rw-r--r--packages/taler-util/src/index.ts1
-rw-r--r--packages/taler-util/src/operation.ts74
11 files changed, 691 insertions, 748 deletions
diff --git a/packages/taler-harness/src/http-client/bank-core.ts b/packages/taler-harness/src/http-client/bank-core.ts
new file mode 100644
index 000000000..c67ff1bf8
--- /dev/null
+++ b/packages/taler-harness/src/http-client/bank-core.ts
@@ -0,0 +1,547 @@
+import { AccessToken, Amounts, TalerCoreBankHttpClient, TalerCorebankApi, encodeCrock, failOrThrow, getRandomBytes, parsePaytoUri, stringifyPaytoUri, succeedOrThrow } from "@gnu-taler/taler-util"
+
+export class BankCoreSmokeTest {
+ constructor(readonly api:TalerCoreBankHttpClient) {
+
+ }
+
+async testConfig() {
+ const config = await this.api.getConfig()
+ if (!this.api.isCompatible(config.body.version)) {
+ throw Error(`not compatible with server ${config.body.version}`)
+ }
+ return config.body
+}
+
+async testCashouts(adminPassword: string) {
+}
+async testMonitor(adminPassword: string) {
+ const { access_token: adminToken } = await succeedOrThrow(() =>
+ this.api.getAuthenticationAPI("admin").createAccessToken(adminPassword, {
+ scope: "readwrite"
+ })
+ )
+
+ await succeedOrThrow(() => (
+ this.api.getMonitor()
+ ))
+
+ await succeedOrThrow(() => (
+ this.api.getMonitor({
+ timeframe: TalerCorebankApi.MonitorTimeframeParam.day,
+ which: (new Date()).getDate() -1
+ })
+ ))
+}
+
+async testAccountManagement(adminPassword: string) {
+
+ const { access_token: adminToken } = await succeedOrThrow(() =>
+ this.api.getAuthenticationAPI("admin").createAccessToken(adminPassword, {
+ scope: "readwrite"
+ })
+ )
+
+ /**
+ * Create account
+ */
+ {
+ const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase();
+
+ // await failOrThrow("invalid-input",() =>
+ // this.api.createAccount(adminToken, {
+ // name: username,
+ // username, password: "123",
+ // challenge_contact_data: {
+ // email: "invalid email",
+ // phone: "invalid phone",
+ // }
+ // })
+ // )
+
+ // await failOrThrow("unable-to-create",() =>
+ // this.api.createAccount(adminToken, {
+ // name: "admin",
+ // username, password: "123"
+ // })
+ // )
+
+ // await failOrThrow("unable-to-create",() =>
+ // this.api.createAccount(adminToken, {
+ // name: "bank",
+ // username, password: "123"
+ // })
+ // )
+
+ await succeedOrThrow(() =>
+ this.api.createAccount(adminToken, {
+ name: username,
+ username, password: "123"
+ })
+ )
+
+ await failOrThrow("already-exist", () =>
+ this.api.createAccount(adminToken, {
+ name: username,
+ username, password: "123"
+ })
+ );
+ }
+
+ /**
+ * Delete account
+ */
+ {
+ const { username, token } = await createRandomTestUser(this.api, adminToken)
+
+ await failOrThrow("not-found", () =>
+ this.api.deleteAccount({ username: "not-found", token: adminToken })
+ )
+ await failOrThrow("unable-to-delete", () =>
+ this.api.deleteAccount({ username: "admin", token: adminToken })
+ )
+ await failOrThrow("unable-to-delete", () =>
+ this.api.deleteAccount({ username: "bank", token: adminToken })
+ )
+
+ await failOrThrow("balance-not-zero", () =>
+ this.api.deleteAccount({ username, token: adminToken })
+ )
+
+ const userInfo = await succeedOrThrow(() =>
+ this.api.getAccount({ username, token })
+ )
+
+ const adminInfo = await succeedOrThrow(() =>
+ this.api.getAccount({ username: "admin", token: adminToken })
+ )
+
+ const adminAccount = parsePaytoUri(adminInfo.payto_uri)!
+ adminAccount.params["message"] = "all my money"
+ const withSubject = stringifyPaytoUri(adminAccount)
+
+ await succeedOrThrow(() =>
+ this.api.createTransaction({ username, token }, {
+ payto_uri: withSubject,
+ amount: userInfo.balance.amount
+ })
+ )
+
+
+ const otherUsername = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase();
+
+ await succeedOrThrow(() =>
+ this.api.createAccount(adminToken, {
+ name: otherUsername,
+ username: otherUsername, password: "123"
+ })
+ )
+
+ await failOrThrow("unauthorized", () =>
+ this.api.deleteAccount({ username: otherUsername, token })
+ )
+
+ await succeedOrThrow(() =>
+ this.api.deleteAccount({ username, token: adminToken })
+ )
+ }
+
+ /**
+ * Update account
+ */
+ {
+ const { username, token } = await createRandomTestUser(this.api, adminToken)
+
+ await failOrThrow("cant-change-legal-name-or-admin", () =>
+ this.api.updateAccount({ username, token }, {
+ name: "something else",
+ })
+ )
+
+ // await failOrThrow("not-found", () =>
+ // this.api.updateAccount({ username: "notfound", token }, {
+ // challenge_contact_data: {
+ // email: "asd@Aasd.com"
+ // }
+ // })
+ // )
+
+ await failOrThrow("unauthorized", () =>
+ this.api.updateAccount({ username: "notfound", token: "wrongtoken" as AccessToken }, {
+ challenge_contact_data: {
+ email: "asd@Aasd.com"
+ }
+ })
+ )
+
+ await succeedOrThrow(() =>
+ this.api.updateAccount({ username, token }, {
+ challenge_contact_data: {
+ email: "asd@Aasd.com"
+ }
+ })
+ )
+ }
+
+ /**
+ * Update password
+ */
+ {
+ const { username, token } = await createRandomTestUser(this.api, adminToken)
+
+ await succeedOrThrow(() =>
+ this.api.updatePassword({ username, token }, {
+ old_password: "123",
+ new_password: "234"
+ })
+ )
+ // await failOrThrow("not-found",() =>
+ // this.api.updatePassword({ username:"notfound", token: userTempToken }, {
+ // old_password: "123",
+ // new_password: "234"
+ // })
+ // )
+ await failOrThrow("unauthorized", () =>
+ this.api.updatePassword({ username: "admin", token }, {
+ old_password: "123",
+ new_password: "234"
+ })
+ )
+ // await failOrThrow("old-password-invalid-or-not-allowed",() =>
+ // this.api.updatePassword({ username, token: userTempToken }, {
+ // old_password: "123",
+ // new_password: "234"
+ // })
+ // )
+
+ }
+
+ /**
+ * public accounts
+ */
+ {
+ const acs = await succeedOrThrow(() => this.api.getPublicAccounts())
+
+ }
+ /**
+ * get accounts
+ */
+ {
+ const { username, token } = await createRandomTestUser(this.api, adminToken)
+ // await failOrThrow("no-rights",() =>
+ // this.api.getAccounts(token)
+ // )
+ await failOrThrow("unauthorized", () =>
+ this.api.getAccounts("ASDASD" as AccessToken)
+ )
+
+ const acs = await succeedOrThrow(() =>
+ this.api.getAccounts(adminToken)
+ )
+ }
+
+}
+
+async testWithdrawals(adminPassword: string) {
+ const { access_token: adminToken } = await succeedOrThrow(() =>
+ this.api.getAuthenticationAPI("admin").createAccessToken(adminPassword, {
+ scope: "readwrite"
+ })
+ )
+ /**
+ * create withdrawals
+ */
+ {
+ const { username, token } = await createRandomTestUser(this.api, adminToken)
+
+ const userInfo = await succeedOrThrow(() =>
+ this.api.getAccount({ username, token })
+ )
+
+ const balance = Amounts.parseOrThrow(userInfo.balance.amount)
+ const moreThanBalance = Amounts.stringify(Amounts.mult(balance, 5).amount)
+ await failOrThrow("insufficient-funds", () =>
+ this.api.createWithdrawal({ username, token }, {
+ amount: moreThanBalance
+ })
+ )
+
+ await failOrThrow("unauthorized", () =>
+ this.api.createWithdrawal({ username, token: "wrongtoken" as AccessToken }, {
+ amount: userInfo.balance.amount
+ })
+ )
+
+ await succeedOrThrow(() =>
+ this.api.createWithdrawal({ username, token }, {
+ amount: userInfo.balance.amount
+ })
+ )
+ }
+
+ /**
+ * get withdrawal
+ */
+ {
+ const { username, token } = await createRandomTestUser(this.api, adminToken)
+
+ const userInfo = await succeedOrThrow(() =>
+ this.api.getAccount({ username, token })
+ )
+
+ const { withdrawal_id } = await succeedOrThrow(() =>
+ this.api.createWithdrawal({ username, token }, {
+ amount: userInfo.balance.amount
+ })
+ )
+
+ await succeedOrThrow(() =>
+ this.api.getWithdrawalById(withdrawal_id)
+ )
+
+ await failOrThrow("invalid-id", () =>
+ this.api.getWithdrawalById("invalid")
+ )
+ await failOrThrow("not-found", () =>
+ this.api.getWithdrawalById("11111111-1111-1111-1111-111111111111")
+ )
+ }
+
+ /**
+ * abort withdrawal
+ */
+ {
+ const { username:exchangeUser, token: exchangeToken } = await createRandomTestUser(this.api, adminToken, {is_taler_exchange: true})
+ const { username, token } = await createRandomTestUser(this.api, adminToken)
+
+ const userInfo = await succeedOrThrow(() =>
+ this.api.getAccount({ username, token })
+ )
+ const exchangeInfo = await succeedOrThrow(() =>
+ this.api.getAccount({ username:exchangeUser, token:exchangeToken })
+ )
+
+ await failOrThrow("invalid-id", () =>
+ this.api.abortWithdrawalById("invalid")
+ )
+ await failOrThrow("not-found", () =>
+ this.api.abortWithdrawalById("11111111-1111-1111-1111-111111111111")
+ )
+
+ const { withdrawal_id:firstWithdrawal } = await succeedOrThrow(() =>
+ this.api.createWithdrawal({ username, token }, {
+ amount: userInfo.balance.amount
+ })
+ )
+
+ await succeedOrThrow(() =>
+ this.api.abortWithdrawalById(firstWithdrawal)
+ )
+
+ const { taler_withdraw_uri: uri, withdrawal_id:secondWithdrawal } = await succeedOrThrow(() =>
+ this.api.createWithdrawal({ username, token }, {
+ amount: userInfo.balance.amount
+ })
+ )
+
+ await succeedOrThrow(() =>
+ this.api.getIntegrationAPI().completeWithdrawalOperationById(secondWithdrawal, {
+ reserve_pub: encodeCrock(getRandomBytes(32)),
+ selected_exchange: exchangeInfo.payto_uri,
+ })
+ )
+ await succeedOrThrow(() =>
+ this.api.confirmWithdrawalById(secondWithdrawal)
+ )
+ await failOrThrow("previously-confirmed", () =>
+ this.api.abortWithdrawalById(secondWithdrawal)
+ )
+ }
+
+ /**
+ * confirm withdrawal
+ */
+ {
+ const { username:exchangeUser, token: exchangeToken } = await createRandomTestUser(this.api, adminToken, {is_taler_exchange: true})
+ const { username, token } = await createRandomTestUser(this.api, adminToken)
+
+ const userInfo = await succeedOrThrow(() =>
+ this.api.getAccount({ username, token })
+ )
+ const exchangeInfo = await succeedOrThrow(() =>
+ this.api.getAccount({ username:exchangeUser, token:exchangeToken })
+ )
+
+ await failOrThrow("invalid-id", () =>
+ this.api.confirmWithdrawalById("invalid")
+ )
+ await failOrThrow("not-found", () =>
+ this.api.confirmWithdrawalById("11111111-1111-1111-1111-111111111111")
+ )
+
+ const { withdrawal_id:firstWithdrawal } = await succeedOrThrow(() =>
+ this.api.createWithdrawal({ username, token }, {
+ amount: userInfo.balance.amount
+ })
+ )
+
+ await failOrThrow("no-exchange-or-reserve-selected", () =>
+ this.api.confirmWithdrawalById(firstWithdrawal)
+ )
+
+ await succeedOrThrow(() =>
+ this.api.getIntegrationAPI().completeWithdrawalOperationById(firstWithdrawal, {
+ reserve_pub: encodeCrock(getRandomBytes(32)),
+ selected_exchange: exchangeInfo.payto_uri,
+ })
+ )
+
+ await succeedOrThrow(() =>
+ this.api.confirmWithdrawalById(firstWithdrawal)
+ )
+
+ const { withdrawal_id:secondWithdrawal } = await succeedOrThrow(() =>
+ this.api.createWithdrawal({ username, token }, {
+ amount: userInfo.balance.amount
+ })
+ )
+
+ await succeedOrThrow(() =>
+ this.api.abortWithdrawalById(secondWithdrawal)
+ )
+ await failOrThrow("previously-aborted", () =>
+ this.api.confirmWithdrawalById(secondWithdrawal)
+ )
+ }
+}
+
+async testTransactions(adminPassword: string) {
+ const { access_token: adminToken } = await succeedOrThrow(() =>
+ this.api.getAuthenticationAPI("admin").createAccessToken(adminPassword, {
+ scope: "readwrite"
+ })
+ )
+ // get transactions
+ {
+ const { username, token } = await createRandomTestUser(this.api, adminToken)
+ // await succeedOrThrow(() => this.api.getTransactions(creds))
+ const txs = await succeedOrThrow(() => this.api.getTransactions({ username, token }, {
+ limit: 5,
+ order: "asc"
+ }))
+ // await failOrThrow("not-found",() => this.api.getTransactions({
+ // username:"not-found",
+ // token: creds.token,
+ // }))
+ await failOrThrow("unauthorized", () => this.api.getTransactions({
+ username: username,
+ token: "wrongtoken" as AccessToken,
+ }))
+ }
+
+ /**
+ * getTxby id
+ */
+ {
+ const { username, token } = await createRandomTestUser(this.api, adminToken)
+ const { username: otherUser, token: otherToken } = await createRandomTestUser(this.api, adminToken)
+
+ const userInfo = await succeedOrThrow(() =>
+ this.api.getAccount({ username, token })
+ )
+ const otherInfo = await succeedOrThrow(() =>
+ this.api.getAccount({ username: otherUser, token: otherToken })
+ )
+ const otherAccount = parsePaytoUri(otherInfo.payto_uri)!
+ otherAccount.params["message"] = "all"
+
+ await succeedOrThrow(() =>
+ this.api.createTransaction({ username, token }, {
+ payto_uri: stringifyPaytoUri(otherAccount),
+ amount: userInfo.balance.amount
+ })
+ )
+
+ const txs = await succeedOrThrow(() => this.api.getTransactions({ username, token }, {
+ limit: 5,
+ order: "asc"
+ }))
+ const rowId = txs.transactions[0].row_id
+
+ await succeedOrThrow(() =>
+ this.api.getTransactionById({ username, token }, rowId)
+ )
+
+ await failOrThrow("not-found", () =>
+ this.api.getTransactionById({ username, token }, 123123123)
+ )
+
+ await failOrThrow("unauthorized", () =>
+ this.api.getTransactionById({ username, token: "wrongtoken" as AccessToken }, 123123123)
+ )
+ }
+
+ /**
+ * create transactions
+ */
+ {
+ const { username, token } = await createRandomTestUser(this.api, adminToken)
+ const { username: otherUser, token: otherToken } = await createRandomTestUser(this.api, adminToken)
+
+ const userInfo = await succeedOrThrow(() =>
+ this.api.getAccount({ username, token })
+ )
+ const otherInfo = await succeedOrThrow(() =>
+ this.api.getAccount({ username: otherUser, token: otherToken })
+ )
+ const otherAccount = parsePaytoUri(otherInfo.payto_uri)!
+ otherAccount.params["message"] = "all"
+
+ await succeedOrThrow(() =>
+ this.api.createTransaction({ username, token }, {
+ payto_uri: stringifyPaytoUri(otherAccount),
+ amount: userInfo.balance.amount
+ })
+ )
+ //missing amount
+ await failOrThrow("invalid-input", () =>
+ this.api.createTransaction({ username, token }, {
+ payto_uri: stringifyPaytoUri(otherAccount),
+ // amount: userInfo.balance.amount
+ })
+ )
+ //missing subject
+ await failOrThrow("invalid-input", () =>
+ this.api.createTransaction({ username, token }, {
+ payto_uri: otherInfo.payto_uri,
+ amount: userInfo.balance.amount
+ })
+ )
+ await failOrThrow("unauthorized", () =>
+ this.api.createTransaction({ username, token: "wrongtoken" as AccessToken }, {
+ payto_uri: otherInfo.payto_uri,
+ amount: userInfo.balance.amount
+ })
+ )
+ }
+}
+
+
+}
+
+export async function createRandomTestUser(api: TalerCoreBankHttpClient, adminToken: AccessToken, options: Partial<TalerCorebankApi.RegisterAccountRequest> = {}) {
+ const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase();
+ await succeedOrThrow(() =>
+ api.createAccount(adminToken, {
+ name: username,
+ username, password: "123",
+ ...options
+ })
+ )
+ const { access_token } = await succeedOrThrow(() =>
+ api.getAuthenticationAPI(username).createAccessToken("123", {
+ scope: "readwrite"
+ })
+ )
+ return { username, token: access_token }
+}
diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts
index 0f93abdbe..c83457be4 100644
--- a/packages/taler-harness/src/index.ts
+++ b/packages/taler-harness/src/index.ts
@@ -31,6 +31,7 @@ import {
RegisterAccountRequest,
TalerCoreBankHttpClient,
TalerCorebankApiClient,
+ TalerError,
addPaytoQueryParams,
decodeCrock,
encodeCrock,
@@ -67,6 +68,7 @@ import {
} from "./harness/harness.js";
import { getTestInfo, runTests } from "./integrationtests/testrunner.js";
import { lintExchangeDeployment } from "./lint.js";
+import { BankCoreSmokeTest } from "http-client/bank-core.js";
const logger = new Logger("taler-harness:index.ts");
@@ -657,7 +659,7 @@ deploymentCli
process.exit(2);
});
- deploymentCli
+deploymentCli
.subcommand("testBankAPI", "test-bank-api", {
help: "test api compatibility.",
})
@@ -666,53 +668,66 @@ deploymentCli
.action(async (args) => {
const httpLib = createPlatformHttpLib();
const api = new TalerCoreBankHttpClient(args.testBankAPI.corebankApiBaseUrl, httpLib);
-
- {
- logger.info("compatibility")
- const resp = await api.getConfig()
- if (!LibtoolVersion.compare(resp.body.version, api.PROTOCOL_VERSION)?.compatible) {
- logger.error("The API client is not compatible with the server", api.PROTOCOL_VERSION, resp.body.version)
- process.exit(1)
+
+ const tester = new BankCoreSmokeTest(api)
+ try {
+ process.stdout.write("config: ");
+ const config = await tester.testConfig()
+ console.log("ok")
+ const admin = args.testBankAPI.adminPwd
+ process.stdout.write("account management: ");
+ const withAdmin = !!admin && admin !== "-"
+ if (withAdmin) {
+ await tester.testAccountManagement(admin)
+ console.log("ok")
+ } else {
+ console.log("skipped")
}
- }
- if (!args.testBankAPI.adminPwd) {
- logger.info("test completed")
- process.exit(0)
- }
- let token: AccessToken;
- {
- logger.info("login admin")
- const resp = await api.getAuthenticationAPI("admin").createAccessToken(args.testBankAPI.adminPwd, {
- scope: "readwrite"
- })
- if (resp.type === "fail") {
- logger.error("login failed", resp.detail)
- process.exit(1)
+ process.stdout.write("transactions: ");
+ if (withAdmin) {
+ await tester.testTransactions(admin)
+ console.log("ok")
+ } else {
+ console.log("skipped")
}
- token = resp.body.access_token;
- }
- logger.info("account management")
- const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase();
- {
- const resp = await api.createAccount(token, { name: username, password: "123", username })
- if (resp.type === "fail") {
- logger.error("create account failed", resp.detail)
- process.exit(1)
+ process.stdout.write("withdrawals: ");
+ if (withAdmin) {
+ await tester.testWithdrawals(admin)
+ console.log("ok")
+ } else {
+ console.log("skipped")
}
- }
- {
- const resp = await api.updateAccount({username, token}, { challenge_contact_data: {email: "asd"} })
- if (resp.type === "fail") {
- logger.error("create account failed", resp.detail)
- process.exit(1)
+
+ process.stdout.write("monitor: ");
+ if (withAdmin && config.have_cashout) {
+ await tester.testMonitor(admin)
+ console.log("ok")
+ } else {
+ console.log("skipped")
}
- }
+ process.stdout.write("cashout: ");
+ if (withAdmin && config.have_cashout) {
+ await tester.testCashouts(admin)
+ console.log("ok")
+ } else {
+ console.log("skipped")
+ }
- logger.info("test completed")
-
+ } catch (e: any) {
+ console.log("")
+ if (e instanceof TalerError) {
+ console.error("FAILED", JSON.stringify(e.errorDetail, undefined, 2))
+ console.error(e.stack)
+ } else if (e instanceof Error) {
+ console.error(`FAILED: ${e.message}`)
+ console.error(e.stack)
+ } else {
+ console.error(`FAILED: ${e}`)
+ }
+ }
});
diff --git a/packages/taler-util/src/http-client/authentication.ts b/packages/taler-util/src/http-client/authentication.ts
index 0c59c9308..b27a266e9 100644
--- a/packages/taler-util/src/http-client/authentication.ts
+++ b/packages/taler-util/src/http-client/authentication.ts
@@ -17,8 +17,9 @@
import { HttpStatusCode } from "../http-status-codes.js";
import { HttpRequestLibrary, createPlatformHttpLib, makeBasicAuthHeader } from "../http.js";
import { LibtoolVersion } from "../libtool-version.js";
-import { AccessToken, TalerAuthentication, UserAndPassword, UserAndToken, codecForTokenSuccessResponse } from "./types.js";
-import { makeBearerTokenAuthHeader, opEmptySuccess, opKnownFailure, opSuccess, opUnknownFailure } from "./utils.js";
+import { opEmptySuccess, opKnownFailure, opSuccess, opUnknownFailure } from "../operation.js";
+import { AccessToken, TalerAuthentication, codecForTokenSuccessResponse } from "./types.js";
+import { makeBearerTokenAuthHeader } from "./utils.js";
export class TalerAuthenticationHttpClient {
public readonly PROTOCOL_VERSION = "0:0:0";
diff --git a/packages/taler-util/src/http-client/bank-core.ts b/packages/taler-util/src/http-client/bank-core.ts
index de3622b8e..e214f6dcd 100644
--- a/packages/taler-util/src/http-client/bank-core.ts
+++ b/packages/taler-util/src/http-client/bank-core.ts
@@ -18,36 +18,23 @@ import {
AmountJson,
Amounts,
HttpStatusCode,
- LibtoolVersion,
- TalerError,
- TalerErrorDetail,
- encodeCrock,
- getRandomBytes,
- parsePaytoUri,
- stringifyPaytoUri
+ LibtoolVersion
} from "@gnu-taler/taler-util";
import {
HttpRequestLibrary,
createPlatformHttpLib
} from "@gnu-taler/taler-util/http";
-import { setShowCurlRequest } from "../http-impl.node.js";
+import { FailCasesByMethod, ResultByMethod, opEmptySuccess, opFixedSuccess, opKnownFailure, opSuccess, opUnknownFailure } from "../operation.js";
import { TalerAuthenticationHttpClient } from "./authentication.js";
import { TalerBankIntegrationHttpClient } from "./bank-integration.js";
import { TalerRevenueHttpClient } from "./bank-revenue.js";
import { TalerWireGatewayHttpClient } from "./bank-wire.js";
-import { AccessToken, OperationOk, OperationResult, PaginationParams, TalerCorebankApi, UserAndToken, codecForAccountData, codecForBankAccountCreateWithdrawalResponse, codecForBankAccountGetWithdrawalResponse, codecForBankAccountTransactionInfo, codecForBankAccountTransactionsResponse, codecForCashoutConversionResponse, codecForCashoutPending, codecForCashoutStatusResponse, codecForCashouts, codecForCoreBankConfig, codecForGlobalCashouts, codecForListBankAccountsResponse, codecForMonitorResponse, codecForPublicAccountsResponse } from "./types.js";
-import { addPaginationParams, makeBearerTokenAuthHeader, opEmptySuccess, opFixedSuccess, opKnownFailure, opSuccess, opUnknownFailure } from "./utils.js";
+import { AccessToken, PaginationParams, TalerCorebankApi, UserAndToken, codecForAccountData, codecForBankAccountCreateWithdrawalResponse, codecForBankAccountGetWithdrawalResponse, codecForBankAccountTransactionInfo, codecForBankAccountTransactionsResponse, codecForCashoutConversionResponse, codecForCashoutPending, codecForCashoutStatusResponse, codecForCashouts, codecForCoreBankConfig, codecForGlobalCashouts, codecForListBankAccountsResponse, codecForMonitorResponse, codecForPublicAccountsResponse } from "./types.js";
+import { addPaginationParams, makeBearerTokenAuthHeader } from "./utils.js";
-type props = keyof TalerCoreBankHttpClient
-
-export type TalerCoreBankResultByMethod<p extends props> = TalerCoreBankHttpClient[p] extends (...args: any[]) => infer Ret ?
- Ret extends Promise<infer Result> ?
- Result :
- never : //api always use Promises
- never; //error cases just for functions
-
-export type TalerCoreBankErrorsByMethod<p extends props> = Exclude<TalerCoreBankResultByMethod<p>, OperationOk<any>>
+export type TalerCoreBankResultByMethod<prop extends keyof TalerCoreBankHttpClient> = ResultByMethod<TalerCoreBankHttpClient, prop>
+export type TalerCoreBankErrorsByMethod<prop extends keyof TalerCoreBankHttpClient> = FailCasesByMethod<TalerCoreBankHttpClient, prop>
/**
* Protocol version spoken with the bank.
@@ -355,7 +342,7 @@ export class TalerCoreBankHttpClient {
});
switch (resp.status) {
case HttpStatusCode.Ok: return opSuccess(resp, codecForBankAccountCreateWithdrawalResponse())
- case HttpStatusCode.PreconditionFailed: return opKnownFailure("insufficient-funds", resp);
+ case HttpStatusCode.Conflict: return opKnownFailure("insufficient-funds", resp);
case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp);
//FIXME: remove when server is updated
case HttpStatusCode.Forbidden: return opKnownFailure("insufficient-funds", resp);
@@ -651,559 +638,6 @@ export class TalerCoreBankHttpClient {
return new TalerAuthenticationHttpClient(url.href, username, this.httpLib,)
}
- async testConfig() {
- const config = await this.getConfig()
- if (!this.isCompatible(config.body.version)) {
- throw Error(`not compatible with server ${config.body.version}`)
- }
- return config.body
- }
-
- async testCashouts(adminPassword: string) {
- }
- async testMonitor(adminPassword: string) {
- const { access_token: adminToken } = await succeedOrThrow(() =>
- this.getAuthenticationAPI("admin").createAccessToken(adminPassword, {
- scope: "readwrite"
- })
- )
-
- await succeedOrThrow(() => (
- this.getMonitor()
- ))
-
- await succeedOrThrow(() => (
- this.getMonitor({
- timeframe: TalerCorebankApi.MonitorTimeframeParam.day,
- which: (new Date()).getDate() -1
- })
- ))
- }
-
- async testAccountManagement(adminPassword: string) {
-
- const { access_token: adminToken } = await succeedOrThrow(() =>
- this.getAuthenticationAPI("admin").createAccessToken(adminPassword, {
- scope: "readwrite"
- })
- )
-
- /**
- * Create account
- */
- {
- const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase();
-
- // await failOrThrow("invalid-input",() =>
- // this.createAccount(adminToken, {
- // name: username,
- // username, password: "123",
- // challenge_contact_data: {
- // email: "invalid email",
- // phone: "invalid phone",
- // }
- // })
- // )
-
- // await failOrThrow("unable-to-create",() =>
- // this.createAccount(adminToken, {
- // name: "admin",
- // username, password: "123"
- // })
- // )
-
- // await failOrThrow("unable-to-create",() =>
- // this.createAccount(adminToken, {
- // name: "bank",
- // username, password: "123"
- // })
- // )
-
- await succeedOrThrow(() =>
- this.createAccount(adminToken, {
- name: username,
- username, password: "123"
- })
- )
-
- await failOrThrow("already-exist", () =>
- this.createAccount(adminToken, {
- name: username,
- username, password: "123"
- })
- );
- }
-
- /**
- * Delete account
- */
- {
- const { username, token } = await createRandomTestUser(this, adminToken)
-
- await failOrThrow("not-found", () =>
- this.deleteAccount({ username: "not-found", token: adminToken })
- )
- await failOrThrow("unable-to-delete", () =>
- this.deleteAccount({ username: "admin", token: adminToken })
- )
- await failOrThrow("unable-to-delete", () =>
- this.deleteAccount({ username: "bank", token: adminToken })
- )
-
- await failOrThrow("balance-not-zero", () =>
- this.deleteAccount({ username, token: adminToken })
- )
-
- const userInfo = await succeedOrThrow(() =>
- this.getAccount({ username, token })
- )
-
- const adminInfo = await succeedOrThrow(() =>
- this.getAccount({ username: "admin", token: adminToken })
- )
-
- const adminAccount = parsePaytoUri(adminInfo.payto_uri)!
- adminAccount.params["message"] = "all my money"
- const withSubject = stringifyPaytoUri(adminAccount)
-
- await succeedOrThrow(() =>
- this.createTransaction({ username, token }, {
- payto_uri: withSubject,
- amount: userInfo.balance.amount
- })
- )
-
-
- const otherUsername = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase();
-
- await succeedOrThrow(() =>
- this.createAccount(adminToken, {
- name: otherUsername,
- username: otherUsername, password: "123"
- })
- )
-
- await failOrThrow("unauthorized", () =>
- this.deleteAccount({ username: otherUsername, token })
- )
-
- await succeedOrThrow(() =>
- this.deleteAccount({ username, token: adminToken })
- )
- }
-
- /**
- * Update account
- */
- {
- const { username, token } = await createRandomTestUser(this, adminToken)
-
- await failOrThrow("cant-change-legal-name-or-admin", () =>
- this.updateAccount({ username, token }, {
- name: "something else",
- })
- )
-
- // await failOrThrow("not-found", () =>
- // this.updateAccount({ username: "notfound", token }, {
- // challenge_contact_data: {
- // email: "asd@Aasd.com"
- // }
- // })
- // )
-
- await failOrThrow("unauthorized", () =>
- this.updateAccount({ username: "notfound", token: "wrongtoken" as AccessToken }, {
- challenge_contact_data: {
- email: "asd@Aasd.com"
- }
- })
- )
-
- await succeedOrThrow(() =>
- this.updateAccount({ username, token }, {
- challenge_contact_data: {
- email: "asd@Aasd.com"
- }
- })
- )
- }
-
- /**
- * Update password
- */
- {
- const { username, token } = await createRandomTestUser(this, adminToken)
-
- await succeedOrThrow(() =>
- this.updatePassword({ username, token }, {
- old_password: "123",
- new_password: "234"
- })
- )
- // await failOrThrow("not-found",() =>
- // this.updatePassword({ username:"notfound", token: userTempToken }, {
- // old_password: "123",
- // new_password: "234"
- // })
- // )
- await failOrThrow("unauthorized", () =>
- this.updatePassword({ username: "admin", token }, {
- old_password: "123",
- new_password: "234"
- })
- )
- // await failOrThrow("old-password-invalid-or-not-allowed",() =>
- // this.updatePassword({ username, token: userTempToken }, {
- // old_password: "123",
- // new_password: "234"
- // })
- // )
-
- }
-
- /**
- * public accounts
- */
- {
- const acs = await succeedOrThrow(() => this.getPublicAccounts())
-
- }
- /**
- * get accounts
- */
- {
- const { username, token } = await createRandomTestUser(this, adminToken)
- // await failOrThrow("no-rights",() =>
- // this.getAccounts(token)
- // )
- await failOrThrow("unauthorized", () =>
- this.getAccounts("ASDASD" as AccessToken)
- )
-
- const acs = await succeedOrThrow(() =>
- this.getAccounts(adminToken)
- )
- }
-
- }
-
- async testWithdrawals(adminPassword: string) {
- const { access_token: adminToken } = await succeedOrThrow(() =>
- this.getAuthenticationAPI("admin").createAccessToken(adminPassword, {
- scope: "readwrite"
- })
- )
- /**
- * create withdrawals
- */
- {
- const { username, token } = await createRandomTestUser(this, adminToken)
-
- const userInfo = await succeedOrThrow(() =>
- this.getAccount({ username, token })
- )
-
- // FIXME: it shoulw warn about not enough balance
- // const balance = Amounts.parseOrThrow(userInfo.balance.amount)
- // const moreThanBalance = Amounts.stringify(Amounts.add(balance, balance).amount)
- // setShowCurlRequest(true)
- // await failOrThrow("insufficient-funds", () =>
- // this.createWithdrawal({ username, token }, {
- // amount: moreThanBalance
- // })
- // )
-
- await failOrThrow("unauthorized", () =>
- this.createWithdrawal({ username, token: "wrongtoken" as AccessToken }, {
- amount: userInfo.balance.amount
- })
- )
-
- await succeedOrThrow(() =>
- this.createWithdrawal({ username, token }, {
- amount: userInfo.balance.amount
- })
- )
- }
-
- /**
- * get withdrawal
- */
- {
- const { username, token } = await createRandomTestUser(this, adminToken)
-
- const userInfo = await succeedOrThrow(() =>
- this.getAccount({ username, token })
- )
-
- const { withdrawal_id } = await succeedOrThrow(() =>
- this.createWithdrawal({ username, token }, {
- amount: userInfo.balance.amount
- })
- )
-
- await succeedOrThrow(() =>
- this.getWithdrawalById(withdrawal_id)
- )
-
- await failOrThrow("invalid-id", () =>
- this.getWithdrawalById("invalid")
- )
- await failOrThrow("not-found", () =>
- this.getWithdrawalById("11111111-1111-1111-1111-111111111111")
- )
- }
-
- /**
- * abort withdrawal
- */
- {
- const { username:exchangeUser, token: exchangeToken } = await createRandomTestUser(this, adminToken, {is_taler_exchange: true})
- const { username, token } = await createRandomTestUser(this, adminToken)
-
- const userInfo = await succeedOrThrow(() =>
- this.getAccount({ username, token })
- )
- const exchangeInfo = await succeedOrThrow(() =>
- this.getAccount({ username:exchangeUser, token:exchangeToken })
- )
-
- await failOrThrow("invalid-id", () =>
- this.abortWithdrawalById("invalid")
- )
- await failOrThrow("not-found", () =>
- this.abortWithdrawalById("11111111-1111-1111-1111-111111111111")
- )
-
- const { withdrawal_id:firstWithdrawal } = await succeedOrThrow(() =>
- this.createWithdrawal({ username, token }, {
- amount: userInfo.balance.amount
- })
- )
-
- await succeedOrThrow(() =>
- this.abortWithdrawalById(firstWithdrawal)
- )
-
- const { taler_withdraw_uri: uri, withdrawal_id:secondWithdrawal } = await succeedOrThrow(() =>
- this.createWithdrawal({ username, token }, {
- amount: userInfo.balance.amount
- })
- )
-
- await succeedOrThrow(() =>
- this.getIntegrationAPI().completeWithdrawalOperationById(secondWithdrawal, {
- reserve_pub: encodeCrock(getRandomBytes(32)),
- selected_exchange: exchangeInfo.payto_uri,
- })
- )
- await succeedOrThrow(() =>
- this.confirmWithdrawalById(secondWithdrawal)
- )
- await failOrThrow("previously-confirmed", () =>
- this.abortWithdrawalById(secondWithdrawal)
- )
- }
-
- /**
- * confirm withdrawal
- */
- {
- const { username:exchangeUser, token: exchangeToken } = await createRandomTestUser(this, adminToken, {is_taler_exchange: true})
- const { username, token } = await createRandomTestUser(this, adminToken)
-
- const userInfo = await succeedOrThrow(() =>
- this.getAccount({ username, token })
- )
- const exchangeInfo = await succeedOrThrow(() =>
- this.getAccount({ username:exchangeUser, token:exchangeToken })
- )
-
- await failOrThrow("invalid-id", () =>
- this.confirmWithdrawalById("invalid")
- )
- await failOrThrow("not-found", () =>
- this.confirmWithdrawalById("11111111-1111-1111-1111-111111111111")
- )
-
- const { withdrawal_id:firstWithdrawal } = await succeedOrThrow(() =>
- this.createWithdrawal({ username, token }, {
- amount: userInfo.balance.amount
- })
- )
-
- await failOrThrow("no-exchange-or-reserve-selected", () =>
- this.confirmWithdrawalById(firstWithdrawal)
- )
-
- await succeedOrThrow(() =>
- this.getIntegrationAPI().completeWithdrawalOperationById(firstWithdrawal, {
- reserve_pub: encodeCrock(getRandomBytes(32)),
- selected_exchange: exchangeInfo.payto_uri,
- })
- )
-
- await succeedOrThrow(() =>
- this.confirmWithdrawalById(firstWithdrawal)
- )
-
- const { withdrawal_id:secondWithdrawal } = await succeedOrThrow(() =>
- this.createWithdrawal({ username, token }, {
- amount: userInfo.balance.amount
- })
- )
-
- await succeedOrThrow(() =>
- this.abortWithdrawalById(secondWithdrawal)
- )
- await failOrThrow("previously-aborted", () =>
- this.confirmWithdrawalById(secondWithdrawal)
- )
- }
- }
-
- async testTransactions(adminPassword: string) {
- const { access_token: adminToken } = await succeedOrThrow(() =>
- this.getAuthenticationAPI("admin").createAccessToken(adminPassword, {
- scope: "readwrite"
- })
- )
- // get transactions
- {
- const { username, token } = await createRandomTestUser(this, adminToken)
- // await succeedOrThrow(() => this.getTransactions(creds))
- const txs = await succeedOrThrow(() => this.getTransactions({ username, token }, {
- limit: 5,
- order: "asc"
- }))
- // await failOrThrow("not-found",() => this.getTransactions({
- // username:"not-found",
- // token: creds.token,
- // }))
- await failOrThrow("unauthorized", () => this.getTransactions({
- username: username,
- token: "wrongtoken" as AccessToken,
- }))
- }
-
- /**
- * getTxby id
- */
- {
- const { username, token } = await createRandomTestUser(this, adminToken)
- const { username: otherUser, token: otherToken } = await createRandomTestUser(this, adminToken)
-
- const userInfo = await succeedOrThrow(() =>
- this.getAccount({ username, token })
- )
- const otherInfo = await succeedOrThrow(() =>
- this.getAccount({ username: otherUser, token: otherToken })
- )
- const otherAccount = parsePaytoUri(otherInfo.payto_uri)!
- otherAccount.params["message"] = "all"
-
- await succeedOrThrow(() =>
- this.createTransaction({ username, token }, {
- payto_uri: stringifyPaytoUri(otherAccount),
- amount: userInfo.balance.amount
- })
- )
-
- const txs = await succeedOrThrow(() => this.getTransactions({ username, token }, {
- limit: 5,
- order: "asc"
- }))
- const rowId = txs.transactions[0].row_id
-
- await succeedOrThrow(() =>
- this.getTransactionById({ username, token }, rowId)
- )
-
- await failOrThrow("not-found", () =>
- this.getTransactionById({ username, token }, 123123123)
- )
-
- await failOrThrow("unauthorized", () =>
- this.getTransactionById({ username, token: "wrongtoken" as AccessToken }, 123123123)
- )
- }
-
- /**
- * create transactions
- */
- {
- const { username, token } = await createRandomTestUser(this, adminToken)
- const { username: otherUser, token: otherToken } = await createRandomTestUser(this, adminToken)
-
- const userInfo = await succeedOrThrow(() =>
- this.getAccount({ username, token })
- )
- const otherInfo = await succeedOrThrow(() =>
- this.getAccount({ username: otherUser, token: otherToken })
- )
- const otherAccount = parsePaytoUri(otherInfo.payto_uri)!
- otherAccount.params["message"] = "all"
-
- await succeedOrThrow(() =>
- this.createTransaction({ username, token }, {
- payto_uri: stringifyPaytoUri(otherAccount),
- amount: userInfo.balance.amount
- })
- )
- //missing amount
- await failOrThrow("invalid-input", () =>
- this.createTransaction({ username, token }, {
- payto_uri: stringifyPaytoUri(otherAccount),
- // amount: userInfo.balance.amount
- })
- )
- //missing subject
- await failOrThrow("invalid-input", () =>
- this.createTransaction({ username, token }, {
- payto_uri: otherInfo.payto_uri,
- amount: userInfo.balance.amount
- })
- )
- await failOrThrow("unauthorized", () =>
- this.createTransaction({ username, token: "wrongtoken" as AccessToken }, {
- payto_uri: otherInfo.payto_uri,
- amount: userInfo.balance.amount
- })
- )
- }
- }
}
-export async function succeedOrThrow<R, E>(cb: () => Promise<OperationResult<R, E>>): Promise<R> {
- const resp = await cb()
- if (resp.type === "ok") return resp.body
- throw TalerError.fromUncheckedDetail({ ...resp.detail, case: resp.case })
-}
-export async function failOrThrow<E>(s: E, cb: () => Promise<OperationResult<unknown, E>>): Promise<TalerErrorDetail> {
- const resp = await cb()
- if (resp.type === "ok") {
- throw TalerError.fromException(new Error(`request succeed but failure "${s}" was expected`))
- }
- if (resp.case === s) {
- return resp.detail
- }
- throw TalerError.fromException(new Error(`request failed but case "${s}" was expected`))
-}
-export async function createRandomTestUser(api: TalerCoreBankHttpClient, adminToken: AccessToken, options: Partial<TalerCorebankApi.RegisterAccountRequest> = {}) {
- const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase();
- await succeedOrThrow(() =>
- api.createAccount(adminToken, {
- name: username,
- username, password: "123",
- ...options
- })
- )
- const { access_token } = await succeedOrThrow(() =>
- api.getAuthenticationAPI(username).createAccessToken("123", {
- scope: "readwrite"
- })
- )
- return { username, token: access_token }
-} \ No newline at end of file
diff --git a/packages/taler-util/src/http-client/bank-integration.ts b/packages/taler-util/src/http-client/bank-integration.ts
index 887dbed1b..e59f6086a 100644
--- a/packages/taler-util/src/http-client/bank-integration.ts
+++ b/packages/taler-util/src/http-client/bank-integration.ts
@@ -1,12 +1,12 @@
import { HttpRequestLibrary, readSuccessResponseJsonOrThrow } from "../http-common.js";
import { HttpStatusCode } from "../http-status-codes.js";
import { createPlatformHttpLib } from "../http.js";
+import { opSuccess, opUnknownFailure } from "../operation.js";
import {
TalerBankIntegrationApi,
codecForBankWithdrawalOperationPostResponse,
codecForBankWithdrawalOperationStatus
} from "./types.js";
-import { opSuccess, opUnknownFailure } from "./utils.js";
export class TalerBankIntegrationHttpClient {
httpLib: HttpRequestLibrary;
diff --git a/packages/taler-util/src/http-client/test.cli.ts b/packages/taler-util/src/http-client/test.cli.ts
deleted file mode 100644
index 08a0c5fd3..000000000
--- a/packages/taler-util/src/http-client/test.cli.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import { HttpLibImpl, setShowCurlRequest as setPrintHttpRequestAsCurl } from "../http-impl.node.js"
-import { AccessToken, TalerError } from "../index.js"
-import { TalerCoreBankHttpClient, createRandomTestUser, succeedOrThrow } from "./bank-core.js"
-
-
-const baseUrl = process.argv[2]
-const admin = process.argv[3] as AccessToken
-// const usrpwd = process.argv[4]
-
-if (!baseUrl) {
- console.error("missing baseUrl")
- process.exit(1)
-}
-
-console.log("trying against ", baseUrl)
-
-const api = new TalerCoreBankHttpClient(baseUrl, new HttpLibImpl())
-try {
- process.stdout.write("config: ");
- const config = await api.testConfig()
- console.log("ok")
-
- setPrintHttpRequestAsCurl(true)
-
- process.stdout.write("account management: ");
- const withAdmin = !!admin && admin !== "-"
- if (withAdmin) {
- await api.testAccountManagement(admin)
- console.log("ok")
- } else {
- console.log("skipped")
- }
-
- process.stdout.write("transactions: ");
- if (withAdmin) {
- await api.testTransactions(admin)
- console.log("ok")
- } else {
- console.log("skipped")
- }
-
- process.stdout.write("withdrawals: ");
- if (withAdmin) {
- await api.testWithdrawals(admin)
- console.log("ok")
- } else {
- console.log("skipped")
- }
-
- process.stdout.write("monitor: ");
- if (withAdmin && config.have_cashout) {
- await api.testMonitor(admin)
- console.log("ok")
- } else {
- console.log("skipped")
- }
-
- process.stdout.write("cashout: ");
- if (withAdmin && config.have_cashout) {
- await api.testCashouts(admin)
- console.log("ok")
- } else {
- console.log("skipped")
- }
-
-} catch (e: any) {
- console.log("")
- if (e instanceof TalerError) {
- console.error("FAILED", JSON.stringify(e.errorDetail, undefined, 2))
- console.error(e.stack)
- } else if (e instanceof Error) {
- console.error(`FAILED: ${e.message}`)
- console.error(e.stack)
- } else {
- console.error(`FAILED: ${e}`)
- }
-}
diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts
index 8af134280..b9a5032d1 100644
--- a/packages/taler-util/src/http-client/types.ts
+++ b/packages/taler-util/src/http-client/types.ts
@@ -35,27 +35,6 @@ export type PaginationParams = {
order: "asc" | "dec"
}
-export type OperationResult<Body, ErrorEnum> =
- | OperationOk<Body>
- | OperationFail<ErrorEnum>;
-
-export interface OperationOk<T> {
- type: "ok",
- body: T;
-}
-export function isOperationOk<T, E>(c: OperationResult<T, E>): c is OperationOk<T> {
- return c.type === "ok"
-}
-export function isOperationFail<T, E>(c: OperationResult<T, E>): c is OperationFail<E> {
- return c.type === "fail"
-}
-export interface OperationFail<T> {
- type: "fail",
- case: T,
- detail: TalerErrorDetail,
-}
-
-
///
/// HASH
///
diff --git a/packages/taler-util/src/http-client/utils.ts b/packages/taler-util/src/http-client/utils.ts
index 3be5d6e89..32a9c4009 100644
--- a/packages/taler-util/src/http-client/utils.ts
+++ b/packages/taler-util/src/http-client/utils.ts
@@ -1,8 +1,8 @@
import { base64FromArrayBuffer } from "../base64.js";
import { HttpResponse, readSuccessResponseJsonOrThrow, readTalerErrorResponse } from "../http-common.js";
-import { Codec, TalerError, TalerErrorCode } from "../index.js";
+import { Codec, TalerError, TalerErrorCode, TalerErrorDetail } from "../index.js";
import { stringToBytes } from "../taler-crypto.js";
-import { AccessToken, OperationFail, OperationOk, PaginationParams } from "./types.js";
+import { AccessToken, PaginationParams } from "./types.js";
/**
* Helper function to generate the "Authorization" HTTP header.
@@ -38,34 +38,3 @@ export function addPaginationParams(url: URL, pagination?: PaginationParams) {
//always send delta
url.searchParams.set("delta", String(order * limit))
}
-
-
-//////
-// Operation Helper Constructors
-//////
-export async function opSuccess<T>(resp: HttpResponse, codec: Codec<T>): Promise<OperationOk<T>> {
- const body = await readSuccessResponseJsonOrThrow(resp, codec)
- return { type: "ok" as const, body }
-}
-export function opFixedSuccess<T>(body: T): OperationOk<T> {
- return { type: "ok" as const, body }
-}
-export function opEmptySuccess(): OperationOk<void> {
- return { type: "ok" as const, body: void 0 }
-}
-export async function opKnownFailure<T extends string>(s: T, resp: HttpResponse): Promise<OperationFail<T>> {
- const detail = await readTalerErrorResponse(resp)
- return { type: "fail", case: s, detail }
-}
-export function opUnknownFailure(resp: HttpResponse, text: string): never {
- throw TalerError.fromDetail(
- TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
- {
- requestUrl: resp.requestUrl,
- requestMethod: resp.requestMethod,
- httpStatusCode: resp.status,
- errorResponse: text,
- },
- `Unexpected HTTP status ${resp.status} in response`,
- );
-}
diff --git a/packages/taler-util/src/http-impl.node.ts b/packages/taler-util/src/http-impl.node.ts
index 11ae9480c..5aca6e99d 100644
--- a/packages/taler-util/src/http-impl.node.ts
+++ b/packages/taler-util/src/http-impl.node.ts
@@ -57,7 +57,7 @@ const logger = new Logger("http-impl.node.ts");
const textDecoder = new TextDecoder();
let SHOW_CURL_HTTP_REQUEST = false;
-export function setShowCurlRequest(b: boolean) {
+export function setPrintHttpRequestAsCurl(b: boolean) {
SHOW_CURL_HTTP_REQUEST = b
}
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
index 71d4253f0..ea5a805a0 100644
--- a/packages/taler-util/src/index.ts
+++ b/packages/taler-util/src/index.ts
@@ -47,3 +47,4 @@ export * from "./http-client/bank-integration.js";
export * from "./http-client/bank-revenue.js";
export * from "./http-client/bank-wire.js";
export * from "./http-client/types.js";
+export * from "./operation.js";
diff --git a/packages/taler-util/src/operation.ts b/packages/taler-util/src/operation.ts
new file mode 100644
index 000000000..aab5dc022
--- /dev/null
+++ b/packages/taler-util/src/operation.ts
@@ -0,0 +1,74 @@
+import { HttpResponse, readSuccessResponseJsonOrThrow, readTalerErrorResponse } from "./http-common.js";
+import { Codec, TalerError, TalerErrorCode, TalerErrorDetail } from "./index.js";
+
+export type OperationResult<Body, ErrorEnum> =
+ | OperationOk<Body>
+ | OperationFail<ErrorEnum>;
+
+export interface OperationOk<T> {
+ type: "ok",
+ body: T;
+}
+export function isOperationOk<T, E>(c: OperationResult<T, E>): c is OperationOk<T> {
+ return c.type === "ok"
+}
+export function isOperationFail<T, E>(c: OperationResult<T, E>): c is OperationFail<E> {
+ return c.type === "fail"
+}
+export interface OperationFail<T> {
+ type: "fail",
+ case: T,
+ detail: TalerErrorDetail,
+}
+
+export async function opSuccess<T>(resp: HttpResponse, codec: Codec<T>): Promise<OperationOk<T>> {
+ const body = await readSuccessResponseJsonOrThrow(resp, codec)
+ return { type: "ok" as const, body }
+}
+export function opFixedSuccess<T>(body: T): OperationOk<T> {
+ return { type: "ok" as const, body }
+}
+export function opEmptySuccess(): OperationOk<void> {
+ return { type: "ok" as const, body: void 0 }
+}
+export async function opKnownFailure<T extends string>(s: T, resp: HttpResponse): Promise<OperationFail<T>> {
+ const detail = await readTalerErrorResponse(resp)
+ return { type: "fail", case: s, detail }
+}
+export function opUnknownFailure(resp: HttpResponse, text: string): never {
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
+ {
+ requestUrl: resp.requestUrl,
+ requestMethod: resp.requestMethod,
+ httpStatusCode: resp.status,
+ errorResponse: text,
+ },
+ `Unexpected HTTP status ${resp.status} in response`,
+ );
+}
+export async function succeedOrThrow<R, E>(cb: () => Promise<OperationResult<R, E>>): Promise<R> {
+ const resp = await cb()
+ if (resp.type === "ok") return resp.body
+ throw TalerError.fromUncheckedDetail({ ...resp.detail, case: resp.case })
+}
+export async function failOrThrow<E>(s: E, cb: () => Promise<OperationResult<unknown, E>>): Promise<TalerErrorDetail> {
+ const resp = await cb()
+ if (resp.type === "ok") {
+ throw TalerError.fromException(new Error(`request succeed but failure "${s}" was expected`))
+ }
+ if (resp.case === s) {
+ return resp.detail
+ }
+ throw TalerError.fromException(new Error(`request failed but case "${s}" was expected`))
+}
+
+
+
+export type ResultByMethod<TT extends object, p extends keyof TT> = TT[p] extends (...args: any[]) => infer Ret ?
+ Ret extends Promise<infer Result> ?
+ Result :
+ never : //api always use Promises
+ never; //error cases just for functions
+
+export type FailCasesByMethod<TT extends object, p extends keyof TT> = Exclude<ResultByMethod<TT, p>, OperationOk<any>>