From c5f97c8f493b52f9e083548d0ac71592c56a2b79 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 30 Jan 2024 22:25:54 +0100 Subject: harness: work on otp test --- .../taler-harness/src/integrationtests/test-otp.ts | 77 +++++++++++++++++++++- .../src/integrationtests/test-payment-template.ts | 6 +- packages/taler-util/src/MerchantApiClient.ts | 29 +++++++- packages/taler-util/src/merchant-api-types.ts | 38 ++++++----- packages/taler-util/src/operation.ts | 26 ++++++++ 5 files changed, 152 insertions(+), 24 deletions(-) diff --git a/packages/taler-harness/src/integrationtests/test-otp.ts b/packages/taler-harness/src/integrationtests/test-otp.ts index dd6c45e4c..d4dee12ad 100644 --- a/packages/taler-harness/src/integrationtests/test-otp.ts +++ b/packages/taler-harness/src/integrationtests/test-otp.ts @@ -17,14 +17,22 @@ /** * Imports. */ +import { + ConfirmPayResultType, + Duration, + MerchantApiClient, + PreparePayResultType, + encodeCrock, + getRandomBytes, + j2s, + narrowOpSuccessOrThrow, +} from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { GlobalTestState } from "../harness/harness.js"; import { createSimpleTestkudosEnvironmentV2, withdrawViaBankV2, - makeTestPaymentV2, } from "../harness/helpers.js"; -import { MerchantApiClient, j2s } from "@gnu-taler/taler-util"; /** * Run test for basic, bank-integrated withdrawal and payment. @@ -36,6 +44,71 @@ export async function runOtpTest(t: GlobalTestState) { await createSimpleTestkudosEnvironmentV2(t); const merchantClient = new MerchantApiClient(merchant.makeInstanceBaseUrl()); + const createOtpRes = await merchantClient.createOtpDevice({ + otp_algorithm: 1, + otp_device_description: "Hello", + otp_device_id: "mydevice", + otp_key: encodeCrock(getRandomBytes(16)), + }); + narrowOpSuccessOrThrow(createOtpRes); + + const createTemplateRes = await merchantClient.createTemplate({ + template_description: "my template", + template_id: "tpl1", + otp_id: "mydevice", + template_contract: { + summary: "test", + amount: "TESTKUDOS:1", + minimum_age: 0, + pay_duration: Duration.toTalerProtocolDuration( + Duration.fromSpec({ hours: 1 }), + ), + }, + }); + narrowOpSuccessOrThrow(createTemplateRes); + + const getTemplateResp = await merchantClient.getTemplate("tpl1"); + narrowOpSuccessOrThrow(getTemplateResp); + console.log(`template: ${j2s(getTemplateResp.body)}`); + + const wres = await withdrawViaBankV2(t, { + walletClient, + bank, + exchange, + amount: "TESTKUDOS:20", + }); + await wres.withdrawalFinishedCond; + + const preparePayResult = await walletClient.call( + WalletApiOperation.PreparePayForTemplate, + { + talerPayTemplateUri: `taler+http://pay-template/localhost:${merchant.port}/tpl1`, + templateParams: {}, + }, + ); + + console.log(preparePayResult); + + t.assertTrue( + preparePayResult.status === PreparePayResultType.PaymentPossible, + ); + + // Pay for it + + const r2 = await walletClient.call(WalletApiOperation.ConfirmPay, { + transactionId: preparePayResult.transactionId, + }); + + t.assertTrue(r2.type === ConfirmPayResultType.Done); + + const transaction = await walletClient.call( + WalletApiOperation.GetTransactionById, + { + transactionId: preparePayResult.transactionId, + }, + ); + + console.log(j2s(transaction)); } runOtpTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-payment-template.ts b/packages/taler-harness/src/integrationtests/test-payment-template.ts index e77236a9a..c9f1caacd 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-template.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-template.ts @@ -22,6 +22,7 @@ import { Duration, MerchantApiClient, PreparePayResultType, + narrowOpSuccessOrThrow, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { GlobalTestState } from "../harness/harness.js"; @@ -41,7 +42,7 @@ export async function runPaymentTemplateTest(t: GlobalTestState) { const merchantClient = new MerchantApiClient(merchant.makeInstanceBaseUrl()); - await merchantClient.createTemplate({ + const createTemplateRes = await merchantClient.createTemplate({ template_id: "template1", template_description: "my test template", template_contract: { @@ -54,6 +55,7 @@ export async function runPaymentTemplateTest(t: GlobalTestState) { summary: "hello, I'm a summary", }, }); + narrowOpSuccessOrThrow(createTemplateRes); // Withdraw digital cash into the wallet. @@ -84,7 +86,7 @@ export async function runPaymentTemplateTest(t: GlobalTestState) { // Pay for it const r2 = await walletClient.call(WalletApiOperation.ConfirmPay, { - proposalId: preparePayResult.proposalId, + transactionId: preparePayResult.transactionId, }); t.assertTrue(r2.type === ConfirmPayResultType.Done); diff --git a/packages/taler-util/src/MerchantApiClient.ts b/packages/taler-util/src/MerchantApiClient.ts index 561226ba9..db1ffef4e 100644 --- a/packages/taler-util/src/MerchantApiClient.ts +++ b/packages/taler-util/src/MerchantApiClient.ts @@ -362,8 +362,30 @@ export class MerchantApiClient { body: req, headers: this.makeAuthHeader(), }); - if (resp.status !== 204) { - throw Error("failed to create template"); + switch (resp.status) { + case HttpStatusCode.Ok: + case HttpStatusCode.NoContent: + return opEmptySuccess(resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await resp.text()); + } + } + + async getTemplate(templateId: string) { + let url = new URL(`private/templates/${templateId}`, this.baseUrl); + const resp = await this.httpClient.fetch(url.href, { + method: "GET", + headers: this.makeAuthHeader(), + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccess(resp, codecForAny()); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await resp.text()); } } @@ -391,7 +413,7 @@ export class MerchantApiClient { async createOtpDevice( req: OtpDeviceAddDetails, ): Promise | OperationFail> { - let url = new URL("private/templates", this.baseUrl); + let url = new URL("private/otp-devices", this.baseUrl); const resp = await this.httpClient.fetch(url.href, { method: "POST", body: req, @@ -399,6 +421,7 @@ export class MerchantApiClient { }); switch (resp.status) { case HttpStatusCode.Ok: + case HttpStatusCode.NoContent: return opEmptySuccess(resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); diff --git a/packages/taler-util/src/merchant-api-types.ts b/packages/taler-util/src/merchant-api-types.ts index 999246597..724e99b55 100644 --- a/packages/taler-util/src/merchant-api-types.ts +++ b/packages/taler-util/src/merchant-api-types.ts @@ -25,29 +25,29 @@ * Imports. */ import { - MerchantContractTerms, - Codec, - buildCodecForObject, - codecForString, - codecOptional, - codecForConstString, - codecForBoolean, - codecForNumber, - codecForMerchantContractTerms, - codecForAny, - buildCodecForUnion, - AmountString, AbsoluteTime, + AmountString, + Codec, CoinPublicKeyString, EddsaPublicKeyString, - codecForAmountString, + ExchangeWireAccount, + FacadeCredentials, + MerchantContractTerms, TalerProtocolDuration, - codecForTimestamp, TalerProtocolTimestamp, - ExchangeWireAccount, + buildCodecForObject, + buildCodecForUnion, + codecForAmountString, + codecForAny, + codecForBoolean, + codecForConstString, codecForExchangeWireAccount, codecForList, - FacadeCredentials, + codecForMerchantContractTerms, + codecForNumber, + codecForString, + codecForTimestamp, + codecOptional, } from "@gnu-taler/taler-util"; export interface MerchantPostOrderRequest { @@ -345,7 +345,7 @@ export interface MerchantTemplateContractDetails { // The price is imposed by the merchant and cannot be changed by the customer. // This parameter is optional. - amount?: AmountString; + amount?: string; // Minimum age buyer must have (in years). Default is 0. minimum_age: number; @@ -369,6 +369,10 @@ export interface MerchantTemplateAddDetails { // Additional information in a separate template. template_contract: MerchantTemplateContractDetails; + + // OTP device ID. + // This parameter is optional. + otp_id?: string; } export interface MerchantReserveCreateConfirmation { diff --git a/packages/taler-util/src/operation.ts b/packages/taler-util/src/operation.ts index 213bfeecd..a554e1f31 100644 --- a/packages/taler-util/src/operation.ts +++ b/packages/taler-util/src/operation.ts @@ -147,6 +147,32 @@ export function opUnknownFailure(resp: HttpResponse, text: string): never { ); } +/** + * Convenience function to throw an error if the operation is not a success. + */ +export function narrowOpSuccessOrThrow( + opRes: OperationResult, +): asserts opRes is OperationOk { + const httpResponse = opRes.httpResp; + if (opRes.type !== "ok") { + throw TalerError.fromDetail( + TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, + { + requestUrl: httpResponse.requestUrl, + requestMethod: httpResponse.requestMethod, + httpStatusCode: httpResponse.status, + errorResponse: + "detail" in opRes + ? opRes.detail + : "body" in opRes + ? opRes.body + : undefined, + }, + `Unexpected HTTP status ${httpResponse.status} in response`, + ); + } +} + export type ResultByMethod< TT extends object, p extends keyof TT, -- cgit v1.2.3