igneum/relay/test/service.test.mjs

112 lines
7.8 KiB
JavaScript

// node --test relay/test/service.test.mjs The relay agent as a logon task and the start-app kind (MF-11, 7 October
// 2026). Structural checks of the manifest, the installer, the hidden loop, the agent's branch and the Mac tool; the
// PowerShell 5.1 parse of the .ps1 files is windows.yml's job.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { KINDS, AGENT_KINDS } from '../lib/relay.mjs';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const read = p => readFileSync(join(ROOT, p), 'utf8');
test('the service manifest: at logon, the run level the installer chooses, interactive, restarted on failure, no time limit, one instance, hidden window', () => {
const x = read('relay/clients/IgneumRelayService.xml');
assert.match(x, /<LogonTrigger>[\s\S]*<UserId>__USER__<\/UserId>[\s\S]*<\/LogonTrigger>/);
assert.match(x, /<Principal id="Author">[\s\S]*<UserId>__USER__<\/UserId>[\s\S]*<LogonType>InteractiveToken<\/LogonType>[\s\S]*<RunLevel>__RUNLEVEL__<\/RunLevel>/);
const restart = /<RestartOnFailure>\s*<Interval>PT(\d+)M<\/Interval>\s*<Count>(\d+)<\/Count>\s*<\/RestartOnFailure>/.exec(x);
assert.ok(restart, 'RestartOnFailure with Interval and Count');
assert.ok(Number(restart[1]) <= 5 && Number(restart[2]) >= 100, `restart every ${restart[1]} min, ${restart[2]} times`);
assert.match(x, /<ExecutionTimeLimit>PT0S<\/ExecutionTimeLimit>/, 'no time limit');
assert.match(x, /<MultipleInstancesPolicy>IgnoreNew<\/MultipleInstancesPolicy>/);
assert.match(x, /<DisallowStartIfOnBatteries>false<\/DisallowStartIfOnBatteries>/);
assert.match(x, /<StopIfGoingOnBatteries>false<\/StopIfGoingOnBatteries>/);
assert.match(x, /<StartWhenAvailable>true<\/StartWhenAvailable>/);
assert.match(x, /<Command>powershell\.exe<\/Command>\s*<Arguments>-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "__AGENT_DIR__\\igneum-agent-service\.ps1"<\/Arguments>/);
assert.match(x, /<URI>\\IgneumRelayService<\/URI>/);
});
test('the installer fills the manifest and registers, runs and reads back the task; it carries no secret', () => {
const s = read('relay/clients/install-agent.ps1');
assert.doesNotMatch(s, /#Requires -RunAsAdministrator/, 'a per-user task needs no administrator (PC 2 has nobody to click a prompt)');
assert.match(s, /\.Replace\('__RUNLEVEL__', \$level\)/);
assert.match(s, /\$level = 'LeastPrivilege'[\s\S]*if \(\$Highest\) \{ \$level = 'HighestAvailable' \}/);
// the stale one-shot tasks (PC 2, 7 October 2026: a logon task at a path that was not there popped a dialog at every boot) go first
assert.match(s, /IgneumRelayAgent\*/);
assert.match(s, /schtasks\.exe \/Delete \/F \/TN \$t/);
assert.match(s, /RunOnce/);
assert.match(s, /\.Replace\('__USER__', \$user\)\.Replace\('__AGENT_DIR__', \$Here\)/);
assert.match(s, /schtasks\.exe \/Create \/F \/TN \$TaskName \/XML \$tmp/);
assert.match(s, /\$TaskName = 'IgneumRelayService'/);
assert.match(s, /schtasks\.exe \/Run \/TN \$TaskName/);
assert.match(s, /schtasks\.exe \/Query \/TN \$TaskName/);
assert.match(s, /machine-secret\.txt/, 'the zip must carry the machine secret for the agent to run anything');
assert.doesNotMatch(s, /__RELAY_(URL|KEY|TOKEN)__|x-relay-token|x-igneum-key/, 'the installer touches no secret');
assert.match(s, /-Remove/);
});
test('the hidden loop keeps the agent running for ever, 15 s after any exit, logs without the idle line, and a missing agent path is a log line, never a dialog', () => {
const s = read('relay/clients/igneum-agent-service.ps1');
// the task runs the loop by its full path under -WindowStyle Hidden (the manifest), and the loop itself opens nothing:
// a missing igneum-agent.ps1 is one line in the log and a 60 s wait, no `cmd /c start`, no message box, no exit
const missing = s.slice(s.indexOf('if (-not (Test-Path $Agent))'), s.indexOf('Note \'starting igneum-agent.ps1\''));
assert.match(missing, /Note \('no agent at ' \+ \$Agent \+ '; waiting'\); Start-Sleep -Seconds 60; continue/);
assert.doesNotMatch(s, /cmd(\.exe)? \/c start|MessageBox|Read-Host|\[System\.Windows\.Forms/);
assert.match(s, /-File \$Agent/, 'the agent runs by its full path');
assert.match(s, /ForEach-Object \{ Add-Content -Path \$log -Value \$_ \}/, 'one write per line: the log is never held open');
assert.match(s, /\$Agent = Join-Path \$Here 'igneum-agent\.ps1'/);
assert.match(s, /while \(\$true\) \{/);
assert.match(s, /powershell\.exe -NoProfile -ExecutionPolicy Bypass -File \$Agent/);
assert.match(s, /Start-Sleep -Seconds 15/);
assert.match(s, /-notmatch 'idle as '/);
assert.match(s, /agent-service\.log/);
assert.doesNotMatch(s, /api\/(quit|pause|resume)/);
});
test('the agent runs a start-app task through its own Start-App after the tag check, never the body, never elevated, never a quit', () => {
const s = read('relay/clients/igneum-agent.ps1');
const run = s.slice(s.indexOf('function Run-Task'), s.indexOf('Log ("igneum relay agent on'));
const check = run.indexOf('$why = Check-Task $task');
const nonce = run.indexOf('Add-Content -Path $NonceFile -Value $task.flags.nonce');
const branch = run.indexOf("if (\"$($task.kind)\" -eq 'start-app') {");
const script = run.indexOf('[IO.File]::WriteAllText($script');
assert.ok(check > 0 && nonce > check && branch > nonce && script > branch, 'tag check, nonce recorded, the start-app branch, and only then a script is written');
assert.match(run, /\$r = Start-App[\s\S]*Post-Result \$task \(\[int\]\$r\.code\) \$log \$r\.note[\s\S]*return\s*\}/);
const start = s.slice(s.indexOf('function Start-App'), s.indexOf('function Sha256-Hex'));
assert.match(start, /\/RL LIMITED/, 'an elevated agent starts the app at the limited run level');
assert.match(start, /Start-Process -FilePath \$exe -ArgumentList '--launch'/);
assert.match(start, /api\/state/);
assert.doesNotMatch(start, /api\/(quit|pause|resume|cards)/);
assert.doesNotMatch(start, /\$task\.body|Invoke-Expression/);
assert.match(s, /agent = 'igneum-agent\.ps1 v3'/);
// the registration carries the app's per-install id, so two PCs with one Windows hostname are two machines
assert.match(s, /Api-Post 'register' @\{ hostname = \(Machine-Hostname\); info = \$info \}/);
assert.match(s, /igneum\\app\\machine-id/);
assert.match(s, /\$id\.Substring\(0, 8\)/);
// the one-shot reboot arm is still the only task the agent removes: the service task is never touched
const disarm = s.slice(s.indexOf('function Disarm-Restart'), s.indexOf('function Sha256-Hex'));
assert.doesNotMatch(disarm, /IgneumRelayService/);
assert.match(disarm, /schtasks\.exe \/Delete \/F \/TN 'IgneumRelayAgent'/);
});
test('the start-app body for older agents does the same thing and never quits the installed app', () => {
const s = read('relay/playbooks/start-app.ps1');
assert.match(s, /api\/state/);
assert.match(s, /\/RL LIMITED/);
assert.doesNotMatch(s, /api\/(quit|pause|resume|cards)/);
assert.doesNotMatch(s, /__DL_BASE__/);
});
test('the kind is known to the relay, the Mac tool signs it, and the zip carries the service files', () => {
assert.ok(KINDS.has('start-app'));
assert.deepEqual(AGENT_KINDS, ['run', 'start-app']);
const m = read('tools/relay.mjs');
assert.match(m, /cmd === 'start-app'/);
assert.match(m, /kind: 'start-app', body, flags: \{ elevated: false/);
const sa = m.slice(m.indexOf("cmd === 'start-app'"), m.indexOf("cmd === 'keygen'"));
assert.match(sa, /signRunTask\(o\)/, 'start-app is signed and tagged like run');
assert.match(sa, /'start-app\.ps1'\)/);
const mk = read('relay/clients/make-clients.sh');
for (const f of ['igneum-agent-service.ps1', 'install-agent.ps1', 'IgneumRelayService.xml']) assert.match(mk, new RegExp(f.replace('.', '\\.')), `${f} is not in the zip`);
});