Files
system_update/server/services/postInstall.test.ts
T
gilles 3b16fdd52a feat(post-install): catalogue de profils — paquets, Docker officiel, partages, VM tools (tâche 2 SJ-9)
- mécanisme presetVars (variables fixes injectées au rendu, surchargées par le formulaire)
- 6 profils : base_tools / network_tools / dev_git (listes de paquets, low),
  docker_official (dépôt officiel Debian, confirmation), sharing (Samba/NFS/mDNS, confirmation),
  vm_guest_tools (qemu/vmware)
- 4 templates custom (install-package-groups, docker-official-debian, sharing, vm-guest-tools)
  émettant PKG_INSTALLED/SERVICE_ENABLED/ERR → réutilise buildPostInstallResult
- l'UI post-install générique les expose automatiquement (manifeste → formulaire → run)

tsc 0 · 104 tests · build OK · boot OK (8 profils servis). Clôt le volet moteur tâche 2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 12:40:20 +02:00

131 lines
4.2 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
PROFILES,
validateProfileValues,
maskSecretValues,
buildPostInstallResult,
previewProfile,
type ProfileManifest,
} from "./postInstall.js";
describe("validateProfileValues", () => {
const identity = PROFILES.identity_network!;
it("échoue si un champ requis manque", () => {
const r = validateProfileValues(identity, { newHostname: "srv1" });
expect(r.ok).toBe(false);
expect(r.errors.some((e) => e.field === "interfaceName")).toBe(true);
});
it("échoue sur une IP/CIDR invalide", () => {
const r = validateProfileValues(identity, {
newHostname: "srv1",
domain: "home",
interfaceName: "eth0",
staticAddress: "999.1.1.1/24",
gateway: "10.0.0.1",
dnsNameservers: "10.0.0.1",
reconnectHost: "10.0.0.50",
});
expect(r.ok).toBe(false);
expect(r.errors.some((e) => e.field === "staticAddress")).toBe(true);
});
it("passe avec des valeurs valides", () => {
const r = validateProfileValues(identity, {
newHostname: "srv1",
domain: "home",
interfaceName: "eth0",
staticAddress: "10.0.0.50/22",
gateway: "10.0.0.1",
dnsNameservers: "10.0.0.1 10.0.0.10",
reconnectHost: "10.0.0.50",
});
expect(r.ok).toBe(true);
expect(r.errors).toHaveLength(0);
});
});
describe("maskSecretValues", () => {
const manifest: ProfileManifest = {
id: "x",
label: "x",
description: "",
risk: "low",
requiresConfirmation: false,
template: "custom/bootstrap-root.sh.tpl",
fields: [
{ name: "user", type: "string", required: true },
{ name: "token", type: "secret", required: true },
],
};
it("masque les champs secret et conserve les autres", () => {
const masked = maskSecretValues(manifest, { user: "gilles", token: "s3cr3t-ABC" });
expect(masked.user).toBe("gilles");
expect(masked.token).toBe("********");
expect(JSON.stringify(masked)).not.toContain("s3cr3t");
});
});
describe("profils SJ-9 (presetVars + sections)", () => {
it("base_tools injecte la liste de paquets fixe", () => {
expect(PROFILES.base_tools).toBeTruthy();
const script = previewProfile("base_tools", {});
expect(script).toContain("nano");
expect(script).toContain("htop");
});
it("sharing ne rend que les paquets cochés", () => {
const script = previewProfile("sharing", { installSamba: true, installNfs: false, installMdns: true });
expect(script).toContain("samba");
expect(script).toContain("avahi-daemon");
expect(script).not.toContain("nfs-kernel-server");
});
it("docker_official exige une confirmation", () => {
expect(PROFILES.docker_official!.requiresConfirmation).toBe(true);
});
});
describe("buildPostInstallResult", () => {
const raw = [
"===SU:CUSTOM_IDENTITY===",
"FILE_MODIFIED=/etc/hosts",
"FILE_MODIFIED=/etc/network/interfaces",
"OLD_ENDPOINT=10.0.0.99",
"HOSTNAME_SET=srv1",
"ERR=interface_not_found",
"NEW_ENDPOINT=10.0.0.50",
"RECONNECT_REQUIRED=1",
"REBOOT_REQUESTED=1",
"===SU:EXIT=0===",
].join("\n");
it("extrait fichiers modifiés, reboot, changement réseau et erreurs", () => {
const r = buildPostInstallResult(raw, ["identity_network"], { newHostname: "srv1" });
expect(r.profilesRun).toEqual(["identity_network"]);
expect(r.filesModified).toContain("/etc/hosts");
expect(r.filesModified).toContain("/etc/network/interfaces");
expect(r.rebootsRequested).toBe(true);
expect(r.networkChange).toEqual({ oldEndpoint: "10.0.0.99", newEndpoint: "10.0.0.50", reconnectHost: "10.0.0.50" });
expect(r.errors?.some((e) => e.kind === "interface_not_found")).toBe(true);
expect(r.variablesUsed.newHostname).toBe("srv1");
});
it("parse les paquets installés du bootstrap", () => {
const boot = [
"===SU:CUSTOM_BOOTSTRAP===",
"PKG_INSTALLED=sudo",
"PKG_INSTALLED=curl",
"GROUP_ADDED=sudo:gilles",
"SUDO_OK=1",
"===SU:EXIT=0===",
].join("\n");
const r = buildPostInstallResult(boot, ["bootstrap_root"], { operatorUser: "gilles" });
expect(r.packagesInstalled).toEqual(["sudo", "curl"]);
expect(r.rebootsRequested).toBe(false);
expect(r.errors ?? []).toHaveLength(0);
});
});