AUDIT / src /lib /__tests__ /executionPlanner.test.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
13.3 kB
/**
* executionPlanner.test.ts — S415 test suite
*
* Copre:
* - buildExecutionPlan(goal, spec, arch) → ExecutionPlan
* - formatExecutionPlan(plan) → string
* - formatExecutionPlanCompact(plan) → string
* - Ordinamento layer: auth → database → backend → frontend → integration
* - Assegnazione reqIds per layer
* - isMinimal per goal semplici
* - criticalPath esclude integration steps
*/
import { describe, it, expect } from "vitest";
import { buildExecutionPlan, formatExecutionPlan, formatExecutionPlanCompact } from "../executionPlanner";
import { decomposeGoal } from "../requirementDecomposer";
import { planArchitecture } from "../architecturePlanner";
import type { GoalSpec } from "../requirementDecomposer";
import type { ArchitecturePlan } from "../architecturePlanner";
// ─── helper per costruire spec/arch minimali senza LLM ────────────────────────
function makeSpec(reqs: Array<{ feature: string; priority?: string }>): GoalSpec {
return {
goal: "test goal",
requirements: reqs.map((r, i) => ({
id: `REQ-${String(i + 1).padStart(3, "0")}`,
feature: r.feature,
priority: (r.priority ?? "high") as "critical" | "high" | "normal",
acceptanceCriteria: [`${r.feature} funzionante`],
})),
complexity: reqs.length >= 4 ? "high" : reqs.length >= 2 ? "medium" : "low",
};
}
function makeArch(layers: Array<import("../architecturePlanner").LayerType>): ArchitecturePlan {
return {
goal: "test goal",
nodes: layers.map((layer, i) => ({
id: `node-${i}`,
layer,
name: `${layer.charAt(0).toUpperCase()}${layer.slice(1)} Service`,
tech: "generic",
description: "",
})),
edges: [],
entryPoint: layers[0] ?? "frontend",
};
}
// ─── 1. buildExecutionPlan — ordinamento layer ─────────────────────────────────
describe("buildExecutionPlan — ordinamento layer", () => {
it("auth → database → backend → frontend (ordine garantito)", () => {
const spec = makeSpec([
{ feature: "Auth/Login", priority: "critical" },
{ feature: "Database schema", priority: "high" },
{ feature: "API REST", priority: "high" },
{ feature: "Dashboard UI", priority: "normal" },
]);
const arch = makeArch(["auth", "database", "backend", "frontend"]);
const plan = buildExecutionPlan("test", spec, arch);
const layers = plan.steps.map(s => s.layer);
const authIdx = layers.indexOf("auth");
const dbIdx = layers.indexOf("database");
const backendIdx = layers.indexOf("backend");
const frontendIdx = layers.indexOf("frontend");
expect(authIdx).toBeLessThan(dbIdx);
expect(dbIdx).toBeLessThan(backendIdx);
expect(backendIdx).toBeLessThan(frontendIdx);
});
it("integration steps vengono DOPO backend", () => {
const spec = makeSpec([
{ feature: "Backend API" },
{ feature: "Redis cache" },
]);
const arch = makeArch(["backend", "cache"]);
const plan = buildExecutionPlan("test", spec, arch);
const backendIdx = plan.steps.findIndex(s => s.layer === "backend");
const integrationIdx = plan.steps.findIndex(s => s.layer === "integration");
if (backendIdx >= 0 && integrationIdx >= 0) {
expect(backendIdx).toBeLessThan(integrationIdx);
}
});
it("senza auth nell'arch, ordine inizia da database o backend", () => {
const spec = makeSpec([{ feature: "Database schema" }, { feature: "API" }]);
const arch = makeArch(["database", "backend"]);
const plan = buildExecutionPlan("test", spec, arch);
expect(plan.steps[0].layer).toBe("database");
});
});
// ─── 2. buildExecutionPlan — struttura ExecutionPlan ──────────────────────────
describe("buildExecutionPlan — struttura output", () => {
it("plan ha goal, steps, criticalPath, totalSteps, isMinimal, estimatedComplexity", () => {
const spec = makeSpec([{ feature: "Login" }, { feature: "CRUD" }]);
const arch = makeArch(["auth", "database"]);
const plan = buildExecutionPlan("app con login e CRUD", spec, arch);
expect(plan).toHaveProperty("goal");
expect(plan).toHaveProperty("steps");
expect(plan).toHaveProperty("criticalPath");
expect(plan).toHaveProperty("totalSteps");
expect(plan).toHaveProperty("isMinimal");
expect(plan).toHaveProperty("estimatedComplexity");
expect(plan.totalSteps).toBe(plan.steps.length);
});
it("ogni step ha order, name, layer, purpose, filesToCreate, verifyHint, deps, isCritical", () => {
const spec = makeSpec([{ feature: "Auth" }, { feature: "Database" }]);
const arch = makeArch(["auth", "database"]);
const plan = buildExecutionPlan("test", spec, arch);
for (const step of plan.steps) {
expect(step).toHaveProperty("order");
expect(step).toHaveProperty("name");
expect(step).toHaveProperty("layer");
expect(step).toHaveProperty("purpose");
expect(step).toHaveProperty("filesToCreate");
expect(step).toHaveProperty("verifyHint");
expect(step).toHaveProperty("deps");
expect(step).toHaveProperty("isCritical");
expect(Array.isArray(step.filesToCreate)).toBe(true);
expect(Array.isArray(step.deps)).toBe(true);
}
});
it("steps ordinati per order (0, 1, 2...)", () => {
const spec = makeSpec([
{ feature: "Auth" }, { feature: "DB" }, { feature: "API" }, { feature: "UI" },
]);
const arch = makeArch(["auth", "database", "backend", "frontend"]);
const plan = buildExecutionPlan("test", spec, arch);
const orders = plan.steps.map(s => s.order);
for (let i = 0; i < orders.length; i++) {
expect(orders[i]).toBe(i);
}
});
it("integration step ha isCritical = false", () => {
const spec = makeSpec([{ feature: "Redis caching" }]);
const arch = makeArch(["cache"]);
const plan = buildExecutionPlan("test", spec, arch);
const integStep = plan.steps.find(s => s.layer === "integration");
if (integStep) {
expect(integStep.isCritical).toBe(false);
}
});
it("auth step ha isCritical = true", () => {
const spec = makeSpec([{ feature: "Login" }]);
const arch = makeArch(["auth"]);
const plan = buildExecutionPlan("test", spec, arch);
const authStep = plan.steps.find(s => s.layer === "auth");
if (authStep) {
expect(authStep.isCritical).toBe(true);
}
});
});
// ─── 3. isMinimal ─────────────────────────────────────────────────────────────
describe("buildExecutionPlan — isMinimal", () => {
it("piano con 1 step → isMinimal = true", () => {
const spec = makeSpec([{ feature: "UI semplice" }]);
const arch = makeArch(["frontend"]);
const plan = buildExecutionPlan("test", spec, arch);
expect(plan.isMinimal).toBe(plan.totalSteps < 2);
});
it("piano con ≥2 step → isMinimal = false", () => {
const spec = makeSpec([{ feature: "Auth" }, { feature: "Database" }]);
const arch = makeArch(["auth", "database"]);
const plan = buildExecutionPlan("test", spec, arch);
if (plan.totalSteps >= 2) expect(plan.isMinimal).toBe(false);
});
});
// ─── 4. estimatedComplexity ────────────────────────────────────────────────────
describe("buildExecutionPlan — estimatedComplexity", () => {
it("≥4 step → high", () => {
const spec = makeSpec([
{ feature: "Auth" }, { feature: "DB" }, { feature: "API" }, { feature: "UI" },
]);
const arch = makeArch(["auth", "database", "backend", "frontend"]);
const plan = buildExecutionPlan("test", spec, arch);
if (plan.totalSteps >= 4) expect(plan.estimatedComplexity).toBe("high");
});
it("2-3 step → medium", () => {
const spec = makeSpec([{ feature: "DB" }, { feature: "API" }]);
const arch = makeArch(["database", "backend"]);
const plan = buildExecutionPlan("test", spec, arch);
if (plan.totalSteps >= 2 && plan.totalSteps < 4) {
expect(["medium", "high"]).toContain(plan.estimatedComplexity);
}
});
});
// ─── 5. criticalPath ──────────────────────────────────────────────────────────
describe("buildExecutionPlan — criticalPath", () => {
it("criticalPath contiene solo step non-integration", () => {
const spec = makeSpec([
{ feature: "Auth" }, { feature: "Backend" }, { feature: "Redis" },
]);
const arch = makeArch(["auth", "backend", "cache"]);
const plan = buildExecutionPlan("test", spec, arch);
for (const idx of plan.criticalPath) {
expect(plan.steps[idx]?.isCritical).toBe(true);
}
});
});
// ─── 6. formatExecutionPlan ───────────────────────────────────────────────────
describe("formatExecutionPlan", () => {
it("piano isMinimal → stringa vuota", () => {
const plan = buildExecutionPlan("test", makeSpec([{ feature: "UI" }]), makeArch(["frontend"]));
if (plan.isMinimal) {
expect(formatExecutionPlan(plan)).toBe("");
}
});
it("piano con ≥2 step → contiene [STEP N]", () => {
const spec = makeSpec([{ feature: "Auth" }, { feature: "Database" }]);
const arch = makeArch(["auth", "database"]);
const plan = buildExecutionPlan("test", spec, arch);
if (!plan.isMinimal) {
const text = formatExecutionPlan(plan);
expect(text).toMatch(/\[STEP \d\]/);
expect(text).toContain("Checkpoint");
expect(text).toContain("REGOLA");
}
});
it("output contiene il critical path", () => {
const spec = makeSpec([{ feature: "Auth" }, { feature: "Database" }]);
const arch = makeArch(["auth", "database"]);
const plan = buildExecutionPlan("test", spec, arch);
if (!plan.isMinimal) {
expect(formatExecutionPlan(plan)).toContain("Critical path");
}
});
it("ogni step contiene verifyHint nel testo", () => {
const spec = makeSpec([{ feature: "Auth" }, { feature: "API" }]);
const arch = makeArch(["auth", "backend"]);
const plan = buildExecutionPlan("test", spec, arch);
if (!plan.isMinimal) {
const text = formatExecutionPlan(plan);
expect(text).toMatch(/Verifica:/);
}
});
});
// ─── 7. formatExecutionPlanCompact ────────────────────────────────────────────
describe("formatExecutionPlanCompact", () => {
it("piano isMinimal → stringa vuota", () => {
const plan = buildExecutionPlan("test", makeSpec([{ feature: "UI" }]), makeArch(["frontend"]));
if (plan.isMinimal) {
expect(formatExecutionPlanCompact(plan)).toBe("");
}
});
it("piano con ≥2 step → contiene 🗺️ e step names", () => {
const spec = makeSpec([{ feature: "Auth" }, { feature: "Database" }]);
const arch = makeArch(["auth", "database"]);
const plan = buildExecutionPlan("test", spec, arch);
if (!plan.isMinimal) {
const compact = formatExecutionPlanCompact(plan);
expect(compact).toContain("🗺️");
expect(compact).toContain("→");
}
});
it("compact è più corto di full", () => {
const spec = makeSpec([{ feature: "Auth" }, { feature: "DB" }, { feature: "API" }]);
const arch = makeArch(["auth", "database", "backend"]);
const plan = buildExecutionPlan("test", spec, arch);
if (!plan.isMinimal) {
expect(formatExecutionPlanCompact(plan).length).toBeLessThan(formatExecutionPlan(plan).length);
}
});
});
// ─── 8. Integrazione con decomposeGoal + planArchitecture ─────────────────────
describe("buildExecutionPlan — integrazione decomposeGoal + planArchitecture", () => {
it("goal 'app con login, database e dashboard' → piano ordinato auth→db→frontend", () => {
const goal = "Crea app con login JWT, schema PostgreSQL e dashboard React";
const spec = decomposeGoal(goal);
const arch = planArchitecture(goal, spec.requirements);
const plan = buildExecutionPlan(goal, spec, arch);
expect(plan.steps.length).toBeGreaterThan(0);
expect(plan.goal).toBe(goal);
// Auth deve venire prima di database se entrambi presenti
const layers = plan.steps.map(s => s.layer);
const authIdx = layers.indexOf("auth");
const dbIdx = layers.indexOf("database");
if (authIdx >= 0 && dbIdx >= 0) {
expect(authIdx).toBeLessThan(dbIdx);
}
});
it("goal semplice → piano con almeno 1 step", () => {
const goal = "Crea una semplice app React";
const spec = decomposeGoal(goal);
const arch = planArchitecture(goal, spec.requirements);
const plan = buildExecutionPlan(goal, spec, arch);
expect(plan.steps.length).toBeGreaterThanOrEqual(1);
});
});