commit 2bc5eec8bd0ea1adb44f184adc22891b6b7c9617
parent 80e1b2669fc32fedb13673da5238f7d26badfb77
Author: Florian Dold <dold@taler.net>
Date: Fri, 31 Jul 2026 11:54:20 +0200
harness: check exchange base URL completion against a real exchange
Issue: https://bugs.taler.net/n/10777
Diffstat:
2 files changed, 106 insertions(+), 0 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-exchange-base-url-completion.ts b/packages/taler-harness/src/integrationtests/test-exchange-base-url-completion.ts
@@ -0,0 +1,104 @@
+/*
+ This file is part of GNU Taler
+ (C) 2026 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Imports.
+ */
+import { j2s } from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { createSimpleTestkudosEnvironmentV3 } from "../harness/environments.js";
+import { GlobalTestState } from "../harness/harness.js";
+
+/**
+ * Check that completing an exchange base URL actually talks to the exchange,
+ * instead of just tidying up the string it was given.
+ */
+export async function runExchangeBaseUrlCompletionTest(
+ t: GlobalTestState,
+): Promise<void> {
+ const { walletClient, exchange, bankClient } =
+ await createSimpleTestkudosEnvironmentV3(t);
+
+ // A URL that a real exchange answers on is completed as-is.
+ {
+ const res = await walletClient.call(
+ WalletApiOperation.CompleteExchangeBaseUrl,
+ { url: exchange.baseUrl },
+ );
+ t.assertDeepEqual(res.status, "ok");
+ if (res.status !== "ok") throw Error("unreachable");
+ t.assertDeepEqual(res.completion, exchange.baseUrl);
+ }
+
+ // The same URL without its trailing slash names the same exchange.
+ {
+ const res = await walletClient.call(
+ WalletApiOperation.CompleteExchangeBaseUrl,
+ { url: exchange.baseUrl.replace(/\/$/, "") },
+ );
+ t.assertDeepEqual(res.status, "ok");
+ if (res.status !== "ok") throw Error("unreachable");
+ t.assertDeepEqual(res.completion, exchange.baseUrl);
+ }
+
+ // Something is listening, but it is not an exchange.
+ {
+ const res = await walletClient.call(
+ WalletApiOperation.CompleteExchangeBaseUrl,
+ { url: bankClient.baseUrl },
+ );
+ console.log(`completing the bank URL gave ${j2s(res)}`);
+ t.assertDeepEqual(res.status, "bad-exchange");
+ }
+
+ // Nothing is listening on that port.
+ {
+ const res = await walletClient.call(
+ WalletApiOperation.CompleteExchangeBaseUrl,
+ { url: "http://localhost:9/" },
+ );
+ console.log(`completing an unused port gave ${j2s(res)}`);
+ t.assertDeepEqual(res.status, "bad-network");
+ }
+
+ // Rejected before any request is made.
+ {
+ const res = await walletClient.call(
+ WalletApiOperation.CompleteExchangeBaseUrl,
+ { url: "ftp://exchange.example.com/" },
+ );
+ t.assertDeepEqual(res.status, "bad-syntax");
+ }
+
+ // A host the wallet already knows is offered when completion fails, so that
+ // a typo does not leave the user with nothing.
+ {
+ await walletClient.call(WalletApiOperation.UpdateExchangeEntry, {
+ exchangeBaseUrl: exchange.baseUrl,
+ force: true,
+ });
+ const exchangeHost = new URL(exchange.baseUrl).hostname;
+ const res = await walletClient.call(
+ WalletApiOperation.CompleteExchangeBaseUrl,
+ { url: `http://${exchangeHost}x:9/` },
+ );
+ console.log(`completing a typo gave ${j2s(res)}`);
+ if (res.status === "ok") throw Error("expected completion to fail");
+ t.assertTrue((res.suggestions ?? []).includes(exchange.baseUrl));
+ }
+}
+
+runExchangeBaseUrlCompletionTest.suites = ["wallet", "exchange"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -62,6 +62,7 @@ import { runDonauKeychangeTest } from "./test-donau-keychange.js";
import { runDonauMinusTTest } from "./test-donau-minus-t.js";
import { runDonauMultiTest } from "./test-donau-multi.js";
import { runDonauTest } from "./test-donau.js";
+import { runExchangeBaseUrlCompletionTest } from "./test-exchange-base-url-completion.js";
import { runExchangeDepositTest } from "./test-exchange-deposit.js";
import { runExchangeEphemeralTest } from "./test-exchange-ephemeral.js";
import { runExchangeKycAuthTest } from "./test-exchange-kyc-auth.js";
@@ -291,6 +292,7 @@ const allTests: TestMainFunction[] = [
runCoinselLegacy2024Test,
runKycChallengerTest,
runExchangePurseTest,
+ runExchangeBaseUrlCompletionTest,
runExchangeDepositTest,
runExchangeEphemeralTest,
runMerchantExchangeConfusionTest,