import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { parseScanOutput, classifyVerdict, resolveInstallerName, discoverArtifacts, } from '../../scripts/defender-scan.mjs'; import { sha256File, parseChecksumsFile, checksumsContent, expectedInstallDir, dryRunInstallPath, checkInstaller, } from '../../scripts/offline-install-check.mjs'; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..'); test('defender: clean output is parsed as clean', () => { const parsed = parseScanOutput('Scan starting...\nScan finished.\nScanning X found no threats.\n'); assert.equal(parsed.status, 'clean'); assert.equal(parsed.threatCount, 0); assert.equal(parsed.hasThreats, false); }); test('defender: threat block exposes count and threat name', () => { const text = 'Scanning X found 1 threats.\n\n<===========================LIST OF DETECTED THREATS==========================>\nThreat : Virus:DOS/EICAR_Test_File\nResources : 1 total\n file : X\n'; const parsed = parseScanOutput(text); assert.equal(parsed.status, 'threat'); assert.equal(parsed.threatCount, 1); assert.ok(parsed.threatNames.includes('Virus:DOS/EICAR_Test_File')); }); test('defender: remediating-mode threat output is recognized', () => { const parsed = parseScanOutput('Scanning X found 2 threats.\nCleaning started...\n'); assert.equal(parsed.status, 'threat'); assert.equal(parsed.threatCount, 2); }); test('defender: skipped output is not treated as clean', () => { const parsed = parseScanOutput('Scan starting...\nScan finished.\nScanning D:\\abc was skipped.\n'); assert.equal(parsed.status, 'skipped'); }); test('defender: verdict classification', () => { assert.equal(classifyVerdict([{ status: 'clean' }]), 'PASS'); assert.equal(classifyVerdict([{ status: 'clean' }, { status: 'threat' }]), 'THREAT'); assert.equal(classifyVerdict([{ status: 'error' }]), 'FAIL'); assert.equal(classifyVerdict([{ status: 'timeout' }]), 'FAIL'); assert.equal(classifyVerdict([{ status: 'skipped' }]), 'PASS'); }); test('defender: installer name resolution from electron-builder template', () => { const config = { artifactName: 'aps-agent-desktop-' + '$' + '{version}' + '.' + '$' + '{ext}', version: '0.1.0' }; assert.equal(resolveInstallerName(config), 'aps-agent-desktop-0.1.0.exe'); }); test('defender: artifact discovery is driven by apps/desktop/package.json', (t) => { const artifacts = discoverArtifacts(repoRoot); assert.deepEqual( artifacts.map((a) => a.id), ['sidecar-exe', 'desktop-frozen-exe', 'bundled-sidecar-exe', 'nsis-installer'], ); const installer = artifacts.find((a) => a.id === 'nsis-installer'); assert.ok(installer.file.endsWith('aps-agent-desktop-0.1.0.exe')); if (!installer.exists) t.skip('NSIS installer not built on this machine'); }); test('offline: sha256File is deterministic lowercase hex', async () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'aps-node-test-')); try { const file = path.join(dir, 'sample.bin'); fs.writeFileSync(file, Buffer.alloc(64 * 1024, 3)); const first = await sha256File(file); const second = await sha256File(file); assert.equal(first, second); assert.match(first, /^[0-9a-f]{64}$/); } finally { fs.rmSync(dir, { recursive: true, force: true }); } }); test('offline: checksumsContent and parseChecksumsFile round trip', async () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'aps-node-test-')); try { const file = path.join(dir, 'installer.exe'); fs.writeFileSync(file, Buffer.alloc(1024, 1)); const hash = await sha256File(file); const content = checksumsContent([{ file, sha256: hash }], dir); const parsed = parseChecksumsFile(content); assert.equal(parsed.length, 1); assert.equal(parsed[0].sha256, hash); assert.equal(parsed[0].file, 'installer.exe'); } finally { fs.rmSync(dir, { recursive: true, force: true }); } }); test('offline: expected install dir is LOCALAPPDATA Programs productName', () => { const dir = expectedInstallDir('C:\\Users\\tester\\AppData\\Local', '工业智核 APS'); assert.equal(dir, 'C:\\Users\\tester\\AppData\\Local\\Programs\\工业智核 APS'); }); test('offline: dry-run detects absent and complete install layouts', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'aps-node-test-')); try { const absent = dryRunInstallPath(path.join(dir, 'missing-local'), '工业智核 APS'); assert.equal(absent.exists, false); const local = path.join(dir, 'LocalAppData'); const installDir = path.join(local, 'Programs', '工业智核 APS'); fs.mkdirSync(path.join(installDir, 'resources', 'sidecar'), { recursive: true }); fs.writeFileSync(path.join(installDir, '工业智核 APS.exe'), 'app'); fs.writeFileSync(path.join(installDir, 'resources', 'sidecar', 'aps-sidecar.exe'), 'sidecar'); const complete = dryRunInstallPath(local, '工业智核 APS'); assert.equal(complete.exists, true); assert.equal(complete.layoutValid, true); } finally { fs.rmSync(dir, { recursive: true, force: true }); } }); test('offline: installer size gate', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'aps-node-test-')); try { const file = path.join(dir, 'big.exe'); fs.writeFileSync(file, Buffer.alloc(4096, 0)); assert.equal(checkInstaller(file, 1024).sizeOk, true); assert.equal(checkInstaller(path.join(dir, 'nope.exe'), 1024).exists, false); assert.equal(checkInstaller(file, 10 * 1024).sizeOk, false); } finally { fs.rmSync(dir, { recursive: true, force: true }); } });