taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 16251fffb2c6fc328aa56e26a04cddcf3c98c885
parent d96795022adbaaa91e18bffa3995a0359a72d24f
Author: Florian Dold <florian@dold.me>
Date:   Tue, 25 Nov 2025 20:16:44 +0100

harness: fix merchant KYC tests

Be tolerant of intermittent exchange unreachable states

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-kyc-merchant-deposit-form.ts | 37++++++++++++++++++++++++++-----------
Mpackages/taler-harness/src/integrationtests/test-kyc-merchant-deposit-rewrite.ts | 99+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mpackages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts | 27+++++++++++++++++++--------
3 files changed, 94 insertions(+), 69 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit-form.ts b/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit-form.ts @@ -96,16 +96,22 @@ function adjustExchangeConfig(config: Configuration) { export async function runKycMerchantDepositFormTest(t: GlobalTestState) { // Set up test environment - const { merchant, bankClient, exchange, bank, wireGatewayApi, merchantAdminAccessToken } = - await createKycTestkudosEnvironment(t, { - adjustExchangeConfig, - }); + const { + merchant, + bankClient, + exchange, + bank, + wireGatewayApi, + merchantAdminAccessToken, + } = await createKycTestkudosEnvironment(t, { + adjustExchangeConfig, + }); let accountPub: string; - const headers = { Authorization: `Bearer ${merchantAdminAccessToken}` } + const headers = { Authorization: `Bearer ${merchantAdminAccessToken}` }; { const instanceUrl = new URL("private", merchant.makeInstanceBaseUrl()); - const resp = await harnessHttpLib.fetch(instanceUrl.href, {headers}); + const resp = await harnessHttpLib.fetch(instanceUrl.href, { headers }); const parsedResp = await readSuccessResponseJsonOrThrow( resp, codecForQueryInstancesResponse(), @@ -121,13 +127,20 @@ export async function runKycMerchantDepositFormTest(t: GlobalTestState) { const kycStatusUrl = new URL("private/kyc", merchant.makeInstanceBaseUrl()) .href; logger.info(`requesting GET ${kycStatusUrl}`); - const resp = await harnessHttpLib.fetch(kycStatusUrl, {headers}); + const resp = await harnessHttpLib.fetch(kycStatusUrl, { headers }); if (resp.status === 200) { - kycRespOne = await readSuccessResponseJsonOrThrow( + const pr = await readSuccessResponseJsonOrThrow( resp, codecForAccountKycRedirects(), ); - break; + if ( + pr.kyc_data[0].status === MerchantAccountKycStatus.EXCHANGE_UNREACHABLE + ) { + logger.info(`exchange still unreachable according to merchant`); + } else { + kycRespOne = pr; + break; + } } // Wait 500ms await delayMs(500); @@ -162,7 +175,7 @@ export async function runKycMerchantDepositFormTest(t: GlobalTestState) { body: { order, }, - headers + headers, }); logger.info(`order creation status: ${resp.status}`); @@ -196,7 +209,9 @@ export async function runKycMerchantDepositFormTest(t: GlobalTestState) { merchant.makeInstanceBaseUrl(), ); kycStatusLongpollUrl.searchParams.set("lpt", "1"); - const resp = await harnessHttpLib.fetch(kycStatusLongpollUrl.href, {headers}); + const resp = await harnessHttpLib.fetch(kycStatusLongpollUrl.href, { + headers, + }); t.assertDeepEqual(resp.status, 200); const parsedResp = await readSuccessResponseJsonOrThrow( resp, diff --git a/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit-rewrite.ts b/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit-rewrite.ts @@ -71,27 +71,28 @@ function adjustMerchantConfig(config: Configuration) { config.setString("merchant-kyccheck", "aml_low_freq", "5s"); } -// FIXME: This is not very readable, replace it with "retryUntilTrue". -async function retryUntil<T, X extends T>( - fx: () => Promise<T>, - condition: (x: T, i: number) => boolean, +/** + * Retry until a maximum of retries / timeout has passed, + * or we get a non-null result. + */ +async function retryUntilNonNull<T>( + fx: () => Promise<T | undefined>, maxRetry: number = 15, waitMs: number = 1000, -): Promise<X> { +): Promise<T> { let i = 0; - let x = await fx(); - while (!condition(x, i)) { - if (i > maxRetry) { - throw Error("Too many retries"); + while (true) { + const res = await fx(); + if (res != null) { + return res; } - logger.info( - `retrying since condition was false: ${j2s({ value: x, time: i })}`, - ); + logger.info(`retrying since result was null-ish: ${j2s({ time: i })}`); await delayMs(waitMs); - x = await fx(); i++; + if (i > maxRetry) { + throw Error("Too many retries"); + } } - return x as X; } /** @@ -119,16 +120,18 @@ export async function runKycMerchantDepositRewriteTest(t: GlobalTestState) { let exchangeWireTarget: string | undefined; let merchantBankAccount: PaytoString | undefined; { - const kycStatus = await retryUntil( - async () => { - return succeedOrThrow<MerchantAccountKycRedirectsResponse | void>( - await merchantApi.getCurrentInstanceKycStatus( - merchantAdminAccessToken, - ), - ); - }, - (x) => !!x, - ); + const kycStatus = await retryUntilNonNull(async () => { + const res = succeedOrThrow<MerchantAccountKycRedirectsResponse | void>( + await merchantApi.getCurrentInstanceKycStatus(merchantAdminAccessToken), + ); + if ( + res && + res.kyc_data[0].status !== MerchantAccountKycStatus.EXCHANGE_UNREACHABLE + ) { + return res; + } + return undefined; + }); logger.info(`mechant kyc status: ${j2s(kycStatus)}`); t.assertTrue(!!kycStatus); @@ -194,17 +197,16 @@ export async function runKycMerchantDepositRewriteTest(t: GlobalTestState) { let merchantAmlAccount; { - const kycStatus = await retryUntil( - async () => { - await merchant.runKyccheckOnce(); - return succeedOrThrow<MerchantAccountKycRedirectsResponse | void>( - await merchantApi.getCurrentInstanceKycStatus( - merchantAdminAccessToken, - ), - ); - }, - (x) => !!x && !x.kyc_data[0].payto_kycauths, - ); + const kycStatus = await retryUntilNonNull(async () => { + await merchant.runKyccheckOnce(); + const x = succeedOrThrow<MerchantAccountKycRedirectsResponse | void>( + await merchantApi.getCurrentInstanceKycStatus(merchantAdminAccessToken), + ); + if (!!x && !x.kyc_data[0].payto_kycauths) { + return x; + } + return undefined; + }); logger.info(`kyc resp 2: ${j2s(kycStatus)}`); t.assertTrue(!!kycStatus); @@ -243,21 +245,18 @@ export async function runKycMerchantDepositRewriteTest(t: GlobalTestState) { }); { - const kycStatus = await retryUntil( - async () => { - // Required, since the merchant only checks infrequently. - await merchant.runKyccheckOnce(); - // Now we can check the status - return succeedOrThrow<MerchantAccountKycRedirectsResponse | void>( - await merchantApi.getCurrentInstanceKycStatus( - merchantAdminAccessToken, - ), - ); - }, - (x, i) => { - return (x?.kyc_data[0].limits?.length ?? 0) === 0; - }, - ); + const kycStatus = await retryUntilNonNull(async () => { + // Required, since the merchant only checks infrequently. + await merchant.runKyccheckOnce(); + // Now we can check the status + const x = succeedOrThrow<MerchantAccountKycRedirectsResponse | void>( + await merchantApi.getCurrentInstanceKycStatus(merchantAdminAccessToken), + ); + if ((x?.kyc_data[0].limits?.length ?? 0) === 0) { + return x; + } + return undefined; + }); t.assertTrue(!!kycStatus); logger.info(`kyc resp 3: ${j2s(kycStatus)}`); } diff --git a/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts b/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts @@ -95,8 +95,8 @@ export async function runKycMerchantDepositTest(t: GlobalTestState) { let accountPub: string; const headers = { - Authorization: `Bearer ${merchantAdminAccessToken}` - } + Authorization: `Bearer ${merchantAdminAccessToken}`, + }; { const instanceUrl = new URL("private", merchant.makeInstanceBaseUrl()); const resp = await harnessHttpLib.fetch(instanceUrl.href, { headers }); @@ -117,11 +117,18 @@ export async function runKycMerchantDepositTest(t: GlobalTestState) { logger.info(`requesting GET ${kycStatusUrl}`); const resp = await harnessHttpLib.fetch(kycStatusUrl, { headers }); if (resp.status === 200) { - kycRespOne = await readSuccessResponseJsonOrThrow( + const pr = await readSuccessResponseJsonOrThrow( resp, codecForAccountKycRedirects(), ); - break; + if ( + pr.kyc_data[0].status === MerchantAccountKycStatus.EXCHANGE_UNREACHABLE + ) { + logger.info(`exchange still unreachable according to merchant`); + } else { + kycRespOne = pr; + break; + } } // Wait 500ms await delayMs(500); @@ -156,7 +163,7 @@ export async function runKycMerchantDepositTest(t: GlobalTestState) { body: { order, }, - headers + headers, }); logger.info(`order creation status: ${resp.status}`); @@ -190,7 +197,9 @@ export async function runKycMerchantDepositTest(t: GlobalTestState) { merchant.makeInstanceBaseUrl(), ); kycStatusLongpollUrl.searchParams.set("lpt", "1"); - const resp = await harnessHttpLib.fetch(kycStatusLongpollUrl.href, { headers }); + const resp = await harnessHttpLib.fetch(kycStatusLongpollUrl.href, { + headers, + }); t.assertDeepEqual(resp.status, 200); const parsedResp = await readSuccessResponseJsonOrThrow( resp, @@ -230,7 +239,7 @@ export async function runKycMerchantDepositTest(t: GlobalTestState) { body: { order, }, - headers + headers, }); logger.info(`POST /private/orders status: ${postOrderResp.status}`); @@ -244,7 +253,9 @@ export async function runKycMerchantDepositTest(t: GlobalTestState) { merchant.makeInstanceBaseUrl(), ); kycStatusLongpollUrl.searchParams.set("lpt", "3"); - const resp = await harnessHttpLib.fetch(kycStatusLongpollUrl.href, { headers }); + const resp = await harnessHttpLib.fetch(kycStatusLongpollUrl.href, { + headers, + }); t.assertDeepEqual(resp.status, 200); const parsedResp = await readSuccessResponseJsonOrThrow( resp,