@ledgerhq/coin-tester
Version:
Deterministic testing of Ledger coin-modules
169 lines • 10.4 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeScenario = executeScenario;
const chalk_1 = __importDefault(require("chalk"));
const rxjs_1 = require("rxjs");
const types_devices_1 = require("@ledgerhq/types-devices");
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_1.default.bgBlue(" Address "), " → ", chalk_1.default.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 (0, rxjs_1.firstValueFrom)(accountBridge
.sync(account, { paginationConfig: {} })
.pipe((0, rxjs_1.reduce)((acc, f) => f(acc), account)));
console.log("Synchronization completed ✓");
await scenario.beforeAll?.(scenarioAccount, strategy);
console.log("BeforeAll completed ✓");
console.log("\n\n");
console.log(chalk_1.default.bgCyan.black.bold(" ✧ "), " ", chalk_1.default.cyan(`Scenario: ${chalk_1.default.italic.bold(scenario.name)}`), " ", chalk_1.default.bgCyan.black.bold(" ✧ "), " → ", chalk_1.default.bold.cyan(" Starting ◌"));
const scenarioTransactions = scenario.getTransactions(account.freshAddress, strategy);
const internalScenarioTransactions = await scenario.getInternalTransactions?.(account.freshAddress, strategy);
for (const testTransaction of scenarioTransactions) {
console.log("\n");
console.log(chalk_1.default.cyan("Transaction:", chalk_1.default.bold(testTransaction.name), "◌"));
await scenario.beforeEach?.(scenarioAccount);
console.log("Before each ✔️");
if (scenarioTransactions.indexOf(testTransaction) > 0) {
await scenario.beforeSync?.();
scenarioAccount = await (0, rxjs_1.firstValueFrom)(accountBridge
.sync(scenarioAccount, { paginationConfig: {} })
.pipe((0, rxjs_1.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_1.default.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_1.default.bold("No status errors detected"), "✓");
const { signedOperation } = await (0, rxjs_1.firstValueFrom)(accountBridge
.signOperation({
account: scenarioAccount,
transaction,
deviceId: "",
deviceModelId: types_devices_1.DeviceModelId.nanoX,
// TODO: use "test" once test signatures from the CAL
// are all compatible with Speculos public key
certificateSignatureKind: "prod",
})
.pipe((0, rxjs_1.map)(e => {
if (e.type === "device-signature-requested") {
onSignerConfirmation?.(e);
}
return e;
}), (0, rxjs_1.first)((e) => e.type === "signed")));
console.log(" → ", "🔏 ", chalk_1.default.bold("Signed the transaction"), "✓");
const optimisticOperation = await accountBridge.broadcast({
signedOperation,
account: scenarioAccount,
});
console.log(" → ", "🛫 ", chalk_1.default.bold("Broadcasted the transaction"), "✓");
const retry_limit = retryLimit ?? 10;
async function expectHandler(retry) {
await scenario.beforeSync?.();
scenarioAccount = await (0, rxjs_1.firstValueFrom)(accountBridge
.sync({ ...scenarioAccount, pendingOperations: [optimisticOperation] }, { paginationConfig: {} })
.pipe((0, rxjs_1.reduce)((acc, f) => f(acc), scenarioAccount)));
if (!testTransaction.expect) {
console.warn(chalk_1.default.yellow(`No expects in the transaction ${chalk_1.default.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_1.default.red(`Retried ${retry_limit} time(s) and could not assert all expects for transaction ${chalk_1.default.bold(testTransaction.name)}`));
throw err;
}
console.warn(chalk_1.default.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_1.default.green("Transaction:", chalk_1.default.bold(testTransaction.name), "completed ✓"));
}
if (internalScenarioTransactions && internalScenarioTransactions.length > 0) {
for (const internalTestTransaction of internalScenarioTransactions) {
console.log("\n");
console.log(chalk_1.default.cyan("Transaction:", chalk_1.default.bold(internalTestTransaction.name), "◌"));
await scenario.beforeEach?.(scenarioAccount);
console.log("Before each ✔️");
if (internalScenarioTransactions.indexOf(internalTestTransaction) > 0) {
await scenario.beforeSync?.();
scenarioAccount = await (0, rxjs_1.firstValueFrom)(accountBridge
.sync(scenarioAccount, { paginationConfig: {} })
.pipe((0, rxjs_1.reduce)((acc, f) => f(acc), scenarioAccount)));
}
const previousAccount = Object.freeze(scenarioAccount);
const retry_limit = retryLimit ?? 10;
async function expectHandler(retry) {
await scenario.beforeSync?.();
scenarioAccount = await (0, rxjs_1.firstValueFrom)(accountBridge
.sync({ ...scenarioAccount }, { paginationConfig: {} })
.pipe((0, rxjs_1.reduce)((acc, f) => f(acc), scenarioAccount)));
if (!(await internalTestTransaction.expect)) {
console.warn(chalk_1.default.yellow(`No expects in the transaction ${chalk_1.default.bold(await internalTestTransaction.name)}. You might want to add tests in this transaction.`));
return;
}
try {
await internalTestTransaction.expect?.(previousAccount, scenarioAccount);
}
catch (err) {
if (!err?.matcherResult?.pass) {
if (retry === 0) {
console.error(chalk_1.default.red(`Retried ${retry_limit} time(s) and could not assert all expects for transaction ${chalk_1.default.bold(await internalTestTransaction.name)}`));
throw err;
}
console.warn(chalk_1.default.magenta("Test asssertion failed. Retrying..."));
await new Promise(resolve => setTimeout(resolve, retryInterval ?? 3 * 1000));
await expectHandler(retry - 1);
}
else {
throw err;
}
}
}
await expectHandler(retry_limit);
}
scenarioAccount = await (0, rxjs_1.firstValueFrom)(accountBridge
.sync(account, { paginationConfig: {} })
.pipe((0, rxjs_1.reduce)((acc, f) => f(acc), account)));
}
console.log("\n");
await scenario.afterAll?.(scenarioAccount, strategy);
console.log("afterAll completed ✓");
await scenario.teardown?.();
console.log("\n\n", chalk_1.default.bgGreen.black.bold(" ✧ "), " ", chalk_1.default.green(`Scenario: ${chalk_1.default.italic.bold(scenario.name)}`), " ", chalk_1.default.bgGreen.black.bold(" ✧ "), " → ", chalk_1.default.bold.green(" Completed 🎉"), "\n\n");
}
catch (err) {
console.error("\n\n", chalk_1.default.bgRed.black.bold(" ✧ "), " ", chalk_1.default.red(`Scenario: ${chalk_1.default.italic.bold(scenario.name)}`), " ", chalk_1.default.bgRed.black.bold(" ✧ "), " → ", chalk_1.default.bold.red(" Failed ❌"), "\n\n");
await scenario.teardown?.();
throw err;
}
}
//# sourceMappingURL=main.js.map