Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LAM-1212 feat: add printer and bill validator testing #1219

Draft
wants to merge 14 commits into
base: dev
Choose a base branch
from
Draft
26 changes: 26 additions & 0 deletions lib/bill-validator.js
Original file line number Diff line number Diff line change
@@ -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 }
25 changes: 2 additions & 23 deletions lib/brain.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 () {
Expand Down
120 changes: 120 additions & 0 deletions lib/hardware-testing/bill-validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
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 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
const enable = () => {
accepted = false
validator.enable()
}

addEventListeners(validator, reject, {
billsAccepted: () => { accepted = true },
billsRead: bills => resolve({ accepted, bills }),
billsRejected: () => enable(),
})

enable()
}
).finally(doFinally(validator))

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',
confirmationMessage: "Did the validator reject the bill?",
test: () =>
new Promise((resolve, reject) => {
addEventListeners(validator, reject, {
billsRejected: () => resolve(),
})
validator.reject()
}).finally(doFinally(validator)),
},

{
name: 'enableStack',
instructionMessage: `Try to insert a ${FIAT_CODE} bill...`,
confirmationMessage: "Did the validator retrieve the bill?",
test: testEnable(validator),
},

{
name: 'stack',
confirmationMessage: "Did the validator stack the bill?",
test: () =>
new Promise((resolve, reject) => {
addEventListeners(validator, reject, {
billsValid: () => resolve(),
})
validator.stack()
}).finally(doFinally(validator)),
},
])

module.exports = steps
71 changes: 71 additions & 0 deletions lib/hardware-testing/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const readline = require('./readline')

const PERIPHERALS = {
printer: require('./printer'),
billValidator: require('./bill-validator'),
}


const defaultPre = () => null
const defaultPost = (error, result) => error ? { error } : { result }

const reducer = (acc, {
name,
instructionMessage,
confirmationMessage,
skipConfirm = false,
keepGoing = false,
pre = defaultPre,
test,
post = defaultPost,
}) =>
acc
.then(([cont, report]) => {
if (!cont) return [cont, report]
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) : readline.confirm(confirmationMessage))
.then(worked => [cont, result, worked])
.catch(error => [false, result, false])
)
.then(([cont, result, confirmed]) => {
report = Object.assign(report, {
[name]: { result, confirmed }
})
return [cont && (keepGoing || confirmed), report]
})
})


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 => {
readline.create()
return Promise.all(Object.entries(peripherals).map(runPeripheral))
.then(Object.fromEntries)
.finally(() => readline.close())
}

runPeripherals(PERIPHERALS)
.then(results => console.log(JSON.stringify(results, null, 2)))
.catch(console.log)
83 changes: 83 additions & 0 deletions lib/hardware-testing/printer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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',
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',
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
Loading