08919752e3
Checkpoint multi-chantiers (arbre vert : tsc 0 erreur, 70 tests, build OK). - tâche 1.9 Phase 1 : schéma socle (machine_state/events/reports/raw_artifacts/ hardware/metrics + colonnes étendues) + wiring refresh/execute. Migration 0002. - tâche 1.9 Phase 2 : machine_credentials + machine_host_keys (non destructif, dual-read + backfill). Migration 0003. Fix séquence journal de migration. - tâche 2 : SJ-0 (types étendus rétro-compatibles, réducteur Docker, resolveTemplate), SJ-1 (update-analyze enrichi), SJ-2 (apply + diff dpkg + timeout inactivité SSH), SJ-3 (reboot vérifié boot_id). - WIP parallèle inclus : /api/capabilities, auth/apiTokens/apiClients, system metrics, scaffold app_rust, ajustements frontend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
109 lines
5.0 KiB
TypeScript
109 lines
5.0 KiB
TypeScript
// server/services/aptParse.test.ts
|
|
import { describe, it, expect } from "vitest";
|
|
import { readFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import { parseAptSimulate, parseRebootRequired, parseAptRemovals, parseHeld, parseRebootDetail, buildAptSnapshotDetail, parseDpkgList, buildAptExecutionResult } from "./aptParse.js";
|
|
|
|
const raw = readFileSync(fileURLToPath(new URL("./__fixtures__/apt-simulate.txt", import.meta.url)), "utf8");
|
|
|
|
const ua = readFileSync(fileURLToPath(new URL("./__fixtures__/apt-update-analyze.txt", import.meta.url)), "utf8");
|
|
function section(rawInput: string, start: string, end: string): string {
|
|
const s = rawInput.indexOf(start); if (s === -1) return "";
|
|
const from = s + start.length; const e = rawInput.indexOf(end, from);
|
|
return rawInput.slice(from, e === -1 ? undefined : e).trim();
|
|
}
|
|
|
|
describe("parseAptSimulate", () => {
|
|
it("extrait les paquets upgradables avec versions et origine", () => {
|
|
const pkgs = parseAptSimulate(raw);
|
|
expect(pkgs).toEqual([
|
|
{ name: "libc6", currentVersion: "2.31-13", targetVersion: "2.31-13+deb11u5", origin: "Debian:11.6/stable" },
|
|
{ name: "pve-manager", currentVersion: "8.4-1", targetVersion: "8.4-3", origin: "Proxmox VE:8.x" },
|
|
{ name: "newpkg", currentVersion: null, targetVersion: "1.0.0", origin: "Debian:11.6/stable" },
|
|
]);
|
|
});
|
|
|
|
it("retourne un tableau vide quand aucun Inst", () => {
|
|
expect(parseAptSimulate("Reading package lists...\nDone")).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("parseRebootRequired", () => {
|
|
it("détecte le marqueur REBOOT_REQUIRED=1", () => {
|
|
expect(parseRebootRequired("REBOOT_REQUIRED=1")).toBe(true);
|
|
expect(parseRebootRequired("REBOOT_REQUIRED=0")).toBe(false);
|
|
expect(parseRebootRequired("rien")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("parseAptRemovals", () => {
|
|
it("extrait les suppressions Remv", () => {
|
|
expect(parseAptRemovals("Remv oldpkg [3.2-1]\nInst x [1] (2 Y [amd64])"))
|
|
.toEqual([{ name: "oldpkg", currentVersion: "3.2-1" }]);
|
|
});
|
|
});
|
|
|
|
describe("parseHeld", () => {
|
|
it("liste les paquets retenus non vides", () => {
|
|
expect(parseHeld("frozenpkg\n\n other ")).toEqual(["frozenpkg", "other"]);
|
|
});
|
|
});
|
|
|
|
describe("parseRebootDetail", () => {
|
|
it("lit le flag et les paquets reboot", () => {
|
|
expect(parseRebootDetail("REBOOT_REQUIRED=1\nPKG=linux-image-amd64\nPKG=foo"))
|
|
.toEqual({ rebootRequired: true, pkgs: ["linux-image-amd64", "foo"] });
|
|
expect(parseRebootDetail("REBOOT_REQUIRED=0")).toEqual({ rebootRequired: false, pkgs: [] });
|
|
});
|
|
});
|
|
|
|
const BEFORE = "libc6\t2.31-13\tamd64\noldpkg\t3.2-1\tamd64\nstable\t1.0\tamd64";
|
|
const AFTER = "libc6\t2.31-14\tamd64\nnewpkg\t1.0.0\tall\nstable\t1.0\tamd64";
|
|
|
|
describe("parseDpkgList", () => {
|
|
it("indexe par package:arch", () => {
|
|
const m = parseDpkgList("libc6\t2.31-13\tamd64");
|
|
expect(m["libc6:amd64"]).toEqual({ version: "2.31-13", arch: "amd64" });
|
|
});
|
|
});
|
|
|
|
describe("buildAptExecutionResult", () => {
|
|
it("calcule le diff réel before/after", () => {
|
|
const r = buildAptExecutionResult(BEFORE, AFTER, "REBOOT_REQUIRED=1");
|
|
expect(r.applied.find((c) => c.name === "libc6")).toMatchObject({ operation: "upgraded", fromVersion: "2.31-13", toVersion: "2.31-14" });
|
|
expect(r.installed.map((c) => c.name)).toEqual(["newpkg"]);
|
|
expect(r.removed.map((c) => c.name)).toEqual(["oldpkg"]);
|
|
expect(r.applied.some((c) => c.name === "stable")).toBe(false); // unchanged exclu
|
|
expect(r.rebootRequiredAfterRun).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("buildAptSnapshotDetail", () => {
|
|
it("construit le détail enrichi depuis les sections", () => {
|
|
const detail = buildAptSnapshotDetail({
|
|
upgradeSim: section(ua, "===SU:APT_SIM_UPGRADE===", "===SU:APT_SIM_DISTUPGRADE==="),
|
|
distUpgradeSim: section(ua, "===SU:APT_SIM_DISTUPGRADE===", "===SU:APT_HELD==="),
|
|
heldRaw: section(ua, "===SU:APT_HELD===", "===SU:REBOOT==="),
|
|
rebootRaw: section(ua, "===SU:REBOOT===", "===SU:EXIT"),
|
|
updateFailed: false,
|
|
});
|
|
expect(detail.enabled).toBe(true);
|
|
expect(detail.count).toBe(2); // 2 Inst en dist-upgrade
|
|
expect(detail.upgradeCount).toBe(1); // 1 Inst en upgrade
|
|
expect(detail.distUpgradeCount).toBe(2);
|
|
expect(detail.rebootRequired).toBe(true);
|
|
expect(detail.rebootPkgs).toEqual(["linux-image-amd64"]);
|
|
expect(detail.held).toEqual(["frozenpkg"]);
|
|
expect(detail.removed?.map((r) => r.name)).toEqual(["oldpkg"]);
|
|
expect(detail.installed?.map((p) => p.name)).toEqual(["newdep"]);
|
|
expect(detail.status).toBe("warning"); // car removed + held non vides
|
|
});
|
|
|
|
it("status=updates_available sans removed/held, error si update échoue", () => {
|
|
const ok = buildAptSnapshotDetail({ upgradeSim: "Inst a [1] (2 Y [amd64])", distUpgradeSim: "Inst a [1] (2 Y [amd64])", heldRaw: "", rebootRaw: "REBOOT_REQUIRED=0", updateFailed: false });
|
|
expect(ok.status).toBe("updates_available");
|
|
const err = buildAptSnapshotDetail({ upgradeSim: "", distUpgradeSim: "", heldRaw: "", rebootRaw: "REBOOT_REQUIRED=0", updateFailed: true });
|
|
expect(err.status).toBe("error");
|
|
});
|
|
});
|