Files
system_update/server/services/postInstall.test.ts
T
gilles e6f4ae470b feat(post-install): moteur de profils + bootstrap + identité/réseau (tâche 2 SJ-8)
- templates custom/bootstrap-root + identity-network (sortie structurée parsable,
  sauvegardes, échec contrôlé, jamais de coupure réseau sans reconnexion)
- postInstall: registre de manifestes (champs typés + defaults/defaultFrom),
  validateProfileValues + maskSecretValues + buildPostInstallResult (TDD),
  renderProfile/previewProfile (masquage secrets), runPostInstall (SSH)
- execute: RunActionOpts.profileId/values + branche post_install (bloc postInstall)
- action_requests: post_install accepté, payload profileId/values transmis à approve
- routes: GET /profiles, POST .../preview (script masqué + validation),
  POST .../run (action_request si requiresConfirmation, sinon direct)

Champs = formulaire (pas de question SSH interactive) ; secrets jamais sérialisés ;
identity_network exige confirmation. tsc 0 · 101 tests · build OK · boot OK.

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

110 lines
3.5 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
PROFILES,
validateProfileValues,
maskSecretValues,
buildPostInstallResult,
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("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);
});
});