lamassu-server/packages/server/tests/unit/test_lnbits_plugin.js
padreug fc761844b7 feat: add LNBits wallet plugin integration
- Introduced LNBits as a Lightning Network wallet provider for Lamassu ATMs.
- Added configuration options for LNBits in the environment variables.
- Implemented core functionalities including invoice creation, payment processing, balance monitoring, and payment status tracking.
- Created unit tests for the LNBits plugin to ensure functionality and error handling.
- Updated development environment setup to include LNBits configuration.
2025-10-12 14:24:29 +02:00

257 lines
No EOL
6.5 KiB
JavaScript

const test = require('tape')
const sinon = require('sinon')
const axios = require('axios')
const BN = require('../../lib/bn')
// Mock the module before requiring the plugin
const lnbits = require('../../lib/plugins/wallet/lnbits/lnbits')
test('LNBits plugin - configuration validation', t => {
t.plan(3)
const validAccount = {
endpoint: 'https://demo.lnbits.com',
adminKey: 'test_admin_key_123'
}
const invalidAccount1 = {
endpoint: 'https://demo.lnbits.com'
// Missing adminKey
}
const invalidAccount2 = {
adminKey: 'test_admin_key_123'
// Missing endpoint
}
// Valid config should not throw
t.doesNotThrow(() => {
lnbits.balance(validAccount, 'LN').catch(() => {})
}, 'Valid configuration passes validation')
// Missing adminKey should throw
lnbits.balance(invalidAccount1, 'LN').catch(err => {
t.ok(err.message.includes('LNBits configuration missing: adminKey'),
'Missing adminKey throws appropriate error')
})
// Missing endpoint should throw
lnbits.balance(invalidAccount2, 'LN').catch(err => {
t.ok(err.message.includes('LNBits configuration missing: endpoint'),
'Missing endpoint throws appropriate error')
})
})
test('LNBits plugin - crypto code validation', t => {
t.plan(2)
const account = {
endpoint: 'https://demo.lnbits.com',
adminKey: 'test_admin_key'
}
// Valid crypto code
lnbits.balance(account, 'LN').catch(() => {
// Expected to fail at API call, not validation
})
t.pass('LN crypto code is accepted')
// Invalid crypto code
lnbits.balance(account, 'BTC').catch(err => {
t.ok(err.message.includes('Unsupported crypto'),
'Invalid crypto code throws appropriate error')
})
})
test('LNBits plugin - invoice detection', t => {
t.plan(6)
// Valid Lightning invoices
t.ok(lnbits.isLnInvoice('lnbc100n1p0example'), 'Detects mainnet Lightning invoice')
t.ok(lnbits.isLnInvoice('lntb100n1p0example'), 'Detects testnet Lightning invoice')
t.ok(lnbits.isLnInvoice('lnbcrt100n1p0example'), 'Detects regtest Lightning invoice')
// Invalid invoices
t.notOk(lnbits.isLnInvoice('bc1qexample'), 'Rejects Bitcoin address')
t.notOk(lnbits.isLnInvoice('lnurl1234'), 'Rejects LNURL')
t.notOk(lnbits.isLnInvoice(''), 'Rejects empty string')
})
test('LNBits plugin - LNURL detection', t => {
t.plan(3)
t.ok(lnbits.isLnurl('lnurl1dp68gurn8ghj7um9'), 'Detects LNURL')
t.notOk(lnbits.isLnurl('lnbc100n1p0example'), 'Rejects Lightning invoice')
t.notOk(lnbits.isLnurl(''), 'Rejects empty string')
})
test('LNBits plugin - network detection', t => {
t.plan(4)
const mainnetAccount = {
endpoint: 'https://lnbits.com',
adminKey: 'test'
}
const testnetAccount = {
endpoint: 'https://testnet.lnbits.com',
adminKey: 'test'
}
const regtestAccount = {
endpoint: 'http://localhost:5000',
adminKey: 'test'
}
const testAccount = {
endpoint: 'https://test.lnbits.com',
adminKey: 'test'
}
t.equal(lnbits.cryptoNetwork(mainnetAccount, 'LN'), 'main', 'Detects mainnet')
t.equal(lnbits.cryptoNetwork(testnetAccount, 'LN'), 'test', 'Detects testnet')
t.equal(lnbits.cryptoNetwork(regtestAccount, 'LN'), 'regtest', 'Detects regtest/local')
t.equal(lnbits.cryptoNetwork(testAccount, 'LN'), 'test', 'Detects test environment')
})
test('LNBits plugin - newAddress creates invoice', async t => {
t.plan(3)
const account = {
endpoint: 'https://demo.lnbits.com',
adminKey: 'test_admin_key'
}
const tx = {
cryptoAtoms: new BN('100000'),
cryptoCode: 'LN'
}
const info = {
cryptoCode: 'LN'
}
// Mock the axios request
const stub = sinon.stub(axios, 'request')
stub.resolves({
data: {
payment_request: 'lnbc1000n1p0example',
payment_hash: 'abc123'
}
})
try {
const invoice = await lnbits.newAddress(account, info, tx)
t.equal(invoice, 'lnbc1000n1p0example', 'Returns Lightning invoice')
t.ok(stub.calledOnce, 'Makes one API request')
const callArgs = stub.firstCall.args[0]
t.equal(callArgs.data.amount, 100000, 'Sends correct amount in satoshis')
} catch (err) {
t.fail(`Unexpected error: ${err.message}`)
} finally {
stub.restore()
}
})
test('LNBits plugin - balance returns BN', async t => {
t.plan(2)
const account = {
endpoint: 'https://demo.lnbits.com',
adminKey: 'test_admin_key'
}
// Mock the axios request
const stub = sinon.stub(axios, 'request')
stub.resolves({
data: {
balance: 500000000, // 500000 sats in millisats
name: 'Test Wallet'
}
})
try {
const balance = await lnbits.balance(account, 'LN')
t.ok(balance instanceof BN, 'Returns BigNumber instance')
t.equal(balance.toString(), '500000', 'Converts millisats to sats correctly')
} catch (err) {
t.fail(`Unexpected error: ${err.message}`)
} finally {
stub.restore()
}
})
test('LNBits plugin - getStatus checks payment', async t => {
t.plan(3)
const account = {
endpoint: 'https://demo.lnbits.com',
adminKey: 'test_admin_key'
}
const paidTx = {
toAddress: 'lnbc1000n1p3q7vlpp5example',
cryptoCode: 'LN'
}
// Mock bolt11 decode
const bolt11 = require('bolt11')
const decodeStub = sinon.stub(bolt11, 'decode')
decodeStub.returns({
tagsObject: {
payment_hash: 'abc123'
}
})
// Mock axios for paid invoice
const axiosStub = sinon.stub(axios, 'request')
axiosStub.onFirstCall().resolves({
data: {
paid: true,
amount: 1000
}
})
try {
const status = await lnbits.getStatus(account, paidTx)
t.equal(status.status, 'confirmed', 'Returns confirmed for paid invoice')
} catch (err) {
t.fail(`Unexpected error: ${err.message}`)
}
// Test pending invoice
axiosStub.onSecondCall().resolves({
data: {
paid: false,
pending: true
}
})
try {
const status = await lnbits.getStatus(account, paidTx)
t.equal(status.status, 'pending', 'Returns pending for unpaid but pending invoice')
} catch (err) {
t.fail(`Unexpected error: ${err.message}`)
}
// Test not found invoice
axiosStub.onThirdCall().rejects({
response: {
status: 404,
data: { detail: 'Payment not found' }
}
})
try {
const status = await lnbits.getStatus(account, paidTx)
t.equal(status.status, 'notSeen', 'Returns notSeen for not found invoice')
} catch (err) {
t.fail(`Unexpected error: ${err.message}`)
} finally {
decodeStub.restore()
axiosStub.restore()
}
})