Skip to content

Commit aab4ef0

Browse files
committed
@W-20683422 increasing unit test coverage for ODS
1 parent d92ba87 commit aab4ef0

8 files changed

Lines changed: 2500 additions & 3 deletions

File tree

packages/b2c-cli/src/commands/ods/list.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,17 @@ export default class OdsList extends OdsCommand<typeof OdsList> {
142142
},
143143
});
144144

145-
if (!result.data?.data) {
145+
if (result.error) {
146+
const errorResponse = result.error as OdsComponents['schemas']['ErrorResponse'] | undefined;
147+
const errorMessage = errorResponse?.error?.message || result.response?.statusText || 'Unknown error';
146148
this.error(
147149
t('commands.ods.list.error', 'Failed to fetch sandboxes: {{message}}', {
148-
message: result.response?.statusText || 'Unknown error',
150+
message: errorMessage,
149151
}),
150152
);
151153
}
152154

153-
const sandboxes = result.data.data;
155+
const sandboxes = result.data?.data ?? [];
154156
const response: OdsListResponse = {
155157
count: sandboxes.length,
156158
data: sandboxes,
Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
/*
2+
* Copyright (c) 2025, Salesforce, Inc.
3+
* SPDX-License-Identifier: Apache-2
4+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
7+
/* eslint-disable @typescript-eslint/no-explicit-any, unicorn/consistent-function-scoping */
8+
import {expect} from 'chai';
9+
import OdsCreate from '../../../src/commands/ods/create.js';
10+
11+
/**
12+
* Unit tests for ODS create command CLI logic.
13+
* Tests settings building, permission logic, wait/poll logic.
14+
* SDK tests cover the actual API calls.
15+
*/
16+
describe('ods create', () => {
17+
describe('buildSettings', () => {
18+
it('should return undefined when set-permissions is false', () => {
19+
const command = new OdsCreate([], {} as any);
20+
(command as any).flags = {'set-permissions': false};
21+
22+
// Accessing private method for testing
23+
const settings = (command as any).buildSettings(false);
24+
25+
expect(settings).to.be.undefined;
26+
});
27+
28+
it('should return undefined when no client ID is configured', () => {
29+
const command = new OdsCreate([], {} as any);
30+
31+
// Mock resolvedConfig with no clientId
32+
Object.defineProperty(command, 'resolvedConfig', {
33+
get: () => ({}),
34+
configurable: true,
35+
});
36+
37+
const settings = (command as any).buildSettings(true);
38+
39+
expect(settings).to.be.undefined;
40+
});
41+
42+
it('should build settings with OCAPI and WebDAV permissions', () => {
43+
const command = new OdsCreate([], {} as any);
44+
45+
// Mock resolvedConfig with clientId
46+
Object.defineProperty(command, 'resolvedConfig', {
47+
get: () => ({clientId: 'test-client-id'}),
48+
configurable: true,
49+
});
50+
51+
const settings = (command as any).buildSettings(true);
52+
53+
expect(settings).to.exist;
54+
expect(settings).to.have.property('ocapi');
55+
expect(settings).to.have.property('webdav');
56+
expect(settings.ocapi).to.be.an('array').with.length.greaterThan(0);
57+
expect(settings.webdav).to.be.an('array').with.length.greaterThan(0);
58+
expect(settings.ocapi[0]).to.have.property('client_id', 'test-client-id');
59+
expect(settings.webdav[0]).to.have.property('client_id', 'test-client-id');
60+
});
61+
62+
it('should include default OCAPI resources', () => {
63+
const command = new OdsCreate([], {} as any);
64+
65+
Object.defineProperty(command, 'resolvedConfig', {
66+
get: () => ({clientId: 'test-client-id'}),
67+
configurable: true,
68+
});
69+
70+
const settings = (command as any).buildSettings(true);
71+
72+
const resources = settings.ocapi[0].resources;
73+
expect(resources).to.be.an('array');
74+
expect(resources.some((r: any) => r.resource_id === '/code_versions')).to.be.true;
75+
expect(resources.some((r: any) => r.resource_id.includes('/jobs/'))).to.be.true;
76+
});
77+
78+
it('should include default WebDAV permissions', () => {
79+
const command = new OdsCreate([], {} as any);
80+
81+
Object.defineProperty(command, 'resolvedConfig', {
82+
get: () => ({clientId: 'test-client-id'}),
83+
configurable: true,
84+
});
85+
86+
const settings = (command as any).buildSettings(true);
87+
88+
const permissions = settings.webdav[0].permissions;
89+
expect(permissions).to.be.an('array');
90+
expect(permissions.some((p: any) => p.path === '/impex')).to.be.true;
91+
expect(permissions.some((p: any) => p.path === '/cartridges')).to.be.true;
92+
});
93+
});
94+
95+
describe('flag defaults', () => {
96+
it('should have correct default TTL', () => {
97+
expect(OdsCreate.flags.ttl.default).to.equal(24);
98+
});
99+
100+
it('should have correct default profile', () => {
101+
expect(OdsCreate.flags.profile.default).to.equal('medium');
102+
});
103+
104+
it('should have correct default for set-permissions', () => {
105+
expect(OdsCreate.flags['set-permissions'].default).to.equal(true);
106+
});
107+
108+
it('should have correct default for auto-scheduled', () => {
109+
expect(OdsCreate.flags['auto-scheduled'].default).to.equal(false);
110+
});
111+
112+
it('should have correct default for wait', () => {
113+
expect(OdsCreate.flags.wait.default).to.equal(false);
114+
});
115+
116+
it('should have correct default poll interval', () => {
117+
expect(OdsCreate.flags['poll-interval'].default).to.equal(10);
118+
});
119+
120+
it('should have correct default timeout', () => {
121+
expect(OdsCreate.flags.timeout.default).to.equal(600);
122+
});
123+
});
124+
125+
describe('profile options', () => {
126+
it('should only allow valid profile values', () => {
127+
const validProfiles = ['medium', 'large', 'xlarge', 'xxlarge'];
128+
expect(OdsCreate.flags.profile.options).to.deep.equal(validProfiles);
129+
});
130+
});
131+
132+
describe('run()', () => {
133+
function setupCreateCommand(): OdsCreate {
134+
const command = new OdsCreate([], {} as any);
135+
136+
// Mock logger
137+
Object.defineProperty(command, 'logger', {
138+
get: () => ({info() {}}),
139+
configurable: true,
140+
});
141+
142+
// Mock log & error
143+
command.log = () => {};
144+
command.error = (msg: string) => {
145+
throw new Error(msg);
146+
};
147+
148+
return command;
149+
}
150+
151+
it('should create sandbox successfully without wait', async () => {
152+
const command = setupCreateCommand();
153+
154+
(command as any).flags = {
155+
realm: 'abcd',
156+
ttl: 24,
157+
profile: 'medium',
158+
'auto-scheduled': false,
159+
wait: false,
160+
'set-permissions': false,
161+
json: true,
162+
};
163+
164+
const mockClient = {
165+
POST: async () => ({
166+
data: {
167+
data: {
168+
id: 'sb-123',
169+
realm: 'abcd',
170+
state: 'creating',
171+
},
172+
},
173+
}),
174+
};
175+
176+
Object.defineProperty(command, 'odsClient', {
177+
get: () => mockClient,
178+
configurable: true,
179+
});
180+
181+
const result = await command.run();
182+
183+
expect(result.id).to.equal('sb-123');
184+
});
185+
186+
it('should throw error when sandbox creation fails', async () => {
187+
const command = setupCreateCommand();
188+
189+
(command as any).flags = {
190+
realm: 'abcd',
191+
ttl: 24,
192+
profile: 'medium',
193+
wait: false,
194+
'set-permissions': false,
195+
};
196+
197+
const mockClient = {
198+
POST: async () => ({
199+
data: undefined,
200+
error: {
201+
error: {message: 'Invalid realm'},
202+
},
203+
response: {
204+
statusText: 'Bad Request',
205+
},
206+
}),
207+
};
208+
209+
Object.defineProperty(command, 'odsClient', {
210+
get: () => mockClient,
211+
configurable: true,
212+
});
213+
214+
try {
215+
await command.run();
216+
expect.fail('Expected error');
217+
} catch (error: any) {
218+
expect(error.message).to.include('Failed to create sandbox');
219+
}
220+
});
221+
222+
it('should not include settings when set-permissions is false', async () => {
223+
const command = setupCreateCommand();
224+
225+
(command as any).flags = {
226+
realm: 'abcd',
227+
ttl: 24,
228+
profile: 'medium',
229+
wait: false,
230+
'set-permissions': false,
231+
};
232+
233+
let requestBody: any;
234+
235+
const mockClient = {
236+
async POST(_url: string, options: any) {
237+
requestBody = options.body;
238+
return {
239+
data: {data: {id: 'sb-1', state: 'creating'}},
240+
};
241+
},
242+
};
243+
244+
Object.defineProperty(command, 'odsClient', {
245+
get: () => mockClient,
246+
configurable: true,
247+
});
248+
249+
await command.run();
250+
251+
expect(requestBody.settings).to.be.undefined;
252+
});
253+
254+
describe('waitForSandbox()', () => {
255+
it('should wait until sandbox reaches started state', async () => {
256+
const command = setupCreateCommand();
257+
let calls = 0;
258+
259+
const mockClient = {
260+
async GET() {
261+
calls++;
262+
return {
263+
data: {
264+
data: {
265+
state: calls < 2 ? 'creating' : 'started',
266+
},
267+
},
268+
};
269+
},
270+
};
271+
272+
Object.defineProperty(command, 'odsClient', {
273+
get: () => mockClient,
274+
configurable: true,
275+
});
276+
277+
const result = await (command as any).waitForSandbox('sb-1', 0, 5);
278+
279+
expect(result.state).to.equal('started');
280+
});
281+
282+
it('should error when sandbox enters failed state', async () => {
283+
const command = setupCreateCommand();
284+
285+
Object.defineProperty(command, 'odsClient', {
286+
get: () => ({
287+
GET: async () => ({
288+
data: {data: {state: 'failed'}},
289+
}),
290+
}),
291+
configurable: true,
292+
});
293+
294+
try {
295+
await (command as any).waitForSandbox('sb-1', 0, 5);
296+
expect.fail('Expected error');
297+
} catch (error: any) {
298+
expect(error.message).to.include('Sandbox creation failed');
299+
}
300+
});
301+
302+
it('should error when sandbox is deleted', async () => {
303+
const command = setupCreateCommand();
304+
305+
Object.defineProperty(command, 'odsClient', {
306+
get: () => ({
307+
GET: async () => ({
308+
data: {data: {state: 'deleted'}},
309+
}),
310+
}),
311+
configurable: true,
312+
});
313+
314+
try {
315+
await (command as any).waitForSandbox('sb-1', 0, 5);
316+
expect.fail('Expected error');
317+
} catch (error: any) {
318+
expect(error.message).to.include('Sandbox was deleted');
319+
}
320+
});
321+
322+
it('should timeout if sandbox never reaches terminal state', async () => {
323+
const command = setupCreateCommand();
324+
325+
Object.defineProperty(command, 'odsClient', {
326+
get: () => ({
327+
GET: async () => ({
328+
data: {data: {state: 'creating'}},
329+
}),
330+
}),
331+
configurable: true,
332+
});
333+
334+
try {
335+
await (command as any).waitForSandbox('sb-1', 0, 1);
336+
expect.fail('Expected timeout');
337+
} catch (error: any) {
338+
expect(error.message).to.include('Timeout waiting for sandbox');
339+
}
340+
});
341+
342+
it('should error if polling API returns no data', async () => {
343+
const command = setupCreateCommand();
344+
345+
Object.defineProperty(command, 'odsClient', {
346+
get: () => ({
347+
GET: async () => ({
348+
data: undefined,
349+
response: {statusText: 'Internal Error'},
350+
}),
351+
}),
352+
configurable: true,
353+
});
354+
355+
try {
356+
await (command as any).waitForSandbox('sb-1', 0, 5);
357+
expect.fail('Expected error');
358+
} catch (error: any) {
359+
expect(error.message).to.include('Failed to fetch sandbox status');
360+
}
361+
});
362+
});
363+
});
364+
});

0 commit comments

Comments
 (0)