Files
pikasTech-unidesk/scripts/server-cleanup-plan-contract-test.ts
T
2026-05-21 14:21:25 +00:00

150 lines
7.8 KiB
TypeScript

import { buildDockerCleanupPlan, type DockerCleanupInventory } from "./src/server-cleanup";
type JsonRecord = Record<string, unknown>;
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
}
const observedAt = "2026-05-21T14:00:00.000Z";
const fixture: DockerCleanupInventory = {
observedAt,
images: [
{
id: "sha256:1111111111111111111111111111111111111111111111111111111111111111",
repoTags: ["unidesk-backend-core:latest"],
repoDigests: [],
sizeBytes: 110 * 1024 * 1024,
createdAt: "2026-05-21T10:00:00.000Z",
labels: { "unidesk.ai/service-id": "backend-core", "unidesk.ai/source-commit": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" },
},
{
id: "sha256:2222222222222222222222222222222222222222222222222222222222222222",
repoTags: ["127.0.0.1:5000/unidesk/frontend:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"],
repoDigests: [],
sizeBytes: 120 * 1024 * 1024,
createdAt: "2026-05-21T08:00:00.000Z",
labels: { "unidesk.ai/service-id": "frontend", "unidesk.ai/source-commit": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" },
},
{
id: "sha256:3333333333333333333333333333333333333333333333333333333333333333",
repoTags: ["old-test-image:local"],
repoDigests: ["old-test-image@sha256:3333333333333333333333333333333333333333333333333333333333333333"],
sizeBytes: 500 * 1024 * 1024,
createdAt: "2026-05-19T14:00:00.000Z",
labels: {},
},
{
id: "sha256:4444444444444444444444444444444444444444444444444444444444444444",
repoTags: [],
repoDigests: [],
sizeBytes: 1024 * 1024 * 1024,
createdAt: "2026-05-18T14:00:00.000Z",
labels: {},
},
],
containers: [
{
id: "container-running-backend-core",
name: "unidesk-backend-core",
imageRef: "unidesk-backend-core:latest",
imageId: "sha256:1111111111111111111111111111111111111111111111111111111111111111",
state: "running",
status: "running",
labels: {},
},
],
desiredImageRefs: [
{
ref: "127.0.0.1:5000/unidesk/frontend:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
source: "CI.json + deploy.json",
serviceId: "frontend",
reason: "current commit-pinned registry artifact",
},
],
desiredCommitsByService: {
"backend-core": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
frontend: ["bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"],
},
protectedStorage: [
{
kind: "docker-volume",
ref: "unidesk_pgdata_10gb",
risk: "blocked",
reason: "database named volume",
},
{
kind: "path",
ref: "/workspace/unidesk/.state/baidu-netdisk",
risk: "blocked",
reason: "Baidu Netdisk state",
},
{
kind: "path",
ref: "/home/ubuntu/.unidesk/registry-storage",
risk: "blocked",
reason: "registry storage",
},
],
collection: {
dockerAvailable: true,
readOnlyCommands: [["docker", "image", "ls", "-q", "--no-trunc"]],
errors: [],
},
};
export function runServerCleanupPlanContract(): JsonRecord {
const plan = buildDockerCleanupPlan(fixture, { minAgeHours: 24, limit: 20 });
const candidateIds = plan.candidateStaleImages.map((image) => image.id);
const protectedIds = plan.protectedImages.map((image) => image.id);
assertCondition(plan.ok === true, "plan should be ok", plan);
assertCondition(plan.dryRun === true && plan.mutation === false, "plan must be dry-run and non-mutating", plan.policy);
assertCondition(plan.policy.deletionExecuted === false, "plan must not execute deletion", plan.policy);
assertCondition(plan.policy.dockerPruneUsed === false, "plan must not use docker prune", plan.policy);
assertCondition(plan.policy.dockerVolumesTouched === false, "plan must not touch docker volumes", plan.policy);
assertCondition(plan.policy.databaseCleanupIncluded === false, "plan must not include database cleanup", plan.policy);
assertCondition(candidateIds.includes("sha256:3333333333333333333333333333333333333333333333333333333333333333"), "tagged stale image should be a candidate", plan.candidateStaleImages);
assertCondition(candidateIds.includes("sha256:4444444444444444444444444444444444444444444444444444444444444444"), "dangling stale image should be a candidate", plan.candidateStaleImages);
assertCondition(protectedIds.includes("sha256:1111111111111111111111111111111111111111111111111111111111111111"), "running image should be protected", plan.protectedImages);
assertCondition(protectedIds.includes("sha256:2222222222222222222222222222222222222222222222222222222222222222"), "desired deploy image should be protected", plan.protectedImages);
assertCondition(plan.candidateStaleImages.length === 2, "only stale non-protected images should be candidates", plan.candidateStaleImages);
assertCondition(plan.risk.medium === 1, "tagged stale candidate should be medium risk", plan.risk);
assertCondition(plan.risk.low === 1, "dangling stale candidate should be low risk", plan.risk);
assertCondition(plan.commandsToReview.length === 2, "commandsToReview should include candidate commands", plan.commandsToReview);
assertCondition(plan.commandsToReview.every((command) => command.requiresManualApproval === true), "commands must require manual approval", plan.commandsToReview);
const taggedCommand = plan.commandsToReview.find((command) => command.imageId === "sha256:3333333333333333333333333333333333333333333333333333333333333333");
assertCondition(taggedCommand?.command.includes("old-test-image:local"), "tagged candidate command should include reviewed tag", plan.commandsToReview);
assertCondition(taggedCommand?.command.includes("old-test-image@sha256:3333333333333333333333333333333333333333333333333333333333333333"), "tagged candidate command should include reviewed digest", plan.commandsToReview);
assertCondition(plan.commandsToReview.some((command) => command.command.includes("sha256:4444444444444444444444444444444444444444444444444444444444444444")), "dangling candidate command should use image id", plan.commandsToReview);
assertCondition(!JSON.stringify(plan.commandsToReview).includes("docker image prune"), "plan must not recommend image prune", plan.commandsToReview);
assertCondition(!JSON.stringify(plan.commandsToReview).includes("docker system prune"), "plan must not recommend system prune", plan.commandsToReview);
assertCondition(plan.prohibitedCommands.includes("docker image prune"), "image prune should be explicitly prohibited", plan.prohibitedCommands);
assertCondition(plan.prohibitedCommands.includes("docker system prune"), "system prune should be explicitly prohibited", plan.prohibitedCommands);
assertCondition(plan.protectedStorage.some((item) => item.ref === "unidesk_pgdata_10gb"), "database volume must be protected", plan.protectedStorage);
assertCondition(plan.protectedStorage.some((item) => String(item.ref).includes("baidu-netdisk")), "Baidu Netdisk state must be protected", plan.protectedStorage);
assertCondition(plan.protectedStorage.some((item) => String(item.ref).includes("registry-storage")), "registry storage must be protected", plan.protectedStorage);
assertCondition(plan.estimatedReclaimBytes === (500 * 1024 * 1024) + (1024 * 1024 * 1024), "estimated reclaim should sum candidate image sizes", plan.estimates);
return {
ok: true,
checks: [
"dry-run non-mutating policy",
"active image protected",
"deploy/CI desired image protected",
"stale candidates emitted",
"risk levels emitted",
"manual commandsToReview emitted",
"database/registry/baidu storage protected",
"prune commands absent",
],
};
}
if (import.meta.main) {
process.stdout.write(`${JSON.stringify(runServerCleanupPlanContract(), null, 2)}\n`);
}