Skip to content

Commit 485b3cf

Browse files
committed
cp dines
1 parent fca093c commit 485b3cf

3 files changed

Lines changed: 14 additions & 138 deletions

File tree

src/commands/blueprint/delete.ts

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/**
22
* Delete blueprint command
3-
* Supports both blueprint ID (bpt_...) and name
43
*/
54

65
import { getClient } from "../../utils/client.js";
@@ -11,41 +10,20 @@ interface DeleteOptions {
1110
}
1211

1312
export async function deleteBlueprint(
14-
nameOrId: string,
13+
id: string,
1514
options: DeleteOptions = {},
1615
) {
1716
try {
1817
const client = getClient();
1918

20-
let blueprintId = nameOrId;
21-
22-
// If it's not an ID, resolve by name
23-
if (!nameOrId.startsWith("bpt_")) {
24-
const result = await client.blueprints.list({ name: nameOrId });
25-
const blueprints = result.blueprints || [];
26-
27-
if (blueprints.length === 0) {
28-
outputError(
29-
`Blueprint not found: ${nameOrId}`,
30-
new Error("Blueprint not found"),
31-
);
32-
return;
33-
}
34-
35-
// Use exact match if available, otherwise first result
36-
const blueprint =
37-
blueprints.find((b) => b.name === nameOrId) || blueprints[0];
38-
blueprintId = blueprint.id;
39-
}
40-
41-
await client.blueprints.delete(blueprintId);
19+
await client.blueprints.delete(id);
4220

4321
// Default: just output the ID for easy scripting
4422
if (!options.output || options.output === "text") {
45-
console.log(blueprintId);
23+
console.log(id);
4624
} else {
4725
output(
48-
{ id: blueprintId, status: "deleted" },
26+
{ id, status: "deleted" },
4927
{ format: options.output, defaultFormat: "json" },
5028
);
5129
}

src/utils/commands.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -469,8 +469,8 @@ export function createProgram(): Command {
469469
});
470470

471471
blueprint
472-
.command("delete <name-or-id>")
473-
.description("Delete a blueprint by name or ID (IDs start with bpt_)")
472+
.command("delete <id>")
473+
.description("Delete a blueprint by ID")
474474
.alias("rm")
475475
.option(
476476
"-o, --output [format]",

tests/__tests__/commands/blueprint/delete.test.ts

Lines changed: 8 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@ import { jest, describe, it, expect, beforeEach } from "@jest/globals";
66

77
// Mock dependencies using the path alias
88
const mockDelete = jest.fn();
9-
const mockList = jest.fn();
109
jest.unstable_mockModule("@/utils/client.js", () => ({
1110
getClient: () => ({
1211
blueprints: {
1312
delete: mockDelete,
14-
list: mockList,
1513
},
1614
}),
1715
}));
@@ -28,106 +26,22 @@ describe("deleteBlueprint", () => {
2826
jest.clearAllMocks();
2927
(console.log as jest.Mock).mockClear();
3028
mockDelete.mockReset();
31-
mockList.mockReset();
3229
mockOutput.mockReset();
3330
mockOutputError.mockReset();
3431
});
3532

36-
it("should delete a blueprint by ID directly", async () => {
33+
it("should delete a blueprint by ID", async () => {
3734
mockDelete.mockResolvedValue(undefined);
3835

3936
const { deleteBlueprint } = await import(
4037
"@/commands/blueprint/delete.js"
4138
);
4239
await deleteBlueprint("bpt_abc123", {});
4340

44-
expect(mockList).not.toHaveBeenCalled();
4541
expect(mockDelete).toHaveBeenCalledWith("bpt_abc123");
4642
expect(console.log).toHaveBeenCalledWith("bpt_abc123");
4743
});
4844

49-
it("should resolve blueprint by name and delete", async () => {
50-
mockList.mockResolvedValue({
51-
blueprints: [{ id: "bpt_resolved", name: "my-blueprint" }],
52-
});
53-
mockDelete.mockResolvedValue(undefined);
54-
55-
const { deleteBlueprint } = await import(
56-
"@/commands/blueprint/delete.js"
57-
);
58-
await deleteBlueprint("my-blueprint", {});
59-
60-
expect(mockList).toHaveBeenCalledWith({ name: "my-blueprint" });
61-
expect(mockDelete).toHaveBeenCalledWith("bpt_resolved");
62-
expect(console.log).toHaveBeenCalledWith("bpt_resolved");
63-
});
64-
65-
it("should prefer exact name match when resolving by name", async () => {
66-
mockList.mockResolvedValue({
67-
blueprints: [
68-
{ id: "bpt_partial", name: "my-blueprint-v2" },
69-
{ id: "bpt_exact", name: "my-blueprint" },
70-
],
71-
});
72-
mockDelete.mockResolvedValue(undefined);
73-
74-
const { deleteBlueprint } = await import(
75-
"@/commands/blueprint/delete.js"
76-
);
77-
await deleteBlueprint("my-blueprint", {});
78-
79-
expect(mockDelete).toHaveBeenCalledWith("bpt_exact");
80-
expect(console.log).toHaveBeenCalledWith("bpt_exact");
81-
});
82-
83-
it("should fall back to first result when no exact name match", async () => {
84-
mockList.mockResolvedValue({
85-
blueprints: [
86-
{ id: "bpt_first", name: "my-blueprint-v1" },
87-
{ id: "bpt_second", name: "my-blueprint-v2" },
88-
],
89-
});
90-
mockDelete.mockResolvedValue(undefined);
91-
92-
const { deleteBlueprint } = await import(
93-
"@/commands/blueprint/delete.js"
94-
);
95-
await deleteBlueprint("my-blueprint", {});
96-
97-
expect(mockDelete).toHaveBeenCalledWith("bpt_first");
98-
expect(console.log).toHaveBeenCalledWith("bpt_first");
99-
});
100-
101-
it("should output error when blueprint name is not found", async () => {
102-
mockList.mockResolvedValue({ blueprints: [] });
103-
104-
const { deleteBlueprint } = await import(
105-
"@/commands/blueprint/delete.js"
106-
);
107-
await deleteBlueprint("nonexistent-blueprint", {});
108-
109-
expect(mockOutputError).toHaveBeenCalledWith(
110-
"Blueprint not found: nonexistent-blueprint",
111-
expect.any(Error),
112-
);
113-
expect(mockDelete).not.toHaveBeenCalled();
114-
});
115-
116-
it("should handle empty blueprints array from API", async () => {
117-
mockList.mockResolvedValue({});
118-
119-
const { deleteBlueprint } = await import(
120-
"@/commands/blueprint/delete.js"
121-
);
122-
await deleteBlueprint("nonexistent", {});
123-
124-
expect(mockOutputError).toHaveBeenCalledWith(
125-
"Blueprint not found: nonexistent",
126-
expect.any(Error),
127-
);
128-
expect(mockDelete).not.toHaveBeenCalled();
129-
});
130-
13145
it("should output JSON format when requested", async () => {
13246
mockDelete.mockResolvedValue(undefined);
13347

@@ -183,7 +97,7 @@ describe("deleteBlueprint", () => {
18397
expect(mockOutput).not.toHaveBeenCalled();
18498
});
18599

186-
it("should handle API errors on delete gracefully", async () => {
100+
it("should handle API errors gracefully", async () => {
187101
const apiError = new Error("API Error: Forbidden");
188102
mockDelete.mockRejectedValue(apiError);
189103

@@ -198,36 +112,20 @@ describe("deleteBlueprint", () => {
198112
);
199113
});
200114

201-
it("should handle API errors on list gracefully", async () => {
202-
const apiError = new Error("API Error: Network failure");
203-
mockList.mockRejectedValue(apiError);
115+
it("should handle dependent snapshot errors gracefully", async () => {
116+
const apiError = new Error(
117+
"Blueprint has dependent snapshots and cannot be deleted",
118+
);
119+
mockDelete.mockRejectedValue(apiError);
204120

205121
const { deleteBlueprint } = await import(
206122
"@/commands/blueprint/delete.js"
207123
);
208-
await deleteBlueprint("some-name", {});
124+
await deleteBlueprint("bpt_has_snapshots", {});
209125

210126
expect(mockOutputError).toHaveBeenCalledWith(
211127
"Failed to delete blueprint",
212128
apiError,
213129
);
214-
expect(mockDelete).not.toHaveBeenCalled();
215-
});
216-
217-
it("should output resolved ID in text format when deleting by name", async () => {
218-
mockList.mockResolvedValue({
219-
blueprints: [{ id: "bpt_resolved_id", name: "named-blueprint" }],
220-
});
221-
mockDelete.mockResolvedValue(undefined);
222-
223-
const { deleteBlueprint } = await import(
224-
"@/commands/blueprint/delete.js"
225-
);
226-
await deleteBlueprint("named-blueprint", { output: "json" });
227-
228-
expect(mockOutput).toHaveBeenCalledWith(
229-
{ id: "bpt_resolved_id", status: "deleted" },
230-
{ format: "json", defaultFormat: "json" },
231-
);
232130
});
233131
});

0 commit comments

Comments
 (0)