Files
poshmanager/server/test/packageBuilder.test.mjs
2026-08-09 17:33:11 -05:00

105 lines
4.8 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import zlib from 'node:zlib';
import { ENTRY_SCRIPT_NAME, buildZip, planPackageLayout, resolveSetupFile } from '../services/packageBuilder.js';
// --- layout planning (pure) --------------------------------------------------
test('planPackageLayout places linked assets by role when no package path is set', () => {
const layout = planPackageLayout([
{ assetId: 'a1', originalName: 'setup.exe', linkRole: 'installer' },
{ assetId: 'a2', originalName: 'config.xml', linkRole: 'support-file' },
{ assetId: 'a3', originalName: 'icon.png', linkRole: 'icon' }
]);
assert.equal(layout.scriptPath, ENTRY_SCRIPT_NAME);
assert.deepEqual(layout.assets.map((a) => a.packagePath), ['Files/setup.exe', 'SupportFiles/config.xml', 'Assets/icon.png']);
assert.ok(layout.directories.includes('Assets'), 'Assets dir added because an icon is present');
});
test('planPackageLayout honors an explicit package path verbatim', () => {
const layout = planPackageLayout([
{ assetId: 'a1', originalName: 'setup.exe', linkRole: 'installer', packagePath: 'Files/x64/setup.exe' }
]);
assert.equal(layout.assets[0].packagePath, 'Files/x64/setup.exe');
});
test('planPackageLayout has no Assets dir when nothing maps there', () => {
const layout = planPackageLayout([{ assetId: 'a1', originalName: 'setup.exe', linkRole: 'installer' }]);
assert.deepEqual(layout.directories, ['Files', 'SupportFiles']);
});
test('planPackageLayout drops colliding targets with a warning', () => {
const layout = planPackageLayout([
{ assetId: 'a1', originalName: 'setup.exe', linkRole: 'installer', packagePath: 'Files/setup.exe' },
{ assetId: 'a2', originalName: 'other.exe', linkRole: 'installer', packagePath: 'files/SETUP.exe' }
]);
assert.equal(layout.assets.length, 1, 'second asset on the same path is skipped');
assert.equal(layout.warnings.length, 1);
});
test('planPackageLayout relocates a bare entry-script name under its role folder, never overwriting root', () => {
const layout = planPackageLayout([
{ assetId: 'a1', originalName: 'Invoke-AppDeployToolkit.ps1', linkRole: 'reference', packagePath: 'Invoke-AppDeployToolkit.ps1' }
]);
assert.equal(layout.assets[0].packagePath, 'SupportFiles/Invoke-AppDeployToolkit.ps1');
assert.notEqual(layout.assets[0].packagePath, ENTRY_SCRIPT_NAME);
});
// --- setup file resolution (pure) --------------------------------------------
test('resolveSetupFile prefers an explicit choice, then installer role, then first Files entry', () => {
const layout = planPackageLayout([
{ assetId: 'a1', originalName: 'helper.exe', linkRole: 'package-file' },
{ assetId: 'a2', originalName: 'setup.exe', linkRole: 'installer' }
]);
assert.equal(resolveSetupFile(layout, 'Files/manual.exe'), 'Files/manual.exe');
assert.equal(resolveSetupFile(layout), 'Files/setup.exe');
const noInstaller = planPackageLayout([{ assetId: 'a1', originalName: 'helper.exe', linkRole: 'package-file' }]);
assert.equal(resolveSetupFile(noInstaller), 'Files/helper.exe');
const empty = planPackageLayout([]);
assert.equal(resolveSetupFile(empty), '');
});
// --- zip writer (pure, round-trips through zlib) -----------------------------
test('buildZip produces a valid archive whose entries inflate back to their input', () => {
const script = 'Write-Output "hello"\n'.repeat(50);
const binary = Buffer.from([0, 1, 2, 3, 255, 254, 253]);
const zip = buildZip([
{ path: ENTRY_SCRIPT_NAME, data: Buffer.from(script, 'utf8') },
{ path: 'Files', isDirectory: true },
{ path: 'Files/payload.bin', data: binary }
]);
assert.equal(zip.readUInt32LE(0), 0x04034b50, 'starts with a local file header');
const recovered = readZip(zip);
assert.equal(recovered.get(ENTRY_SCRIPT_NAME).toString('utf8'), script);
assert.ok(recovered.has('Files/'), 'directory entry is present');
assert.deepEqual([...recovered.get('Files/payload.bin')], [...binary]);
});
test('buildZip stores incompressible/empty data without inflating it', () => {
const zip = buildZip([{ path: 'a.txt', data: Buffer.alloc(0) }]);
assert.equal(readZip(zip).get('a.txt').length, 0);
});
// Minimal central-directory reader for the test: walk local headers and inflate.
function readZip(buf) {
const out = new Map();
let pos = 0;
while (buf.readUInt32LE(pos) === 0x04034b50) {
const method = buf.readUInt16LE(pos + 8);
const compSize = buf.readUInt32LE(pos + 18);
const nameLen = buf.readUInt16LE(pos + 26);
const extraLen = buf.readUInt16LE(pos + 28);
const name = buf.slice(pos + 30, pos + 30 + nameLen).toString('utf8');
const dataStart = pos + 30 + nameLen + extraLen;
const stored = buf.slice(dataStart, dataStart + compSize);
out.set(name, method === 8 ? zlib.inflateRawSync(stored) : stored);
pos = dataStart + compSize;
}
return out;
}