import test from 'node:test'; import assert from 'node:assert/strict'; import {spawnSync} from 'node:child_process'; import {readFileSync,writeFileSync,mkdirSync,unlinkSync,chmodSync,existsSync} from 'node:fs'; import {join,dirname} from 'node:path'; import {fileURLToPath} from 'node:url'; import {makeRoot} from './helpers.mjs'; const ROOT=fileURLToPath(new URL('../../../',import.meta.url)).replace(/\/$/,''); const SCRIPT=join(ROOT,'scripts/discord-service.sh'); function setup(){const root=makeRoot(),copies=join(root,'copies'),id='a'.repeat(40),copy=join(copies,id),units=join(root,'units');mkdirSync(join(copy,'packages/discord/src'),{recursive:true});mkdirSync(join(copy,'node_modules/.bin'),{recursive:true});writeFileSync(join(copy,'packages/discord/src/cli.mjs'),'// fixture');writeFileSync(join(copy,'node_modules/.bin/pi'),'#!/bin/sh\nexit 0\n',{mode:0o755});const env={PATH:process.env.PATH,HOME:root,XDG_CONFIG_HOME:join(root,'config')};const run=args=>spawnSync(SCRIPT,args,{env,encoding:'utf8',timeout:5000});return {root,copies,id,copy,units,run,env};} test('pinned Discord render/install and missing prerequisites; checkout bytes unchanged',()=>{ const s=setup(),args=['--copy',s.id,'--copies',s.copies],r=s.run(['render',...args]);assert.equal(r.status,0,r.stderr); assert.ok(r.stdout.includes(`WorkingDirectory=${s.copy}\n`));assert.ok(r.stdout.includes(`ExecStart=${process.execPath} ${s.copy}/packages/discord/src/cli.mjs run %i --supervised --repo ${s.copy}\n`)); for(const line of r.stdout.split('\n').filter(l=>/^(ExecStart|WorkingDirectory)=/.test(l)))assert.ok(!line.includes(ROOT),line); const legacy=s.run(['render']);assert.equal(legacy.status,0,legacy.stderr); const nodeDir=dirname(process.execPath),originalPath='/usr/local/bin:/usr/bin:/bin',path=originalPath.split(':').includes(nodeDir)?originalPath:nodeDir+':'+originalPath; // Row 190: the default instance deletes the @CONFIG_ENV@ line. const expected=readFileSync(join(ROOT,'packages/discord/systemd/mosaic-discord@.service.in'),'utf8').replace('@CONFIG_ENV@\n','').replaceAll('@REPO@',ROOT).replaceAll('@PATH@',path);assert.equal(legacy.stdout,expected); assert.equal(r.stdout.split('\n').filter(l=>!/^(ExecStart|WorkingDirectory)=/.test(l)).join('\n'),legacy.stdout.split('\n').filter(l=>!/^(ExecStart|WorkingDirectory)=/.test(l)).join('\n')); const installed=s.run(['install',...args,'--dir',s.units,'--no-reload']);assert.equal(installed.status,0,installed.stderr);assert.equal(readFileSync(join(s.units,'mosaic-discord@.service'),'utf8'),r.stdout); for(const id of ['','short','b'.repeat(40)])assert.equal(s.run(['install','--copy',id,'--copies',s.copies,'--dir',join(s.root,'missing'),'--no-reload']).status,1); assert.ok(!existsSync(join(s.root,'missing/mosaic-discord@.service'))); const cli=join(s.copy,'packages/discord/src/cli.mjs');unlinkSync(cli);assert.equal(s.run(['install',...args,'--dir',join(s.root,'missing-cli'),'--no-reload']).status,1);assert.ok(!existsSync(join(s.root,'missing-cli/mosaic-discord@.service')));writeFileSync(cli,'// fixture');unlinkSync(join(s.copy,'node_modules/.bin/pi'));assert.equal(s.run(['install',...args,'--dir',join(s.root,'missing-pi'),'--no-reload']).status,1);assert.ok(!existsSync(join(s.root,'missing-pi/mosaic-discord@.service'))); }); test('Discord status reports effective WorkingDirectory from a fake user manager',()=>{ const s=setup(),bin=join(s.root,'bin');mkdirSync(bin);const script=join(bin,'systemctl');writeFileSync(script,`#!/bin/sh\ncase "$*" in *show*)echo 'WorkingDirectory=${s.copy}';; *)echo inactive;;esac\n`);chmodSync(script,0o755);s.env.PATH=bin+':'+s.env.PATH;const r=s.run(['status','fixture']);assert.equal(r.status,0,r.stderr);assert.ok(r.stdout.includes(`copy: ${s.id}`));assert.ok(r.stdout.includes(`tree: ${s.copy}`)); });