aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2021-08-12 21:01:40 +0200
committerFlorian Dold <florian@dold.me>2021-08-12 21:01:40 +0200
commit2f945b2aebf3378496c8be3ef48a16253dde3358 (patch)
treef99f0dcac04e0c9032e0f5c28abb920feee9e14b
parentdf7767697798e1c2d9d1c8c777577adea7f4b342 (diff)
merchant test cases
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-merchant-spec-public-orders.ts332
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-paywall-flow.ts4
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/testrunner.ts2
-rw-r--r--packages/taler-wallet-core/src/headless/NodeHttpLib.ts3
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts7
5 files changed, 346 insertions, 2 deletions
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
new file mode 100644
index 000000000..98528ada4
--- /dev/null
+++ b/packages/taler-wallet-cli/src/integrationtests/test-merchant-spec-public-orders.ts
@@ -0,0 +1,332 @@
+/*
+ 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 {
+ ConfirmPayResultType,
+ PreparePayResultType,
+ URL,
+} from "@gnu-taler/taler-util";
+import {
+ encodeCrock,
+ getRandomBytes,
+ NodeHttpLib,
+ WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { GlobalTestState, MerchantPrivateApi, WalletCli } from "./harness";
+import {
+ createSimpleTestkudosEnvironment,
+ withdrawViaBank,
+} from "./helpers.js";
+
+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.
+
+ await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" });
+ await withdrawViaBank(t, {
+ wallet: wallet2,
+ bank,
+ exchange,
+ amount: "TESTKUDOS:20",
+ });
+ // 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
+ }
+
+ 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",
+ },
+ });
+
+ const claimToken = orderResp.token;
+ const orderId = orderResp.order_id;
+ t.assertTrue(!!claimToken);
+ let talerPayUri: string;
+
+ {
+ const httpResp = await httpLib.get(
+ new URL(`orders/${orderId}`, merchantBaseUrl).href,
+ );
+ const r = await httpResp.json();
+ t.assertDeepEqual(httpResp.status, 202);
+ 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);
+ console.log(r);
+ talerPayUri = r.taler_pay_uri;
+ t.assertTrue(!!talerPayUri);
+ }
+
+ {
+ 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);
+ }
+
+ 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
+ {
+ 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 confirmPayRes2 = await wallet.client.call(
+ WalletApiOperation.ConfirmPay,
+ {
+ proposalId: proposalId,
+ sessionId: "mysession",
+ },
+ );
+
+ 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", "mysession");
+ 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", "mysession");
+ 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");
+ }
+}
+
+runMerchantSpecPublicOrdersTest.suites = ["merchant"];
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-paywall-flow.ts b/packages/taler-wallet-cli/src/integrationtests/test-paywall-flow.ts
index 25c8862cc..04eee79e3 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-paywall-flow.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-paywall-flow.ts
@@ -134,10 +134,10 @@ export async function runPaywallFlowTest(t: GlobalTestState) {
console.log(publicOrderStatusResp.data);
- if (publicOrderStatusResp.status != 202) {
+ if (publicOrderStatusResp.status != 200) {
console.log(publicOrderStatusResp.data);
throw Error(
- `expected status 202 (after paying), but got ${publicOrderStatusResp.status}`,
+ `expected status 200 (after paying), but got ${publicOrderStatusResp.status}`,
);
}
diff --git a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
index 5e0cb9f57..c2e626436 100644
--- a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
@@ -76,6 +76,7 @@ import { runMerchantInstancesDeleteTest } from "./test-merchant-instances-delete
import { runWalletBackupDoublespendTest } from "./test-wallet-backup-doublespend";
import { runPaymentForgettableTest } from "./test-payment-forgettable.js";
import { runPaymentZeroTest } from "./test-payment-zero.js";
+import { runMerchantSpecPublicOrdersTest } from "./test-merchant-spec-public-orders.js";
/**
* Test runner.
@@ -113,6 +114,7 @@ const allTests: TestMainFunction[] = [
runMerchantInstancesDeleteTest,
runMerchantInstancesUrlsTest,
runMerchantLongpollingTest,
+ runMerchantSpecPublicOrdersTest,
runMerchantRefundApiTest,
runPayAbortTest,
runPaymentClaimTest,
diff --git a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts
index 1186ea4d6..9655d363f 100644
--- a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts
+++ b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts
@@ -52,6 +52,8 @@ export class NodeHttpLib implements HttpRequestLibrary {
const method = opt?.method ?? "GET";
let body = opt?.body;
+ logger.trace(`Requesting ${method} ${url}`);
+
const parsedUrl = new URL(url);
if (this.throttlingEnabled && this.throttle.applyThrottle(url)) {
throw OperationFailedError.fromCode(
@@ -79,6 +81,7 @@ export class NodeHttpLib implements HttpRequestLibrary {
transformResponse: (x) => x,
data: body,
timeout,
+ maxRedirects: 0,
});
} catch (e) {
throw OperationFailedError.fromCode(
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts
index 52b9819dd..a07551e82 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -403,6 +403,13 @@ export enum TombstoneTag {
DeleteRefund = "delete-refund",
}
+export async function retryTransactionNow(
+ ws: InternalWalletState,
+ transactionId: string,
+): Promise<void> {
+ const [type, ...rest] = transactionId.split(":");
+}
+
/**
* Immediately retry the underlying operation
* of a transaction.