Files
MikrotikManager/backend/scripts/acceptance/run-installation-memory.mjs
T

64 lines
5.3 KiB
JavaScript

/** Local smoke preparation only: owns a new embedded PG and one compiled child.
* Does not represent frontend/portal/Docker/whole-installation acceptance. */
import assert from 'node:assert/strict'
import fs from 'node:fs/promises'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { join,resolve,dirname,basename } from 'node:path'
import { fileURLToPath } from 'node:url'
import { createServer } from 'node:net'
import { randomBytes,createHash } from 'node:crypto'
import { spawn } from 'node:child_process'
const root=resolve(dirname(fileURLToPath(import.meta.url)),'../../..')
assert(process.argv.length>=3&&process.argv.length<=5,'Supply a unique evidence name, optional compiled output and delayed-approval scenario')
const delayedApproval=process.argv[4]==='delayed-approval'
assert(process.argv[4]===undefined||delayedApproval,'Unknown isolated scenario')
const name=process.argv[2];assert(/^[a-z0-9][a-z0-9-]{3,99}$/.test(name),'Invalid evidence name')
const outputName=process.argv[3]??'dist-installation-memory'
assert(/^dist-installation-memory(?:-[a-z0-9-]{3,64})?$/.test(outputName),'Invalid compiled output name')
const prefix=resolve(root,'tmp',name), statusPath=prefix+'-status.json'
const exclusive=await fs.open(statusPath,'wx');await exclusive.close()
const state={scope:'local-backend-coload-only',wholeInstallation2GbAccepted:false,compiledOutputName:outputName,delayedHumanApprovalScenario:delayedApproval,status:'starting',nodeExitCode:null,sourcePinsUnchanged:false,cleanupConfirmed:false,startedAt:new Date().toISOString(),wrapperPid:process.pid}
const save=()=>fs.writeFile(statusPath,JSON.stringify(state,null,2)+'\n');await save()
const require=createRequire(new URL('../../package.json',import.meta.url)),EmbeddedPostgres=require('embedded-postgres').default
const directory=await fs.mkdtemp(join(tmpdir(),'mm-mcp-test-')),server=createServer()
await new Promise(done=>server.listen(0,'127.0.0.1',done));const port=server.address().port;await new Promise(done=>server.close(done))
const password=randomBytes(24).toString('hex')
const postgres=new EmbeddedPostgres({databaseDir:join(directory,'pg'),port,user:'mcp_test',password,persistent:true,createPostgresUser:false,
initdbFlags:['--encoding=UTF8'],postgresFlags:['-h','127.0.0.1'],onLog:()=>{},onError:()=>{}})
const output=resolve(root,'backend',outputName),entry=resolve(output,'services/installation-co-load.owned.test.js')
async function pins() {
const rows=[]
async function walk(dir){for(const item of await fs.readdir(dir,{withFileTypes:true})){const file=join(dir,item.name);if(item.isDirectory())await walk(file);else rows.push({path:file.slice(root.length+1).replaceAll('\\','/'),sha256:createHash('sha256').update(await fs.readFile(file)).digest('hex')})}}
await walk(output);return rows.sort((a,b)=>a.path.localeCompare(b.path))
}
const before=await pins();await fs.writeFile(prefix+'-pins.json',JSON.stringify({before},null,2)+'\n')
let log,timer
try {
await postgres.initialise();await postgres.start()
log=await fs.open(prefix+'.log','wx')
const retained=Object.fromEntries(['SystemRoot','WINDIR','TEMP','TMP','LANG','LC_ALL'].filter(key=>process.env[key]).map(key=>[key,process.env[key]]))
const child=spawn(process.execPath,['--max-old-space-size=192','--max-semi-space-size=16',entry],{cwd:directory,windowsHide:true,stdio:['ignore',log.fd,log.fd],env:{...retained,
NODE_OPTIONS:'',NODE_ENV:'test',PORT:'8000',AUTH_ISSUER:'https://auth.shnt.top',AUTH_PORTAL_URL:'http://localhost:5175',SQLITE_IMPORT_FULL_HISTORY:'false',
MM_FLOW_READ_RESOURCE_PROFILE:'constrained',DATABASE_URL:`postgresql://mcp_test:${password}@127.0.0.1:${port}/postgres`,DATABASE_PATH:join(directory,'no-legacy.sqlite'),
MCP_STATE_DIR:join(directory,'state'),AUTH_REQUIRED:'true',AUTH_JWT_SECRET:randomBytes(32).toString('hex'),MCP_PUBLIC_URL:'http://localhost:8000/mcp',MMAPP_OWN_INSTALLATION_COLOAD:'true',MMAPP_OWN_COLOAD_DELAYED_APPROVAL:String(delayedApproval)}})
state.status='running';state.childPid=child.pid;await save()
timer=setTimeout(()=>{state.outerTimedOut=true;child.kill()},300000)
state.nodeExitCode=await new Promise((done,reject)=>{child.once('error',reject);child.once('exit',code=>done(code??1))})
clearTimeout(timer);await log.close();log=undefined
const after=await pins();state.sourcePinsUnchanged=JSON.stringify(before)===JSON.stringify(after)
await fs.writeFile(prefix+'-pins.json',JSON.stringify({before,after,unchanged:state.sourcePinsUnchanged},null,2)+'\n')
process.exitCode=state.nodeExitCode===0&&state.sourcePinsUnchanged?0:1
}catch(error){state.failure=error instanceof Error?error.name:'UnknownError';process.exitCode=1}
finally {
if(timer)clearTimeout(timer);if(log)await log.close()
try{
if(postgres.process?.exitCode===null)await postgres.stop()
const actual=await fs.realpath(directory),parent=await fs.realpath(tmpdir())
assert.equal(dirname(actual),parent);assert.match(basename(actual),/^mm-mcp-test-[A-Za-z0-9-]+$/);assert.equal((await fs.lstat(directory)).isSymbolicLink(),false)
await fs.rm(actual,{recursive:true,force:true,maxRetries:20,retryDelay:100});state.cleanupConfirmed=true
}catch{state.cleanupConfirmed=false;process.exitCode=1}
state.status='terminal';state.finishedAt=new Date().toISOString();await save();console.log(JSON.stringify(state))
}
if(state.cleanupConfirmed)process.exit(process.exitCode??1)