From b232bfe91e1bd9f778b1ee7d3fa227c7705f6c92 Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 13 Nov 2024 14:16:32 +0000 Subject: [PATCH 01/13] feat: add printer testing --- lib/hardware-testing/common.js | 77 ++++++++++++++++++++++++ lib/hardware-testing/printer/genmega.js | 78 +++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 lib/hardware-testing/common.js create mode 100644 lib/hardware-testing/printer/genmega.js diff --git a/lib/hardware-testing/common.js b/lib/hardware-testing/common.js new file mode 100644 index 000000000..b240f6500 --- /dev/null +++ b/lib/hardware-testing/common.js @@ -0,0 +1,77 @@ +const readline = require('node:readline'); +const { stdin: input, stdout: output } = require('node:process'); + +let rl = null + +const confirmOnce = msg => new Promise((resolve, reject) => + rl.question( + `${msg} ([y]es/[n]) `, + answer => + ["y", "yes"].includes(answer) ? resolve(true) : + ["n", "no"].includes(answer) ? resolve(false) : + reject(true) + ) +) + +const confirm = async msg => { + msg ??= "Did it work?" + const retry = true + while (retry) { + try { + return await confirmOnce() + } catch (newRetry) { + retry = typeof(newRetry) === 'boolean' && newRetry + } + } + return false +} + +const defaultPost = (error, result) => error ? { error } : { result } + +const reducer = (acc, { + name, + confirmMessage, + skipConfirm, + keepGoing, + pre, + test, + post = defaultPost, +}) => + acc + .then(([cont, report]) => { + if (!cont) return [cont, report] + const args = pre() + return Promise.resolve(test(args)) // ensure it's a promise + .then( + result => [true, post(null, result)], + error => [!!keepGoing, post(error, null)], + ) + .then(([cont, result]) => + (skipConfirm ? Promise.resolve(true) : confirm(confirmMessage)) + .then(worked => [cont, result, worked]) + .catch(error => [false, result, false]) + ) + .then(([cont, result, confirmed]) => { + report = Object.assign(report, { + [name]: { result, confirmed } + }) + return [cont, report] + }) + }) + +const runSteps = steps => { + rl = readline.createInterface({ + input, + output, + terminal: true, + }) + return steps.reduce(reducer, Promise.resolve([true, {}])) + .finally(() => { + rl.close() + rl = null + }) +} + +module.exports = { + runSteps, +} diff --git a/lib/hardware-testing/printer/genmega.js b/lib/hardware-testing/printer/genmega.js new file mode 100644 index 000000000..957aa4a70 --- /dev/null +++ b/lib/hardware-testing/printer/genmega.js @@ -0,0 +1,78 @@ +const { confirm } = require('@inquirer/prompts') +const printer = require('../../printer/genmega') +const { runSteps } = require('../common') + +const PRINTER_CONFIG = { + model: "genmega", + address: "/dev/ttyS4", +} + +const RECEIPT_DATA = { + operatorInfo: { + name: "operator name", + website: "operator website", + email: "operator email", + phone: "operator phone", + companyNumber: "operator companyNumber", + }, + location: "location", + customer: "customer", + session: "session", + time: "14:29", + direction: 'Cash-in', + fiat: "200 EUR", + crypto: "1.234 BTC", + rate: "1 BTC = 123.456 EUR", + address: "bc1qdg6hsh9w8xwdz66khec3fl8c9wva0ys2tc277f", + txId: "07b2c12b88a2208f21fefc0a65dd045e073c566e47b721c51238886702539283", +} + +const RECEIPT_CONFIG = Object.fromEntries([ + "operatorWebsite", + "operatorEmail", + "operatorPhone", + "companyNumber", + "machineLocation", + "customerNameOrPhoneNumber", + "exchangeRate", + "addressQRCode", +].map(k => [k, true])) + +const WALLET = { privateKey: "private key" } + +const STEPS = [ + { + name: 'checkStatus', + skipConfirm: true, + pre: () => PRINTER_CONFIG, + test: printerCfg => printer.checkStatus(printerCfg), + }, + + { + name: 'printReceipt', + keepGoing: true, + pre: () => ({ + receiptData: RECEIPT_DATA, + printerCfg: PRINTER_CONFIG, + receiptCfg: RECEIPT_CONFIG, + }), + test: ({ receiptData, printerCfg, receiptCfg }) => + printer.printReceipt(receiptData, printerCfg, receiptCfg), + }, + + { + name: 'printWallet', + keepGoing: true, + pre: () => ({ + wallet: WALLET, + printerCfg: PRINTER_CONFIG, + cryptoCode: 'BTC', + }), + test: ({ wallet, printerCfg, cryptoCode }) => + printer.printWallet(wallet, printerCfg, cryptoCode), + }, +] + +runSteps(STEPS) + .then(report => console.log(report)) + .catch(error => console.log(error)) From 0009480b575fd27c7ec5690bbf5edcaef80c4d47 Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 13 Nov 2024 17:58:10 +0000 Subject: [PATCH 02/13] feat: add support for several peripherals --- lib/hardware-testing/{common.js => index.js} | 32 ++++++-- lib/hardware-testing/printer.js | 79 ++++++++++++++++++++ lib/hardware-testing/printer/genmega.js | 78 ------------------- 3 files changed, 105 insertions(+), 84 deletions(-) rename lib/hardware-testing/{common.js => index.js} (76%) create mode 100644 lib/hardware-testing/printer.js delete mode 100644 lib/hardware-testing/printer/genmega.js diff --git a/lib/hardware-testing/common.js b/lib/hardware-testing/index.js similarity index 76% rename from lib/hardware-testing/common.js rename to lib/hardware-testing/index.js index b240f6500..b3076f421 100644 --- a/lib/hardware-testing/common.js +++ b/lib/hardware-testing/index.js @@ -1,6 +1,11 @@ const readline = require('node:readline'); const { stdin: input, stdout: output } = require('node:process'); +const PERIPHERALS = { + printer: require('./printer'), +} + + let rl = null const confirmOnce = msg => new Promise((resolve, reject) => @@ -18,7 +23,7 @@ const confirm = async msg => { const retry = true while (retry) { try { - return await confirmOnce() + return await confirmOnce(msg) } catch (newRetry) { retry = typeof(newRetry) === 'boolean' && newRetry } @@ -26,6 +31,7 @@ const confirm = async msg => { return false } + const defaultPost = (error, result) => error ? { error } : { result } const reducer = (acc, { @@ -59,19 +65,33 @@ const reducer = (acc, { }) }) -const runSteps = steps => { + +const runSteps = steps => + steps.reduce(reducer, Promise.resolve([true, {}])) + + +const runPeripherals = peripherals => { rl = readline.createInterface({ input, output, terminal: true, }) - return steps.reduce(reducer, Promise.resolve([true, {}])) + return Promise.all( + Object.entries(peripherals) + .map(([k, p]) => + Promise.all([ + k, + p().catch(() => []).then(runSteps), + ]) + ) + ) + .then(Object.fromEntries) .finally(() => { rl.close() rl = null }) } -module.exports = { - runSteps, -} +runPeripherals(PERIPHERALS) + .then(console.log) + .catch(console.log) diff --git a/lib/hardware-testing/printer.js b/lib/hardware-testing/printer.js new file mode 100644 index 000000000..88e890c8a --- /dev/null +++ b/lib/hardware-testing/printer.js @@ -0,0 +1,79 @@ +const { confirm } = require('@inquirer/prompts') +const { load } = require('../printer/loader') + +const PRINTER_CONFIG = { + model: "genmega", + address: "/dev/ttyS4", +} + +const RECEIPT_DATA = { + operatorInfo: { + name: "operator name", + website: "operator website", + email: "operator email", + phone: "operator phone", + companyNumber: "operator companyNumber", + }, + location: "location", + customer: "customer", + session: "session", + time: "14:29", + direction: 'Cash-in', + fiat: "200 EUR", + crypto: "1.234 BTC", + rate: "1 BTC = 123.456 EUR", + address: "bc1qdg6hsh9w8xwdz66khec3fl8c9wva0ys2tc277f", + txId: "07b2c12b88a2208f21fefc0a65dd045e073c566e47b721c51238886702539283", +} + +const RECEIPT_CONFIG = Object.fromEntries([ + "operatorWebsite", + "operatorEmail", + "operatorPhone", + "companyNumber", + "machineLocation", + "customerNameOrPhoneNumber", + "exchangeRate", + "addressQRCode", +].map(k => [k, true])) + +const WALLET = { privateKey: "private key" } + +const steps = () => load().then(printer => + [ + { + name: 'checkStatus', + skipConfirm: true, + pre: () => PRINTER_CONFIG, + test: printerCfg => printer.checkStatus(printerCfg), + }, + + { + name: 'printReceipt', + confirmMessage: "Was a receipt printed?", + keepGoing: true, + pre: () => ({ + receiptData: RECEIPT_DATA, + printerCfg: PRINTER_CONFIG, + receiptCfg: RECEIPT_CONFIG, + }), + test: ({ receiptData, printerCfg, receiptCfg }) => + printer.printReceipt(receiptData, printerCfg, receiptCfg), + }, + + { + name: 'printWallet', + confirmMessage: "Was a wallet printed?", + keepGoing: true, + pre: () => ({ + wallet: WALLET, + printerCfg: PRINTER_CONFIG, + cryptoCode: 'BTC', + }), + test: ({ wallet, printerCfg, cryptoCode }) => + printer.printWallet(wallet, printerCfg, cryptoCode), + }, + ] +) + +module.exports = steps diff --git a/lib/hardware-testing/printer/genmega.js b/lib/hardware-testing/printer/genmega.js deleted file mode 100644 index 957aa4a70..000000000 --- a/lib/hardware-testing/printer/genmega.js +++ /dev/null @@ -1,78 +0,0 @@ -const { confirm } = require('@inquirer/prompts') -const printer = require('../../printer/genmega') -const { runSteps } = require('../common') - -const PRINTER_CONFIG = { - model: "genmega", - address: "/dev/ttyS4", -} - -const RECEIPT_DATA = { - operatorInfo: { - name: "operator name", - website: "operator website", - email: "operator email", - phone: "operator phone", - companyNumber: "operator companyNumber", - }, - location: "location", - customer: "customer", - session: "session", - time: "14:29", - direction: 'Cash-in', - fiat: "200 EUR", - crypto: "1.234 BTC", - rate: "1 BTC = 123.456 EUR", - address: "bc1qdg6hsh9w8xwdz66khec3fl8c9wva0ys2tc277f", - txId: "07b2c12b88a2208f21fefc0a65dd045e073c566e47b721c51238886702539283", -} - -const RECEIPT_CONFIG = Object.fromEntries([ - "operatorWebsite", - "operatorEmail", - "operatorPhone", - "companyNumber", - "machineLocation", - "customerNameOrPhoneNumber", - "exchangeRate", - "addressQRCode", -].map(k => [k, true])) - -const WALLET = { privateKey: "private key" } - -const STEPS = [ - { - name: 'checkStatus', - skipConfirm: true, - pre: () => PRINTER_CONFIG, - test: printerCfg => printer.checkStatus(printerCfg), - }, - - { - name: 'printReceipt', - keepGoing: true, - pre: () => ({ - receiptData: RECEIPT_DATA, - printerCfg: PRINTER_CONFIG, - receiptCfg: RECEIPT_CONFIG, - }), - test: ({ receiptData, printerCfg, receiptCfg }) => - printer.printReceipt(receiptData, printerCfg, receiptCfg), - }, - - { - name: 'printWallet', - keepGoing: true, - pre: () => ({ - wallet: WALLET, - printerCfg: PRINTER_CONFIG, - cryptoCode: 'BTC', - }), - test: ({ wallet, printerCfg, cryptoCode }) => - printer.printWallet(wallet, printerCfg, cryptoCode), - }, -] - -runSteps(STEPS) - .then(report => console.log(report)) - .catch(error => console.log(error)) From af8d5d36e1fb2575ca2a0ed6d8e334471b6e2cb1 Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 11 Dec 2024 10:55:12 +0000 Subject: [PATCH 03/13] chore: remove unused `require` --- lib/hardware-testing/printer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/hardware-testing/printer.js b/lib/hardware-testing/printer.js index 88e890c8a..c18bf4e84 100644 --- a/lib/hardware-testing/printer.js +++ b/lib/hardware-testing/printer.js @@ -1,4 +1,3 @@ -const { confirm } = require('@inquirer/prompts') const { load } = require('../printer/loader') const PRINTER_CONFIG = { From 40d8c662fcbf2e53bc31ac489d594c099a9184d7 Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 11 Dec 2024 13:33:13 +0000 Subject: [PATCH 04/13] refactor: take bill validator loading code out of `brain.js` --- lib/bill-validator.js | 26 ++++++++++++++++++++++++++ lib/brain.js | 25 ++----------------------- 2 files changed, 28 insertions(+), 23 deletions(-) create mode 100644 lib/bill-validator.js diff --git a/lib/bill-validator.js b/lib/bill-validator.js new file mode 100644 index 000000000..b664b1db7 --- /dev/null +++ b/lib/bill-validator.js @@ -0,0 +1,26 @@ +const pickClass = ({ deviceType }, mock) => { + if (mock) { + switch (deviceType) { + case 'hcm2': return require('./mocks/hcm2/hcm2') + case 'gsr50': return require('./mocks/gsr50/gsr50') + default: return require('./mocks/id003') + } + } + + switch (deviceType) { + case 'genmega': return require('./genmega/genmega-validator/genmega-validator') + case 'cashflowSc': return require('./mei/cashflow_sc') + case 'bnrAdvance': return require('./mei/bnr_advance') + case 'ccnet': return require('./ccnet/ccnet') + case 'hcm2': return require('./hcm2/hcm2') + case 'gsr50': return require('./gsr50/gsr50') + default: return require('./id003/id003') + } +} + +const load = (deviceConfig, mock = false) => { + const validatorClass = pickClass(deviceConfig, mock) + return validatorClass.factory(deviceConfig) +} + +module.exports = { load } diff --git a/lib/brain.js b/lib/brain.js index 82aa9c2ec..782a754fa 100644 --- a/lib/brain.js +++ b/lib/brain.js @@ -43,6 +43,7 @@ const { getLowestAmountPerRequirement, getAmountToHardLimit, getTriggered } = re const { ORDERED_REQUIREMENTS, REQUIREMENTS } = require('./compliance/triggers/consts') const printerLoader = require('./printer/loader') +const billValidator = require('./bill-validator') const BigNumber = BN.klass let transitionTime @@ -274,30 +275,8 @@ Brain.prototype.prunePending = function prunePending (txs) { .then(() => pendingTxs.length) } -Brain.prototype.selectBillValidatorClass = function selectBillValidatorClass () { - const billValidator = this.rootConfig.billValidator.deviceType - if (commandLine.mockBillValidator) { - switch (billValidator) { - case 'hcm2': return require('./mocks/hcm2/hcm2') - case 'gsr50': return require('./mocks/gsr50/gsr50') - default: return require('./mocks/id003') - } - } - - switch (billValidator) { - case 'genmega': return require('./genmega/genmega-validator/genmega-validator') - case 'cashflowSc': return require('./mei/cashflow_sc') - case 'bnrAdvance': return require('./mei/bnr_advance') - case 'ccnet': return require('./ccnet/ccnet') - case 'hcm2': return require('./hcm2/hcm2') - case 'gsr50': return require('./gsr50/gsr50') - default: return require('./id003/id003') - } -} - Brain.prototype.loadBillValidator = function loadBillValidator () { - const billValidatorClass = this.selectBillValidatorClass() - return billValidatorClass.factory(this.rootConfig.billValidator) + return billValidator.load(this.rootConfig.billValidator, !!commandLine.mockBillValidator) } Brain.prototype.billValidatorHasShutter = function billValidatorHasShutter () { From bee0cc73d5c2acfb2931ca97cf0cd181d866db72 Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 11 Dec 2024 13:35:43 +0000 Subject: [PATCH 05/13] refactor: extract confirmation code out to its own file --- lib/hardware-testing/confirm.js | 50 +++++++++++++++++++++++++++++++++ lib/hardware-testing/index.js | 40 ++------------------------ 2 files changed, 53 insertions(+), 37 deletions(-) create mode 100644 lib/hardware-testing/confirm.js diff --git a/lib/hardware-testing/confirm.js b/lib/hardware-testing/confirm.js new file mode 100644 index 000000000..8b104e549 --- /dev/null +++ b/lib/hardware-testing/confirm.js @@ -0,0 +1,50 @@ +const readline = require('node:readline'); +const { stdin: input, stdout: output } = require('node:process'); + +let rl = null + + +const createReadline = () => { + rl = readline.createInterface({ + input, + output, + terminal: true, + }) +} + + +const closeReadline = () => { + rl.close() + rl = null +} + + +const confirmOnce = msg => new Promise((resolve, reject) => + rl.question( + `${msg} ([y]es/[n]) `, + answer => + ["y", "yes"].includes(answer) ? resolve(true) : + ["n", "no"].includes(answer) ? resolve(false) : + reject(true) + ) +) + +const confirm = async msg => { + msg ??= "Did it work?" + const retry = true + while (retry) { + try { + return await confirmOnce(msg) + } catch (newRetry) { + retry = typeof(newRetry) === 'boolean' && newRetry + } + } + return false +} + + +module.exports = { + createReadline, + confirm, + closeReadline, +} diff --git a/lib/hardware-testing/index.js b/lib/hardware-testing/index.js index b3076f421..63898e061 100644 --- a/lib/hardware-testing/index.js +++ b/lib/hardware-testing/index.js @@ -1,37 +1,10 @@ -const readline = require('node:readline'); -const { stdin: input, stdout: output } = require('node:process'); +const { createReadline, confirm, closeReadline } = require('./confirm') const PERIPHERALS = { printer: require('./printer'), } -let rl = null - -const confirmOnce = msg => new Promise((resolve, reject) => - rl.question( - `${msg} ([y]es/[n]) `, - answer => - ["y", "yes"].includes(answer) ? resolve(true) : - ["n", "no"].includes(answer) ? resolve(false) : - reject(true) - ) -) - -const confirm = async msg => { - msg ??= "Did it work?" - const retry = true - while (retry) { - try { - return await confirmOnce(msg) - } catch (newRetry) { - retry = typeof(newRetry) === 'boolean' && newRetry - } - } - return false -} - - const defaultPost = (error, result) => error ? { error } : { result } const reducer = (acc, { @@ -71,11 +44,7 @@ const runSteps = steps => const runPeripherals = peripherals => { - rl = readline.createInterface({ - input, - output, - terminal: true, - }) + createReadline() return Promise.all( Object.entries(peripherals) .map(([k, p]) => @@ -86,10 +55,7 @@ const runPeripherals = peripherals => { ) ) .then(Object.fromEntries) - .finally(() => { - rl.close() - rl = null - }) + .finally(() => closeReadline()) } runPeripherals(PERIPHERALS) From 4e23ecfaf0b9f56e5da34de065edd37dc9ddbbe6 Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 11 Dec 2024 13:37:38 +0000 Subject: [PATCH 06/13] feat: provide a way to skip a given peripheral --- lib/hardware-testing/index.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/hardware-testing/index.js b/lib/hardware-testing/index.js index 63898e061..ed42cecfd 100644 --- a/lib/hardware-testing/index.js +++ b/lib/hardware-testing/index.js @@ -42,18 +42,17 @@ const reducer = (acc, { const runSteps = steps => steps.reduce(reducer, Promise.resolve([true, {}])) +const runPeripheral = ([peripheral, steps]) => + steps() + .then( + steps => steps === null ? { skipped: true } : runSteps(steps), + error => { error } + ) + .then(result => [peripheral, result]) const runPeripherals = peripherals => { createReadline() - return Promise.all( - Object.entries(peripherals) - .map(([k, p]) => - Promise.all([ - k, - p().catch(() => []).then(runSteps), - ]) - ) - ) + return Promise.all(Object.entries(peripherals).map(runPeripheral)) .then(Object.fromEntries) .finally(() => closeReadline()) } From 4f7244bf2140bbbaee1eeb0b8c1e355fb17c8f5c Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 11 Dec 2024 15:51:09 +0000 Subject: [PATCH 07/13] refactor: import report printing --- lib/hardware-testing/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hardware-testing/index.js b/lib/hardware-testing/index.js index ed42cecfd..3af2cd95e 100644 --- a/lib/hardware-testing/index.js +++ b/lib/hardware-testing/index.js @@ -58,5 +58,5 @@ const runPeripherals = peripherals => { } runPeripherals(PERIPHERALS) - .then(console.log) + .then(results => console.log(JSON.stringify(results, null, 2))) .catch(console.log) From 409cb5a4e61b797e1201e3532093cf07aee85c60 Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 11 Dec 2024 15:53:00 +0000 Subject: [PATCH 08/13] feat: support skipping printer if there's no printer --- lib/hardware-testing/index.js | 9 ++-- lib/hardware-testing/printer.js | 73 ++++++++++++++++++--------------- 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/lib/hardware-testing/index.js b/lib/hardware-testing/index.js index 3af2cd95e..880f01361 100644 --- a/lib/hardware-testing/index.js +++ b/lib/hardware-testing/index.js @@ -5,14 +5,15 @@ const PERIPHERALS = { } +const defaultPre = () => null const defaultPost = (error, result) => error ? { error } : { result } const reducer = (acc, { name, - confirmMessage, - skipConfirm, - keepGoing, - pre, + confirmationMessage, + skipConfirm = false, + keepGoing = false, + pre = defaultPre, test, post = defaultPost, }) => diff --git a/lib/hardware-testing/printer.js b/lib/hardware-testing/printer.js index c18bf4e84..e90086d06 100644 --- a/lib/hardware-testing/printer.js +++ b/lib/hardware-testing/printer.js @@ -38,41 +38,46 @@ const RECEIPT_CONFIG = Object.fromEntries([ const WALLET = { privateKey: "private key" } -const steps = () => load().then(printer => - [ - { - name: 'checkStatus', - skipConfirm: true, - pre: () => PRINTER_CONFIG, - test: printerCfg => printer.checkStatus(printerCfg), - }, +const steps = () => load() + .then( + printer => [ + { + name: 'checkStatus', + skipConfirm: true, + pre: () => PRINTER_CONFIG, + test: printerCfg => printer.checkStatus(printerCfg), + }, - { - name: 'printReceipt', - confirmMessage: "Was a receipt printed?", - keepGoing: true, - pre: () => ({ - receiptData: RECEIPT_DATA, - printerCfg: PRINTER_CONFIG, - receiptCfg: RECEIPT_CONFIG, - }), - test: ({ receiptData, printerCfg, receiptCfg }) => - printer.printReceipt(receiptData, printerCfg, receiptCfg), - }, + { + name: 'printReceipt', + confirmationMessage: "Was a receipt printed?", + keepGoing: true, + pre: () => ({ + receiptData: RECEIPT_DATA, + printerCfg: PRINTER_CONFIG, + receiptCfg: RECEIPT_CONFIG, + }), + test: ({ receiptData, printerCfg, receiptCfg }) => + printer.printReceipt(receiptData, printerCfg, receiptCfg), + }, - { - name: 'printWallet', - confirmMessage: "Was a wallet printed?", - keepGoing: true, - pre: () => ({ - wallet: WALLET, - printerCfg: PRINTER_CONFIG, - cryptoCode: 'BTC', - }), - test: ({ wallet, printerCfg, cryptoCode }) => - printer.printWallet(wallet, printerCfg, cryptoCode), - }, - ] -) + { + name: 'printWallet', + confirmationMessage: "Was a wallet printed?", + keepGoing: true, + pre: () => ({ + wallet: WALLET, + printerCfg: PRINTER_CONFIG, + cryptoCode: 'BTC', + }), + test: ({ wallet, printerCfg, cryptoCode }) => + printer.printWallet(wallet, printerCfg, cryptoCode), + }, + ], + error => + error.message === 'noPrinterConfiguredError' ? + null : + Promise.reject(error) + ) module.exports = steps From 0c19b4b49071a8f0e9f2ba83dfa98d17481f114e Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 11 Dec 2024 15:53:56 +0000 Subject: [PATCH 09/13] feat: add instruction message --- lib/hardware-testing/index.js | 19 +++++++++++++------ .../{confirm.js => readline.js} | 19 +++++++++++++++---- 2 files changed, 28 insertions(+), 10 deletions(-) rename lib/hardware-testing/{confirm.js => readline.js} (75%) diff --git a/lib/hardware-testing/index.js b/lib/hardware-testing/index.js index 880f01361..cdefbe09f 100644 --- a/lib/hardware-testing/index.js +++ b/lib/hardware-testing/index.js @@ -1,4 +1,4 @@ -const { createReadline, confirm, closeReadline } = require('./confirm') +const readline = require('./readline') const PERIPHERALS = { printer: require('./printer'), @@ -10,6 +10,7 @@ const defaultPost = (error, result) => error ? { error } : { result } const reducer = (acc, { name, + instructionMessage, confirmationMessage, skipConfirm = false, keepGoing = false, @@ -20,14 +21,20 @@ const reducer = (acc, { acc .then(([cont, report]) => { if (!cont) return [cont, report] - const args = pre() - return Promise.resolve(test(args)) // ensure it's a promise + return (instructionMessage ? + readline.instruct(instructionMessage) : + Promise.resolve() + ) + .then(() => { + const args = pre() + return test(args) + }) .then( result => [true, post(null, result)], error => [!!keepGoing, post(error, null)], ) .then(([cont, result]) => - (skipConfirm ? Promise.resolve(true) : confirm(confirmMessage)) + (skipConfirm ? Promise.resolve(true) : readline.confirm(confirmationMessage)) .then(worked => [cont, result, worked]) .catch(error => [false, result, false]) ) @@ -52,10 +59,10 @@ const runPeripheral = ([peripheral, steps]) => .then(result => [peripheral, result]) const runPeripherals = peripherals => { - createReadline() + readline.create() return Promise.all(Object.entries(peripherals).map(runPeripheral)) .then(Object.fromEntries) - .finally(() => closeReadline()) + .finally(() => readline.close()) } runPeripherals(PERIPHERALS) diff --git a/lib/hardware-testing/confirm.js b/lib/hardware-testing/readline.js similarity index 75% rename from lib/hardware-testing/confirm.js rename to lib/hardware-testing/readline.js index 8b104e549..b3bdd2749 100644 --- a/lib/hardware-testing/confirm.js +++ b/lib/hardware-testing/readline.js @@ -4,7 +4,7 @@ const { stdin: input, stdout: output } = require('node:process'); let rl = null -const createReadline = () => { +const create = () => { rl = readline.createInterface({ input, output, @@ -13,12 +13,22 @@ const createReadline = () => { } -const closeReadline = () => { +const close = () => { rl.close() rl = null } +const instruct = msg => new Promise((resolve, reject) => { + try { + rl.write(`\n${msg}\n`) + return resolve() + } catch (error) { + return reject(error) + } +}) + + const confirmOnce = msg => new Promise((resolve, reject) => rl.question( `${msg} ([y]es/[n]) `, @@ -44,7 +54,8 @@ const confirm = async msg => { module.exports = { - createReadline, + create, + instruct, confirm, - closeReadline, + close, } From a9f03da39bc1f201657f83eb3c62bce3467b156a Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 11 Dec 2024 17:20:44 +0000 Subject: [PATCH 10/13] feat: add basic bill validator tests --- lib/hardware-testing/bill-validator.js | 74 ++++++++++++++++++++++++++ lib/hardware-testing/index.js | 1 + 2 files changed, 75 insertions(+) create mode 100644 lib/hardware-testing/bill-validator.js diff --git a/lib/hardware-testing/bill-validator.js b/lib/hardware-testing/bill-validator.js new file mode 100644 index 000000000..21dfe84bf --- /dev/null +++ b/lib/hardware-testing/bill-validator.js @@ -0,0 +1,74 @@ +const { load } = require("../bill-validator") + +const DEVICE_CONFIG = { + deviceType: "cashflowSc", + rs232: { + device: "/dev/ttyUSB0", + }, +} + +const FIAT_CODE = 'EUR' + +const BOXES = { + numberOfCashboxes: 1, + cassettes: 0, + recyclers: 0, +} + +const testEnable = validator => () => new Promise( + (resolve, reject) => { + let accepted = false + const enable = () => { + accepted = false + validator.enable() + } + + validator.on('billsAccepted', () => { accepted = true }) + validator.on('billsRead', bills => resolve({ accepted, bills })) + validator.on('billsRejected', () => enable()) + validator.on('error', error => reject({ accepted, error })) + + enable() + } +).finally(() => validator.removeAllListeners()) + +const steps = () => + new Promise((resolve, reject) => { + const validator = load(DEVICE_CONFIG, false) + validator.setFiatCode(FIAT_CODE) + validator.run( + err => err ? reject(err) : resolve(validator), + BOXES, + ) + }) + .then(validator => [ + { + name: 'enableReject', + instructionMessage: `Try to insert a ${FIAT_CODE} bill...`, + confirmationMessage: "Did the validator retrieve the bill?", + test: testEnable(validator), + }, + + { + name: 'reject', + instructionMessage: `Try to insert a ${FIAT_CODE} bill...`, + confirmationMessage: "Did the validator reject the bill?", + test: () => validator.reject(), + }, + + { + name: 'enableStack', + instructionMessage: `Try to insert a ${FIAT_CODE} bill...`, + confirmationMessage: "Did the validator retrieve the bill?", + test: testEnable(validator), + }, + + { + name: 'stack', + instructionMessage: `Try to insert a ${FIAT_CODE} bill...`, + confirmationMessage: "Did the validator stack the bill?", + test: () => validator.stack(), + }, + ]) + +module.exports = steps diff --git a/lib/hardware-testing/index.js b/lib/hardware-testing/index.js index cdefbe09f..0ac5b3595 100644 --- a/lib/hardware-testing/index.js +++ b/lib/hardware-testing/index.js @@ -2,6 +2,7 @@ const readline = require('./readline') const PERIPHERALS = { printer: require('./printer'), + billValidator: require('./bill-validator'), } From 77167f0262bf80d2b2b55ea60355a1cf98dea296 Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 11 Dec 2024 19:41:34 +0000 Subject: [PATCH 11/13] fix: bill validator idling event --- lib/mei/cashflow_sc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mei/cashflow_sc.js b/lib/mei/cashflow_sc.js index 9e07f9572..9e4fd58ff 100644 --- a/lib/mei/cashflow_sc.js +++ b/lib/mei/cashflow_sc.js @@ -218,7 +218,7 @@ function parseStatus (data) { (data[0].returned || data[1].cheated || data[1].rejected) ? 'billsRejected' : data[1].jammed ? 'jam' : !data[1].cassetteAttached ? 'stackerOpen' : - data[0].idling ? 'idle' : + data[0].idling ? 'standby' : null } From c28c57767f1a7bb4c5cf5a2bd32ab7c601525dd7 Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 11 Dec 2024 19:44:22 +0000 Subject: [PATCH 12/13] refactor: improve basic bill validator tests --- lib/hardware-testing/bill-validator.js | 64 ++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/lib/hardware-testing/bill-validator.js b/lib/hardware-testing/bill-validator.js index 21dfe84bf..0b844f01d 100644 --- a/lib/hardware-testing/bill-validator.js +++ b/lib/hardware-testing/bill-validator.js @@ -15,6 +15,41 @@ const BOXES = { recyclers: 0, } +const addEventListeners = (validator, reject, eventListenersToAdd) => { + const allEvents = [ + 'actionRequiredMaintenance', + 'billsAccepted', + 'billsRead', + 'billsRejected', + 'billsValid', + 'cashSlotRemoveBills', + 'disconnected', + 'enabled', + 'error', + 'jam', + 'leftoverBillsInCashSlot', + 'stackerClosed', + 'stackerOpen', + 'standby', + ] + + const handleUnexpected = eventName => (...eventArguments) => { + const error = new Error("Unexpected event received") + error.eventName = eventName + error.eventArguments = [...eventArguments] + reject(error) + } + + allEvents.forEach( + ev => validator.on(ev, eventListenersToAdd[ev] ?? handleUnexpected) + ) +} + +const doFinally = validator => () => { + validator.removeAllListeners() + validator.disable() +} + const testEnable = validator => () => new Promise( (resolve, reject) => { let accepted = false @@ -23,14 +58,15 @@ const testEnable = validator => () => new Promise( validator.enable() } - validator.on('billsAccepted', () => { accepted = true }) - validator.on('billsRead', bills => resolve({ accepted, bills })) - validator.on('billsRejected', () => enable()) - validator.on('error', error => reject({ accepted, error })) + addEventListeners(validator, reject, { + billsAccepted: () => { accepted = true }, + billsRead: bills => resolve({ accepted, bills }), + billsRejected: () => enable(), + }) enable() } -).finally(() => validator.removeAllListeners()) +).finally(doFinally(validator)) const steps = () => new Promise((resolve, reject) => { @@ -51,9 +87,14 @@ const steps = () => { name: 'reject', - instructionMessage: `Try to insert a ${FIAT_CODE} bill...`, confirmationMessage: "Did the validator reject the bill?", - test: () => validator.reject(), + test: () => + new Promise((resolve, reject) => { + addEventListeners(validator, reject, { + billsRejected: () => resolve(), + }) + validator.reject() + }).finally(doFinally(validator)), }, { @@ -65,9 +106,14 @@ const steps = () => { name: 'stack', - instructionMessage: `Try to insert a ${FIAT_CODE} bill...`, confirmationMessage: "Did the validator stack the bill?", - test: () => validator.stack(), + test: () => + new Promise((resolve, reject) => { + addEventListeners(validator, reject, { + billsValid: () => resolve(), + }) + validator.stack() + }).finally(doFinally(validator)), }, ]) From 7d462cdce9b6bd3543bbbfd8b30a1b034011ddcd Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 11 Dec 2024 20:33:34 +0000 Subject: [PATCH 13/13] refactor: continue if confirmed or should keep going --- lib/hardware-testing/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hardware-testing/index.js b/lib/hardware-testing/index.js index 0ac5b3595..6c4569030 100644 --- a/lib/hardware-testing/index.js +++ b/lib/hardware-testing/index.js @@ -43,7 +43,7 @@ const reducer = (acc, { report = Object.assign(report, { [name]: { result, confirmed } }) - return [cont, report] + return [cont && (keepGoing || confirmed), report] }) })