swallowkit 1.0.0-beta.54 → 1.0.0-beta.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/add-auth.d.ts.map +1 -1
- package/dist/cli/commands/add-auth.js +154 -157
- package/dist/cli/commands/add-auth.js.map +1 -1
- package/dist/cli/commands/init.d.ts +5 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +178 -25
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +7 -2
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +111 -123
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +127 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +8 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/operations/auth-operations.d.ts +57 -0
- package/dist/core/operations/auth-operations.d.ts.map +1 -0
- package/dist/core/operations/auth-operations.js +269 -0
- package/dist/core/operations/auth-operations.js.map +1 -0
- package/dist/core/operations/create-model.d.ts.map +1 -1
- package/dist/core/operations/create-model.js +23 -13
- package/dist/core/operations/create-model.js.map +1 -1
- package/dist/core/operations/file-session.d.ts +53 -0
- package/dist/core/operations/file-session.d.ts.map +1 -0
- package/dist/core/operations/file-session.js +195 -0
- package/dist/core/operations/file-session.js.map +1 -0
- package/dist/core/operations/provision-operations.d.ts +52 -0
- package/dist/core/operations/provision-operations.d.ts.map +1 -0
- package/dist/core/operations/provision-operations.js +211 -0
- package/dist/core/operations/provision-operations.js.map +1 -0
- package/dist/core/operations/runtime.d.ts +5 -0
- package/dist/core/operations/runtime.d.ts.map +1 -1
- package/dist/core/operations/runtime.js +23 -0
- package/dist/core/operations/runtime.js.map +1 -1
- package/dist/core/operations/scaffold-apply.d.ts +21 -0
- package/dist/core/operations/scaffold-apply.d.ts.map +1 -0
- package/dist/core/operations/scaffold-apply.js +57 -0
- package/dist/core/operations/scaffold-apply.js.map +1 -0
- package/dist/core/operations/scaffold-machine.d.ts.map +1 -1
- package/dist/core/operations/scaffold-machine.js +1 -13
- package/dist/core/operations/scaffold-machine.js.map +1 -1
- package/dist/core/operations/scaffold-plan.d.ts +43 -0
- package/dist/core/operations/scaffold-plan.d.ts.map +1 -0
- package/dist/core/operations/scaffold-plan.js +195 -0
- package/dist/core/operations/scaffold-plan.js.map +1 -0
- package/dist/core/project/artifacts.d.ts +58 -0
- package/dist/core/project/artifacts.d.ts.map +1 -0
- package/dist/core/project/artifacts.js +179 -0
- package/dist/core/project/artifacts.js.map +1 -0
- package/dist/core/project/boundaries.d.ts +50 -0
- package/dist/core/project/boundaries.d.ts.map +1 -0
- package/dist/core/project/boundaries.js +117 -0
- package/dist/core/project/boundaries.js.map +1 -0
- package/dist/core/project/drift.d.ts +29 -0
- package/dist/core/project/drift.d.ts.map +1 -0
- package/dist/core/project/drift.js +188 -0
- package/dist/core/project/drift.js.map +1 -0
- package/dist/core/project/infra.d.ts +49 -0
- package/dist/core/project/infra.d.ts.map +1 -0
- package/dist/core/project/infra.js +167 -0
- package/dist/core/project/infra.js.map +1 -0
- package/dist/core/project/state.d.ts +15 -0
- package/dist/core/project/state.d.ts.map +1 -0
- package/dist/core/project/state.js +106 -0
- package/dist/core/project/state.js.map +1 -0
- package/dist/core/project/workflows.d.ts +43 -0
- package/dist/core/project/workflows.d.ts.map +1 -0
- package/dist/core/project/workflows.js +255 -0
- package/dist/core/project/workflows.js.map +1 -0
- package/dist/core/scaffold/auth-generator.d.ts +1 -1
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -1
- package/dist/core/scaffold/auth-generator.js +108 -52
- package/dist/core/scaffold/auth-generator.js.map +1 -1
- package/dist/core/scaffold/functions-generator.js +9 -9
- package/dist/core/scaffold/nextjs-generator.js +6 -6
- package/dist/core/scaffold/ui-generator.js +19 -19
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- package/dist/core/verify/index.d.ts +62 -0
- package/dist/core/verify/index.d.ts.map +1 -0
- package/dist/core/verify/index.js +343 -0
- package/dist/core/verify/index.js.map +1 -0
- package/dist/machine/contracts.d.ts +8 -0
- package/dist/machine/contracts.d.ts.map +1 -1
- package/dist/machine/errors.d.ts +4 -1
- package/dist/machine/errors.d.ts.map +1 -1
- package/dist/machine/errors.js +9 -1
- package/dist/machine/errors.js.map +1 -1
- package/dist/machine/index.d.ts.map +1 -1
- package/dist/machine/index.js +350 -16
- package/dist/machine/index.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +202 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/types/index.d.ts +14 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +17 -17
- package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +6 -6
- package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +11 -11
- package/src/__tests__/agent-loop-phase34.test.ts +449 -0
- package/src/__tests__/bff-swa-auth.test.ts +275 -0
- package/src/__tests__/file-session.test.ts +98 -0
- package/src/__tests__/init.test.ts +50 -0
- package/src/__tests__/mcp.test.ts +113 -5
- package/src/__tests__/multi-auth.test.ts +1 -1
- package/src/__tests__/plan-apply.test.ts +294 -0
- package/src/cli/commands/add-auth.ts +164 -164
- package/src/cli/commands/init.ts +186 -24
- package/src/cli/commands/scaffold.ts +128 -129
- package/src/cli/index.ts +148 -2
- package/src/core/config.ts +5 -4
- package/src/core/operations/auth-operations.ts +353 -0
- package/src/core/operations/create-model.ts +25 -13
- package/src/core/operations/file-session.ts +210 -0
- package/src/core/operations/provision-operations.ts +297 -0
- package/src/core/operations/runtime.ts +23 -0
- package/src/core/operations/scaffold-apply.ts +106 -0
- package/src/core/operations/scaffold-machine.ts +2 -16
- package/src/core/operations/scaffold-plan.ts +224 -0
- package/src/core/project/artifacts.ts +205 -0
- package/src/core/project/boundaries.ts +170 -0
- package/src/core/project/drift.ts +202 -0
- package/src/core/project/infra.ts +196 -0
- package/src/core/project/state.ts +77 -0
- package/src/core/project/workflows.ts +249 -0
- package/src/core/scaffold/auth-generator.ts +119 -60
- package/src/core/scaffold/functions-generator.ts +9 -9
- package/src/core/scaffold/nextjs-generator.ts +6 -6
- package/src/core/scaffold/ui-generator.ts +19 -19
- package/src/core/verify/index.ts +392 -0
- package/src/machine/contracts.ts +16 -0
- package/src/machine/errors.ts +12 -1
- package/src/machine/index.ts +444 -15
- package/src/mcp/index.ts +198 -2
- package/src/types/index.ts +17 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as os from "os";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { runMachineCli } from "../machine";
|
|
5
|
+
|
|
6
|
+
jest.mock("child_process", () => {
|
|
7
|
+
const actual = jest.requireActual<typeof import("child_process")>("child_process");
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
spawn: jest.fn(),
|
|
11
|
+
spawnSync: jest.fn(),
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const repoRoot = process.cwd();
|
|
16
|
+
|
|
17
|
+
function writeFile(filePath: string, content: string): void {
|
|
18
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
19
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createModelSource(name: string, extraField = ""): string {
|
|
23
|
+
return `import { z } from 'zod/v4';
|
|
24
|
+
|
|
25
|
+
export const ${name} = z.object({
|
|
26
|
+
id: z.string(),
|
|
27
|
+
name: z.string().min(1),${extraField}
|
|
28
|
+
createdAt: z.string().optional(),
|
|
29
|
+
updatedAt: z.string().optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export type ${name} = z.infer<typeof ${name}>;
|
|
33
|
+
|
|
34
|
+
export const displayName = '${name}';
|
|
35
|
+
`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createProjectFixture(rootDir: string): void {
|
|
39
|
+
writeFile(path.join(rootDir, "package.json"), JSON.stringify({ name: "sample-app" }, null, 2));
|
|
40
|
+
writeFile(
|
|
41
|
+
path.join(rootDir, "swallowkit.config.js"),
|
|
42
|
+
`module.exports = {
|
|
43
|
+
database: {
|
|
44
|
+
connectionString: 'AccountEndpoint=https://example.local;',
|
|
45
|
+
},
|
|
46
|
+
backend: {
|
|
47
|
+
language: 'typescript',
|
|
48
|
+
},
|
|
49
|
+
api: {
|
|
50
|
+
endpoint: '/api/_swallowkit',
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
`
|
|
54
|
+
);
|
|
55
|
+
writeFile(path.join(rootDir, "shared", "package.json"), JSON.stringify({ name: "@sample-app/shared" }, null, 2));
|
|
56
|
+
writeFile(path.join(rootDir, "shared", "index.ts"), "export {};\n");
|
|
57
|
+
writeFile(path.join(rootDir, "shared", "models", "todo.ts"), createModelSource("Todo"));
|
|
58
|
+
fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true });
|
|
59
|
+
fs.symlinkSync(
|
|
60
|
+
path.join(repoRoot, "node_modules", "zod"),
|
|
61
|
+
path.join(rootDir, "node_modules", "zod"),
|
|
62
|
+
"junction"
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function runMachine(argv: string[]): Promise<{ response: any; exitCode: number }> {
|
|
67
|
+
const writes: string[] = [];
|
|
68
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
69
|
+
const originalExitCode = process.exitCode;
|
|
70
|
+
|
|
71
|
+
(process.stdout.write as unknown as (chunk: string | Uint8Array) => boolean) = ((chunk: string | Uint8Array) => {
|
|
72
|
+
writes.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf-8"));
|
|
73
|
+
return true;
|
|
74
|
+
}) as typeof process.stdout.write;
|
|
75
|
+
|
|
76
|
+
process.exitCode = 0;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await runMachineCli(argv);
|
|
80
|
+
return {
|
|
81
|
+
response: JSON.parse(writes.join("")),
|
|
82
|
+
exitCode: process.exitCode || 0,
|
|
83
|
+
};
|
|
84
|
+
} finally {
|
|
85
|
+
process.stdout.write = originalWrite;
|
|
86
|
+
process.exitCode = originalExitCode;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function machineArgs(...args: string[]): string[] {
|
|
91
|
+
return ["node", "swallowkit", "machine", ...args];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
describe("plan / apply / drift / verify machine commands", () => {
|
|
95
|
+
const originalCwd = process.cwd();
|
|
96
|
+
let tempDir: string;
|
|
97
|
+
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "swallowkit-agent-"));
|
|
100
|
+
process.chdir(tempDir);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
afterEach(() => {
|
|
104
|
+
process.chdir(originalCwd);
|
|
105
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("plans a scaffold without writing files and reports operations", async () => {
|
|
109
|
+
createProjectFixture(tempDir);
|
|
110
|
+
|
|
111
|
+
const { response, exitCode } = await runMachine(machineArgs("plan", "scaffold", "todo", "--api-only"));
|
|
112
|
+
|
|
113
|
+
expect(exitCode).toBe(0);
|
|
114
|
+
expect(response.ok).toBe(true);
|
|
115
|
+
expect(response.command).toBe("plan-scaffold");
|
|
116
|
+
expect(response.status).toBe("complete");
|
|
117
|
+
expect(response.data.planId).toMatch(/^[0-9a-f]{12}$/);
|
|
118
|
+
expect(response.data.requiresApproval).toBe(false);
|
|
119
|
+
expect(response.data.operations).toEqual(
|
|
120
|
+
expect.arrayContaining([
|
|
121
|
+
expect.objectContaining({ path: "app/api/todo/route.ts", action: "create", ownership: "managed" }),
|
|
122
|
+
expect.objectContaining({ path: "functions/src/todo.ts", action: "create" }),
|
|
123
|
+
])
|
|
124
|
+
);
|
|
125
|
+
expect(response.nextActions[0].command).toContain(`apply scaffold --plan ${response.data.planId}`);
|
|
126
|
+
|
|
127
|
+
// No generated files were written
|
|
128
|
+
expect(fs.existsSync(path.join(tempDir, "app"))).toBe(false);
|
|
129
|
+
expect(fs.existsSync(path.join(tempDir, "functions"))).toBe(false);
|
|
130
|
+
// But the plan state was persisted
|
|
131
|
+
expect(
|
|
132
|
+
fs.existsSync(path.join(tempDir, ".swallowkit", "state", "plans", `${response.data.planId}.json`))
|
|
133
|
+
).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("applies a previously computed plan and records the artifact ledger", async () => {
|
|
137
|
+
createProjectFixture(tempDir);
|
|
138
|
+
|
|
139
|
+
const plan = await runMachine(machineArgs("plan", "scaffold", "todo", "--api-only"));
|
|
140
|
+
const planId = plan.response.data.planId;
|
|
141
|
+
|
|
142
|
+
const { response, exitCode } = await runMachine(machineArgs("apply", "scaffold", "--plan", planId));
|
|
143
|
+
|
|
144
|
+
expect(exitCode).toBe(0);
|
|
145
|
+
expect(response.ok).toBe(true);
|
|
146
|
+
expect(response.command).toBe("apply-scaffold");
|
|
147
|
+
expect(response.status).toBe("complete");
|
|
148
|
+
expect(response.data.createdFiles).toEqual(
|
|
149
|
+
expect.arrayContaining(["app/api/todo/route.ts", "functions/src/todo.ts"])
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Artifact ledger created and tracks the generated files
|
|
153
|
+
const ledgerPath = path.join(tempDir, ".swallowkit", "artifacts.json");
|
|
154
|
+
expect(fs.existsSync(ledgerPath)).toBe(true);
|
|
155
|
+
const ledger = JSON.parse(fs.readFileSync(ledgerPath, "utf-8"));
|
|
156
|
+
expect(ledger.artifacts).toEqual(
|
|
157
|
+
expect.arrayContaining([
|
|
158
|
+
expect.objectContaining({ path: "app/api/todo/route.ts", ownership: "managed", sourceModel: "Todo" }),
|
|
159
|
+
])
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Applied plan state is cleaned up
|
|
163
|
+
expect(fs.existsSync(path.join(tempDir, ".swallowkit", "state", "plans", `${planId}.json`))).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("rejects a stale plan when files change after planning", async () => {
|
|
167
|
+
createProjectFixture(tempDir);
|
|
168
|
+
|
|
169
|
+
const plan = await runMachine(machineArgs("plan", "scaffold", "todo", "--api-only"));
|
|
170
|
+
const planId = plan.response.data.planId;
|
|
171
|
+
|
|
172
|
+
// Model schema changes after the plan was computed
|
|
173
|
+
writeFile(
|
|
174
|
+
path.join(tempDir, "shared", "models", "todo.ts"),
|
|
175
|
+
createModelSource("Todo", "\n priority: z.number().optional(),")
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const { response, exitCode } = await runMachine(machineArgs("apply", "scaffold", "--plan", planId));
|
|
179
|
+
|
|
180
|
+
expect(exitCode).toBe(1);
|
|
181
|
+
expect(response.ok).toBe(false);
|
|
182
|
+
expect(response.error.code).toBe("stale-plan");
|
|
183
|
+
expect(response.status).toBe("blocked");
|
|
184
|
+
expect(response.error.details.changedFiles).toContain("shared/models/todo.ts");
|
|
185
|
+
// Nothing was applied
|
|
186
|
+
expect(fs.existsSync(path.join(tempDir, "app"))).toBe(false);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("requires approval to overwrite files modified after generation, then applies with --approve", async () => {
|
|
190
|
+
createProjectFixture(tempDir);
|
|
191
|
+
|
|
192
|
+
await runMachine(machineArgs("apply", "scaffold", "todo", "--api-only"));
|
|
193
|
+
|
|
194
|
+
// User modifies a managed artifact
|
|
195
|
+
const routePath = path.join(tempDir, "app", "api", "todo", "route.ts");
|
|
196
|
+
const customized = `// customized by user\n${fs.readFileSync(routePath, "utf-8")}`;
|
|
197
|
+
fs.writeFileSync(routePath, customized, "utf-8");
|
|
198
|
+
|
|
199
|
+
const denied = await runMachine(machineArgs("apply", "scaffold", "todo", "--api-only"));
|
|
200
|
+
|
|
201
|
+
expect(denied.exitCode).toBe(1);
|
|
202
|
+
expect(denied.response.ok).toBe(false);
|
|
203
|
+
expect(denied.response.error.code).toBe("approval-required");
|
|
204
|
+
expect(denied.response.status).toBe("requires-human");
|
|
205
|
+
expect(denied.response.error.details.conflicts).toEqual(
|
|
206
|
+
expect.arrayContaining([
|
|
207
|
+
expect.objectContaining({
|
|
208
|
+
path: "app/api/todo/route.ts",
|
|
209
|
+
action: "overwrite",
|
|
210
|
+
conflictReason: "modified-after-generation",
|
|
211
|
+
}),
|
|
212
|
+
])
|
|
213
|
+
);
|
|
214
|
+
// The customization is untouched
|
|
215
|
+
expect(fs.readFileSync(routePath, "utf-8")).toBe(customized);
|
|
216
|
+
|
|
217
|
+
const approved = await runMachine(machineArgs("apply", "scaffold", "todo", "--api-only", "--approve"));
|
|
218
|
+
|
|
219
|
+
expect(approved.exitCode).toBe(0);
|
|
220
|
+
expect(approved.response.ok).toBe(true);
|
|
221
|
+
expect(approved.response.data.approvedConflicts).toEqual(
|
|
222
|
+
expect.arrayContaining([expect.objectContaining({ path: "app/api/todo/route.ts" })])
|
|
223
|
+
);
|
|
224
|
+
expect(fs.readFileSync(routePath, "utf-8")).not.toContain("customized by user");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("inspects artifacts and detects drift after manual edits and schema changes", async () => {
|
|
228
|
+
createProjectFixture(tempDir);
|
|
229
|
+
await runMachine(machineArgs("apply", "scaffold", "todo", "--api-only"));
|
|
230
|
+
|
|
231
|
+
const artifacts = await runMachine(machineArgs("inspect", "artifacts"));
|
|
232
|
+
expect(artifacts.response.ok).toBe(true);
|
|
233
|
+
expect(artifacts.response.data.ledgerFound).toBe(true);
|
|
234
|
+
expect(artifacts.response.data.artifacts).toEqual(
|
|
235
|
+
expect.arrayContaining([
|
|
236
|
+
expect.objectContaining({ path: "functions/src/todo.ts", exists: true, modified: false }),
|
|
237
|
+
])
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
// Clean project → no error/warning drift about artifacts
|
|
241
|
+
const cleanDrift = await runMachine(machineArgs("inspect", "drift"));
|
|
242
|
+
expect(cleanDrift.response.ok).toBe(true);
|
|
243
|
+
expect(
|
|
244
|
+
cleanDrift.response.data.findings.filter((f: any) => f.kind === "artifact-modified" && f.severity !== "info")
|
|
245
|
+
).toEqual([]);
|
|
246
|
+
|
|
247
|
+
// Manual edit to a managed artifact + schema change
|
|
248
|
+
fs.appendFileSync(path.join(tempDir, "functions", "src", "todo.ts"), "\n// manual edit\n");
|
|
249
|
+
writeFile(
|
|
250
|
+
path.join(tempDir, "shared", "models", "todo.ts"),
|
|
251
|
+
createModelSource("Todo", "\n priority: z.number().optional(),")
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
const drift = await runMachine(machineArgs("inspect", "drift"));
|
|
255
|
+
expect(drift.response.ok).toBe(true);
|
|
256
|
+
expect(drift.response.data.findings).toEqual(
|
|
257
|
+
expect.arrayContaining([
|
|
258
|
+
expect.objectContaining({ kind: "artifact-modified", path: "functions/src/todo.ts", severity: "warning" }),
|
|
259
|
+
expect.objectContaining({ kind: "schema-drift", entity: "Todo" }),
|
|
260
|
+
])
|
|
261
|
+
);
|
|
262
|
+
expect(drift.response.data.findings.every((f: any) => typeof f.repairAction === "string")).toBe(true);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("verifies the project and explains failures", async () => {
|
|
266
|
+
createProjectFixture(tempDir);
|
|
267
|
+
await runMachine(machineArgs("apply", "scaffold", "todo", "--api-only"));
|
|
268
|
+
|
|
269
|
+
const verify = await runMachine(machineArgs("verify", "project", "--checks", "structure,drift"));
|
|
270
|
+
|
|
271
|
+
expect(verify.exitCode).toBe(0);
|
|
272
|
+
expect(verify.response.ok).toBe(true);
|
|
273
|
+
expect(verify.response.command).toBe("verify-project");
|
|
274
|
+
expect(verify.response.status).toBe("complete");
|
|
275
|
+
expect(verify.response.data.summary.done).toBe(true);
|
|
276
|
+
expect(verify.response.data.checks.map((check: any) => check.id)).toEqual(["structure", "drift"]);
|
|
277
|
+
|
|
278
|
+
// explain failure with no failing checks returns an empty list
|
|
279
|
+
const explain = await runMachine(machineArgs("explain", "failure"));
|
|
280
|
+
expect(explain.response.ok).toBe(true);
|
|
281
|
+
expect(explain.response.data.failures).toEqual([]);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("returns no-verification-result when explain failure runs before verify", async () => {
|
|
285
|
+
createProjectFixture(tempDir);
|
|
286
|
+
|
|
287
|
+
const { response, exitCode } = await runMachine(machineArgs("explain", "failure"));
|
|
288
|
+
|
|
289
|
+
expect(exitCode).toBe(1);
|
|
290
|
+
expect(response.ok).toBe(false);
|
|
291
|
+
expect(response.error.code).toBe("no-verification-result");
|
|
292
|
+
expect(response.status).toBe("blocked");
|
|
293
|
+
});
|
|
294
|
+
});
|