/* eslint-disable @typescript-eslint/no-require-imports -- CommonJS fixtures exercise the native Node process supervisor. */ const {test}=require('node:test');const assert=require('node:assert/strict');const {spawn}=require('node:child_process');const {mkdtempSync,mkdirSync,writeFileSync,rmSync}=require('node:fs');const {tmpdir}=require('node:os');const {join,resolve}=require('node:path'); function fixture(server,worker){const root=mkdtempSync(join(tmpdir(),'kita-runtime-test-'));mkdirSync(join(root,'scripts'));writeFileSync(join(root,'server.js'),server);writeFileSync(join(root,'scripts/notdienst-alarm-worker.mjs'),worker);return root;} function waitForOutput(child,marker){return new Promise((resolvePromise,reject)=>{let text='';const timer=setTimeout(()=>reject(new Error(`Missing ${marker}`)),10000);child.stdout.on('data',chunk=>{text+=chunk;if(text.includes(marker)){clearTimeout(timer);resolvePromise(text);}});});} function exited(child){return new Promise(resolvePromise=>child.once('exit',(code,signal)=>resolvePromise({code,signal})));} function gone(pid){try{process.kill(pid,0);return false;}catch{return true;}} test('Worker crash is restarted while the server stays alive; shutdown stops both',async()=>{ const root=fixture('console.log(`server:${process.pid}`);setInterval(()=>{},1000);',`import {existsSync,writeFileSync} from 'node:fs';if(!existsSync('once')){writeFileSync('once','1');console.log('worker-crashed');process.exit(1);}console.log(\`worker-ready:\${process.pid}\`);setInterval(()=>{},1000);`); const child=spawn(process.execPath,[resolve('scripts/start-runtime.mjs')],{cwd:root,env:{...process.env,NOTDIENST_JOB_SECRET:'test-only-secret-32-characters-long'},stdio:['ignore','pipe','pipe']}); try{const text=await waitForOutput(child,'worker-ready:');const serverPid=Number(text.match(/server:(\d+)/)[1]);const workerPid=Number(text.match(/worker-ready:(\d+)/)[1]);assert.equal(gone(serverPid),false);const ended=exited(child);child.kill('SIGTERM');assert.equal((await ended).code,0);assert.equal(gone(serverPid),true);assert.equal(gone(workerPid),true);}finally{if(child.exitCode===null)child.kill('SIGTERM');rmSync(root,{recursive:true,force:true});} }); test('Server failure shuts down the worker and preserves the failure exit code',async()=>{ const root=fixture('setTimeout(()=>process.exit(7),150);', 'console.log(`worker:${process.pid}`);setInterval(()=>{},1000);'); const child=spawn(process.execPath,[resolve('scripts/start-runtime.mjs')],{cwd:root,env:{...process.env,NOTDIENST_JOB_SECRET:'test-only-secret-32-characters-long'},stdio:['ignore','pipe','pipe']}); try{const ended=exited(child);const text=await waitForOutput(child,'worker:');const pid=Number(text.match(/worker:(\d+)/)[1]);assert.equal((await ended).code,7);assert.equal(gone(pid),true);}finally{if(child.exitCode===null)child.kill('SIGTERM');rmSync(root,{recursive:true,force:true});} });