From ec713f04b83126139087d908cb4b9d012da6c982 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 16 Aug 2021 13:33:21 +0200 Subject: better coverage for merchant spec test --- .../test-merchant-spec-public-orders.ts | 393 ++++++++++++++++++--- 1 file changed, 337 insertions(+), 56 deletions(-) (limited to 'packages') diff --git a/packages/taler-wallet-cli/src/integrationtests/test-merchant-spec-public-orders.ts b/packages/taler-wallet-cli/src/integrationtests/test-merchant-spec-public-orders.ts index 523f760dd..ab7bf1acd 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-merchant-spec-public-orders.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-merchant-spec-public-orders.ts @@ -28,7 +28,14 @@ import { NodeHttpLib, WalletApiOperation, } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState, MerchantPrivateApi, WalletCli } from "./harness"; +import { + BankService, + ExchangeService, + GlobalTestState, + MerchantPrivateApi, + MerchantService, + WalletCli, +} from "./harness"; import { createSimpleTestkudosEnvironment, withdrawViaBank, @@ -36,65 +43,277 @@ import { const httpLib = new NodeHttpLib(); -/** - * Checks for the /orders/{id} endpoint of the merchant. - * - * The tests here should exercise all code paths in the executable - * specification of the endpoint. - */ -export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { - const { - wallet, - bank, - exchange, - merchant, - } = await createSimpleTestkudosEnvironment(t); - const wallet2 = new WalletCli(t); - - // Withdraw digital cash into the wallet. +interface Context { + merchant: MerchantService; + merchantBaseUrl: string; + bank: BankService; + exchange: ExchangeService; +} +async function testWithClaimToken( + t: GlobalTestState, + c: Context, +): Promise { + const wallet = new WalletCli(t, "withclaimtoken"); + const { bank, exchange } = c; + const { merchant, merchantBaseUrl } = c; await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); - await withdrawViaBank(t, { - wallet: wallet2, - bank, - exchange, - amount: "TESTKUDOS:20", + const sessionId = "mysession"; + const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", { + order: { + summary: "Buy me!", + amount: "TESTKUDOS:5", + fulfillment_url: "https://example.com/article42", + public_reorder_url: "https://example.com/article42-share", + }, }); - // Base URL for the default instance. - const merchantBaseUrl = merchant.makeInstanceBaseUrl(); + + const claimToken = orderResp.token; + const orderId = orderResp.order_id; + t.assertTrue(!!claimToken); + let talerPayUri: string; { - const httpResp = await httpLib.get(new URL("config", merchantBaseUrl).href); + const httpResp = await httpLib.get( + new URL(`orders/${orderId}`, merchantBaseUrl).href, + ); const r = await httpResp.json(); + t.assertDeepEqual(httpResp.status, 202); console.log(r); - t.assertDeepEqual(r.currency, "TESTKUDOS"); } { - const httpResp = await httpLib.get( - new URL("orders/foo", merchantBaseUrl).href, - ); + const url = new URL(`orders/${orderId}`, merchantBaseUrl); + url.searchParams.set("token", claimToken); + const httpResp = await httpLib.get(url.href); const r = await httpResp.json(); + t.assertDeepEqual(httpResp.status, 402); console.log(r); - t.assertDeepEqual(httpResp.status, 404); - // FIXME: also check Taler error code + talerPayUri = r.taler_pay_uri; + t.assertTrue(!!talerPayUri); } { - const httpResp = await httpLib.get( - new URL("orders/foo", merchantBaseUrl).href, - { - headers: { - Accept: "text/html", - }, + const url = new URL(`orders/${orderId}`, merchantBaseUrl); + url.searchParams.set("token", claimToken); + const httpResp = await httpLib.get(url.href, { + headers: { + Accept: "text/html", }, - ); + }); const r = await httpResp.text(); + t.assertDeepEqual(httpResp.status, 402); console.log(r); - t.assertDeepEqual(httpResp.status, 404); - // FIXME: also check Taler error code } + const preparePayResp = await wallet.client.call( + WalletApiOperation.PreparePayForUri, + { + talerPayUri, + }, + ); + + t.assertTrue(preparePayResp.status === PreparePayResultType.PaymentPossible); + const contractTermsHash = preparePayResp.contractTermsHash; + const proposalId = preparePayResp.proposalId; + + // claimed, unpaid, access with wrong h_contract + { + const url = new URL(`orders/${orderId}`, merchantBaseUrl); + const hcWrong = encodeCrock(getRandomBytes(64)); + url.searchParams.set("h_contract", hcWrong); + const httpResp = await httpLib.get(url.href); + const r = await httpResp.json(); + console.log(r); + t.assertDeepEqual(httpResp.status, 403); + } + + // claimed, unpaid, access with wrong claim token + { + const url = new URL(`orders/${orderId}`, merchantBaseUrl); + const ctWrong = encodeCrock(getRandomBytes(16)); + url.searchParams.set("token", ctWrong); + const httpResp = await httpLib.get(url.href); + const r = await httpResp.json(); + console.log(r); + t.assertDeepEqual(httpResp.status, 403); + } + + // claimed, unpaid, access with correct claim token + { + const url = new URL(`orders/${orderId}`, merchantBaseUrl); + url.searchParams.set("token", claimToken); + const httpResp = await httpLib.get(url.href); + const r = await httpResp.json(); + console.log(r); + t.assertDeepEqual(httpResp.status, 402); + } + + // claimed, unpaid, access with correct contract terms hash + { + const url = new URL(`orders/${orderId}`, merchantBaseUrl); + url.searchParams.set("h_contract", contractTermsHash); + const httpResp = await httpLib.get(url.href); + const r = await httpResp.json(); + console.log(r); + t.assertDeepEqual(httpResp.status, 402); + } + + // claimed, unpaid, access without credentials + { + const url = new URL(`orders/${orderId}`, merchantBaseUrl); + const httpResp = await httpLib.get(url.href); + const r = await httpResp.json(); + console.log(r); + t.assertDeepEqual(httpResp.status, 202); + } + + const confirmPayRes = await wallet.client.call( + WalletApiOperation.ConfirmPay, + { + proposalId: proposalId, + }, + ); + + t.assertTrue(confirmPayRes.type === ConfirmPayResultType.Done); + + // paid, access without credentials + { + const url = new URL(`orders/${orderId}`, merchantBaseUrl); + const httpResp = await httpLib.get(url.href); + const r = await httpResp.json(); + console.log(r); + t.assertDeepEqual(httpResp.status, 202); + } + + // paid, access with wrong h_contract + { + const url = new URL(`orders/${orderId}`, merchantBaseUrl); + const hcWrong = encodeCrock(getRandomBytes(64)); + url.searchParams.set("h_contract", hcWrong); + const httpResp = await httpLib.get(url.href); + const r = await httpResp.json(); + console.log(r); + t.assertDeepEqual(httpResp.status, 403); + } + + // paid, access with wrong claim token + { + const url = new URL(`orders/${orderId}`, merchantBaseUrl); + const ctWrong = encodeCrock(getRandomBytes(16)); + url.searchParams.set("token", ctWrong); + const httpResp = await httpLib.get(url.href); + const r = await httpResp.json(); + console.log(r); + t.assertDeepEqual(httpResp.status, 403); + } + + // paid, access with correct h_contract + { + const url = new URL(`orders/${orderId}`, merchantBaseUrl); + url.searchParams.set("h_contract", contractTermsHash); + const httpResp = await httpLib.get(url.href); + const r = await httpResp.json(); + console.log(r); + t.assertDeepEqual(httpResp.status, 200); + } + + // paid, access with correct claim token, JSON + { + const url = new URL(`orders/${orderId}`, merchantBaseUrl); + url.searchParams.set("token", claimToken); + const httpResp = await httpLib.get(url.href); + const r = await httpResp.json(); + console.log(r); + t.assertDeepEqual(httpResp.status, 200); + const respFulfillmentUrl = r.fulfillment_url; + t.assertDeepEqual(respFulfillmentUrl, "https://example.com/article42"); + } + + // paid, access with correct claim token, HTML + { + const url = new URL(`orders/${orderId}`, merchantBaseUrl); + url.searchParams.set("token", claimToken); + const httpResp = await httpLib.get(url.href, { + headers: { Accept: "text/html" }, + }); + t.assertDeepEqual(httpResp.status, 200); + } + + const confirmPayRes2 = await wallet.client.call( + WalletApiOperation.ConfirmPay, + { + proposalId: proposalId, + sessionId: sessionId, + }, + ); + + t.assertTrue(confirmPayRes2.type === ConfirmPayResultType.Done); + + // Create another order with identical fulfillment URL to test the "already paid" flow + const alreadyPaidOrderResp = await MerchantPrivateApi.createOrder( + merchant, + "default", + { + order: { + summary: "Buy me!", + amount: "TESTKUDOS:5", + fulfillment_url: "https://example.com/article42", + public_reorder_url: "https://example.com/article42-share", + }, + }, + ); + + const apOrderId = alreadyPaidOrderResp.order_id; + const apToken = alreadyPaidOrderResp.token; + t.assertTrue(!!apToken); + + { + const url = new URL(`orders/${apOrderId}`, merchantBaseUrl); + url.searchParams.set("token", apToken); + const httpResp = await httpLib.get(url.href); + const r = await httpResp.json(); + console.log(r); + t.assertDeepEqual(httpResp.status, 402); + } + + // Check for already paid session ID, JSON + { + const url = new URL(`orders/${apOrderId}`, merchantBaseUrl); + url.searchParams.set("token", apToken); + url.searchParams.set("session_id", sessionId); + const httpResp = await httpLib.get(url.href); + const r = await httpResp.json(); + console.log(r); + t.assertDeepEqual(httpResp.status, 402); + const alreadyPaidOrderId = r.already_paid_order_id; + t.assertDeepEqual(alreadyPaidOrderId, orderId); + } + + // Check for already paid session ID, HTML + { + const url = new URL(`orders/${apOrderId}`, merchantBaseUrl); + url.searchParams.set("token", apToken); + url.searchParams.set("session_id", sessionId); + const httpResp = await httpLib.get(url.href, { + headers: { Accept: "text/html" }, + }); + t.assertDeepEqual(httpResp.status, 302); + const location = httpResp.headers.get("Location"); + console.log("location header:", location); + t.assertDeepEqual(location, "https://example.com/article42"); + } +} + +async function testWithoutClaimToken( + t: GlobalTestState, + c: Context, +): Promise { + const wallet = new WalletCli(t, "withoutct"); + const sessionId = "mysession2"; + const { bank, exchange } = c; + const { merchant, merchantBaseUrl } = c; + await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", { order: { summary: "Buy me!", @@ -102,11 +321,10 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { fulfillment_url: "https://example.com/article42", public_reorder_url: "https://example.com/article42-share", }, + create_token: false, }); - const claimToken = orderResp.token; const orderId = orderResp.order_id; - t.assertTrue(!!claimToken); let talerPayUri: string; { @@ -114,13 +332,12 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { new URL(`orders/${orderId}`, merchantBaseUrl).href, ); const r = await httpResp.json(); - t.assertDeepEqual(httpResp.status, 202); + t.assertDeepEqual(httpResp.status, 402); console.log(r); } { const url = new URL(`orders/${orderId}`, merchantBaseUrl); - url.searchParams.set("token", claimToken); const httpResp = await httpLib.get(url.href); const r = await httpResp.json(); t.assertDeepEqual(httpResp.status, 402); @@ -131,7 +348,6 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { { const url = new URL(`orders/${orderId}`, merchantBaseUrl); - url.searchParams.set("token", claimToken); const httpResp = await httpLib.get(url.href, { headers: { Accept: "text/html", @@ -149,6 +365,8 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { }, ); + console.log(preparePayResp); + t.assertTrue(preparePayResp.status === PreparePayResultType.PaymentPossible); const contractTermsHash = preparePayResp.contractTermsHash; const proposalId = preparePayResp.proposalId; @@ -175,10 +393,9 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { t.assertDeepEqual(httpResp.status, 403); } - // claimed, unpaid, access with correct claim token + // claimed, unpaid, no claim token { const url = new URL(`orders/${orderId}`, merchantBaseUrl); - url.searchParams.set("token", claimToken); const httpResp = await httpLib.get(url.href); const r = await httpResp.json(); console.log(r); @@ -201,7 +418,10 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { const httpResp = await httpLib.get(url.href); const r = await httpResp.json(); console.log(r); - t.assertDeepEqual(httpResp.status, 202); + // No credentials, but the order doesn't require a claim token. + // This effectively means that the order ID is already considered + // enough authentication, at least to check for the basic order status + t.assertDeepEqual(httpResp.status, 402); } const confirmPayRes = await wallet.client.call( @@ -219,7 +439,7 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { const httpResp = await httpLib.get(url.href); const r = await httpResp.json(); console.log(r); - t.assertDeepEqual(httpResp.status, 202); + t.assertDeepEqual(httpResp.status, 200); } // paid, access with wrong h_contract @@ -254,10 +474,9 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { t.assertDeepEqual(httpResp.status, 200); } - // paid, access with correct claim token, JSON + // paid, JSON { const url = new URL(`orders/${orderId}`, merchantBaseUrl); - url.searchParams.set("token", claimToken); const httpResp = await httpLib.get(url.href); const r = await httpResp.json(); console.log(r); @@ -266,10 +485,9 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { t.assertDeepEqual(respFulfillmentUrl, "https://example.com/article42"); } - // paid, access with correct claim token, HTML + // paid, HTML { const url = new URL(`orders/${orderId}`, merchantBaseUrl); - url.searchParams.set("token", claimToken); const httpResp = await httpLib.get(url.href, { headers: { Accept: "text/html" }, }); @@ -280,7 +498,7 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { WalletApiOperation.ConfirmPay, { proposalId: proposalId, - sessionId: "mysession", + sessionId: sessionId, }, ); @@ -317,7 +535,7 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { { const url = new URL(`orders/${apOrderId}`, merchantBaseUrl); url.searchParams.set("token", apToken); - url.searchParams.set("session_id", "mysession"); + url.searchParams.set("session_id", sessionId); const httpResp = await httpLib.get(url.href); const r = await httpResp.json(); console.log(r); @@ -330,7 +548,7 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { { const url = new URL(`orders/${apOrderId}`, merchantBaseUrl); url.searchParams.set("token", apToken); - url.searchParams.set("session_id", "mysession"); + url.searchParams.set("session_id", sessionId); const httpResp = await httpLib.get(url.href, { headers: { Accept: "text/html" }, }); @@ -341,4 +559,67 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { } } +/** + * Checks for the /orders/{id} endpoint of the merchant. + * + * The tests here should exercise all code paths in the executable + * specification of the endpoint. + */ +export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { + const { + bank, + exchange, + merchant, + } = await createSimpleTestkudosEnvironment(t); + + // Base URL for the default instance. + const merchantBaseUrl = merchant.makeInstanceBaseUrl(); + + { + const httpResp = await httpLib.get(new URL("config", merchantBaseUrl).href); + const r = await httpResp.json(); + console.log(r); + t.assertDeepEqual(r.currency, "TESTKUDOS"); + } + + { + const httpResp = await httpLib.get( + new URL("orders/foo", merchantBaseUrl).href, + ); + const r = await httpResp.json(); + console.log(r); + t.assertDeepEqual(httpResp.status, 404); + // FIXME: also check Taler error code + } + + { + const httpResp = await httpLib.get( + new URL("orders/foo", merchantBaseUrl).href, + { + headers: { + Accept: "text/html", + }, + }, + ); + const r = await httpResp.text(); + console.log(r); + t.assertDeepEqual(httpResp.status, 404); + // FIXME: also check Taler error code + } + + await testWithClaimToken(t, { + merchant, + merchantBaseUrl, + exchange, + bank, + }); + + await testWithoutClaimToken(t, { + merchant, + merchantBaseUrl, + exchange, + bank, + }); +} + runMerchantSpecPublicOrdersTest.suites = ["merchant"]; -- cgit v1.2.3