From 22f225240de246eb0a5413c3cb2b793b93ab4e6d Mon Sep 17 00:00:00 2001 From: William Ruemmele Date: Wed, 11 Nov 2020 16:50:14 -0700 Subject: [PATCH] fix: add unit tests --- messages/password.generate.json | 2 +- package.json | 4 +- src/commands/user/create.ts | 22 +-- src/commands/user/list.ts | 1 - src/commands/user/password/generate.ts | 10 +- src/commands/user/permset/assign.ts | 15 +- test/commands/user/create.test.ts | 161 +++++++++++++++++++ test/commands/user/display.test.ts | 158 ++++++++++++++++++ test/commands/user/list.test.ts | 88 ++++++++++ test/commands/user/password/generate.test.ts | 93 +++++++++++ test/commands/user/permset/assign.test.ts | 104 ++++++++++++ yarn.lock | 78 ++++----- 12 files changed, 673 insertions(+), 63 deletions(-) create mode 100644 test/commands/user/create.test.ts create mode 100644 test/commands/user/password/generate.test.ts create mode 100644 test/commands/user/permset/assign.test.ts diff --git a/messages/password.generate.json b/messages/password.generate.json index 128d3f3d..262cf8c2 100644 --- a/messages/password.generate.json +++ b/messages/password.generate.json @@ -8,7 +8,7 @@ "flags": { "onBehalfOf": "comma-separated list of usernames or aliases to assign the password to" }, - "noSelfSetAction": "Create a scratch org with the enableSetPasswordInApi org security setting set to TRUE and try again.", + "noSelfSetError": "Create a scratch org with the enableSetPasswordInApi org security setting set to TRUE and try again.", "success": "Successfully set the password \"%s\" for user %s.", "successMultiple": "Successfully set passwords:%s", "viewWithCommand": "You can see the password again by running \"sfdx force:user:display -u %s\"." diff --git a/package.json b/package.json index c8e995e4..37ce2ae1 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "bugs": "https://github.com/forcedotcom/cli/issues", "dependencies": { "@oclif/config": "^1.17.0", - "@salesforce/command": "^3.0.3", - "@salesforce/core": "^2.14.0", + "@salesforce/command": "^3.0.4", + "@salesforce/core": "^2.14.2", "tslib": "^1" }, "devDependencies": { diff --git a/src/commands/user/create.ts b/src/commands/user/create.ts index 978828b4..119b47a1 100644 --- a/src/commands/user/create.ts +++ b/src/commands/user/create.ts @@ -17,8 +17,8 @@ import { User, UserFields, } from '@salesforce/core'; +import { get } from '@salesforce/ts-types'; import { flags, FlagsConfig, SfdxCommand } from '@salesforce/command'; -import get = Reflect.get; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/plugin-user', 'create'); @@ -55,10 +55,11 @@ export class UserCreateCommand extends SfdxCommand { private successes: SuccessMsg[]; private failures: FailureMsg[]; - // todo: typing - public async run(): Promise { + public async run(): Promise { this.logger = await Logger.child(this.constructor.name); - const defaultUserFields = await DefaultUserFields.create({ templateUser: this.org.getUsername() }); + const defaultUserFields: DefaultUserFields = await DefaultUserFields.create({ + templateUser: this.org.getUsername(), + }); const user: User = await User.create({ org: this.org }); // merge defaults with provided values from cli -> file -> defaults @@ -70,8 +71,8 @@ export class UserCreateCommand extends SfdxCommand { await this.catchCreateUser(e, fields); } - const permsets = this.varargs.permsets as string; - const generatepassword = this.varargs.generatepassword; + const permsets: string = fields['permsets']; + const generatepassword: string = fields['varargs']; // Assign permission sets to the created user if (permsets) { @@ -90,7 +91,7 @@ export class UserCreateCommand extends SfdxCommand { } // Generate and set a password if specified - if (generatepassword) { + if (generatepassword === 'true') { try { const password = User.generatePasswordUtf8(); // await this.user.assignPassword(await AuthInfo.create({ username: fields.username }), password); @@ -116,13 +117,13 @@ export class UserCreateCommand extends SfdxCommand { this.print(fields); - return Promise.resolve(Object.assign({ orgId: this.org.getOrgId() }, this.user)); + return Promise.resolve(Object.assign({ orgId: this.org.getOrgId() }, fields)); } private async catchCreateUser(respBody: Error, fields: UserFields): Promise { // For Gacks, the error message is on response.body[0].message but for handled errors // the error message is on response.body.Errors[0].description. - const errMessage = get(respBody, 'message') || 'Unknown Error'; + const errMessage = (get(respBody, 'message') as string) || 'Unknown Error'; const conn: Connection = this.org.getConnection(); // Provide a more user friendly error message for certain server errors. @@ -138,10 +139,11 @@ export class UserCreateCommand extends SfdxCommand { } private async aggregateFields(defaultFields: UserFields): Promise { - // take from cli params, then file, then default + // start with the default fields, then add the fields from the file, then (possibly overwritting) add the fields from the cli varargs param if (this.flags.definitionfile) { const content = await fs.readJson(this.flags.definitionfile); Object.keys(content).forEach((key) => { + // we overload the UserField type by doing this defaultFields[key] = content[key]; }); } diff --git a/src/commands/user/list.ts b/src/commands/user/list.ts index c8621be5..08570696 100644 --- a/src/commands/user/list.ts +++ b/src/commands/user/list.ts @@ -68,7 +68,6 @@ export class UserListCommand extends SfdxCommand { }); const columns = ['Default', 'Alias', 'Username', 'Profile Name', 'User ID']; - // TODO: this used to print in blue, are we still doing that? this.ux.styledHeader(`Users in org ${this.org.getOrgId()}`); this.ux.table(trimmedList, columns); diff --git a/src/commands/user/password/generate.ts b/src/commands/user/password/generate.ts index abe0dbcb..0f5ae297 100644 --- a/src/commands/user/password/generate.ts +++ b/src/commands/user/password/generate.ts @@ -33,7 +33,13 @@ export class UserPasswordGenerateCommand extends SfdxCommand { private passwordData: PasswordData[] = []; public async run(): Promise { - this.usernames = (this.flags.onbehalfof || this.org.getUsername()).trim().split(','); + // split the usernames, trim them down, and then join them back + if (this.flags.onbehalfof) { + this.usernames = this.flags.onbehalfof.join(',').trim().split(','); + } else { + this.usernames = [this.org.getUsername()]; + } + for (let username of this.usernames) { try { // Convert any aliases to usernames @@ -54,7 +60,7 @@ export class UserPasswordGenerateCommand extends SfdxCommand { }); } catch (e) { if (e.message.includes('Cannot set password for self')) { - e.action = messages.getMessage('noSelfSetAction'); + throw SfdxError.create('@salesforce/plugin-user', 'password.generate', 'noSelfSetError'); } throw SfdxError.wrap(e); } diff --git a/src/commands/user/permset/assign.ts b/src/commands/user/permset/assign.ts index 1005b95d..00c22094 100644 --- a/src/commands/user/permset/assign.ts +++ b/src/commands/user/permset/assign.ts @@ -43,15 +43,15 @@ export class UserPermsetAssignCommand extends SfdxCommand { public async run(): Promise<{ successes: SuccessMsg[]; failures: FailureMsg[] }> { try { - if (this.flags && this.flags.onbehalfof && this.flags.onbehalfof.length > 0) { - this.usernames = this.flags.onbehalfof.trim().split(','); + if (this.flags.onbehalfof) { + this.usernames = this.flags.onbehalfof.join(',').trim().split(','); } else { this.usernames = [this.org.getUsername()]; } - for (let username of this.usernames) { + for (const username of this.usernames) { // Convert any aliases to usernames - username = (await Aliases.fetch(username)) || username; + const aliasOrUsername = (await Aliases.fetch(username)) || username; const connection: Connection = await Connection.create({ authInfo: await AuthInfo.create({ username }), }); @@ -59,16 +59,15 @@ export class UserPermsetAssignCommand extends SfdxCommand { const user: User = await User.create({ org }); const fields: UserFields = await user.retrieve(username); - await user.assignPermissionSets(fields.id, this.flags.permsetname.trim().split(',')); - try { + await user.assignPermissionSets(fields.id, this.flags.permsetname.split(',')); this.successes.push({ - name: fields.username, + name: aliasOrUsername, value: this.flags.permsetname, }); } catch (e) { this.failures.push({ - name: fields.username, + name: aliasOrUsername, message: e.message, }); } diff --git a/test/commands/user/create.test.ts b/test/commands/user/create.test.ts new file mode 100644 index 00000000..4a766254 --- /dev/null +++ b/test/commands/user/create.test.ts @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2020, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { $$, expect, test } from '@salesforce/command/lib/test'; +import { Aliases, Connection, DefaultUserFields, fs, Org, User } from '@salesforce/core'; +import { stubMethod } from '@salesforce/ts-sinon'; + +const username = 'defaultusername@test.com'; + +describe('force:user:create', () => { + async function prepareStubs(throws: { license?: boolean; duplicate?: boolean } = {}, readsFile = false) { + stubMethod($$.SANDBOX, Org.prototype, 'getConnection').callsFake(() => Connection.prototype); + stubMethod($$.SANDBOX, DefaultUserFields, 'create').resolves({ + getFields: () => { + return { + id: '0052D0000043PawWWR', + username: '1605130295132_test-j6asqt5qoprs@example.com', + alias: 'testAlias', + email: username, + emailEncodingKey: 'UTF-8', + languageLocaleKey: 'en_US', + localeSidKey: 'en_US', + profileId: '00e2D000000bNexWWR', + lastName: 'User', + timeZoneSidKey: 'America/Los_Angeles', + }; + }, + }); + stubMethod($$.SANDBOX, Aliases, 'fetch').resolves('testAlias'); + stubMethod($$.SANDBOX, User, 'create').callsFake(() => User.prototype); + stubMethod($$.SANDBOX, Org.prototype, 'getUsername').returns(username); + stubMethod($$.SANDBOX, Org.prototype, 'getOrgId').returns('abc123'); + + if (throws.license) { + stubMethod($$.SANDBOX, User.prototype, 'createUser').throws(new Error('LICENSE_LIMIT_EXCEEDED')); + stubMethod($$.SANDBOX, Connection.prototype, 'query').resolves({ records: [{ Name: 'testName' }] }); + } else if (throws.duplicate) { + stubMethod($$.SANDBOX, User.prototype, 'createUser').throws(new Error('DUPLICATE_USERNAME')); + } else { + stubMethod($$.SANDBOX, User.prototype, 'createUser').resolves(); + } + + if (readsFile) { + stubMethod($$.SANDBOX, fs, 'readJson').resolves({ generatepassword: true }); + } + } + + test + .do(async () => { + await prepareStubs({}, false); + }) + .stdout() + .command([ + 'user:create', + '--json', + '--targetusername', + 'testUser1@test.com', + '--targetdevhubusername', + 'devhub@test.com', + ]) + .it('default create creates user exactly from DefaultUserFields', (ctx) => { + const expected = { + alias: 'testAlias', + email: username, + emailEncodingKey: 'UTF-8', + id: '0052D0000043PawWWR', + languageLocaleKey: 'en_US', + lastName: 'User', + localeSidKey: 'en_US', + orgId: 'abc123', + profileId: '00e2D000000bNexWWR', + timeZoneSidKey: 'America/Los_Angeles', + username: '1605130295132_test-j6asqt5qoprs@example.com', + }; + const result = JSON.parse(ctx.stdout).result; + expect(result).to.deep.equal(expected); + }); + + test + .do(async () => { + await prepareStubs({}, true); + }) + .stdout() + .command([ + 'user:create', + '--json', + '--definitionfile', + 'parent/child/file.json', + '--targetusername', + 'testUser1@test.com', + '--targetdevhubusername', + 'devhub@test.com', + 'email=me@my.org', + 'generatepassword=false', + ]) + // we set generatepassword=false in the varargs, in the definitionfile we have generatepassword=true, so we SHOULD NOT generate a password + .it('will merge fields from the cli args, and the definitionfile correctly, preferring cli args', (ctx) => { + const expected = { + alias: 'testAlias', + email: 'me@my.org', + emailEncodingKey: 'UTF-8', + generatepassword: 'false', + id: '0052D0000043PawWWR', + languageLocaleKey: 'en_US', + lastName: 'User', + localeSidKey: 'en_US', + orgId: 'abc123', + profileId: '00e2D000000bNexWWR', + timeZoneSidKey: 'America/Los_Angeles', + username: '1605130295132_test-j6asqt5qoprs@example.com', + }; + const result = JSON.parse(ctx.stdout).result; + expect(result).to.deep.equal(expected); + }); + + test + .do(async () => { + await prepareStubs({ license: true }, false); + }) + .stdout() + .command([ + 'user:create', + '--json', + '--targetusername', + 'testUser1@test.com', + '--targetdevhubusername', + 'devhub@test.com', + ]) + .it('will handle a failed `createUser` call with a licenseLimitExceeded error', (ctx) => { + const result = JSON.parse(ctx.stdout); + expect(result.status).to.equal(1); + expect(result.message).to.equal('There are no available user licenses for the user profile "testName".'); + expect(result.name).to.equal('licenseLimitExceeded'); + }); + + test + .do(async () => { + await prepareStubs({ duplicate: true }, false); + }) + .stdout() + .command([ + 'user:create', + '--json', + '--targetusername', + 'testUser1@test.com', + '--targetdevhubusername', + 'devhub@test.com', + ]) + .it('will handle a failed `createUser` call with a DuplicateUsername error', (ctx) => { + const result = JSON.parse(ctx.stdout); + expect(result.status).to.equal(1); + expect(result.name).to.equal('duplicateUsername'); + expect(result.message).to.equal( + 'The username "1605130295132_test-j6asqt5qoprs@example.com" already exists in this or another Salesforce org. Usernames must be unique across all Salesforce orgs.' + ); + }); +}); diff --git a/test/commands/user/display.test.ts b/test/commands/user/display.test.ts index 67ff46a7..d9b03df5 100644 --- a/test/commands/user/display.test.ts +++ b/test/commands/user/display.test.ts @@ -4,3 +4,161 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ + +import { $$, expect, test } from '@salesforce/command/lib/test'; +import { Aliases, Connection, Org } from '@salesforce/core'; +import { stubMethod } from '@salesforce/ts-sinon'; +import { Crypto } from '@salesforce/core/lib/crypto'; + +const username = 'defaultusername@test.com'; + +describe('force:user:display', () => { + async function prepareStubs(queries = false) { + stubMethod($$.SANDBOX, Crypto.prototype, 'decrypt').returns('fakepassword'); + stubMethod($$.SANDBOX, Org.prototype, 'getConnection').callsFake(() => Connection.prototype); + stubMethod($$.SANDBOX, Org.prototype, 'getUsername').returns(username); + stubMethod($$.SANDBOX, Org.prototype, 'getOrgId').returns('abc123'); + if (queries) { + stubMethod($$.SANDBOX, Org.prototype, 'readUserAuthFiles').returns([ + { + getFields: () => { + return { + username: 'defaultusername@test.com', + instanceUrl: 'instanceURL', + loginUrl: 'login.test.com', + }; + }, + }, + ]); + stubMethod($$.SANDBOX, Connection.prototype, 'query') + .withArgs(`SELECT name FROM Profile WHERE Id IN (SELECT profileid FROM User WHERE username='${username}')`) + .resolves({ + records: [{ Name: 'QueriedName' }], + }) + .withArgs(`SELECT id FROM User WHERE username='${username}'`) + .resolves({ records: [{ Id: 'QueriedId' }] }); + } else { + stubMethod($$.SANDBOX, Org.prototype, 'readUserAuthFiles').returns([ + { + getFields: () => { + return { + username: 'defaultusername@test.com', + userProfileName: 'profileName', + userId: '1234567890', + instanceUrl: 'instanceURL', + loginUrl: 'login.test.com', + password: '-a098u234/1!@#', + }; + }, + }, + ]); + } + + stubMethod($$.SANDBOX, Aliases, 'fetch').resolves('testAlias'); + } + + test + .do(async () => { + await prepareStubs(); + }) + .stdout() + .command([ + 'user:display', + '--json', + '--targetusername', + 'testUser1@test.com', + '--targetdevhubusername', + 'devhub@test.com', + ]) + .it('should display the correct information from the default user', (ctx) => { + // testUser1@test.com is aliased to testUser + const expected = [ + { + Key: 'Access Token', + }, + { + Key: 'Id', + Value: '1234567890', + }, + { + Key: 'Instance Url', + Value: 'instanceURL', + }, + { + Key: 'Login Url', + Value: 'login.test.com', + }, + { + Key: 'Org Id', + Value: 'abc123', + }, + { + Key: 'Profile Name', + Value: 'profileName', + }, + { + Key: 'Username', + Value: 'defaultusername@test.com', + }, + { Key: 'Alias', Value: 'testAlias' }, + { + Key: 'Password', + Value: 'fakepassword', + }, + ]; + const result = JSON.parse(ctx.stdout).result; + expect(result).to.deep.equal(expected); + }); + + test + .do(async () => { + await prepareStubs(true); + }) + .stdout() + .command([ + 'user:display', + '--json', + '--targetusername', + 'testUser1@test.com', + '--targetdevhubusername', + 'devhub@test.com', + ]) + .it('should make queries to the server to get userId and profileName', (ctx) => { + // testUser1@test.com is aliased to testUser + const expected = [ + { + Key: 'Access Token', + }, + { + Key: 'Id', + Value: 'QueriedId', + }, + { + Key: 'Instance Url', + Value: 'instanceURL', + }, + { + Key: 'Login Url', + Value: 'login.test.com', + }, + { + Key: 'Org Id', + Value: 'abc123', + }, + { + Key: 'Profile Name', + Value: 'QueriedName', + }, + { + Key: 'Username', + Value: 'defaultusername@test.com', + }, + { + Key: 'Alias', + Value: 'testAlias', + }, + ]; + const result = JSON.parse(ctx.stdout).result; + expect(result).to.deep.equal(expected); + }); +}); diff --git a/test/commands/user/list.test.ts b/test/commands/user/list.test.ts index 67ff46a7..382ba74c 100644 --- a/test/commands/user/list.test.ts +++ b/test/commands/user/list.test.ts @@ -4,3 +4,91 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ + +import { $$, expect, test } from '@salesforce/command/lib/test'; +import { Aliases, Connection, Org } from '@salesforce/core'; +import { stubMethod } from '@salesforce/ts-sinon'; + +describe('force:user:list', () => { + async function prepareStubs() { + stubMethod($$.SANDBOX, Org.prototype, 'getConnection').callsFake(() => Connection.prototype); + stubMethod($$.SANDBOX, Org.prototype, 'readUserAuthFiles').returns([ + { + getUsername: () => 'testuser@test.com', + getFields: () => { + return { + username: 'defaultusername@test.com', + userProfileName: 'profileName', + userId: '1234567890', + instanceUrl: 'instanceURL', + loginUrl: 'login.test.com', + accessToken: 'accessToken', + }; + }, + }, + ]); + stubMethod($$.SANDBOX, Org.prototype, 'getOrgId').returns('abc123'); + stubMethod($$.SANDBOX, Aliases, 'fetch').resolves('testAlias'); + stubMethod($$.SANDBOX, Connection.prototype, 'query') + .withArgs('SELECT username, profileid, id FROM User') + .resolves({ + records: [ + { + Username: 'automatedclean@00d2d000000dz5fuag', + ProfileId: '00e2D000000bNeMQAU', + Id: '0052D0000043PbBQAU', + }, + { + Username: 'testuser@test.com', + ProfileId: '00e2D000000bNeMQAU', + Id: '0052D0000043PbGQAU', + }, + ], + }) + .withArgs('SELECT id, name FROM Profile') + .resolves({ + records: [ + { + Id: '0052D0000043PbGQAU', + Name: 'System Administrator', + }, + { + Id: '00e2D000000bNeMQAU', + Name: 'Analytics Cloud Integration User', + }, + ], + }); + } + + test + .do(async () => { + await prepareStubs(); + }) + .stdout() + .command([ + 'user:list', + '--json', + '--targetusername', + 'testUser1@test.com', + '--targetdevhubusername', + 'devhub@test.com', + ]) + .it('should display the correct information from the default user', (ctx) => { + // testUser1@test.com is aliased to testUser + const expected = [ + { + defaultMarker: '(A)', + alias: 'testAlias', + username: 'testuser@test.com', + profileName: 'Analytics Cloud Integration User', + orgId: 'abc123', + accessToken: 'accessToken', + instanceUrl: 'instanceURL', + loginUrl: 'login.test.com', + userId: '0052D0000043PbGQAU', + }, + ]; + const result = JSON.parse(ctx.stdout).result; + expect(result).to.deep.equal(expected); + }); +}); diff --git a/test/commands/user/password/generate.test.ts b/test/commands/user/password/generate.test.ts new file mode 100644 index 00000000..6bf274a8 --- /dev/null +++ b/test/commands/user/password/generate.test.ts @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { $$, expect, test } from '@salesforce/command/lib/test'; +import { Aliases, AuthInfo, Connection, Org, User, AuthInfoConfig, Messages } from '@salesforce/core'; +import { StubbedType, stubInterface, stubMethod } from '@salesforce/ts-sinon'; +import { MockTestOrgData } from '@salesforce/core/lib/testSetup'; +import { SecureBuffer } from '@salesforce/core/lib/secureBuffer'; + +Messages.importMessagesDirectory(__dirname); +const messages = Messages.loadMessages('@salesforce/plugin-user', 'password.generate'); + +describe('force:user:password:generate', () => { + let authInfoStub: StubbedType; + let authInfoConfigStub: StubbedType; + const testData = new MockTestOrgData(); + + async function prepareStubs(throws = false) { + const authFields = await testData.getConfig(); + authInfoStub = stubInterface($$.SANDBOX, { getFields: () => authFields }); + authInfoConfigStub = stubInterface($$.SANDBOX, { + getContents: () => authFields, + }); + stubMethod($$.SANDBOX, AuthInfoConfig, 'create').callsFake(async () => authInfoConfigStub); + stubMethod($$.SANDBOX, AuthInfo, 'create').callsFake(async () => authInfoStub); + stubMethod($$.SANDBOX, Connection, 'create').callsFake(async () => Connection.prototype); + stubMethod($$.SANDBOX, Org, 'create').callsFake(async () => Org.prototype); + stubMethod($$.SANDBOX, Org.prototype, 'getUsername').returns('defaultusername@test.com'); + stubMethod($$.SANDBOX, User, 'create').callsFake(async () => User.prototype); + stubMethod($$.SANDBOX, User.prototype, 'retrieve').resolves({ + id: '0052D0000043PawWWR', + }); + + const secureBuffer: SecureBuffer = new SecureBuffer(); + secureBuffer.consume(Buffer.from('abc', 'utf8')); + stubMethod($$.SANDBOX, User, 'generatePasswordUtf8').returns(secureBuffer); + + if (throws) { + stubMethod($$.SANDBOX, User.prototype, 'assignPassword').throws(new Error('Cannot set password for self')); + } else { + stubMethod($$.SANDBOX, User.prototype, 'assignPassword').resolves(); + } + stubMethod($$.SANDBOX, Aliases, 'fetch').withArgs('testUser1@test.com').resolves('testAlias'); + } + + test + .do(async () => { + await prepareStubs(); + }) + .stdout() + .command(['user:password:generate', '--json', '--onbehalfof', 'testUser1@test.com, testUser2@test.com']) + .it('should generate a new password for the user', (ctx) => { + // testUser1@test.com is aliased to testUser + const expected = [ + { + username: 'testAlias', + password: 'abc', + }, + { + username: ' testUser2@test.com', + password: 'abc', + }, + ]; + const result = JSON.parse(ctx.stdout).result; + expect(result).to.deep.equal(expected); + }); + + test + .do(() => prepareStubs()) + .stdout() + .command(['user:password:generate', '--json']) + .it('should generate a new password for the default user', (ctx) => { + const expected = [{ username: 'defaultusername@test.com', password: 'abc' }]; + const result = JSON.parse(ctx.stdout).result; + expect(result).to.deep.equal(expected); + }); + + test + .do(async () => await prepareStubs(true)) + + .stdout() + .command(['user:password:generate', '--json']) + .it('should throw the correct errror with warning message', (ctx) => { + const result = JSON.parse(ctx.stdout); + expect(result.message).to.equal(messages.getMessage('noSelfSetError')); + expect(result.status).to.equal(1); + expect(result.name).to.equal('noSelfSetError'); + }); +}); diff --git a/test/commands/user/permset/assign.test.ts b/test/commands/user/permset/assign.test.ts new file mode 100644 index 00000000..1b24df61 --- /dev/null +++ b/test/commands/user/permset/assign.test.ts @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2020, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { $$, expect, test } from '@salesforce/command/lib/test'; +import { Aliases, AuthInfo, Connection, Org, User } from '@salesforce/core'; +import { StubbedType, stubInterface, stubMethod } from '@salesforce/ts-sinon'; +import { MockTestOrgData } from '@salesforce/core/lib/testSetup'; + +describe('force:user:permset:assign', () => { + let authInfoStub: StubbedType; + const testData = new MockTestOrgData(); + + async function prepareStubs(throws = false) { + const authFields = await testData.getConfig(); + authInfoStub = stubInterface($$.SANDBOX, { getFields: () => authFields }); + + stubMethod($$.SANDBOX, AuthInfo, 'create').callsFake(async () => authInfoStub); + stubMethod($$.SANDBOX, Connection, 'create').callsFake(async () => Connection.prototype); + stubMethod($$.SANDBOX, Org, 'create').callsFake(async () => Org.prototype); + stubMethod($$.SANDBOX, Org.prototype, 'getUsername').returns('defaultusername@test.com'); + stubMethod($$.SANDBOX, User, 'create').callsFake(async () => User.prototype); + stubMethod($$.SANDBOX, User.prototype, 'retrieve').resolves({ + id: '0052D0000043PawWWR', + }); + if (throws) { + stubMethod($$.SANDBOX, User.prototype, 'assignPermissionSets').throws( + new Error('Permission set "abc" not found in target org. Do you need to push source?') + ); + } else { + stubMethod($$.SANDBOX, User.prototype, 'assignPermissionSets').resolves(); + } + + stubMethod($$.SANDBOX, Aliases, 'fetch').withArgs('testUser1@test.com').resolves('testAlias'); + } + + test + .do(async () => { + await prepareStubs(); + }) + .stdout() + .command([ + 'user:permset:assign', + '--json', + '--onbehalfof', + 'testUser1@test.com, testUser2@test.com', + '--permsetname', + 'DreamHouse', + ]) + .it('should assign the one permset to both users', (ctx) => { + // testUser1@test.com is aliased to testUser + const expected = [ + { + name: 'testAlias', + value: 'DreamHouse', + }, + { + name: ' testUser2@test.com', + value: 'DreamHouse', + }, + ]; + const result = JSON.parse(ctx.stdout).result; + expect(result.successes).to.deep.equal(expected); + }); + + test + .do(async () => { + await prepareStubs(); + }) + .stdout() + .command(['user:permset:assign', '--json', '--permsetname', 'DreamHouse, PERM2']) + .it('should assign both permsets to the default user', (ctx) => { + // testUser1@test.com is aliased to testUser + const expected = [ + { + name: 'defaultusername@test.com', + value: 'DreamHouse, PERM2', + }, + ]; + const result = JSON.parse(ctx.stdout).result; + expect(result.successes).to.deep.equal(expected); + }); + + test + .do(async () => { + await prepareStubs(true); + }) + .stdout() + .command(['user:permset:assign', '--json', '--permsetname', 'PERM2']) + .it('should fail with the correct error message', (ctx) => { + // testUser1@test.com is aliased to testUser + const expected = [ + { + name: 'defaultusername@test.com', + message: 'Permission set "abc" not found in target org. Do you need to push source?', + }, + ]; + const result = JSON.parse(ctx.stdout).result; + expect(result.failures).to.deep.equal(expected); + }); +}); diff --git a/yarn.lock b/yarn.lock index 35be22e0..7c9f680f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -511,10 +511,10 @@ mv "~2" safe-json-stringify "~1" -"@salesforce/command@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@salesforce/command/-/command-3.0.3.tgz#e750268bda094560992f1f494774d75f3644f6e3" - integrity sha512-ntHH64Badr46/S3J1wT8GTQ5LBnxHkrXreby5t/IV+06ZaUQMQgpdaijKBAvTGI7jtLh5sG0gnXLt6+mGsDC6A== +"@salesforce/command@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@salesforce/command/-/command-3.0.4.tgz#2ef88c6f5e47f0650800b3c72ca90d4a99ddb0e2" + integrity sha512-1tkhoIpnf/Fu+fG2ITBU9wfjGVtnhFkORIlYRgceydRS2P66JkgQ2Tb+kteiA8I+YsqxkBZK1w+5m9VIlTRcrw== dependencies: "@oclif/command" "^1.5.17" "@oclif/errors" "^1.2.2" @@ -527,10 +527,10 @@ chalk "^2.4.2" cli-ux "^4.9.3" -"@salesforce/core@^2.14.0", "@salesforce/core@^2.6.0": - version "2.15.1" - resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-2.15.1.tgz#c5fdd1a95575f4a20cf707e0ad0b80d17ab03c6d" - integrity sha512-XDnsXe++9eUYuMIlBl0OcEfElnWdJlx/a43BMpFK9rOy2S7xoUEkPzXm7fjQXBj52mTMG+6FXPYLQEs+hZZ+5w== +"@salesforce/core@^2.14.2", "@salesforce/core@^2.6.0": + version "2.15.2" + resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-2.15.2.tgz#7e2b0ac6c1d67f850a461e007298d1381b37c07a" + integrity sha512-PCP9HdGEl4us8X66tOfwlTOGFRju8m7ezqfBlH7nRzmKw57i2l0LhXBEZ+DPwEBtAwa9wlvd9FhMmlhUYhm/EQ== dependencies: "@salesforce/bunyan" "^2.0.0" "@salesforce/kit" "^1.3.3" @@ -739,9 +739,9 @@ integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== "@types/minimist@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" - integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256" + integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg== "@types/mkdirp@1.0.0": version "1.0.0" @@ -756,14 +756,14 @@ integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w== "@types/node@*": - version "14.14.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.6.tgz#146d3da57b3c636cc0d1769396ce1cfa8991147f" - integrity sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw== + version "14.14.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.7.tgz#8ea1e8f8eae2430cf440564b98c6dfce1ec5945d" + integrity sha512-Zw1vhUSQZYw+7u5dAwNbIA9TuTotpzY/OF7sJM9FqPOF3SPjKnxrjoTktXDZgUjybf4cWVBP7O8wvKdSaGHweg== "@types/node@^12.12.6": - version "12.19.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.3.tgz#a6e252973214079155f749e8bef99cc80af182fa" - integrity sha512-8Jduo8wvvwDzEVJCOvS/G6sgilOLvvhn1eMmK3TW8/T217O7u1jdrK6ImKLv80tVryaPSVeKu6sjDEiFjd4/eg== + version "12.19.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.4.tgz#cdfbb62e26c7435ed9aab9c941393cc3598e9b46" + integrity sha512-o3oj1bETk8kBwzz1WlO6JWL/AfAA3Vm6J1B3C9CsdxHYp7XgPiH7OEXPUbZTndHlRaIElrANkQfe6ZmfJb3H2w== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -1094,9 +1094,9 @@ balanced-match@^1.0.0: integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= base64-js@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== base64-url@^2.2.0: version "2.3.3" @@ -1662,9 +1662,9 @@ copy-descriptor@^0.1.0: integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= core-js@^3.6.1: - version "3.6.5" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" - integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== + version "3.7.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.7.0.tgz#b0a761a02488577afbf97179e4681bf49568520f" + integrity sha512-NwS7fI5M5B85EwpWuIwJN4i/fbisQUwLwiSNUWeXlkAZ0sbBjLEvLvFLf1uzAUV66PcEPt4xCGCmOZSxVf3xzA== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -1779,9 +1779,9 @@ dayjs-plugin-utc@^0.1.2: integrity sha512-ExERH5o3oo6jFOdkvMP3gytTCQ9Ksi5PtylclJWghr7k7m3o2U5QrwtdiJkOxLOH4ghr0EKhpqGefzGz1VvVJg== dayjs@^1.8.16: - version "1.9.5" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.9.5.tgz#fd49994ebe71639d2ce9575e97186642dfce9808" - integrity sha512-WULIw7UpW/E0y6VywewpbXAMH3d5cZijEhoHLwM+OMVbk/NtchKS/W+57H/0P1rqU7gHrAArjiRLHCUhgMQl6w== + version "1.9.6" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.9.6.tgz#6f0c77d76ac1ff63720dd1197e5cb87b67943d70" + integrity sha512-HngNLtPEBWRo8EFVmHFmSXAjtCX8rGNqeXQI0Gh7wCTSqwaKgPIDqu9m07wABVopNwzvOeCb+2711vQhDlcIXw== debug@3.2.6, debug@^3.1.0: version "3.2.6" @@ -3247,7 +3247,7 @@ is-callable@^1.1.4, is-callable@^1.2.2: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== -is-core-module@^2.0.0: +is-core-module@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.1.0.tgz#a4cc031d9b1aca63eecbd18a650e13cb4eeab946" integrity sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA== @@ -5159,11 +5159,11 @@ resolve-url@^0.2.1: integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= resolve@^1.1.6, resolve@^1.10.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2: - version "1.18.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" - integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== + version "1.19.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== dependencies: - is-core-module "^2.0.0" + is-core-module "^2.1.0" path-parse "^1.0.6" restore-cursor@^2.0.0: @@ -5798,16 +5798,16 @@ table@^5.2.3: string-width "^3.0.0" tar-fs@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.0.tgz#d1cdd121ab465ee0eb9ccde2d35049d3f3daf0d5" - integrity sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg== + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== dependencies: chownr "^1.1.1" mkdirp-classic "^0.5.2" pump "^3.0.0" - tar-stream "^2.0.0" + tar-stream "^2.1.4" -tar-stream@^2.0.0: +tar-stream@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.4.tgz#c4fb1a11eb0da29b893a5b25476397ba2d053bfa" integrity sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw== @@ -6362,9 +6362,9 @@ yargs-parser@^18.1.2: decamelize "^1.2.0" yargs-parser@^20.2.3: - version "20.2.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.3.tgz#92419ba867b858c868acf8bae9bf74af0dd0ce26" - integrity sha512-emOFRT9WVHw03QSvN5qor9QQT9+sw5vwxfYweivSMHTcAXPefwVae2FjO7JJjj8hCE4CzPOPeFM83VwT29HCww== + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== yargs-unparser@1.6.0: version "1.6.0"