harness: improve peer-pull integration test, check notifications

This commit is contained in:
Florian Dold 2023-02-21 21:02:36 +01:00
parent a3c7da975b
commit b648238c41
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
5 changed files with 147 additions and 24 deletions

View File

@ -31,6 +31,7 @@ import {
PreparePayResultType,
NotificationType,
WithdrawalGroupFinishedNotification,
WalletNotification,
} from "@gnu-taler/taler-util";
import {
BankAccessApi,
@ -297,7 +298,6 @@ export async function createSimpleTestkudosEnvironmentV2(
const walletService = new WalletService(t, {
name: "wallet",
useInMemoryDb: true,
});
await walletService.start();
await walletService.pingUntilAvailable();
@ -326,6 +326,39 @@ export async function createSimpleTestkudosEnvironmentV2(
};
}
export interface CreateWalletArgs {
handleNotification?(wn: WalletNotification): void;
name: string;
}
export async function createWalletDaemonWithClient(
t: GlobalTestState,
args: CreateWalletArgs,
): Promise<{ walletClient: WalletClient; walletService: WalletService }> {
const walletService = new WalletService(t, {
name: "wallet",
useInMemoryDb: true,
});
await walletService.start();
await walletService.pingUntilAvailable();
const walletClient = new WalletClient({
unixPath: walletService.socketPath,
onNotification(n) {
console.log("got notification", n);
if (args.handleNotification) {
args.handleNotification(n);
}
},
});
await walletClient.connect();
await walletClient.client.call(WalletApiOperation.InitWallet, {
skipDefaults: true,
});
return { walletClient, walletService };
}
export interface FaultyMerchantTestEnvironment {
commonDb: DbInfo;
bank: BankService;

View File

@ -17,12 +17,21 @@
/**
* Imports.
*/
import { AbsoluteTime, Duration, j2s } from "@gnu-taler/taler-util";
import {
AbsoluteTime,
Duration,
j2s,
NotificationType,
WalletNotification,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { GlobalTestState, WalletCli } from "../harness/harness.js";
import {
createSimpleTestkudosEnvironment,
createSimpleTestkudosEnvironmentV2,
createWalletDaemonWithClient,
withdrawViaBank,
withdrawViaBankV2,
} from "../harness/helpers.js";
/**
@ -31,19 +40,40 @@ import {
export async function runPeerToPeerPullTest(t: GlobalTestState) {
// Set up test environment
const { bank, exchange } = await createSimpleTestkudosEnvironment(t);
const { bank, exchange } = await createSimpleTestkudosEnvironmentV2(t);
let allW1Notifications: WalletNotification[] = [];
let allW2Notifications: WalletNotification[] = [];
const w1 = await createWalletDaemonWithClient(t, {
name: "w1",
handleNotification(wn) {
allW1Notifications.push(wn);
},
});
const w2 = await createWalletDaemonWithClient(t, {
name: "w2",
handleNotification(wn) {
allW2Notifications.push(wn);
},
});
// Withdraw digital cash into the wallet.
const wallet1 = new WalletCli(t, "w1");
const wallet2 = new WalletCli(t, "w2");
await withdrawViaBank(t, {
wallet: wallet2,
const wallet1 = w1.walletClient;
const wallet2 = w2.walletClient;
const withdrawalDoneCond = wallet2.waitForNotificationCond(
(x) => x.type === NotificationType.WithdrawGroupFinished,
);
await withdrawViaBankV2(t, {
walletClient: wallet2,
bank,
exchange,
amount: "TESTKUDOS:20",
});
await wallet1.runUntilDone();
await withdrawalDoneCond;
const purse_expiration = AbsoluteTime.toTimestamp(
AbsoluteTime.addDuration(
@ -52,6 +82,10 @@ export async function runPeerToPeerPullTest(t: GlobalTestState) {
),
);
const peerPullCreditReadyCond = wallet2.waitForNotificationCond(
(x) => x.type === NotificationType.PeerPullCreditReady,
);
const resp = await wallet1.client.call(
WalletApiOperation.InitiatePeerPullCredit,
{
@ -64,9 +98,7 @@ export async function runPeerToPeerPullTest(t: GlobalTestState) {
},
);
// Wait until the initiation is actually done.
// FIXME: Use the daemonized wallet and notifications to know this
await wallet1.runPending();
await peerPullCreditReadyCond;
const checkResp = await wallet2.client.call(
WalletApiOperation.PreparePeerPullDebit,
@ -77,20 +109,23 @@ export async function runPeerToPeerPullTest(t: GlobalTestState) {
console.log(`checkResp: ${j2s(checkResp)}`);
const acceptResp = await wallet2.client.call(
WalletApiOperation.ConfirmPeerPullDebit,
{
peerPullPaymentIncomingId: checkResp.peerPullPaymentIncomingId,
},
// FIXME: The wallet should emit a more appropriate notification here.
// Yes, it's technically a withdrawal.
const peerPullCreditDoneCond = wallet1.waitForNotificationCond(
(x) => x.type === NotificationType.WithdrawGroupFinished,
);
await wallet1.runUntilDone();
await wallet2.runUntilDone();
await wallet2.client.call(WalletApiOperation.ConfirmPeerPullDebit, {
peerPullPaymentIncomingId: checkResp.peerPullPaymentIncomingId,
});
await peerPullCreditDoneCond;
const txn1 = await wallet1.client.call(
WalletApiOperation.GetTransactions,
{},
);
const txn2 = await wallet2.client.call(
WalletApiOperation.GetTransactions,
{},
@ -98,6 +133,11 @@ export async function runPeerToPeerPullTest(t: GlobalTestState) {
console.log(`txn1: ${j2s(txn1)}`);
console.log(`txn2: ${j2s(txn2)}`);
console.log(`w1 notifications: ${j2s(allW1Notifications)}`);
// Check that we don't have an excessive number of notifications.
t.assertTrue(allW1Notifications.length <= 60);
}
runPeerToPeerPullTest.suites = ["wallet"];

View File

@ -65,6 +65,7 @@ export enum NotificationType {
WithdrawalGroupKycRequested = "withdrawal-group-kyc-requested",
WithdrawalGroupBankConfirmed = "withdrawal-group-bank-confirmed",
WithdrawalGroupReserveReady = "withdrawal-group-reserve-ready",
PeerPullCreditReady = "peer-pull-credit-ready",
DepositOperationError = "deposit-operation-error",
}
@ -135,11 +136,20 @@ export interface WithdrawalGroupBankConfirmed {
transactionId: string;
}
export interface WithdrawalGroupReserveReady {
export interface WithdrawalGroupReserveReadyNotification {
type: NotificationType.WithdrawalGroupReserveReady;
transactionId: string;
}
/**
* The purse creation of a peer-pull-credit transaction
* is done, and the other party can now pay.
*/
export interface PeerPullCreditReadyNotification {
type: NotificationType.PeerPullCreditReady;
transactionId: string;
}
export interface RefreshRevealedNotification {
type: NotificationType.RefreshRevealed;
}
@ -316,4 +326,5 @@ export type WalletNotification =
| PayOperationSuccessNotification
| WithdrawalGroupKycRequested
| WithdrawalGroupBankConfirmed
| WithdrawalGroupReserveReady;
| WithdrawalGroupReserveReadyNotification
| PeerPullCreditReadyNotification;

View File

@ -72,6 +72,7 @@ import {
codecOptional,
codecForTimestamp,
CancellationToken,
NotificationType,
} from "@gnu-taler/taler-util";
import { SpendCoinDetails } from "../crypto/cryptoImplementation.js";
import {
@ -119,7 +120,10 @@ import {
processWithdrawalGroup,
} from "./withdraw.js";
import { PendingTaskType } from "../pending-types.js";
import { stopLongpolling } from "./transactions.js";
import {
constructTransactionIdentifier,
stopLongpolling,
} from "./transactions.js";
const logger = new Logger("operations/peer-to-peer.ts");
@ -1507,6 +1511,14 @@ export async function processPeerPullCredit(
await tx.peerPullPaymentInitiations.put(pi2);
});
ws.notify({
type: NotificationType.PeerPullCreditReady,
transactionId: constructTransactionIdentifier({
tag: TransactionType.PeerPullCredit,
pursePub: pullIni.pursePub,
}),
});
return {
type: OperationAttemptResultType.Finished,
result: undefined,
@ -1626,9 +1638,6 @@ export async function initiatePeerPullPayment(
const pursePair = await ws.cryptoApi.createEddsaKeypair({});
const mergePair = await ws.cryptoApi.createEddsaKeypair({});
const instructedAmount = Amounts.parseOrThrow(
req.partialContractTerms.amount,
);
const contractTerms = req.partialContractTerms;
const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);

View File

@ -64,6 +64,7 @@ import {
} from "../db.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { PendingTaskType } from "../pending-types.js";
import { assertUnreachable } from "../util/assertUnreachable.js";
import { checkDbInvariant } from "../util/invariants.js";
import { constructTaskIdentifier, TaskIdentifiers } from "../util/retries.js";
import {
@ -1376,6 +1377,35 @@ export type ParsedTransactionIdentifier =
| { tag: TransactionType.Tip; walletTipId: string }
| { tag: TransactionType.Withdrawal; withdrawalGroupId: string };
export function constructTransactionIdentifier(
pTxId: ParsedTransactionIdentifier,
): string {
switch (pTxId.tag) {
case TransactionType.Deposit:
return `txn:${pTxId.tag}:${pTxId.depositGroupId}`;
case TransactionType.Payment:
return `txn:${pTxId.tag}:${pTxId.proposalId}`;
case TransactionType.PeerPullCredit:
return `txn:${pTxId.tag}:${pTxId.pursePub}`;
case TransactionType.PeerPullDebit:
return `txn:${pTxId.tag}:${pTxId.peerPullPaymentIncomingId}`;
case TransactionType.PeerPushCredit:
return `txn:${pTxId.tag}:${pTxId.peerPushPaymentIncomingId}`;
case TransactionType.PeerPushDebit:
return `txn:${pTxId.tag}:${pTxId.pursePub}`;
case TransactionType.Refresh:
return `txn:${pTxId.tag}:${pTxId.refreshGroupId}`;
case TransactionType.Refund:
return `txn:${pTxId.tag}:${pTxId.proposalId}:${pTxId.executionTime}`;
case TransactionType.Tip:
return `txn:${pTxId.tag}:${pTxId.walletTipId}`;
case TransactionType.Withdrawal:
return `txn:${pTxId.tag}:${pTxId.withdrawalGroupId}`;
default:
assertUnreachable(pTxId);
}
}
/**
* Parse a transaction identifier string into a typed, structured representation.
*/