@ledgerhq/coin-tester
Version:
Deterministic testing of Ledger coin-modules
115 lines • 6.61 kB
JavaScript
import chalk from "chalk";
import { first, firstValueFrom, map, reduce } from "rxjs";
import { DeviceModelId } from "@ledgerhq/types-devices";
export async function executeScenario(scenario, strategy = "legacy") {
try {
const { accountBridge, currencyBridge, account, retryInterval, retryLimit, onSignerConfirmation, } = await scenario.setup(strategy);
console.log("Setup completed ✓");
console.log("\n");
console.log(chalk.bgBlue(" Address "), " → ", chalk.bold.blue(account.freshAddress), "\n\n");
const data = await currencyBridge.preload(account.currency);
currencyBridge.hydrate(data, account.currency);
console.log("Preload + hydrate completed ✓");
await scenario.beforeSync?.();
console.log("Running a synchronization on the account...");
let scenarioAccount = await firstValueFrom(accountBridge
.sync(account, { paginationConfig: {} })
.pipe(reduce((acc, f) => f(acc), account)));
console.log("Synchronization completed ✓");
await scenario.beforeAll?.(scenarioAccount);
console.log("BeforeAll completed ✓");
console.log("\n\n");
console.log(chalk.bgCyan.black.bold(" ✧ "), " ", chalk.cyan(`Scenario: ${chalk.italic.bold(scenario.name)}`), " ", chalk.bgCyan.black.bold(" ✧ "), " → ", chalk.bold.cyan(" Starting ◌"));
const scenarioTransactions = scenario.getTransactions(account.freshAddress);
for (const testTransaction of scenarioTransactions) {
console.log("\n");
console.log(chalk.cyan("Transaction:", chalk.bold(testTransaction.name), "◌"));
await scenario.beforeEach?.(scenarioAccount);
console.log("Before each ✔️");
if (scenarioTransactions.indexOf(testTransaction) > 0) {
await scenario.beforeSync?.();
scenarioAccount = await firstValueFrom(accountBridge
.sync(scenarioAccount, { paginationConfig: {} })
.pipe(reduce((acc, f) => f(acc), scenarioAccount)));
}
const previousAccount = Object.freeze(scenarioAccount);
const defaultTransaction = accountBridge.createTransaction(scenarioAccount);
const transaction = await accountBridge.prepareTransaction(scenarioAccount, {
...defaultTransaction,
...testTransaction,
});
console.log(" → ", "🧑🍳 ", chalk.bold("Prepared the transaction"), "✓");
const status = await accountBridge.getTransactionStatus(scenarioAccount, transaction);
if (Object.entries(status.errors).length) {
throw new Error(`${testTransaction.name} transaction\nError in transaction status: ${JSON.stringify(status.errors, null, 3)}`);
}
console.log(" → ", "🪲 ", chalk.bold("No status errors detected"), "✓");
const { signedOperation } = await firstValueFrom(accountBridge
.signOperation({
account: scenarioAccount,
transaction,
deviceId: "",
deviceModelId: DeviceModelId.nanoX,
// TODO: use "test" once test signatures from the CAL
// are all compatible with Speculos public key
certificateSignatureKind: "prod",
})
.pipe(map(e => {
if (e.type === "device-signature-requested") {
onSignerConfirmation?.(e);
}
return e;
}), first((e) => e.type === "signed")));
console.log(" → ", "🔏 ", chalk.bold("Signed the transaction"), "✓");
const optimisticOperation = await accountBridge.broadcast({
signedOperation,
account: scenarioAccount,
});
console.log(" → ", "🛫 ", chalk.bold("Broadcasted the transaction"), "✓");
const retry_limit = retryLimit ?? 10;
async function expectHandler(retry) {
await scenario.beforeSync?.();
scenarioAccount = await firstValueFrom(accountBridge
.sync({ ...scenarioAccount, pendingOperations: [optimisticOperation] }, { paginationConfig: {} })
.pipe(reduce((acc, f) => f(acc), scenarioAccount)));
if (!testTransaction.expect) {
console.warn(chalk.yellow(`No expects in the transaction ${chalk.bold(testTransaction.name)}. You might want to add tests in this transaction.`));
return;
}
try {
testTransaction.expect?.(previousAccount, scenarioAccount);
}
catch (err) {
if (!err?.matcherResult?.pass) {
if (retry === 0) {
console.error(chalk.red(`Retried ${retry_limit} time(s) and could not assert all expects for transaction ${chalk.bold(testTransaction.name)}`));
throw err;
}
console.warn(chalk.magenta("Test asssertion failed. Retrying..."));
await new Promise(resolve => setTimeout(resolve, retryInterval ?? 3 * 1000));
await expectHandler(retry - 1);
}
else {
throw err;
}
}
}
await scenario.mockIndexer?.(scenarioAccount, optimisticOperation);
await expectHandler(retry_limit);
await scenario.afterEach?.(scenarioAccount);
console.log("After each ✔️");
console.log(chalk.green("Transaction:", chalk.bold(testTransaction.name), "completed ✓"));
}
console.log("\n");
await scenario.afterAll?.(scenarioAccount);
console.log("afterAll completed ✓");
await scenario.teardown?.();
console.log("\n\n", chalk.bgGreen.black.bold(" ✧ "), " ", chalk.green(`Scenario: ${chalk.italic.bold(scenario.name)}`), " ", chalk.bgGreen.black.bold(" ✧ "), " → ", chalk.bold.green(" Completed 🎉"), "\n\n");
}
catch (err) {
console.error("\n\n", chalk.bgRed.black.bold(" ✧ "), " ", chalk.red(`Scenario: ${chalk.italic.bold(scenario.name)}`), " ", chalk.bgRed.black.bold(" ✧ "), " → ", chalk.bold.red(" Failed ❌"), "\n\n");
await scenario.teardown?.();
throw err;
}
}
//# sourceMappingURL=main.js.map