110 lines
5.8 KiB
JavaScript
110 lines
5.8 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const vm = require('node:vm');
|
|
const html = fs.readFileSync('docker/lnd-ui/index.html', 'utf8');
|
|
function extract(name) {
|
|
const start = html.indexOf(' function '+name+'(');
|
|
const end = html.indexOf('\n }', start)+10;
|
|
assert.ok(start >= 0 && end > start);
|
|
return html.slice(start, end);
|
|
}
|
|
function fixture() {
|
|
const elements = new Map();
|
|
const el = id => { if (!elements.has(id)) elements.set(id,{style:{},textContent:'',className:''}); return elements.get(id) };
|
|
const ctx = {state:{info:null,readiness:null}, document:{getElementById:el}, setText:(id,v)=>el(id).textContent=v,fmtCount:String};
|
|
vm.createContext(ctx);
|
|
vm.runInContext(extract('renderHeader')+'\n'+extract('validBalance'),ctx);
|
|
return {ctx,el};
|
|
}
|
|
test('missing, starting and syncing Bitcoin each show waiting and recover',()=>{
|
|
const {ctx,el}=fixture();
|
|
for (const [state,message] of [['waiting_install','Waiting for Bitcoin to be installed'],['waiting_start','Waiting for Bitcoin to start'],['waiting_sync','Waiting for Bitcoin to sync']]) {
|
|
ctx.state.readiness={state,message}; ctx.renderHeader();
|
|
assert.equal(el('headerStatusText').textContent,message);
|
|
assert.equal(el('syncCard').style.display,'');
|
|
assert.match(el('syncSubtitle').textContent,/automatically/);
|
|
assert.equal(el('syncPercent').textContent,'');
|
|
}
|
|
ctx.state.readiness={state:'bitcoin_ready'};
|
|
ctx.state.info={synced_to_chain:true,synced_to_graph:true};
|
|
ctx.renderHeader();
|
|
assert.equal(el('headerStatusText').textContent,'Running');
|
|
assert.equal(el('syncCard').style.display,'none');
|
|
ctx.state.info=null;ctx.state.readiness=null;ctx.renderHeader();
|
|
assert.equal(el('headerStatusText').textContent,'Connecting to LND');
|
|
});
|
|
test('balances reject missing, malformed, fractional and negative values; real zero remains valid',()=>{
|
|
const {ctx}=fixture();
|
|
for (const v of [null,undefined,'',{},false,-1,'-1','garbage',1.5,'1.5',Infinity,Number.MAX_SAFE_INTEGER+1]) assert.equal(ctx.validBalance(v),false,String(v));
|
|
for(const v of [0,'0',123,'123']) assert.equal(ctx.validBalance(v),true,String(v));
|
|
});
|
|
|
|
test('failed and partial balance polls retain known balances and label them stale; recovery clears flags',async()=>{
|
|
const {ctx,el}=fixture();
|
|
el('refreshIcon').classList={add(){},remove(){}};
|
|
const start=html.indexOf(' async function refreshAll()');
|
|
const end=html.indexOf('\n }',start)+10;
|
|
vm.runInContext(html.slice(start,end),ctx);
|
|
let responses={
|
|
'/v1/getinfo':{synced_to_chain:true},
|
|
'/v1/balance/blockchain':{confirmed_balance:'500',unconfirmed_balance:'0'},
|
|
'/v1/balance/channels':{local_balance:{sat:'250'}},
|
|
};
|
|
ctx.lndSafe=async(path,fallback)=>responses[path]??fallback;
|
|
ctx.renderAll=()=>{};
|
|
await ctx.refreshAll();
|
|
assert.equal(ctx.state.onchain.confirmed_balance,'500');
|
|
assert.equal(ctx.state.chanbal.local_balance.sat,'250');
|
|
responses={};await ctx.refreshAll();
|
|
assert.equal(ctx.state.onchain.confirmed_balance,'500');
|
|
assert.equal(ctx.state.chanbal.local_balance.sat,'250');
|
|
assert.equal(ctx.state.onchainStale,true);assert.equal(ctx.state.chanbalStale,true);
|
|
responses={'/v1/balance/blockchain':{confirmed_balance:'0'},'/v1/balance/channels':{error:'locked'}};
|
|
await ctx.refreshAll();
|
|
assert.equal(ctx.state.onchain.confirmed_balance,'0');
|
|
assert.equal(ctx.state.chanbal.local_balance.sat,'250');
|
|
assert.equal(ctx.state.onchainStale,false);assert.equal(ctx.state.chanbalStale,true);
|
|
responses={'/v1/balance/blockchain':{confirmed_balance:'600'},'/v1/balance/channels':{local_balance:{sat:'300'}}};
|
|
await ctx.refreshAll();
|
|
assert.equal(ctx.state.onchain.confirmed_balance,'600');
|
|
assert.equal(ctx.state.chanbal.local_balance.sat,'300');
|
|
assert.equal(ctx.state.onchainStale,false);assert.equal(ctx.state.chanbalStale,false);
|
|
});
|
|
|
|
test('waiting renders promptly without querying unavailable LND endpoints, then resumes after Bitcoin sync',async()=>{
|
|
const {ctx,el}=fixture();
|
|
el('refreshIcon').classList={add(){},remove(){}};
|
|
const start=html.indexOf(' async function refreshAll()');
|
|
vm.runInContext(html.slice(start,html.indexOf('\n }',start)+10),ctx);
|
|
let readiness={state:'waiting_sync',message:'Waiting for Bitcoin to sync'};
|
|
const calls=[];let renders=0;
|
|
ctx.lndSafe=async(path,fallback)=>{calls.push(path);return path==='/archy-status'?readiness:fallback};
|
|
ctx.renderAll=()=>{renders++;ctx.renderHeader()};
|
|
await ctx.refreshAll();
|
|
assert.deepEqual(calls,['/archy-status']);
|
|
assert.equal(renders,1);
|
|
assert.equal(el('headerStatusText').textContent,'Waiting for Bitcoin to sync');
|
|
assert.equal(ctx.state.onchain,undefined);
|
|
assert.equal(ctx.state.refreshing,false);
|
|
readiness={state:'bitcoin_ready',message:'Bitcoin is ready'};
|
|
await ctx.refreshAll();
|
|
assert.ok(calls.includes('/v1/getinfo'));
|
|
assert.ok(calls.includes('/v1/balance/blockchain'));
|
|
});
|
|
|
|
test('cold waiting never invents zero channel capacity or an empty wallet recommendation',()=>{
|
|
const {ctx,el}=fixture();
|
|
Object.assign(ctx,{num:v=>Number(v)||0,fmtAmount:String,fmtAmountShort:String,setBalance:(id,v)=>el(id).value=v});
|
|
vm.runInContext(extract('renderBalances')+'\n'+extract('renderSummary')+'\n'+extract('renderChannels'),ctx);
|
|
ctx.state.channels=[];
|
|
ctx.renderBalances();ctx.renderSummary();ctx.renderChannels();
|
|
for(const id of ['liqLocal','liqRemote','statActiveChannels','healthPending','chActive']) assert.equal(el(id).textContent,'—');
|
|
assert.equal(el('balTotal').value,null);
|
|
assert.match(el('liqHint').textContent,/waiting for LND/);
|
|
assert.doesNotMatch(el('channelList').innerHTML,/No payment channels yet/);
|
|
ctx.state.info={};ctx.state.chanbal={local_balance:{sat:'0'}};
|
|
ctx.renderBalances();
|
|
assert.equal(el('liqLocal').textContent,'0');
|
|
});
|