PSA/ee/appliance/ubuntu-iso/tests/t001-build-smoke.test.mjs
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

280 lines
14 KiB
JavaScript

import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { parse, parseAllDocuments } from 'yaml';
const repoRoot = path.resolve(path.join(import.meta.dirname, '..', '..', '..', '..'));
const buildScript = path.join(repoRoot, 'ee', 'appliance', 'ubuntu-iso', 'scripts', 'build-ubuntu-appliance-iso.sh');
const userDataPath = path.join(repoRoot, 'ee', 'appliance', 'ubuntu-iso', 'config', 'nocloud', 'user-data');
const overlayRoot = path.join(repoRoot, 'ee', 'appliance', 'ubuntu-iso', 'overlay', 'opt', 'alga-appliance');
const storageManifestPath = path.join(repoRoot, 'ee', 'appliance', 'manifests', 'local-path-storage.yaml');
const algaCoreChartPath = path.join(repoRoot, 'helm');
const temporalChartPath = path.join(repoRoot, 'ee', 'helm', 'temporal');
const temporalWorkerChartPath = path.join(repoRoot, 'ee', 'helm', 'temporal-worker');
const temporalProfileValuesPath = path.join(repoRoot, 'ee', 'appliance', 'flux', 'profiles', 'single-node', 'values', 'temporal.single-node.yaml');
const temporalWorkerProfileValuesPath = path.join(repoRoot, 'ee', 'appliance', 'flux', 'profiles', 'single-node', 'values', 'temporal-worker.single-node.yaml');
const fluxReleaseDir = path.join(repoRoot, 'ee', 'appliance', 'flux', 'base', 'releases');
const applianceStatusManifestPath = path.join(repoRoot, 'ee', 'appliance', 'flux', 'base', 'platform', 'appliance-status.yaml');
function run(command, args, env = process.env) {
return spawnSync(command, args, { cwd: repoRoot, encoding: 'utf8', env });
}
function writeFakeXorriso(binDir, logFile, labelFile) {
const fakeXorriso = path.join(binDir, 'xorriso');
fs.writeFileSync(fakeXorriso, `#!/usr/bin/env bash
set -euo pipefail
printf '%s\\n' "$*" >> "$FAKE_XORRISO_LOG"
if [[ "\${1:-}" == "-osirrox" ]]; then
args=("$@")
dest="\${args[$((\${#args[@]} - 1))]}"
mkdir -p "$dest/boot/grub" "$dest/casper" "$dest/EFI/boot"
cat > "$dest/boot/grub/grub.cfg" <<'CFG'
set timeout=30
menuentry "Try or Install Ubuntu Server" {
set gfxpayload=keep
linux /casper/vmlinuz ---
initrd /casper/initrd
}
CFG
cp "$dest/boot/grub/grub.cfg" "$dest/boot/grub/loopback.cfg"
touch "$dest/casper/vmlinuz" "$dest/casper/initrd" "$dest/EFI/boot/bootx64.efi"
exit 0
fi
if [[ "\${1:-}" == "-as" && "\${2:-}" == "mkisofs" ]]; then
args=("$@")
output=""
label=""
for ((i = 0; i < \${#args[@]}; i++)); do
case "\${args[$i]}" in
-o)
output="\${args[$((i + 1))]}"
;;
-V)
label="\${args[$((i + 1))]}"
;;
esac
done
printf '%s\\n' "$label" > "$FAKE_XORRISO_LABEL"
printf 'fake iso\\n' > "$output"
exit 0
fi
exit 2
`);
fs.chmodSync(fakeXorriso, 0o755);
}
function helmTemplate(chartPath, valuesPath) {
const args = ['template', 'test-release', chartPath, '--namespace', 'msp'];
if (valuesPath) {
args.push('-f', valuesPath);
}
const result = run('helm', args);
assert.equal(result.status, 0, result.stderr || result.stdout);
return parseAllDocuments(result.stdout).map((doc) => doc.toJSON()).filter(Boolean);
}
function runBuildWithFakeXorriso() {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'alga-ubuntu-iso-test-'));
const tmpIso = path.join(tmp, 'base.iso');
const binDir = path.join(tmp, 'bin');
const workRoot = path.join(tmp, 'work');
const outputRoot = path.join(tmp, 'output');
const logFile = path.join(tmp, 'xorriso.log');
const labelFile = path.join(tmp, 'xorriso-label.txt');
const releaseVersion = `test-${Date.now()}`;
fs.mkdirSync(binDir);
fs.mkdirSync(workRoot);
fs.mkdirSync(outputRoot);
fs.writeFileSync(tmpIso, 'fake-iso-content');
writeFakeXorriso(binDir, logFile, labelFile);
const env = {
...process.env,
PATH: `${binDir}${path.delimiter}${process.env.PATH}`,
FAKE_XORRISO_LOG: logFile,
FAKE_XORRISO_LABEL: labelFile,
ALGA_APPLIANCE_ISO_WORK_DIR: workRoot,
ALGA_APPLIANCE_ISO_OUTPUT_DIR: outputRoot
};
const build = run('bash', [buildScript, '--base-iso', tmpIso, '--release-version', releaseVersion], env);
assert.equal(build.status, 0, build.stderr || build.stdout);
return { workRoot, outputRoot, releaseVersion, labelFile };
}
test('T001 build smoke: remastered ISO is branded and includes the offline appliance overlay', () => {
const tmpIso = path.join(os.tmpdir(), `alga-ubuntu-base-${Date.now()}.iso`);
const releaseVersion = `test-${Date.now()}`;
fs.writeFileSync(tmpIso, 'fake-iso-content');
const dryRun = run('bash', [buildScript, '--base-iso', tmpIso, '--release-version', releaseVersion, '--dry-run']);
assert.equal(dryRun.status, 0, dryRun.stderr || dryRun.stdout);
assert.match(dryRun.stdout, /layout validated/i);
const build = runBuildWithFakeXorriso();
assert.equal(fs.existsSync(path.join(overlayRoot, 'appliance')), true);
assert.equal(fs.existsSync(path.join(overlayRoot, 'host-service')), true);
assert.equal(fs.existsSync(path.join(overlayRoot, 'operator')), true);
assert.equal(fs.existsSync(path.join(overlayRoot, 'scripts')), true);
assert.equal(fs.existsSync(path.join(overlayRoot, 'manifests', 'local-path-storage.yaml')), true);
assert.equal(fs.existsSync(path.join(overlayRoot, 'flux')), true);
assert.equal(fs.existsSync(path.join(overlayRoot, 'releases')), false);
assert.equal(fs.existsSync(path.join(overlayRoot, 'status-ui', 'dist', 'index.html')), true);
assert.equal(fs.existsSync(path.join(overlayRoot, 'status-ui', 'dist', 'setup', 'index.html')), true);
const isoRoot = path.join(build.workRoot, 'iso-root');
assert.equal(fs.readFileSync(path.join(isoRoot, '.disk', 'info'), 'utf8'), 'AlgaPSA Install\n');
assert.equal(fs.readFileSync(build.labelFile, 'utf8'), 'ALGAPSA_INSTALL\n');
assert.equal(fs.existsSync(path.join(isoRoot, 'alga-overlay', 'opt', 'alga-appliance', 'host-service')), true);
assert.equal(fs.existsSync(path.join(isoRoot, 'alga-overlay', 'opt', 'alga-appliance', 'releases')), false);
const grubConfig = fs.readFileSync(path.join(isoRoot, 'boot', 'grub', 'grub.cfg'), 'utf8');
assert.match(grubConfig, /menuentry "AlgaPSA Install"/);
assert.match(grubConfig, /autoinstall ds=nocloud\\;s=\/cdrom\/nocloud\//);
const isoOut = path.join(build.outputRoot, `alga-appliance-ubuntu-${build.releaseVersion}.iso`);
const shaOut = `${isoOut}.sha256`;
assert.equal(fs.existsSync(isoOut), true);
assert.equal(fs.existsSync(shaOut), true);
});
test('T002 installer config sanity: network and storage remain user-confirmed before install actions', () => {
const userData = parse(fs.readFileSync(userDataPath, 'utf8'));
const lateCommands = userData.autoinstall['late-commands'];
assert.deepEqual(userData.autoinstall['interactive-sections'], ['network', 'storage']);
assert.equal(userData.autoinstall.storage.layout.name, 'direct');
assert.equal(userData.autoinstall.storage.layout['sizing-policy'], 'all');
assert.equal(userData.autoinstall.ssh['install-server'], true);
assert.equal(userData.autoinstall.ssh['allow-pw'], true);
assert.equal(lateCommands.some((command) => command.includes('passwd -l alga-admin')), false);
});
test('T003 storage manifest avoids k3s default local-path RBAC collisions and grants configmap access', () => {
const docs = parseAllDocuments(fs.readFileSync(storageManifestPath, 'utf8')).map((doc) => doc.toJSON());
const clusterRole = docs.find((doc) => doc?.kind === 'ClusterRole' && doc.metadata?.name === 'alga-local-path-provisioner-cluster-role');
const clusterRoleBinding = docs.find((doc) => doc?.kind === 'ClusterRoleBinding' && doc.metadata?.name === 'alga-local-path-provisioner-cluster-bind');
const namespacedRole = docs.find((doc) => doc?.kind === 'Role' && doc.metadata?.name === 'alga-local-path-provisioner-namespaced-role');
const namespacedRoleBinding = docs.find((doc) => doc?.kind === 'RoleBinding' && doc.metadata?.name === 'alga-local-path-provisioner-namespaced-bind');
assert.ok(clusterRole);
assert.ok(clusterRoleBinding);
assert.ok(namespacedRole);
assert.ok(namespacedRoleBinding);
assert.equal(docs.some((doc) => doc?.kind === 'ClusterRoleBinding' && doc.metadata?.name === 'local-path-provisioner-bind'), false);
assert.equal(clusterRoleBinding.roleRef.name, 'alga-local-path-provisioner-cluster-role');
assert.equal(namespacedRoleBinding.roleRef.name, 'alga-local-path-provisioner-namespaced-role');
assert.ok(namespacedRole.rules.some((rule) => rule.resources.includes('configmaps') && rule.verbs.includes('get')));
assert.ok(clusterRole.rules.some((rule) => rule.resources.includes('configmaps') && rule.verbs.includes('get')));
});
test('T004 Temporal chart waits for schema-safe startup and creates the default namespace with a Helm hook', () => {
const docs = helmTemplate(temporalChartPath, temporalProfileValuesPath);
const deployment = docs.find((doc) => doc.kind === 'Deployment' && doc.metadata?.name === 'test-release-temporal');
const job = docs.find((doc) => doc.kind === 'Job' && doc.metadata?.name === 'test-release-temporal-namespace-init');
assert.ok(deployment);
assert.ok(job);
const temporalContainer = deployment.spec.template.spec.containers.find((container) => container.name === 'temporal');
const env = Object.fromEntries(temporalContainer.env.map((entry) => [entry.name, entry.value]));
assert.equal(env.DEFAULT_NAMESPACE, 'default');
assert.equal(env.SKIP_DEFAULT_NAMESPACE_CREATION, 'true');
assert.equal(env.SKIP_ADD_CUSTOM_SEARCH_ATTRIBUTES, 'true');
assert.equal(temporalContainer.livenessProbe.initialDelaySeconds, 300);
assert.ok(temporalContainer.startupProbe.failureThreshold >= 60);
assert.equal(job.metadata.annotations['helm.sh/hook'], 'post-install,post-upgrade');
assert.match(job.spec.template.spec.containers[0].command.join('\n'), /temporal operator namespace create/);
});
test('T005 temporal-worker profile injects NEXTAUTH_SECRET from alga-core secrets', () => {
const docs = helmTemplate(temporalWorkerChartPath, temporalWorkerProfileValuesPath);
const deployment = docs.find((doc) => doc.kind === 'Deployment' && doc.metadata?.name === 'test-release-temporal-worker');
assert.ok(deployment);
const env = deployment.spec.template.spec.containers[0].env;
const nextAuth = env.find((entry) => entry.name === 'NEXTAUTH_SECRET');
assert.deepEqual(nextAuth.valueFrom.secretKeyRef, {
name: 'alga-core-sebastian-secrets',
key: 'NEXTAUTH_SECRET'
});
});
test('T006 alga-core appliance profile gives the app Deployment a first-install-safe progress deadline', () => {
const docs = helmTemplate(algaCoreChartPath, path.join(repoRoot, 'ee', 'appliance', 'flux', 'profiles', 'single-node', 'values', 'alga-core.single-node.yaml'));
const deployment = docs.find((doc) => doc.kind === 'Deployment' && doc.metadata?.name === 'test-release-sebastian');
assert.ok(deployment);
assert.ok(deployment.spec.progressDeadlineSeconds >= 1800);
});
test('T006b alga-core appliance bootstrap is serialized and avoids duplicate first-boot app image pulls', () => {
const docs = helmTemplate(algaCoreChartPath, path.join(repoRoot, 'ee', 'appliance', 'flux', 'profiles', 'single-node', 'values', 'alga-core.single-node.yaml'));
const job = docs.find((doc) => doc.kind === 'Job' && doc.metadata?.name === 'test-release-sebastian-bootstrap');
const deployment = docs.find((doc) => doc.kind === 'Deployment' && doc.metadata?.name === 'test-release-sebastian');
const configMap = docs.find((doc) => doc.kind === 'ConfigMap' && doc.metadata?.name === 'test-release-sebastian-appliance-bootstrap');
assert.ok(job);
assert.equal(job.metadata.annotations['helm.sh/hook'], 'post-install,post-upgrade');
assert.match(job.metadata.annotations['helm.sh/hook-delete-policy'], /before-hook-creation/);
assert.equal(job.spec.backoffLimit, 0);
assert.equal(job.spec.template.spec.containers[0].imagePullPolicy, 'IfNotPresent');
assert.ok(deployment);
const waitInit = deployment.spec.template.spec.initContainers.find((container) => container.name === 'wait-for-bootstrap');
assert.ok(waitInit);
assert.equal(waitInit.image, 'ankane/pgvector:latest');
assert.equal(waitInit.imagePullPolicy, 'IfNotPresent');
assert.equal(deployment.spec.template.spec.containers[0].imagePullPolicy, 'IfNotPresent');
assert.ok(configMap);
const script = configMap.data['appliance-bootstrap.sh'];
assert.match(script, /createdb_admin\(\)/);
assert.match(script, /acquire_bootstrap_lock\(\)/);
assert.match(script, /alga_appliance_bootstrap_lock/);
assert.match(script, /clear_stale_knex_migration_lock/);
assert.match(script, /Database .* was created by another bootstrap attempt/);
});
test('T007 Flux HelmReleases retry transient single-node install stalls', () => {
for (const file of fs.readdirSync(fluxReleaseDir).filter((name) => name.endsWith('.yaml'))) {
const doc = parse(fs.readFileSync(path.join(fluxReleaseDir, file), 'utf8'));
assert.ok(doc.spec.install.remediation.retries >= 1, `${file} install retries`);
assert.ok(doc.spec.upgrade.remediation.retries >= 1, `${file} upgrade retries`);
}
});
test('T008 platform appliance-status does not bind host port 8080 used by the Ubuntu host service', () => {
const docs = parseAllDocuments(fs.readFileSync(applianceStatusManifestPath, 'utf8')).map((doc) => doc.toJSON()).filter(Boolean);
const deployment = docs.find((doc) => doc.kind === 'Deployment' && doc.metadata?.name === 'appliance-status');
assert.ok(deployment);
assert.notEqual(deployment.spec.template.spec.hostNetwork, true);
assert.equal(deployment.spec.template.spec.dnsPolicy, 'ClusterFirst');
});
test('T009/T010 install flow sanity: payload copy and disk-first marker are present in generated installer inputs', () => {
const userData = parse(fs.readFileSync(userDataPath, 'utf8'));
const lateCommands = userData.autoinstall['late-commands'];
const build = runBuildWithFakeXorriso();
const grubConfig = fs.readFileSync(path.join(build.workRoot, 'iso-root', 'boot', 'grub', 'grub.cfg'), 'utf8');
assert.ok(lateCommands.some((command) => command.includes('/cdrom/alga-overlay') && command.includes('/target/')));
assert.ok(lateCommands.some((command) => command.includes('systemctl enable alga-appliance.service')));
assert.ok(lateCommands.some((command) => command.includes('systemctl enable alga-appliance-console.service')));
assert.ok(lateCommands.some((command) => command.includes('/target/etc/alga-appliance/booted-from-disk')));
assert.ok(lateCommands.some((command) => command.includes('lvextend -r -l +100%FREE /dev/ubuntu-vg/ubuntu-lv')));
assert.match(grubConfig, /search --no-floppy --file --set=alga_root \/etc\/alga-appliance\/booted-from-disk/);
assert.match(grubConfig, /configfile \/boot\/grub\/grub.cfg/);
});