import test from 'node:test'; import assert from 'node:assert/strict'; import {mkdtemp,mkdir,writeFile,rm} from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import {createWindowsPackagePlan} from '../scripts/windows-package-plan.mjs'; import {buildVerifiedInstaller} from '../scripts/verified-installer.mjs'; const identity={sourceSha256:'a'.repeat(64),runtimeSha256:'b'.repeat(64),packageSha256:'c'.repeat(64)}; async function fixture(t){ const root=await mkdtemp(path.join(os.tmpdir(),'safetynet-installer-verifier-'));t.after(()=>rm(root,{force:true,recursive:true})); const output=path.join(root,'dist');await mkdir(output);const plan=createWindowsPackagePlan({workspace:root,outputDirectory:output,electronDirectory:path.join(root,'electron'),platform:'win32'}); return {plan,expectedIdentity:identity,readIdentity:async()=>identity,verify:async()=>({identity,fuses:true,resources:true,broker:true,runtimeAcl:true}),installerPath:path.join(output,'fixture.exe')}; } test('actual invoked installer arguments require before and after package verification',async t=>{ const f=await fixture(t);let calls=0,verifications=0; const result=await buildVerifiedInstaller({...f,verify:async()=>{verifications++;return f.verify();},run:async args=>{calls++;assert.ok(args.includes('--prepackaged'));const fake=Buffer.alloc(1024);fake.write('MZ');await writeFile(f.installerPath,fake);return {status:0};}}); assert.equal(calls,1);assert.equal(verifications,2);assert.equal(result.bytes,1024);assert.match(result.sha256,/^[a-f0-9]{64}$/); // Synthetic PE prefix exercises orchestration only, never installer validity. }); test('source mutation during successful CLI run invalidates the installer result',async t=>{ const f=await fixture(t);let changed=false; await assert.rejects(buildVerifiedInstaller({...f,readIdentity:async()=>changed?{...identity,sourceSha256:'d'.repeat(64)}:identity,run:async()=>{changed=true;return {status:0};}}),{code:'installer_identity_changed'}); }); test('failed CLI and missing or stale artifact cannot pass based on a good plan',async t=>{ const f=await fixture(t); await assert.rejects(buildVerifiedInstaller({...f,run:async()=>({status:1})}),{code:'installer_build_failed'}); await assert.rejects(buildVerifiedInstaller({...f,run:async()=>({status:0})}),{code:'installer_artifact_invalid'}); const fake=Buffer.alloc(1024);fake.write('MZ');await writeFile(f.installerPath,fake); await assert.rejects(buildVerifiedInstaller({...f,run:async()=>({status:0})}),{code:'installer_artifact_invalid'}); }); test('post-invocation mandatory security verification cannot be skipped',async t=>{ const f=await fixture(t);let calls=0; await assert.rejects(buildVerifiedInstaller({...f,verify:async()=>({...await f.verify(),broker:++calls===1}),run:async()=>({status:0})}),{code:'installer_verification_failed'}); }); test('identity stays pinned through final artifact hashing and result delivery',async t=>{ const f=await fixture(t);let reads=0; await assert.rejects(buildVerifiedInstaller({...f,readIdentity:async()=>++reads>=6?{...identity,sourceSha256:'d'.repeat(64)}:identity,run:async()=>{const fake=Buffer.alloc(1024);fake.write('MZ');await writeFile(f.installerPath,fake);return {status:0};}}),{code:'installer_identity_changed'}); });