Files

53 lines
3.5 KiB
JavaScript
Raw Permalink Normal View History

// Execute the actual destructive-action callback using an inert transport.
const assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path'),vm=require('node:vm')
const app=path.resolve(__dirname,'../AppImage'),ts=require(path.join(app,'node_modules/typescript'))
const tree=ts.createSourceFile('controls.tsx',fs.readFileSync(path.join(app,'components/host-update-status.tsx'),'utf8'),ts.ScriptTarget.Latest,true,ts.ScriptKind.TSX)
let callback,triggerDisabled
function walk(n){
if(ts.isVariableDeclaration(n)&&n.name.getText(tree)==='confirm')callback=n.initializer.getText(tree)
if(ts.isJsxElement(n)&&n.openingElement.tagName.getText(tree)==='Button'&&n.children.some(c=>c.getText(tree).includes('healthStatus.restart.button'))){
triggerDisabled=n.openingElement.attributes.properties.find(p=>p.name?.getText(tree)==='disabled').initializer.expression.getText(tree)
}
ts.forEachChild(n,walk)
}
walk(tree);assert.ok(callback)
function fixture(){
const calls=[],events=[]
const scope={AbortController,JSON,Error,disabled:false,unavailable:false,context:{allowed:true,hostname:'fixture-host',boot_id:'fixture-boot'},submitted:false,
sending:{current:false},alive:{current:true},postRequest:{current:null},getApiUrl:p=>p,headers:()=>({}),
setSubmitted:v=>events.push(['submitted',v]),setPending:v=>events.push(['pending',v]),setError:v=>events.push(['error',v]),
setAccepted:v=>events.push(['accepted',v]),setOpen:v=>events.push(['open',v]),
setTimeout:()=>1,clearTimeout(){},fetch:async(p,options)=>{calls.push({p,options});return {status:202,json:async()=>({accepted:true})}}}
const run=vm.runInNewContext(ts.transpileModule('('+callback+')',{compilerOptions:{target:ts.ScriptTarget.ES2020}}).outputText,scope)
return {scope,calls,events,run}
}
async function main(){
assert.ok(triggerDisabled)
assert.equal(vm.runInNewContext(triggerDisabled,{disabled:false,unavailable:false,context:{allowed:true},accepted:false,pending:true}),true,'closing confirmation while its request is pending must not enable another request')
for(const block of ['disabled','unavailable','submitted','sending','missing-context','not-allowed']){
const x=fixture()
if(block==='sending')x.scope.sending.current=true
else if(block==='missing-context')x.scope.context=null
else if(block==='not-allowed')x.scope.context.allowed=false
else x.scope[block]=true
await x.run();assert.equal(x.calls.length,0);assert.equal(x.events.length,0)
}
const x=fixture();await x.run();await x.run()
assert.equal(x.calls.length,1,'duplicate confirm never sends a second request')
assert.equal(x.calls[0].p,'/api/health/reboot-after-update')
assert.equal(x.calls[0].options.method,'POST')
assert.deepEqual(JSON.parse(x.calls[0].options.body),{confirm:true,hostname:'fixture-host',boot_id:'fixture-boot'})
assert.ok(x.events.some(e=>e[0]==='accepted'&&e[1]===true))
assert.equal(x.scope.postRequest.current,null)
for(const response of [{status:409},{status:202,json:async()=>({accepted:false})}]){
const e=fixture();e.scope.fetch=async()=>response;await e.run()
assert.ok(e.events.some(v=>v[0]==='error'&&v[1]===true))
assert.ok(!e.events.some(v=>v[0]==='accepted'))
}
const e=fixture();e.scope.fetch=async()=>{throw Error('unknown outcome')};await e.run()
assert.ok(!e.events.some(v=>v[0]==='accepted'))
assert.ok(e.scope.sending.current,'unknown outcome is not automatically retried')
console.log('PASS actual confirmation guards, exact payload, duplicate suppression and unknown outcomes')
}
main().catch(e=>{console.error(e);process.exitCode=1})