merchant test cases
This commit is contained in:
parent
df77676977
commit
2f945b2aeb
@ -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"];
|
@ -134,10 +134,10 @@ export async function runPaywallFlowTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
console.log(publicOrderStatusResp.data);
|
console.log(publicOrderStatusResp.data);
|
||||||
|
|
||||||
if (publicOrderStatusResp.status != 202) {
|
if (publicOrderStatusResp.status != 200) {
|
||||||
console.log(publicOrderStatusResp.data);
|
console.log(publicOrderStatusResp.data);
|
||||||
throw Error(
|
throw Error(
|
||||||
`expected status 202 (after paying), but got ${publicOrderStatusResp.status}`,
|
`expected status 200 (after paying), but got ${publicOrderStatusResp.status}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +76,7 @@ import { runMerchantInstancesDeleteTest } from "./test-merchant-instances-delete
|
|||||||
import { runWalletBackupDoublespendTest } from "./test-wallet-backup-doublespend";
|
import { runWalletBackupDoublespendTest } from "./test-wallet-backup-doublespend";
|
||||||
import { runPaymentForgettableTest } from "./test-payment-forgettable.js";
|
import { runPaymentForgettableTest } from "./test-payment-forgettable.js";
|
||||||
import { runPaymentZeroTest } from "./test-payment-zero.js";
|
import { runPaymentZeroTest } from "./test-payment-zero.js";
|
||||||
|
import { runMerchantSpecPublicOrdersTest } from "./test-merchant-spec-public-orders.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test runner.
|
* Test runner.
|
||||||
@ -113,6 +114,7 @@ const allTests: TestMainFunction[] = [
|
|||||||
runMerchantInstancesDeleteTest,
|
runMerchantInstancesDeleteTest,
|
||||||
runMerchantInstancesUrlsTest,
|
runMerchantInstancesUrlsTest,
|
||||||
runMerchantLongpollingTest,
|
runMerchantLongpollingTest,
|
||||||
|
runMerchantSpecPublicOrdersTest,
|
||||||
runMerchantRefundApiTest,
|
runMerchantRefundApiTest,
|
||||||
runPayAbortTest,
|
runPayAbortTest,
|
||||||
runPaymentClaimTest,
|
runPaymentClaimTest,
|
||||||
|
@ -52,6 +52,8 @@ export class NodeHttpLib implements HttpRequestLibrary {
|
|||||||
const method = opt?.method ?? "GET";
|
const method = opt?.method ?? "GET";
|
||||||
let body = opt?.body;
|
let body = opt?.body;
|
||||||
|
|
||||||
|
logger.trace(`Requesting ${method} ${url}`);
|
||||||
|
|
||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
if (this.throttlingEnabled && this.throttle.applyThrottle(url)) {
|
if (this.throttlingEnabled && this.throttle.applyThrottle(url)) {
|
||||||
throw OperationFailedError.fromCode(
|
throw OperationFailedError.fromCode(
|
||||||
@ -79,6 +81,7 @@ export class NodeHttpLib implements HttpRequestLibrary {
|
|||||||
transformResponse: (x) => x,
|
transformResponse: (x) => x,
|
||||||
data: body,
|
data: body,
|
||||||
timeout,
|
timeout,
|
||||||
|
maxRedirects: 0,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw OperationFailedError.fromCode(
|
throw OperationFailedError.fromCode(
|
||||||
|
@ -403,6 +403,13 @@ export enum TombstoneTag {
|
|||||||
DeleteRefund = "delete-refund",
|
DeleteRefund = "delete-refund",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function retryTransactionNow(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
transactionId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const [type, ...rest] = transactionId.split(":");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immediately retry the underlying operation
|
* Immediately retry the underlying operation
|
||||||
* of a transaction.
|
* of a transaction.
|
||||||
|
Loading…
Reference in New Issue
Block a user