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:
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,