Files
system_update/server/services/dockerApply.test.ts
T
gilles edb22a59c7 feat(docker): apply/prune/down + socle action_requests (tâche 2 SJ-6)
- migration 0005 : tables docker_image_events + action_requests
- templates apply-compose (up -d --remove-orphans), prune-images (safe/agressif),
  down-compose (sans volumes/rmi)
- dockerApply: parsers TDD (apply recreated/running/exited, prune images+bytes,
  down removed, parseHumanBytes) + orchestration applyStack/pruneImages/downStack
  réservée aux stacks enabled, insère docker_image_events
- actionRequests: create/approve/reject/list — actions destructives validées
  explicitement (Hermes propose, opérateur approuve, run en arrière-plan) ;
  hors API directe (POST /:id/actions reste passif uniquement)
- routes /machines/:id/action-requests + /action-requests/:id[/approve|/reject]
- execute: RunActionOpts.aggressive, branches apply/prune/down, helper
  archiveExecution mutualisant le boilerplate d'archivage

tsc 0 erreur · 91 tests · build OK · boot OK (migrations 0000→0005).

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

100 lines
3.2 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
parseDockerApply,
parseDockerPrune,
parseDockerDown,
parseHumanBytes,
} from "./dockerApply.js";
describe("parseDockerApply", () => {
const RAW = [
"===SU:DOCKER_APPLY===",
" Container media-app-1 Recreate",
" Container media-app-1 Recreated",
" Container media-worker-1 Created",
" Container media-db-1 Running",
" Container media-app-1 Started",
"===SU:DOCKER_PS_AFTER===",
'{"Name":"media-app-1","Service":"app","State":"running","Health":""}',
'{"Name":"media-db-1","Service":"db","State":"running","Health":"healthy"}',
'{"Name":"media-worker-1","Service":"worker","State":"exited","Health":""}',
"===SU:DOCKER_INSPECT_AFTER===",
"IMG\tsha256:newapp\tapp@sha256:dapp",
"IMG\tsha256:db\tdb@sha256:ddb",
"===SU:EXIT=0===",
].join("\n");
it("liste les conteneurs recréés/créés et l'état running/exited", () => {
const r = parseDockerApply(RAW);
expect(r.recreated.sort()).toEqual(["media-app-1", "media-worker-1"]);
expect(r.running.sort()).toEqual(["media-app-1", "media-db-1"]);
expect(r.exited).toEqual(["media-worker-1"]);
expect(r.errors).toHaveLength(0);
expect(r.exitCode).toBe(0);
});
it("remonte une erreur d'application nettoyée", () => {
const bad = [
"===SU:DOCKER_APPLY===",
' Container app-1 Error pulling image from https://reg.example/v2 token=SECRET123',
"===SU:DOCKER_PS_AFTER===",
"===SU:DOCKER_INSPECT_AFTER===",
"===SU:EXIT=1===",
].join("\n");
const r = parseDockerApply(bad);
expect(r.errors.length).toBeGreaterThan(0);
expect(r.errors[0]!.message).not.toContain("reg.example");
expect(r.errors[0]!.message).not.toContain("SECRET123");
expect(r.exitCode).toBe(1);
});
});
describe("parseHumanBytes", () => {
it("convertit les unités décimales Docker", () => {
expect(parseHumanBytes("0B")).toBe(0);
expect(parseHumanBytes("512MB")).toBe(512_000_000);
expect(parseHumanBytes("1.234GB")).toBe(Math.round(1.234 * 1e9));
expect(parseHumanBytes("1.5kB")).toBe(1500);
});
it("renvoie 0 pour une entrée illisible", () => {
expect(parseHumanBytes("n/a")).toBe(0);
});
});
describe("parseDockerPrune", () => {
const RAW = [
"===SU:DOCKER_PRUNE===",
"Deleted Images:",
"untagged: redis:6",
"deleted: sha256:aaa",
"deleted: sha256:bbb",
"",
"Total reclaimed space: 1.234GB",
"===SU:EXIT=0===",
].join("\n");
it("liste les images supprimées et l'espace récupéré", () => {
const r = parseDockerPrune(RAW);
expect(r.imagesDeleted).toEqual(["sha256:aaa", "sha256:bbb"]);
expect(r.bytesReclaimed).toBe(Math.round(1.234 * 1e9));
expect(r.errors).toHaveLength(0);
});
});
describe("parseDockerDown", () => {
it("liste les conteneurs retirés", () => {
const RAW = [
"===SU:DOCKER_DOWN===",
" Container media-app-1 Stopping",
" Container media-app-1 Stopped",
" Container media-app-1 Removing",
" Container media-app-1 Removed",
" Network media_default Removed",
"===SU:EXIT=0===",
].join("\n");
const r = parseDockerDown(RAW);
expect(r.removed).toEqual(["media-app-1"]);
expect(r.errors).toHaveLength(0);
});
});