commit d199cbb5795a294dbba7fc533afc2d51bab7586d
parent eea9e522e24a4fe606f1dd67d14c81560d42b639
Author: Florian Dold <florian@dold.me>
Date: Fri, 17 Jul 2026 23:51:46 +0200
harness: reproduce error on upper-cased instance creation
Diffstat:
2 files changed, 215 insertions(+), 0 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-merchant-self-provision-casing.ts b/packages/taler-harness/src/integrationtests/test-merchant-self-provision-casing.ts
@@ -0,0 +1,213 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 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 {
+ alternativeOrThrow,
+ Duration,
+ HttpStatusCode,
+ Logger,
+ LoginTokenScope,
+ MerchantAuthMethod,
+ succeedOrThrow,
+ TalerMerchantInstanceHttpClient,
+ TalerMerchantManagementHttpClient,
+ TanChannel,
+} from "@gnu-taler/taler-util";
+import { createSimpleTestkudosEnvironmentV3 } from "../harness/environments.js";
+import { GlobalTestState } from "../harness/harness.js";
+import {
+ configureTestMerchantMfa,
+ makeMfaConfigEmailSms,
+ solveMFA,
+ wait2FaCode,
+} from "../harness/tan-helper.js";
+
+export const logger = new Logger("test-merchant-self-provision-activation.ts");
+
+/**
+ * Regression test for bad case normalization in the
+ * self onboarding.
+ */
+export async function runMerchantSelfProvisionCasingTest(
+ t: GlobalTestState,
+) {
+ // Set up test environment
+
+ const instanceInfo = {
+ id: "MYUPPERCASEINSTANCE",
+ name: "My awesome instance",
+ auth: {
+ method: MerchantAuthMethod.TOKEN,
+ password: "123",
+ },
+ default_pay_delay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ days: 14 }),
+ ),
+ default_wire_transfer_delay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ days: 14 }),
+ ),
+ jurisdiction: {},
+ address: {},
+ email: "some@taler.net",
+ phone_number: "+1111",
+ use_stefan: false,
+ };
+
+ const mfaConfig = makeMfaConfigEmailSms(
+ t,
+ instanceInfo.email,
+ instanceInfo.phone_number,
+ );
+
+ const { merchant, merchantAdminAccessToken } =
+ await createSimpleTestkudosEnvironmentV3(t, undefined, {
+ additionalMerchantConfig(m) {
+ m.modifyConfig(async (cfg) => {
+ cfg.setString("merchant", "ENABLE_SELF_PROVISIONING", "yes");
+ configureTestMerchantMfa(cfg, mfaConfig);
+ });
+ },
+ });
+
+ const merchantClient = new TalerMerchantManagementHttpClient(
+ merchant.makeInstanceBaseUrl(),
+ );
+
+ {
+ const r = succeedOrThrow(
+ await merchantClient.listInstances(merchantAdminAccessToken),
+ );
+ t.assertDeepEqual(r.instances.length, 2);
+ }
+
+ const signupStart = alternativeOrThrow(
+ await merchantClient.createInstanceSelfProvision(instanceInfo),
+ HttpStatusCode.Accepted,
+ );
+
+ // creation requires 2fa
+ t.assertDeepEqual(signupStart.challenges.length, 2);
+ t.assertDeepEqual(signupStart.combi_and, true);
+
+ const firstChallenge = signupStart.challenges[0];
+ const secondChallenge = signupStart.challenges[1];
+
+ // FIXME: check the order
+ // always first emails since is cheaper
+ t.assertTrue(firstChallenge.tan_channel === TanChannel.EMAIL);
+ t.assertTrue(secondChallenge.tan_channel === TanChannel.SMS);
+
+ {
+ // new instance is pending, then is not listed
+ const r = succeedOrThrow(
+ await merchantClient.listInstances(merchantAdminAccessToken),
+ );
+ t.assertDeepEqual(r.instances.length, 2);
+ }
+
+ {
+ succeedOrThrow(
+ await merchantClient.sendChallenge(firstChallenge.challenge_id),
+ );
+ const res = await wait2FaCode(mfaConfig.email.path);
+ console.log(`MFA message: ${res.message}`);
+ const loginMatch = res.message.match(/^Login: (.*)$/m);
+ const login = loginMatch?.[1];
+ console.log(`login account: ${login}`);
+ t.assertDeepEqual(res.address, instanceInfo.email);
+ succeedOrThrow(
+ await merchantClient.confirmChallenge(firstChallenge.challenge_id, {
+ tan: res.code,
+ }),
+ );
+ }
+
+ {
+ succeedOrThrow(
+ await merchantClient.sendChallenge(secondChallenge.challenge_id),
+ );
+ const res = await wait2FaCode(mfaConfig.sms.path);
+ t.assertDeepEqual(res.address, instanceInfo.phone_number);
+ succeedOrThrow(
+ await merchantClient.confirmChallenge(secondChallenge.challenge_id, {
+ tan: res.code,
+ }),
+ );
+ }
+
+ const completeSignup = await merchantClient.createInstanceSelfProvision(
+ instanceInfo,
+ {
+ challengeIds: [firstChallenge.challenge_id, secondChallenge.challenge_id],
+ },
+ );
+
+ t.assertDeepEqual(completeSignup.type, "ok");
+
+ const instanceApi = new TalerMerchantInstanceHttpClient(
+ merchantClient.getSubInstanceAPI(instanceInfo.id),
+ merchantClient.httpLib,
+ );
+
+ {
+ // new instance is completed, now it should be visible
+ const r = succeedOrThrow(
+ await merchantClient.listInstances(merchantAdminAccessToken),
+ );
+ t.assertDeepEqual(r.instances.length, 3);
+ }
+
+ const loginChallenge = alternativeOrThrow(
+ await instanceApi.createAccessToken(
+ instanceInfo.id,
+ instanceInfo.auth.password,
+ {
+ scope: LoginTokenScope.All,
+ },
+ ),
+ HttpStatusCode.Accepted,
+ );
+
+ await solveMFA(t, merchantClient, loginChallenge, mfaConfig);
+
+ const { access_token: token } = succeedOrThrow(
+ await instanceApi.createAccessToken(
+ instanceInfo.id,
+ instanceInfo.auth.password,
+ {
+ scope: LoginTokenScope.All,
+ },
+ {
+ challengeIds: loginChallenge.challenges.map((c) => c.challenge_id),
+ },
+ ),
+ );
+
+ const det = succeedOrThrow(
+ await instanceApi.getCurrentInstanceDetails(token),
+ );
+
+ // check that the instance has the new email
+ t.assertDeepEqual(det.email, instanceInfo.email);
+ t.assertDeepEqual(det.email_validated, true);
+ t.assertDeepEqual(det.phone_number, instanceInfo.phone_number);
+ t.assertDeepEqual(det.phone_validated, true);
+}
+
+runMerchantSelfProvisionCasingTest.suites = ["merchant", "self-provision"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -122,6 +122,7 @@ import { runMerchantRefundFeesTest } from "./test-merchant-refund-fees.js";
import { runMerchantReportsTest } from "./test-merchant-reports.js";
import { runMerchantSelfProvisionActivationTwoBankAccountsTest } from "./test-merchant-self-provision-activation-two-bank-account.js";
import { runMerchantSelfProvisionActivationTest } from "./test-merchant-self-provision-activation.js";
+import { runMerchantSelfProvisionCasingTest } from "./test-merchant-self-provision-casing.js";
import { runMerchantSelfProvisionForgotPasswordTest } from "./test-merchant-self-provision-forgot-password.js";
import { runMerchantSelfProvisionInactiveAccountPermissionsTest } from "./test-merchant-self-provision-inactive-account-permissions.js";
import { runMerchantSpecPublicOrdersTest } from "./test-merchant-spec-public-orders.js";
@@ -442,6 +443,7 @@ const allTests: TestMainFunction[] = [
runTopsNexusBasicTest,
runTopsNexusSwtTest,
runTopsMerchantSwtKycauthTest,
+ runMerchantSelfProvisionCasingTest,
];
export interface TestRunSpec {