diff options
author | Sebastian <sebasjm@gmail.com> | 2023-10-26 11:15:45 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2023-10-26 11:15:45 -0300 |
commit | a4c7bc4b284fe7dc4c65ceaad96fc67c40c9a708 (patch) | |
tree | f407ad06733a3a82a7c5655d65a81fc3e5248cc1 /packages | |
parent | e812eae32daddad372c7629867298ca28678a44c (diff) | |
download | wallet-core-a4c7bc4b284fe7dc4c65ceaad96fc67c40c9a708.tar.xz |
moving cli test to harness
Diffstat (limited to 'packages')
-rw-r--r-- | packages/taler-harness/src/http-client/bank-core.ts | 547 | ||||
-rw-r--r-- | packages/taler-harness/src/index.ts | 95 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/authentication.ts | 5 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/bank-core.ts | 580 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/bank-integration.ts | 2 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/test.cli.ts | 77 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/types.ts | 21 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/utils.ts | 35 | ||||
-rw-r--r-- | packages/taler-util/src/http-impl.node.ts | 2 | ||||
-rw-r--r-- | packages/taler-util/src/index.ts | 1 | ||||
-rw-r--r-- | packages/taler-util/src/operation.ts | 74 |
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>> |