Skip to content

Commit 3e7fbdc

Browse files
committed
Add tests for bm whoami, bm users, and bm access-key commands
10 new test files following the bm/roles test patterns. Each covers: JSON-mode return shape, non-JSON output (where applicable), flag/arg behavior, and OCAPI error paths via the expectError helper. - whoami.test.ts (3 cases) - users/{list,get,delete}.test.ts (3 cases each) - users/search.test.ts (5 cases — covers convenience flags, raw --query passthrough, and invalid JSON rejection) - users/update.test.ts (4 cases — covers the field→snake_case mapping and "no fields" guard) - access-key/{get,delete}.test.ts cover both the explicit-login and whoami-fallback branches via two-call OCAPI stubs - access-key/{create,set}.test.ts cover scope flag and PATCH body shape CLI tests now: 1218 passing (was 1184). SDK tests unchanged at 1722.
1 parent 1c8e419 commit 3e7fbdc

10 files changed

Lines changed: 866 additions & 0 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
import {ux} from '@oclif/core';
8+
import {expect} from 'chai';
9+
import {afterEach, beforeEach} from 'mocha';
10+
import sinon from 'sinon';
11+
import BmAccessKeyCreate from '../../../../src/commands/bm/access-key/create.js';
12+
import {createIsolatedConfigHooks, createTestCommand, expectError} from '../../../helpers/test-setup.js';
13+
14+
describe('bm access-key create', () => {
15+
const hooks = createIsolatedConfigHooks();
16+
17+
beforeEach(hooks.beforeEach);
18+
19+
afterEach(hooks.afterEach);
20+
21+
async function createCommand(flags: Record<string, unknown> = {}, args: Record<string, unknown> = {}) {
22+
return createTestCommand(BmAccessKeyCreate, hooks.getConfig(), flags, args);
23+
}
24+
25+
function stubCommon(command: any, {jsonEnabled}: {jsonEnabled: boolean}) {
26+
sinon.stub(command, 'requireOAuthCredentials').returns(void 0);
27+
sinon.stub(command, 'resolvedConfig').get(() => ({values: {hostname: 'example.com'}}));
28+
sinon.stub(command, 'jsonEnabled').returns(jsonEnabled);
29+
}
30+
31+
it('creates an access key and returns secret in JSON mode', async () => {
32+
const command: any = await createCommand({scope: 'STOREFRONT'}, {login: 'user@x.com'});
33+
stubCommon(command, {jsonEnabled: true});
34+
sinon.stub(command, 'log').returns(void 0);
35+
36+
const ocapiPut = sinon.stub().resolves({
37+
data: {access_key: 'secret-value', enabled: true, expiration_date: '2027-01-01'},
38+
error: undefined,
39+
});
40+
sinon.stub(command, 'instance').get(() => ({ocapi: {PUT: ocapiPut}}));
41+
42+
const result = await command.run();
43+
expect(result.access_key).to.equal('secret-value');
44+
expect(result.enabled).to.equal(true);
45+
expect(ocapiPut.calledOnce).to.equal(true);
46+
expect(ocapiPut.firstCall.args[0]).to.equal('/users/{login}/access_key/{scope}');
47+
expect(ocapiPut.firstCall.args[1].params.path).to.deep.equal({
48+
login: 'user@x.com',
49+
scope: 'STOREFRONT',
50+
});
51+
});
52+
53+
it('displays access key details in non-JSON mode', async () => {
54+
const command: any = await createCommand({scope: 'WEBDAV_AND_STUDIO'}, {login: 'user@x.com'});
55+
stubCommon(command, {jsonEnabled: false});
56+
sinon.stub(command, 'log').returns(void 0);
57+
58+
const ocapiPut = sinon.stub().resolves({
59+
data: {access_key: 'secret-value', enabled: true, expiration_date: '2027-01-01'},
60+
error: undefined,
61+
});
62+
sinon.stub(command, 'instance').get(() => ({ocapi: {PUT: ocapiPut}}));
63+
64+
const stdoutStub = sinon.stub(ux, 'stdout').returns(void 0 as any);
65+
66+
const result = await command.run();
67+
expect(result.access_key).to.equal('secret-value');
68+
expect(stdoutStub.calledOnce).to.equal(true);
69+
});
70+
71+
it('throws when API returns a fault', async () => {
72+
const command: any = await createCommand({scope: 'WEBDAV_AND_STUDIO'}, {login: 'user@x.com'});
73+
sinon.stub(command, 'requireOAuthCredentials').returns(void 0);
74+
sinon.stub(command, 'resolvedConfig').get(() => ({values: {hostname: 'example.com'}}));
75+
sinon.stub(command, 'log').returns(void 0);
76+
77+
const ocapiPut = sinon.stub().resolves({
78+
data: undefined,
79+
error: {fault: {message: 'Bad request'}},
80+
response: {status: 400, statusText: 'Bad Request'},
81+
});
82+
sinon.stub(command, 'instance').get(() => ({ocapi: {PUT: ocapiPut}}));
83+
84+
await expectError(() => command.run(), /Failed to create access key/);
85+
});
86+
});
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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+
import {expect} from 'chai';
8+
import {afterEach, beforeEach} from 'mocha';
9+
import sinon from 'sinon';
10+
import BmAccessKeyDelete from '../../../../src/commands/bm/access-key/delete.js';
11+
import {createIsolatedConfigHooks, createTestCommand, expectError} from '../../../helpers/test-setup.js';
12+
13+
describe('bm access-key delete', () => {
14+
const hooks = createIsolatedConfigHooks();
15+
16+
beforeEach(hooks.beforeEach);
17+
18+
afterEach(hooks.afterEach);
19+
20+
async function createCommand(flags: Record<string, unknown> = {}, args: Record<string, unknown> = {}) {
21+
return createTestCommand(BmAccessKeyDelete, hooks.getConfig(), flags, args);
22+
}
23+
24+
function stubCommon(command: any, {jsonEnabled}: {jsonEnabled: boolean}) {
25+
sinon.stub(command, 'requireOAuthCredentials').returns(void 0);
26+
sinon.stub(command, 'resolvedConfig').get(() => ({values: {hostname: 'example.com'}}));
27+
sinon.stub(command, 'jsonEnabled').returns(jsonEnabled);
28+
}
29+
30+
it('deletes an access key with --force in JSON mode', async () => {
31+
const command: any = await createCommand({force: true, scope: 'WEBDAV_AND_STUDIO'}, {login: 'user@x.com'});
32+
stubCommon(command, {jsonEnabled: true});
33+
sinon.stub(command, 'log').returns(void 0);
34+
35+
const ocapiDelete = sinon.stub().resolves({data: undefined, error: undefined});
36+
sinon.stub(command, 'instance').get(() => ({ocapi: {DELETE: ocapiDelete}}));
37+
38+
const result = await command.run();
39+
expect(result).to.deep.equal({
40+
success: true,
41+
login: 'user@x.com',
42+
scope: 'WEBDAV_AND_STUDIO',
43+
hostname: 'example.com',
44+
});
45+
expect(ocapiDelete.calledOnce).to.equal(true);
46+
expect(ocapiDelete.firstCall.args[0]).to.equal('/users/{login}/access_key/{scope}');
47+
expect(ocapiDelete.firstCall.args[1].params.path).to.deep.equal({
48+
login: 'user@x.com',
49+
scope: 'WEBDAV_AND_STUDIO',
50+
});
51+
});
52+
53+
it('falls back to whoami when login arg is omitted', async () => {
54+
const command: any = await createCommand({force: true, scope: 'WEBDAV_AND_STUDIO'}, {});
55+
stubCommon(command, {jsonEnabled: true});
56+
sinon.stub(command, 'log').returns(void 0);
57+
58+
const ocapiGet = sinon.stub().resolves({data: {login: 'me@x.com'}, error: undefined});
59+
const ocapiDelete = sinon.stub().resolves({data: undefined, error: undefined});
60+
sinon.stub(command, 'instance').get(() => ({ocapi: {GET: ocapiGet, DELETE: ocapiDelete}}));
61+
62+
const result = await command.run();
63+
expect(result.success).to.equal(true);
64+
expect(result.login).to.equal('me@x.com');
65+
expect(ocapiGet.calledOnce).to.equal(true);
66+
expect(ocapiGet.firstCall.args[0]).to.equal('/users/this');
67+
expect(ocapiDelete.calledOnce).to.equal(true);
68+
expect(ocapiDelete.firstCall.args[1].params.path).to.deep.equal({
69+
login: 'me@x.com',
70+
scope: 'WEBDAV_AND_STUDIO',
71+
});
72+
});
73+
74+
it('throws on 404', async () => {
75+
const command: any = await createCommand({force: true, scope: 'WEBDAV_AND_STUDIO'}, {login: 'user@x.com'});
76+
sinon.stub(command, 'requireOAuthCredentials').returns(void 0);
77+
sinon.stub(command, 'resolvedConfig').get(() => ({values: {hostname: 'example.com'}}));
78+
sinon.stub(command, 'jsonEnabled').returns(true);
79+
sinon.stub(command, 'log').returns(void 0);
80+
81+
const ocapiDelete = sinon.stub().resolves({
82+
data: undefined,
83+
error: {fault: {message: 'Access key not found'}},
84+
response: {status: 404, statusText: 'Not Found'},
85+
});
86+
sinon.stub(command, 'instance').get(() => ({ocapi: {DELETE: ocapiDelete}}));
87+
88+
await expectError(() => command.run(), /Failed to delete access key/);
89+
});
90+
});
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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+
import {ux} from '@oclif/core';
8+
import {expect} from 'chai';
9+
import {afterEach, beforeEach} from 'mocha';
10+
import sinon from 'sinon';
11+
import BmAccessKeyGet from '../../../../src/commands/bm/access-key/get.js';
12+
import {createIsolatedConfigHooks, createTestCommand, expectError} from '../../../helpers/test-setup.js';
13+
14+
describe('bm access-key get', () => {
15+
const hooks = createIsolatedConfigHooks();
16+
17+
beforeEach(hooks.beforeEach);
18+
19+
afterEach(hooks.afterEach);
20+
21+
async function createCommand(flags: Record<string, unknown> = {}, args: Record<string, unknown> = {}) {
22+
return createTestCommand(BmAccessKeyGet, hooks.getConfig(), flags, args);
23+
}
24+
25+
function stubCommon(command: any, {jsonEnabled}: {jsonEnabled: boolean}) {
26+
sinon.stub(command, 'requireOAuthCredentials').returns(void 0);
27+
sinon.stub(command, 'resolvedConfig').get(() => ({values: {hostname: 'example.com'}}));
28+
sinon.stub(command, 'jsonEnabled').returns(jsonEnabled);
29+
}
30+
31+
it('returns access key details in JSON mode with explicit login', async () => {
32+
const command: any = await createCommand({scope: 'WEBDAV_AND_STUDIO'}, {login: 'user@x.com'});
33+
stubCommon(command, {jsonEnabled: true});
34+
sinon.stub(command, 'log').returns(void 0);
35+
36+
const ocapiGet = sinon.stub().resolves({
37+
data: {enabled: true, expiration_date: '2027-01-01'},
38+
error: undefined,
39+
});
40+
sinon.stub(command, 'instance').get(() => ({ocapi: {GET: ocapiGet}}));
41+
42+
const result = await command.run();
43+
expect(result.enabled).to.equal(true);
44+
expect(result.expiration_date).to.equal('2027-01-01');
45+
expect(ocapiGet.calledOnce).to.equal(true);
46+
expect(ocapiGet.firstCall.args[0]).to.equal('/users/{login}/access_key/{scope}');
47+
expect(ocapiGet.firstCall.args[1].params.path).to.deep.equal({
48+
login: 'user@x.com',
49+
scope: 'WEBDAV_AND_STUDIO',
50+
});
51+
});
52+
53+
it('falls back to whoami when login arg is omitted', async () => {
54+
const command: any = await createCommand({scope: 'WEBDAV_AND_STUDIO'}, {});
55+
stubCommon(command, {jsonEnabled: true});
56+
sinon.stub(command, 'log').returns(void 0);
57+
58+
const ocapiGet = sinon.stub().callsFake((path: string) => {
59+
if (path === '/users/this') {
60+
return Promise.resolve({data: {login: 'me@x.com'}, error: undefined});
61+
}
62+
return Promise.resolve({data: {enabled: true}, error: undefined});
63+
});
64+
sinon.stub(command, 'instance').get(() => ({ocapi: {GET: ocapiGet}}));
65+
66+
const result = await command.run();
67+
expect(result.enabled).to.equal(true);
68+
expect(ocapiGet.callCount).to.equal(2);
69+
expect(ocapiGet.firstCall.args[0]).to.equal('/users/this');
70+
expect(ocapiGet.secondCall.args[0]).to.equal('/users/{login}/access_key/{scope}');
71+
expect(ocapiGet.secondCall.args[1].params.path).to.deep.equal({
72+
login: 'me@x.com',
73+
scope: 'WEBDAV_AND_STUDIO',
74+
});
75+
});
76+
77+
it('displays access key fields in non-JSON mode', async () => {
78+
const command: any = await createCommand({scope: 'WEBDAV_AND_STUDIO'}, {login: 'user@x.com'});
79+
stubCommon(command, {jsonEnabled: false});
80+
sinon.stub(command, 'log').returns(void 0);
81+
82+
const ocapiGet = sinon.stub().resolves({
83+
data: {enabled: true, expiration_date: '2027-01-01'},
84+
error: undefined,
85+
});
86+
sinon.stub(command, 'instance').get(() => ({ocapi: {GET: ocapiGet}}));
87+
88+
const stdoutStub = sinon.stub(ux, 'stdout').returns(void 0 as any);
89+
90+
const result = await command.run();
91+
expect(result.enabled).to.equal(true);
92+
expect(stdoutStub.calledOnce).to.equal(true);
93+
});
94+
95+
it('throws on 404', async () => {
96+
const command: any = await createCommand({scope: 'WEBDAV_AND_STUDIO'}, {login: 'user@x.com'});
97+
sinon.stub(command, 'requireOAuthCredentials').returns(void 0);
98+
sinon.stub(command, 'resolvedConfig').get(() => ({values: {hostname: 'example.com'}}));
99+
sinon.stub(command, 'log').returns(void 0);
100+
101+
const ocapiGet = sinon.stub().resolves({
102+
data: undefined,
103+
error: {fault: {message: 'Access key not found'}},
104+
response: {status: 404, statusText: 'Not Found'},
105+
});
106+
sinon.stub(command, 'instance').get(() => ({ocapi: {GET: ocapiGet}}));
107+
108+
await expectError(() => command.run(), /Failed to get access key/);
109+
});
110+
});
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
import {expect} from 'chai';
8+
import {afterEach, beforeEach} from 'mocha';
9+
import sinon from 'sinon';
10+
import BmAccessKeySet from '../../../../src/commands/bm/access-key/set.js';
11+
import {createIsolatedConfigHooks, createTestCommand, expectError} from '../../../helpers/test-setup.js';
12+
13+
describe('bm access-key set', () => {
14+
const hooks = createIsolatedConfigHooks();
15+
16+
beforeEach(hooks.beforeEach);
17+
18+
afterEach(hooks.afterEach);
19+
20+
async function createCommand(flags: Record<string, unknown> = {}, args: Record<string, unknown> = {}) {
21+
return createTestCommand(BmAccessKeySet, hooks.getConfig(), flags, args);
22+
}
23+
24+
function stubCommon(command: any, {jsonEnabled}: {jsonEnabled: boolean}) {
25+
sinon.stub(command, 'requireOAuthCredentials').returns(void 0);
26+
sinon.stub(command, 'resolvedConfig').get(() => ({values: {hostname: 'example.com'}}));
27+
sinon.stub(command, 'jsonEnabled').returns(jsonEnabled);
28+
}
29+
30+
it('enables an access key with --enabled', async () => {
31+
const command: any = await createCommand({enabled: true, scope: 'WEBDAV_AND_STUDIO'}, {login: 'user@x.com'});
32+
stubCommon(command, {jsonEnabled: true});
33+
sinon.stub(command, 'log').returns(void 0);
34+
35+
const ocapiPatch = sinon.stub().resolves({
36+
data: {enabled: true, expiration_date: '2027-01-01'},
37+
error: undefined,
38+
});
39+
sinon.stub(command, 'instance').get(() => ({ocapi: {PATCH: ocapiPatch}}));
40+
41+
const result = await command.run();
42+
expect(result.enabled).to.equal(true);
43+
expect(ocapiPatch.calledOnce).to.equal(true);
44+
expect(ocapiPatch.firstCall.args[0]).to.equal('/users/{login}/access_key/{scope}');
45+
expect(ocapiPatch.firstCall.args[1].params.path).to.deep.equal({
46+
login: 'user@x.com',
47+
scope: 'WEBDAV_AND_STUDIO',
48+
});
49+
expect(ocapiPatch.firstCall.args[1].body).to.deep.equal({enabled: true});
50+
});
51+
52+
it('disables an access key with --no-enabled', async () => {
53+
const command: any = await createCommand({enabled: false, scope: 'WEBDAV_AND_STUDIO'}, {login: 'user@x.com'});
54+
stubCommon(command, {jsonEnabled: true});
55+
sinon.stub(command, 'log').returns(void 0);
56+
57+
const ocapiPatch = sinon.stub().resolves({
58+
data: {enabled: false, expiration_date: '2027-01-01'},
59+
error: undefined,
60+
});
61+
sinon.stub(command, 'instance').get(() => ({ocapi: {PATCH: ocapiPatch}}));
62+
63+
const result = await command.run();
64+
expect(result.enabled).to.equal(false);
65+
expect(ocapiPatch.firstCall.args[1].body).to.deep.equal({enabled: false});
66+
});
67+
68+
it('throws on 404', async () => {
69+
const command: any = await createCommand({enabled: true, scope: 'WEBDAV_AND_STUDIO'}, {login: 'user@x.com'});
70+
sinon.stub(command, 'requireOAuthCredentials').returns(void 0);
71+
sinon.stub(command, 'resolvedConfig').get(() => ({values: {hostname: 'example.com'}}));
72+
sinon.stub(command, 'log').returns(void 0);
73+
74+
const ocapiPatch = sinon.stub().resolves({
75+
data: undefined,
76+
error: {fault: {message: 'Access key not found'}},
77+
response: {status: 404, statusText: 'Not Found'},
78+
});
79+
sinon.stub(command, 'instance').get(() => ({ocapi: {PATCH: ocapiPatch}}));
80+
81+
await expectError(() => command.run(), /Failed to update access key/);
82+
});
83+
});

0 commit comments

Comments
 (0)