taler-typescript-core

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

commit 2536a6504a299d2310d894c9969b3f5d58299990
parent 22fba35a812d0685adf57f5e15be67f8d99954ae
Author: Florian Dold <florian@dold.me>
Date:   Wed, 24 Jun 2026 20:04:17 +0200

harness: extend token families test

Diffstat:
Mpackages/taler-harness/src/index.ts | 2++
Mpackages/taler-harness/src/integrationtests/test-merchant-tokenfamilies.ts | 97++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mpackages/taler-harness/src/playground.ts | 211+++++++++++++++++++++++++++++++++++++++++++------------------------------------
3 files changed, 206 insertions(+), 104 deletions(-)

diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts @@ -1798,10 +1798,12 @@ playgroundCli .subcommand("exp", "advanced-tokens-1", {}) .requiredOption("merchantUrl", ["--merchant-url"], clk.STRING) .requiredOption("merchantPw", ["--merchant-pw"], clk.STRING) + .maybeOption("continueFamily", ["--continue"], clk.STRING) .action(async (args) => { await runPlaygroundAdvancedTokens1({ merchantPw: args.exp.merchantPw, merchantUrl: args.exp.merchantUrl, + continueFamily: args.exp.continueFamily, }); }); diff --git a/packages/taler-harness/src/integrationtests/test-merchant-tokenfamilies.ts b/packages/taler-harness/src/integrationtests/test-merchant-tokenfamilies.ts @@ -20,9 +20,14 @@ NU General Public License along with import { AbsoluteTime, Duration, + encodeCrock, + getRandomBytes, + j2s, Order, OrderInputType, OrderOutputType, + OrderVersion, + PostOrderRequest, succeedOrThrow, TalerMerchantInstanceHttpClient, TalerProtocolTimestamp, @@ -33,7 +38,7 @@ import { applyTimeTravelV2, createSimpleTestkudosEnvironmentV3, } from "../harness/environments.js"; -import { GlobalTestState } from "../harness/harness.js"; +import { GlobalTestState, waitMs } from "../harness/harness.js"; export async function runMerchantTokenfamiliesTest(t: GlobalTestState) { let { merchant, walletClient, merchantAdminAccessToken, exchange } = @@ -49,7 +54,7 @@ export async function runMerchantTokenfamiliesTest(t: GlobalTestState) { }, ); - const merchantApi = new TalerMerchantInstanceHttpClient( + const merchantClient = new TalerMerchantInstanceHttpClient( merchant.makeInstanceBaseUrl(), ); @@ -68,13 +73,13 @@ export async function runMerchantTokenfamiliesTest(t: GlobalTestState) { ), duration: Duration.toTalerProtocolDuration(Duration.fromSpec({ days: 90 })), validity_granularity: Duration.toTalerProtocolDuration( - Duration.fromSpec({ days: 1 }), + Duration.fromSpec({ hours: 1 }), ), }; // setup discount token family succeedOrThrow( - await merchantApi.createTokenFamily( + await merchantClient.createTokenFamily( merchantAdminAccessToken, tokenFamilyJson, ), @@ -116,13 +121,13 @@ export async function runMerchantTokenfamiliesTest(t: GlobalTestState) { { const orderResp = succeedOrThrow( - await merchantApi.createOrder(merchantAdminAccessToken, { + await merchantClient.createOrder(merchantAdminAccessToken, { order: orderJsonDiscount, }), ); let orderStatus = succeedOrThrow( - await merchantApi.getOrderDetails( + await merchantClient.getOrderDetails( merchantAdminAccessToken, orderResp.order_id, ), @@ -141,13 +146,13 @@ export async function runMerchantTokenfamiliesTest(t: GlobalTestState) { { const orderResp = succeedOrThrow( - await merchantApi.createOrder(merchantAdminAccessToken, { + await merchantClient.createOrder(merchantAdminAccessToken, { order: orderJsonDiscount, }), ); let orderStatus = succeedOrThrow( - await merchantApi.getOrderDetails( + await merchantClient.getOrderDetails( merchantAdminAccessToken, orderResp.order_id, ), @@ -155,6 +160,82 @@ export async function runMerchantTokenfamiliesTest(t: GlobalTestState) { t.assertTrue(orderStatus.order_status === "unpaid"); } + + // Now try with a more complex order that has multiple + // token families, used multiple times. + + { + let suffix: string; + suffix = encodeCrock(getRandomBytes(16)); + + const slugDiscount1 = `slugdiscount1-${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, + }, + ], + }, + { + amount: "TESTKUDOS:1", + inputs: [ + { + type: OrderInputType.Token, + token_family_slug: slugDiscount1, + }, + ], + outputs: [], + }, + ], + }, + }; + + for (let i = 0; i < 5; i++) { + console.log(`iteration ${i}`); + const createResp = succeedOrThrow( + await merchantClient.createOrder(merchantAdminAccessToken, req), + ); + + const st = succeedOrThrow( + await merchantClient.getOrderDetails( + merchantAdminAccessToken, + createResp.order_id, + ), + ); + + console.log(`status: ${j2s(st)}`); + + await waitMs(200); + } + } } runMerchantTokenfamiliesTest.suites = ["merchant"]; diff --git a/packages/taler-harness/src/playground.ts b/packages/taler-harness/src/playground.ts @@ -23,6 +23,7 @@ import { OrderInputType, OrderOutputType, OrderVersion, + PostOrderRequest, TalerMerchantInstanceHttpClient, TokenFamilyKind, encodeCrock, @@ -121,6 +122,10 @@ export interface PlaygroundAdvancedTokensArgs { merchantUrl: string; merchantPw: string; payDelay?: string; + /** + * If specified, continue with the given token family suffix. + */ + continueFamily?: string; } export async function runPlaygroundAdvancedTokens1( @@ -141,48 +146,55 @@ export async function runPlaygroundAdvancedTokens1( ), ); - const rnd = encodeCrock(getRandomBytes(16)); + let suffix: string; + if (args.continueFamily != null) { + suffix = args.continueFamily; + } else { + suffix = encodeCrock(getRandomBytes(16)); + } - const slugDiscount1 = `slugdiscount1-${rnd}`; - const slugSubscription1 = `slugsubscription1-${rnd}`; + const slugDiscount1 = `slugdiscount1-${suffix}`; + const slugSubscription1 = `slugsubscription1-${suffix}`; - await merchantClient.createTokenFamily(tok.access_token, { - name: "discount1", - slug: slugDiscount1, - description: "My Discount 1 (valid two minutes)", - duration: Duration.toTalerProtocolDuration( - Duration.fromSpec({ minutes: 2 }), - ), - valid_before: AbsoluteTime.toProtocolTimestamp( - AbsoluteTime.addDuration( - AbsoluteTime.now(), - Duration.fromSpec({ hours: 1 }), + if (args.continueFamily == null) { + await merchantClient.createTokenFamily(tok.access_token, { + name: "discount1", + slug: slugDiscount1, + description: "My Discount 1 (valid two minutes)", + duration: Duration.toTalerProtocolDuration( + Duration.fromSpec({ minutes: 10 }), ), - ), - kind: TokenFamilyKind.Discount, - validity_granularity: Duration.toTalerProtocolDuration( - Duration.fromSpec({ minutes: 1 }), - ), - }); + valid_before: AbsoluteTime.toProtocolTimestamp( + AbsoluteTime.addDuration( + AbsoluteTime.now(), + Duration.fromSpec({ hours: 1 }), + ), + ), + kind: TokenFamilyKind.Discount, + validity_granularity: Duration.toTalerProtocolDuration( + Duration.fromSpec({ minutes: 1 }), + ), + }); - await merchantClient.createTokenFamily(tok.access_token, { - name: "subscription1", - slug: slugSubscription1, - description: "My Subscription 1 (valid one minute)", - duration: Duration.toTalerProtocolDuration( - Duration.fromSpec({ minutes: 2 }), - ), - valid_before: AbsoluteTime.toProtocolTimestamp( - AbsoluteTime.addDuration( - AbsoluteTime.now(), - Duration.fromSpec({ hours: 1 }), + await merchantClient.createTokenFamily(tok.access_token, { + name: "subscription1", + slug: slugSubscription1, + description: "My Subscription 1 (valid one minute)", + duration: Duration.toTalerProtocolDuration( + Duration.fromSpec({ minutes: 10 }), ), - ), - kind: TokenFamilyKind.Subscription, - validity_granularity: Duration.toTalerProtocolDuration( - Duration.fromSpec({ minutes: 1 }), - ), - }); + valid_before: AbsoluteTime.toProtocolTimestamp( + AbsoluteTime.addDuration( + AbsoluteTime.now(), + Duration.fromSpec({ hours: 1 }), + ), + ), + kind: TokenFamilyKind.Subscription, + validity_granularity: Duration.toTalerProtocolDuration( + Duration.fromSpec({ minutes: 1 }), + ), + }); + } const payDeadline = AbsoluteTime.toProtocolTimestamp( AbsoluteTime.addDuration( @@ -195,66 +207,68 @@ export async function runPlaygroundAdvancedTokens1( const articleAmount = `${config.currency}:1` as AmountString; const articleDiscountedAmount = `${config.currency}:0.5` as AmountString; + const req: PostOrderRequest = { + order: { + pay_deadline: payDeadline, + version: OrderVersion.V1, + summary: "Test Payment", + choices: [ + { + amount: articleAmount, + description: "Buy an individual article", + }, + { + amount: subscriptionAmount as AmountString, + description: "Article and discount", + outputs: [ + { + type: OrderOutputType.Token, + token_family_slug: slugDiscount1, + }, + ], + }, + { + amount: subscriptionAmount as AmountString, + description: "Article and subscription", + outputs: [ + { + type: OrderOutputType.Token, + token_family_slug: slugSubscription1, + }, + ], + }, + { + amount: articleDiscountedAmount, + inputs: [ + { + type: OrderInputType.Token, + token_family_slug: slugDiscount1, + }, + ], + outputs: [], + }, + { + amount: articleAmount, + description: "Article via subscription", + inputs: [ + { + type: OrderInputType.Token, + token_family_slug: slugSubscription1, + }, + ], + outputs: [ + { + type: OrderOutputType.Token, + token_family_slug: slugSubscription1, + }, + ], + }, + ], + }, + }; + const createResp = succeedOrThrow( - await merchantClient.createOrder(tok.access_token, { - order: { - pay_deadline: payDeadline, - version: OrderVersion.V1, - summary: "Test Payment", - choices: [ - { - amount: articleAmount, - description: "Buy an individual article", - }, - { - amount: subscriptionAmount as AmountString, - description: "Article and discount", - outputs: [ - { - type: OrderOutputType.Token, - token_family_slug: slugDiscount1, - }, - ], - }, - { - amount: subscriptionAmount as AmountString, - description: "Article and subscription", - outputs: [ - { - type: OrderOutputType.Token, - token_family_slug: slugSubscription1, - }, - ], - }, - { - amount: articleDiscountedAmount, - inputs: [ - { - type: OrderInputType.Token, - token_family_slug: slugDiscount1, - }, - ], - outputs: [], - }, - { - amount: articleAmount, - description: "Article via subscription", - inputs: [ - { - type: OrderInputType.Token, - token_family_slug: slugSubscription1, - }, - ], - outputs: [ - { - type: OrderOutputType.Token, - token_family_slug: slugSubscription1, - }, - ], - }, - ], - }, - }), + await merchantClient.createOrder(tok.access_token, req), ); const st = succeedOrThrow( @@ -262,6 +276,11 @@ export async function runPlaygroundAdvancedTokens1( ); if (st.order_status === "unpaid") { + if (args.continueFamily) { + console.log(`continued from ${args.continueFamily}`); + } else { + console.log(`continue with --continue=${suffix}`); + } console.log(st.taler_pay_uri); } }