commit 95c78fb581a415b3a8660065ac75f17aef22902b
parent ba14f5cc8f5a4b9c3768c9471da6a688f6e06c59
Author: Florian Dold <florian@dold.me>
Date: Fri, 26 Jun 2026 22:59:58 +0200
harness: expand merchant-tokenfamilies test to repro taler-merchant-httpd crash
Diffstat:
1 file changed, 108 insertions(+), 1 deletion(-)
diff --git a/packages/taler-harness/src/integrationtests/test-merchant-tokenfamilies.ts b/packages/taler-harness/src/integrationtests/test-merchant-tokenfamilies.ts
@@ -32,16 +32,20 @@ import {
TalerMerchantInstanceHttpClient,
TalerProtocolTimestamp,
TokenFamilyKind,
+ TransactionMajorState,
+ TransactionMinorState,
} from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { defaultCoinConfig } from "../harness/denomStructures.js";
import {
applyTimeTravelV2,
createSimpleTestkudosEnvironmentV3,
+ withdrawViaBankV4,
} from "../harness/environments.js";
import { GlobalTestState, waitMs } from "../harness/harness.js";
export async function runMerchantTokenfamiliesTest(t: GlobalTestState) {
- let { merchant, walletClient, merchantAdminAccessToken, exchange } =
+ let { merchant, walletClient, bank, merchantAdminAccessToken, exchange } =
await createSimpleTestkudosEnvironmentV3(
t,
defaultCoinConfig.map((x) => x("TESTKUDOS")),
@@ -54,6 +58,15 @@ export async function runMerchantTokenfamiliesTest(t: GlobalTestState) {
},
);
+ // withdraw some test money
+ const wres = await withdrawViaBankV4(t, {
+ walletClient,
+ exchange,
+ amount: "TESTKUDOS:40",
+ bank,
+ });
+ await wres.withdrawalFinishedCond;
+
const merchantClient = new TalerMerchantInstanceHttpClient(
merchant.makeInstanceBaseUrl(),
);
@@ -241,6 +254,100 @@ export async function runMerchantTokenfamiliesTest(t: GlobalTestState) {
await waitMs(200);
}
}
+
+ // Now let's to multiple tokens
+
+ {
+ let suffix: string;
+ suffix = encodeCrock(getRandomBytes(16));
+
+ const slugDiscount1 = `slugdiscount2-${suffix}`;
+ await merchantClient.createTokenFamily(merchantAdminAccessToken, {
+ name: "discount1",
+ slug: slugDiscount1,
+ description: "My Discount 1 (valid two minutes)",
+ duration: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 2 }),
+ ),
+ valid_after: TalerProtocolTimestamp.now(),
+ valid_before: AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ Duration.fromSpec({ years: 1 }),
+ ),
+ ),
+ kind: TokenFamilyKind.Discount,
+ validity_granularity: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ });
+
+ const req: PostOrderRequest = {
+ order: {
+ version: OrderVersion.V1,
+ summary: "Test Payment",
+ choices: [
+ {
+ amount: "TESTKUDOS:1",
+ description: "Article and discount",
+ outputs: [
+ {
+ type: OrderOutputType.Token,
+ token_family_slug: slugDiscount1,
+ count: 3,
+ },
+ ],
+ },
+ {
+ amount: "TESTKUDOS:1",
+ inputs: [
+ {
+ type: OrderInputType.Token,
+ token_family_slug: slugDiscount1,
+ },
+ ],
+ outputs: [],
+ },
+ ],
+ },
+ };
+
+ const createResp = succeedOrThrow(
+ await merchantClient.createOrder(merchantAdminAccessToken, req),
+ );
+
+ const st = succeedOrThrow(
+ await merchantClient.getOrderDetails(
+ merchantAdminAccessToken,
+ createResp.order_id,
+ ),
+ );
+ t.assertTrue(st.order_status === "unpaid");
+ const wr = await walletClient.call(WalletApiOperation.PreparePayForUriV2, {
+ talerPayUri: st.taler_pay_uri,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: wr.transactionId,
+ txState: {
+ major: TransactionMajorState.Dialog,
+ minor: TransactionMinorState.Proposed,
+ },
+ });
+
+ await walletClient.call(WalletApiOperation.ConfirmPay, {
+ transactionId: wr.transactionId,
+ choiceIndex: 0,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: wr.transactionId,
+ txState: {
+ major: TransactionMajorState.Done,
+ minor: TransactionMinorState.Proposed,
+ },
+ });
+ }
}
runMerchantTokenfamiliesTest.suites = ["merchant"];