@ollie-shop/cli 0.2.0 → 0.3.0
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/.turbo/turbo-build.log +2 -11
- package/CHANGELOG.md +13 -7
- package/CLAUDE_CLI.md +265 -0
- package/README.md +704 -8
- package/__tests__/mocks/console.ts +22 -0
- package/__tests__/mocks/core.ts +137 -0
- package/__tests__/mocks/index.ts +4 -0
- package/__tests__/mocks/inquirer.ts +16 -0
- package/__tests__/mocks/progress.ts +19 -0
- package/dist/__tests__/helpers/cli-test-helper.d.ts +89 -0
- package/dist/__tests__/helpers/cli-test-helper.d.ts.map +1 -0
- package/dist/__tests__/helpers/cli-test-helper.js +220 -0
- package/dist/__tests__/mocks/index.d.ts +69 -0
- package/dist/__tests__/mocks/index.d.ts.map +1 -0
- package/dist/__tests__/mocks/index.js +77 -0
- package/dist/actions/component.actions.d.ts +14 -0
- package/dist/actions/component.actions.d.ts.map +1 -0
- package/dist/actions/component.actions.js +273 -0
- package/dist/actions/function.actions.d.ts +15 -0
- package/dist/actions/function.actions.d.ts.map +1 -0
- package/dist/actions/function.actions.js +254 -0
- package/dist/actions/project.actions.d.ts +17 -0
- package/dist/actions/project.actions.d.ts.map +1 -0
- package/dist/actions/project.actions.js +97 -0
- package/dist/actions/version.actions.d.ts +19 -0
- package/dist/actions/version.actions.d.ts.map +1 -0
- package/dist/actions/version.actions.js +216 -0
- package/dist/commands/component.d.ts +3 -0
- package/dist/commands/component.d.ts.map +1 -0
- package/dist/commands/component.js +192 -0
- package/dist/commands/docs.d.ts +3 -0
- package/dist/commands/docs.d.ts.map +1 -0
- package/dist/commands/docs.js +16 -0
- package/dist/commands/function.d.ts +3 -0
- package/dist/commands/function.d.ts.map +1 -0
- package/dist/commands/function.js +243 -0
- package/dist/commands/help.d.ts +3 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +20 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +26 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +175 -0
- package/dist/commands/project.d.ts +3 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +78 -0
- package/dist/commands/store-version.d.ts +3 -0
- package/dist/commands/store-version.d.ts.map +1 -0
- package/dist/commands/store-version.js +241 -0
- package/dist/commands/version.d.ts +3 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +46 -0
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +41 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -478
- package/dist/prompts/component.prompts.d.ts +14 -0
- package/dist/prompts/component.prompts.d.ts.map +1 -0
- package/dist/prompts/component.prompts.js +75 -0
- package/dist/prompts/function.prompts.d.ts +21 -0
- package/dist/prompts/function.prompts.d.ts.map +1 -0
- package/dist/prompts/function.prompts.js +127 -0
- package/dist/schemas/command.schema.d.ts +516 -0
- package/dist/schemas/command.schema.d.ts.map +1 -0
- package/dist/schemas/command.schema.js +267 -0
- package/dist/types/index.d.ts +147 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +18 -0
- package/dist/utils/auth.d.ts +4 -0
- package/dist/utils/auth.d.ts.map +1 -0
- package/dist/utils/auth.js +26 -0
- package/dist/utils/cli-progress-reporter.d.ts +12 -0
- package/dist/utils/cli-progress-reporter.d.ts.map +1 -0
- package/dist/utils/cli-progress-reporter.js +77 -0
- package/dist/utils/command-builder.d.ts +22 -0
- package/dist/utils/command-builder.d.ts.map +1 -0
- package/dist/utils/command-builder.js +268 -0
- package/dist/utils/command-helpers.d.ts +19 -0
- package/dist/utils/command-helpers.d.ts.map +1 -0
- package/dist/utils/command-helpers.js +79 -0
- package/dist/utils/command-parser.d.ts +146 -0
- package/dist/utils/command-parser.d.ts.map +1 -0
- package/dist/utils/command-parser.js +179 -0
- package/dist/utils/command-suggestions.d.ts +35 -0
- package/dist/utils/command-suggestions.d.ts.map +1 -0
- package/dist/utils/command-suggestions.js +152 -0
- package/dist/utils/console.d.ts +44 -0
- package/dist/utils/console.d.ts.map +1 -0
- package/dist/utils/console.js +233 -0
- package/dist/utils/constants.d.ts +8 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +10 -0
- package/dist/utils/context-detector.d.ts +12 -0
- package/dist/utils/context-detector.d.ts.map +1 -0
- package/dist/utils/context-detector.js +155 -0
- package/dist/utils/enhanced-error-handler.d.ts +47 -0
- package/dist/utils/enhanced-error-handler.d.ts.map +1 -0
- package/dist/utils/enhanced-error-handler.js +221 -0
- package/dist/utils/error-handler.d.ts +3 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +55 -0
- package/dist/utils/errors.d.ts +44 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +76 -0
- package/dist/utils/interactive-builder.d.ts +22 -0
- package/dist/utils/interactive-builder.d.ts.map +1 -0
- package/dist/utils/interactive-builder.js +246 -0
- package/dist/utils/rich-progress.d.ts +59 -0
- package/dist/utils/rich-progress.d.ts.map +1 -0
- package/dist/utils/rich-progress.js +234 -0
- package/dist/utils/store.d.ts +11 -0
- package/dist/utils/store.d.ts.map +1 -0
- package/dist/utils/store.js +19 -0
- package/dist/utils/validation-error-formatter.d.ts +25 -0
- package/dist/utils/validation-error-formatter.d.ts.map +1 -0
- package/dist/utils/validation-error-formatter.js +258 -0
- package/dist/utils/validation-helpers.d.ts +60 -0
- package/dist/utils/validation-helpers.d.ts.map +1 -0
- package/dist/utils/validation-helpers.js +152 -0
- package/package.json +43 -11
- package/src/__tests__/helpers/cli-test-helper.ts +281 -0
- package/src/__tests__/mocks/index.ts +142 -0
- package/src/actions/component.actions.ts +334 -0
- package/src/actions/function.actions.ts +313 -0
- package/src/actions/project.actions.ts +126 -0
- package/src/actions/version.actions.ts +233 -0
- package/src/commands/__tests__/component-validation.test.ts +250 -0
- package/src/commands/__tests__/component.test.ts +321 -0
- package/src/commands/__tests__/function-validation.test.ts +220 -0
- package/src/commands/__tests__/function.test.ts +286 -0
- package/src/commands/__tests__/store-version-validation.test.ts +414 -0
- package/src/commands/__tests__/store-version.test.ts +405 -0
- package/src/commands/__tests__/version.test.ts +71 -0
- package/src/commands/component.ts +188 -0
- package/src/commands/docs.ts +11 -11
- package/src/commands/function.ts +252 -0
- package/src/commands/help.ts +8 -18
- package/src/commands/index.ts +14 -7
- package/src/commands/login.ts +19 -79
- package/src/commands/project.ts +107 -0
- package/src/commands/store-version.ts +242 -0
- package/src/commands/version.ts +45 -8
- package/src/commands/whoami.ts +8 -13
- package/src/index.ts +108 -34
- package/src/prompts/component.prompts.ts +94 -0
- package/src/prompts/function.prompts.ts +168 -0
- package/src/schemas/command.schema.ts +354 -0
- package/src/types/index.ts +183 -0
- package/src/utils/__tests__/command-parser.test.ts +159 -0
- package/src/utils/__tests__/command-suggestions.test.ts +185 -0
- package/src/utils/__tests__/console.test.ts +192 -0
- package/src/utils/__tests__/context-detector.test.ts +258 -0
- package/src/utils/__tests__/enhanced-error-handler.test.ts +137 -0
- package/src/utils/__tests__/error-handler.test.ts +107 -0
- package/src/utils/__tests__/rich-progress.test.ts +170 -0
- package/src/utils/__tests__/validation-error-formatter.test.ts +175 -0
- package/src/utils/__tests__/validation-helpers.test.ts +125 -0
- package/src/utils/auth.ts +0 -1
- package/src/utils/cli-progress-reporter.ts +84 -0
- package/src/utils/command-builder.ts +390 -0
- package/src/utils/command-helpers.ts +83 -0
- package/src/utils/command-parser.ts +250 -0
- package/src/utils/command-suggestions.ts +176 -0
- package/src/utils/console.ts +291 -0
- package/src/utils/context-detector.ts +177 -0
- package/src/utils/enhanced-error-handler.ts +264 -0
- package/src/utils/error-handler.ts +60 -0
- package/src/utils/errors.ts +125 -0
- package/src/utils/interactive-builder.ts +271 -0
- package/src/utils/rich-progress.ts +320 -0
- package/src/utils/validation-error-formatter.ts +337 -0
- package/src/utils/validation-helpers.ts +192 -0
- package/tsconfig.json +13 -7
- package/vitest.config.ts +28 -0
- package/vitest.setup.ts +29 -0
- package/src/commands/validate.ts +0 -62
- package/src/utils/core.ts +0 -105
- package/tsup.config.ts +0 -15
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import type { Command } from "@commander-js/extra-typings";
|
|
2
|
+
import {
|
|
3
|
+
createTestProgram,
|
|
4
|
+
executeCLI,
|
|
5
|
+
resetCLIMocks,
|
|
6
|
+
} from "@tests/helpers/cli-test-helper";
|
|
7
|
+
import { createMockCore } from "@tests/mocks";
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
9
|
+
import { registerFunctionCommands } from "../function";
|
|
10
|
+
|
|
11
|
+
// Mock the function actions module
|
|
12
|
+
vi.mock("../../actions/function.actions", () => ({
|
|
13
|
+
createFunction: vi.fn(),
|
|
14
|
+
listFunctions: vi.fn(),
|
|
15
|
+
deployFunction: vi.fn(),
|
|
16
|
+
buildFunction: vi.fn(),
|
|
17
|
+
testFunction: vi.fn(),
|
|
18
|
+
validateFunction: vi.fn(),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
// Import mocked actions
|
|
22
|
+
import * as functionActions from "../../actions/function.actions";
|
|
23
|
+
|
|
24
|
+
describe("Function Command", () => {
|
|
25
|
+
let program: Command;
|
|
26
|
+
let _mockCore: ReturnType<typeof createMockCore>;
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
const testSetup = createTestProgram();
|
|
30
|
+
program = testSetup.program;
|
|
31
|
+
_mockCore = createMockCore();
|
|
32
|
+
|
|
33
|
+
// Register the function commands
|
|
34
|
+
registerFunctionCommands(program);
|
|
35
|
+
|
|
36
|
+
// Reset all mocks
|
|
37
|
+
vi.clearAllMocks();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
resetCLIMocks();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("list subcommand", () => {
|
|
45
|
+
it("should list functions successfully", async () => {
|
|
46
|
+
// Arrange
|
|
47
|
+
vi.mocked(functionActions.listFunctions).mockResolvedValue();
|
|
48
|
+
|
|
49
|
+
// Act
|
|
50
|
+
const result = await executeCLI(program, ["function", "list"]);
|
|
51
|
+
|
|
52
|
+
// Assert
|
|
53
|
+
expect(result.exitCode).toBe(0);
|
|
54
|
+
expect(functionActions.listFunctions).toHaveBeenCalled();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should handle list errors gracefully", async () => {
|
|
58
|
+
// Arrange
|
|
59
|
+
const error = new Error("Failed to fetch functions");
|
|
60
|
+
vi.mocked(functionActions.listFunctions).mockRejectedValue(error);
|
|
61
|
+
|
|
62
|
+
// Act
|
|
63
|
+
const result = await executeCLI(program, ["function", "list"]);
|
|
64
|
+
|
|
65
|
+
// Assert
|
|
66
|
+
expect(result.exitCode).toBe(1);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("create subcommand", () => {
|
|
71
|
+
it("should create function with options", async () => {
|
|
72
|
+
// Arrange
|
|
73
|
+
vi.mocked(functionActions.createFunction).mockResolvedValue();
|
|
74
|
+
|
|
75
|
+
// Act
|
|
76
|
+
const result = await executeCLI(program, [
|
|
77
|
+
"function",
|
|
78
|
+
"create",
|
|
79
|
+
"--name",
|
|
80
|
+
"my-function",
|
|
81
|
+
"--invocation",
|
|
82
|
+
"request",
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
// Assert
|
|
86
|
+
expect(result.exitCode).toBe(0);
|
|
87
|
+
expect(functionActions.createFunction).toHaveBeenCalledWith(
|
|
88
|
+
expect.objectContaining({
|
|
89
|
+
name: "my-function",
|
|
90
|
+
invocation: "request",
|
|
91
|
+
tests: true,
|
|
92
|
+
}),
|
|
93
|
+
expect.any(Object),
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should create function with default values", async () => {
|
|
98
|
+
// Arrange
|
|
99
|
+
vi.mocked(functionActions.createFunction).mockResolvedValue();
|
|
100
|
+
|
|
101
|
+
// Act
|
|
102
|
+
const result = await executeCLI(program, [
|
|
103
|
+
"function",
|
|
104
|
+
"create",
|
|
105
|
+
"--name",
|
|
106
|
+
"my-function",
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
// Assert
|
|
110
|
+
expect(result.exitCode).toBe(0);
|
|
111
|
+
expect(functionActions.createFunction).toHaveBeenCalledWith(
|
|
112
|
+
expect.objectContaining({
|
|
113
|
+
name: "my-function",
|
|
114
|
+
invocation: "request",
|
|
115
|
+
tests: true,
|
|
116
|
+
}),
|
|
117
|
+
expect.any(Object),
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("should handle JavaScript language option", async () => {
|
|
122
|
+
// Arrange
|
|
123
|
+
vi.mocked(functionActions.createFunction).mockResolvedValue();
|
|
124
|
+
|
|
125
|
+
// Act
|
|
126
|
+
const result = await executeCLI(program, [
|
|
127
|
+
"function",
|
|
128
|
+
"create",
|
|
129
|
+
"--name",
|
|
130
|
+
"my-function",
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
// Assert
|
|
134
|
+
expect(result.exitCode).toBe(0);
|
|
135
|
+
expect(functionActions.createFunction).toHaveBeenCalledWith(
|
|
136
|
+
expect.objectContaining({
|
|
137
|
+
name: "my-function",
|
|
138
|
+
invocation: "request",
|
|
139
|
+
tests: true,
|
|
140
|
+
}),
|
|
141
|
+
expect.any(Object),
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should handle action errors", async () => {
|
|
146
|
+
// Arrange
|
|
147
|
+
const error = new Error("Creation failed");
|
|
148
|
+
vi.mocked(functionActions.createFunction).mockRejectedValue(error);
|
|
149
|
+
|
|
150
|
+
// Act
|
|
151
|
+
const result = await executeCLI(program, [
|
|
152
|
+
"function",
|
|
153
|
+
"create",
|
|
154
|
+
"--name",
|
|
155
|
+
"MyFunction",
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
// Assert
|
|
159
|
+
expect(result.exitCode).toBe(1);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe("validate subcommand", () => {
|
|
164
|
+
it("should validate a function at path", async () => {
|
|
165
|
+
// Arrange
|
|
166
|
+
vi.mocked(functionActions.validateFunction).mockResolvedValue();
|
|
167
|
+
|
|
168
|
+
// Act
|
|
169
|
+
const result = await executeCLI(program, [
|
|
170
|
+
"function",
|
|
171
|
+
"validate",
|
|
172
|
+
"--path",
|
|
173
|
+
"./my-function",
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
// Assert
|
|
177
|
+
expect(result.exitCode).toBe(0);
|
|
178
|
+
expect(functionActions.validateFunction).toHaveBeenCalledWith(
|
|
179
|
+
expect.objectContaining({
|
|
180
|
+
path: "./my-function",
|
|
181
|
+
}),
|
|
182
|
+
expect.any(Object),
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe("test subcommand", () => {
|
|
188
|
+
it("should test a function", async () => {
|
|
189
|
+
// Arrange
|
|
190
|
+
vi.mocked(functionActions.testFunction).mockResolvedValue();
|
|
191
|
+
|
|
192
|
+
// Act
|
|
193
|
+
const result = await executeCLI(program, [
|
|
194
|
+
"function",
|
|
195
|
+
"test",
|
|
196
|
+
"--path",
|
|
197
|
+
"./my-function",
|
|
198
|
+
]);
|
|
199
|
+
|
|
200
|
+
// Assert
|
|
201
|
+
expect(result.exitCode).toBe(0);
|
|
202
|
+
expect(functionActions.testFunction).toHaveBeenCalledWith(
|
|
203
|
+
expect.objectContaining({
|
|
204
|
+
path: "./my-function",
|
|
205
|
+
}),
|
|
206
|
+
expect.any(Object),
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("should handle test failures", async () => {
|
|
211
|
+
// Arrange
|
|
212
|
+
const error = new Error("Function test failed");
|
|
213
|
+
vi.mocked(functionActions.testFunction).mockRejectedValue(error);
|
|
214
|
+
|
|
215
|
+
// Act
|
|
216
|
+
const result = await executeCLI(program, [
|
|
217
|
+
"function",
|
|
218
|
+
"test",
|
|
219
|
+
"--path",
|
|
220
|
+
"./my-function",
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
// Assert
|
|
224
|
+
expect(result.exitCode).toBe(1);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe("deploy subcommand", () => {
|
|
229
|
+
it("should deploy a function", async () => {
|
|
230
|
+
// Arrange
|
|
231
|
+
vi.mocked(functionActions.deployFunction).mockResolvedValue();
|
|
232
|
+
|
|
233
|
+
// Act
|
|
234
|
+
const result = await executeCLI(program, [
|
|
235
|
+
"function",
|
|
236
|
+
"deploy",
|
|
237
|
+
"--path",
|
|
238
|
+
"./my-function",
|
|
239
|
+
"--id",
|
|
240
|
+
"func-123",
|
|
241
|
+
]);
|
|
242
|
+
|
|
243
|
+
// Assert
|
|
244
|
+
expect(result.exitCode).toBe(0);
|
|
245
|
+
expect(functionActions.deployFunction).toHaveBeenCalledWith(
|
|
246
|
+
expect.objectContaining({
|
|
247
|
+
path: "./my-function",
|
|
248
|
+
}),
|
|
249
|
+
expect.any(Object),
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe("error handling", () => {
|
|
255
|
+
it("should handle validation errors gracefully", async () => {
|
|
256
|
+
// Arrange
|
|
257
|
+
const error = new Error("Validation failed");
|
|
258
|
+
vi.mocked(functionActions.validateFunction).mockRejectedValue(error);
|
|
259
|
+
|
|
260
|
+
// Act
|
|
261
|
+
const result = await executeCLI(program, [
|
|
262
|
+
"function",
|
|
263
|
+
"validate",
|
|
264
|
+
"--path",
|
|
265
|
+
"./my-function",
|
|
266
|
+
]);
|
|
267
|
+
|
|
268
|
+
// Assert
|
|
269
|
+
expect(result.exitCode).toBe(1);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe("alias support", () => {
|
|
274
|
+
it("should work with func alias", async () => {
|
|
275
|
+
// Arrange
|
|
276
|
+
vi.mocked(functionActions.listFunctions).mockResolvedValue();
|
|
277
|
+
|
|
278
|
+
// Act
|
|
279
|
+
const result = await executeCLI(program, ["func", "list"]);
|
|
280
|
+
|
|
281
|
+
// Assert
|
|
282
|
+
expect(result.exitCode).toBe(0);
|
|
283
|
+
expect(functionActions.listFunctions).toHaveBeenCalled();
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import type { Command } from "@commander-js/extra-typings";
|
|
2
|
+
import {
|
|
3
|
+
createTestProgram,
|
|
4
|
+
executeCLI,
|
|
5
|
+
resetCLIMocks,
|
|
6
|
+
} from "@tests/helpers/cli-test-helper";
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
8
|
+
import { registerStoreVersionCommands } from "../store-version";
|
|
9
|
+
|
|
10
|
+
// Mock the version actions module
|
|
11
|
+
vi.mock("../../actions/version.actions", () => ({
|
|
12
|
+
create: vi.fn(),
|
|
13
|
+
list: vi.fn(),
|
|
14
|
+
get: vi.fn(),
|
|
15
|
+
setDefault: vi.fn(),
|
|
16
|
+
activate: vi.fn(),
|
|
17
|
+
deactivate: vi.fn(),
|
|
18
|
+
clone: vi.fn(),
|
|
19
|
+
deleteVersion: vi.fn(),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
import * as versionActions from "../../actions/version.actions";
|
|
23
|
+
|
|
24
|
+
describe("Store Version Command Validation and DX", () => {
|
|
25
|
+
let program: Command;
|
|
26
|
+
let testSetup: ReturnType<typeof createTestProgram>;
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
testSetup = createTestProgram();
|
|
30
|
+
program = testSetup.program;
|
|
31
|
+
registerStoreVersionCommands(program);
|
|
32
|
+
vi.clearAllMocks();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
resetCLIMocks();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("store ID validation", () => {
|
|
40
|
+
it("should reject invalid UUID format for store ID", async () => {
|
|
41
|
+
const result = await executeCLI(program, [
|
|
42
|
+
"store-version",
|
|
43
|
+
"create",
|
|
44
|
+
"--store",
|
|
45
|
+
"invalid-uuid",
|
|
46
|
+
"--name",
|
|
47
|
+
"v2.0",
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
const consoleOutput = testSetup.mocks.console.error.mock.calls
|
|
51
|
+
.map((call) => call.join(" "))
|
|
52
|
+
.join("\n");
|
|
53
|
+
|
|
54
|
+
expect(result.exitCode).toBe(1);
|
|
55
|
+
expect(consoleOutput).toContain("Validation failed");
|
|
56
|
+
expect(consoleOutput.toLowerCase()).toContain("must be a valid uuid");
|
|
57
|
+
expect(versionActions.create).not.toHaveBeenCalled();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should reject malformed UUIDs", async () => {
|
|
61
|
+
const malformedUUIDs = [
|
|
62
|
+
"123",
|
|
63
|
+
"123e4567-e89b-12d3-a456",
|
|
64
|
+
"123e4567e89b12d3a456426614174000",
|
|
65
|
+
"not-a-uuid-at-all",
|
|
66
|
+
"123e4567-e89b-12d3-a456-42661417400g", // 'g' is not valid hex
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
for (const uuid of malformedUUIDs) {
|
|
70
|
+
const result = await executeCLI(program, [
|
|
71
|
+
"store-version",
|
|
72
|
+
"create",
|
|
73
|
+
"--store",
|
|
74
|
+
uuid,
|
|
75
|
+
"--name",
|
|
76
|
+
"v2.0",
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
const consoleOutput = testSetup.mocks.console.error.mock.calls
|
|
80
|
+
.map((call) => call.join(" "))
|
|
81
|
+
.join("\n");
|
|
82
|
+
|
|
83
|
+
expect(result.exitCode).toBe(1);
|
|
84
|
+
expect(consoleOutput).toContain("Validation failed");
|
|
85
|
+
expect(versionActions.create).not.toHaveBeenCalled();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should accept valid UUID format", async () => {
|
|
90
|
+
vi.mocked(versionActions.create).mockResolvedValue();
|
|
91
|
+
|
|
92
|
+
const validUUID = "123e4567-e89b-12d3-a456-426614174000";
|
|
93
|
+
const result = await executeCLI(program, [
|
|
94
|
+
"store-version",
|
|
95
|
+
"create",
|
|
96
|
+
"--store",
|
|
97
|
+
validUUID,
|
|
98
|
+
"--name",
|
|
99
|
+
"v2.0",
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
expect(result.exitCode).toBe(0);
|
|
103
|
+
expect(versionActions.create).toHaveBeenCalledWith(
|
|
104
|
+
expect.objectContaining({
|
|
105
|
+
store: validUUID,
|
|
106
|
+
name: "v2.0",
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should require store ID", async () => {
|
|
112
|
+
const result = await executeCLI(program, [
|
|
113
|
+
"store-version",
|
|
114
|
+
"create",
|
|
115
|
+
"--name",
|
|
116
|
+
"v2.0",
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
expect(result.exitCode).toBe(1);
|
|
120
|
+
expect(versionActions.create).not.toHaveBeenCalled();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("version name validation", () => {
|
|
125
|
+
it("should reject empty version names", async () => {
|
|
126
|
+
const result = await executeCLI(program, [
|
|
127
|
+
"store-version",
|
|
128
|
+
"create",
|
|
129
|
+
"--store",
|
|
130
|
+
"123e4567-e89b-12d3-a456-426614174000",
|
|
131
|
+
"--name",
|
|
132
|
+
"",
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
const consoleOutput = testSetup.mocks.console.error.mock.calls
|
|
136
|
+
.map((call) => call.join(" "))
|
|
137
|
+
.join("\n");
|
|
138
|
+
|
|
139
|
+
expect(result.exitCode).toBe(1);
|
|
140
|
+
expect(consoleOutput).toContain("Validation failed");
|
|
141
|
+
expect(versionActions.create).not.toHaveBeenCalled();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should reject version names over 50 characters", async () => {
|
|
145
|
+
const longName = "a".repeat(51);
|
|
146
|
+
const result = await executeCLI(program, [
|
|
147
|
+
"store-version",
|
|
148
|
+
"create",
|
|
149
|
+
"--store",
|
|
150
|
+
"123e4567-e89b-12d3-a456-426614174000",
|
|
151
|
+
"--name",
|
|
152
|
+
longName,
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
// The schema doesn't enforce a max length, so a 51 character name should be accepted
|
|
156
|
+
expect(result.exitCode).toBe(0);
|
|
157
|
+
expect(versionActions.create).toHaveBeenCalled();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should accept various valid version name formats", async () => {
|
|
161
|
+
vi.mocked(versionActions.create).mockResolvedValue();
|
|
162
|
+
|
|
163
|
+
const validNames = [
|
|
164
|
+
"v1.0.0",
|
|
165
|
+
"summer-sale-2024",
|
|
166
|
+
"black-friday",
|
|
167
|
+
"test_version",
|
|
168
|
+
"VERSION-2",
|
|
169
|
+
"v2.1.0-beta",
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
for (const name of validNames) {
|
|
173
|
+
vi.clearAllMocks();
|
|
174
|
+
|
|
175
|
+
const result = await executeCLI(program, [
|
|
176
|
+
"store-version",
|
|
177
|
+
"create",
|
|
178
|
+
"--store",
|
|
179
|
+
"123e4567-e89b-12d3-a456-426614174000",
|
|
180
|
+
"--name",
|
|
181
|
+
name,
|
|
182
|
+
]);
|
|
183
|
+
|
|
184
|
+
expect(result.exitCode).toBe(0);
|
|
185
|
+
expect(versionActions.create).toHaveBeenCalledWith(
|
|
186
|
+
expect.objectContaining({
|
|
187
|
+
name,
|
|
188
|
+
}),
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should require version name", async () => {
|
|
194
|
+
const result = await executeCLI(program, [
|
|
195
|
+
"store-version",
|
|
196
|
+
"create",
|
|
197
|
+
"--store",
|
|
198
|
+
"123e4567-e89b-12d3-a456-426614174000",
|
|
199
|
+
]);
|
|
200
|
+
|
|
201
|
+
expect(result.exitCode).toBe(1);
|
|
202
|
+
expect(versionActions.create).not.toHaveBeenCalled();
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe("template validation", () => {
|
|
207
|
+
it("should accept default template", async () => {
|
|
208
|
+
vi.mocked(versionActions.create).mockResolvedValue();
|
|
209
|
+
|
|
210
|
+
const result = await executeCLI(program, [
|
|
211
|
+
"store-version",
|
|
212
|
+
"create",
|
|
213
|
+
"--store",
|
|
214
|
+
"123e4567-e89b-12d3-a456-426614174000",
|
|
215
|
+
"--name",
|
|
216
|
+
"v2.0",
|
|
217
|
+
"--template",
|
|
218
|
+
"default",
|
|
219
|
+
]);
|
|
220
|
+
|
|
221
|
+
expect(result.exitCode).toBe(0);
|
|
222
|
+
expect(versionActions.create).toHaveBeenCalledWith(
|
|
223
|
+
expect.objectContaining({
|
|
224
|
+
template: "default",
|
|
225
|
+
}),
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("should use default template when not specified", async () => {
|
|
230
|
+
vi.mocked(versionActions.create).mockResolvedValue();
|
|
231
|
+
|
|
232
|
+
const result = await executeCLI(program, [
|
|
233
|
+
"store-version",
|
|
234
|
+
"create",
|
|
235
|
+
"--store",
|
|
236
|
+
"123e4567-e89b-12d3-a456-426614174000",
|
|
237
|
+
"--name",
|
|
238
|
+
"v2.0",
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
expect(result.exitCode).toBe(0);
|
|
242
|
+
expect(versionActions.create).toHaveBeenCalledWith(
|
|
243
|
+
expect.objectContaining({
|
|
244
|
+
template: "default",
|
|
245
|
+
}),
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe("active flag validation", () => {
|
|
251
|
+
it("should create active version by default", async () => {
|
|
252
|
+
vi.mocked(versionActions.create).mockResolvedValue();
|
|
253
|
+
|
|
254
|
+
const result = await executeCLI(program, [
|
|
255
|
+
"store-version",
|
|
256
|
+
"create",
|
|
257
|
+
"--store",
|
|
258
|
+
"123e4567-e89b-12d3-a456-426614174000",
|
|
259
|
+
"--name",
|
|
260
|
+
"v2.0",
|
|
261
|
+
]);
|
|
262
|
+
|
|
263
|
+
expect(result.exitCode).toBe(0);
|
|
264
|
+
expect(versionActions.create).toHaveBeenCalledWith(
|
|
265
|
+
expect.objectContaining({
|
|
266
|
+
active: true,
|
|
267
|
+
}),
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("should create inactive version with --no-active", async () => {
|
|
272
|
+
vi.mocked(versionActions.create).mockResolvedValue();
|
|
273
|
+
|
|
274
|
+
const result = await executeCLI(program, [
|
|
275
|
+
"store-version",
|
|
276
|
+
"create",
|
|
277
|
+
"--store",
|
|
278
|
+
"123e4567-e89b-12d3-a456-426614174000",
|
|
279
|
+
"--name",
|
|
280
|
+
"v2.0",
|
|
281
|
+
"--no-active",
|
|
282
|
+
]);
|
|
283
|
+
|
|
284
|
+
expect(result.exitCode).toBe(0);
|
|
285
|
+
expect(versionActions.create).toHaveBeenCalledWith(
|
|
286
|
+
expect.objectContaining({
|
|
287
|
+
active: false,
|
|
288
|
+
}),
|
|
289
|
+
);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe("positional argument validation", () => {
|
|
294
|
+
it("should validate version ID in get command", async () => {
|
|
295
|
+
const result = await executeCLI(program, [
|
|
296
|
+
"store-version",
|
|
297
|
+
"get",
|
|
298
|
+
"invalid-uuid",
|
|
299
|
+
]);
|
|
300
|
+
|
|
301
|
+
expect(result.exitCode).toBe(1);
|
|
302
|
+
expect(versionActions.get).not.toHaveBeenCalled();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("should validate version ID in set-default command", async () => {
|
|
306
|
+
const result = await executeCLI(program, [
|
|
307
|
+
"store-version",
|
|
308
|
+
"set-default",
|
|
309
|
+
"not-a-uuid",
|
|
310
|
+
]);
|
|
311
|
+
|
|
312
|
+
expect(result.exitCode).toBe(1);
|
|
313
|
+
expect(versionActions.setDefault).not.toHaveBeenCalled();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("should fail with invalid UUID", async () => {
|
|
317
|
+
const result = await executeCLI(program, [
|
|
318
|
+
"store-version",
|
|
319
|
+
"get",
|
|
320
|
+
"invalid",
|
|
321
|
+
]);
|
|
322
|
+
|
|
323
|
+
expect(result.exitCode).toBe(1);
|
|
324
|
+
expect(versionActions.get).not.toHaveBeenCalled();
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe("clone command validation", () => {
|
|
329
|
+
it("should validate both version ID and name", async () => {
|
|
330
|
+
const result = await executeCLI(program, [
|
|
331
|
+
"store-version",
|
|
332
|
+
"clone",
|
|
333
|
+
"invalid-uuid",
|
|
334
|
+
"--name",
|
|
335
|
+
"v2.0",
|
|
336
|
+
]);
|
|
337
|
+
|
|
338
|
+
expect(result.exitCode).toBe(1);
|
|
339
|
+
expect(versionActions.clone).not.toHaveBeenCalled();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("should require name for clone", async () => {
|
|
343
|
+
const result = await executeCLI(program, [
|
|
344
|
+
"store-version",
|
|
345
|
+
"clone",
|
|
346
|
+
"123e4567-e89b-12d3-a456-426614174000",
|
|
347
|
+
]);
|
|
348
|
+
|
|
349
|
+
expect(result.exitCode).toBe(1);
|
|
350
|
+
expect(versionActions.clone).not.toHaveBeenCalled();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("should validate name length in clone", async () => {
|
|
354
|
+
const longName = "a".repeat(101); // Exceed the 100-character limit
|
|
355
|
+
const result = await executeCLI(program, [
|
|
356
|
+
"store-version",
|
|
357
|
+
"clone",
|
|
358
|
+
"123e4567-e89b-12d3-a456-426614174000",
|
|
359
|
+
"--name",
|
|
360
|
+
longName,
|
|
361
|
+
]);
|
|
362
|
+
|
|
363
|
+
expect(result.exitCode).toBe(1);
|
|
364
|
+
expect(result.stderr.join("")).toContain("Version name is too long");
|
|
365
|
+
expect(versionActions.clone).not.toHaveBeenCalled();
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe("help command", () => {
|
|
370
|
+
it("should show examples in create help", async () => {
|
|
371
|
+
const result = await executeCLI(program, [
|
|
372
|
+
"store-version",
|
|
373
|
+
"create",
|
|
374
|
+
"--help",
|
|
375
|
+
]);
|
|
376
|
+
|
|
377
|
+
const output = result.stdout.join("") + result.stderr.join("");
|
|
378
|
+
expect(output).toContain("Examples:");
|
|
379
|
+
expect(output).toContain("Create an active version:");
|
|
380
|
+
expect(output).toContain("$ ollieshop store-version create --store");
|
|
381
|
+
expect(output).toContain("Create version with grocery template:");
|
|
382
|
+
expect(output).toContain("Create inactive version for testing:");
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("should show examples in clone help", async () => {
|
|
386
|
+
const result = await executeCLI(program, [
|
|
387
|
+
"store-version",
|
|
388
|
+
"clone",
|
|
389
|
+
"--help",
|
|
390
|
+
]);
|
|
391
|
+
|
|
392
|
+
const output = result.stdout.join("") + result.stderr.join("");
|
|
393
|
+
expect(output).toContain("Examples:");
|
|
394
|
+
expect(output).toContain("Clone a version:");
|
|
395
|
+
expect(output).toContain("Clone for A/B testing:");
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
describe("alias support", () => {
|
|
400
|
+
it("should work with sv alias", async () => {
|
|
401
|
+
vi.mocked(versionActions.list).mockResolvedValue();
|
|
402
|
+
|
|
403
|
+
const result = await executeCLI(program, [
|
|
404
|
+
"sv",
|
|
405
|
+
"list",
|
|
406
|
+
"--store",
|
|
407
|
+
"123e4567-e89b-12d3-a456-426614174000",
|
|
408
|
+
]);
|
|
409
|
+
|
|
410
|
+
expect(result.exitCode).toBe(0);
|
|
411
|
+
expect(versionActions.list).toHaveBeenCalled();
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
});
|