diff --git a/.gitignore b/.gitignore index 2fbeb2cf..53839d57 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,7 @@ node_modules # os specific files .DS_Store .idea + +# exclude sfdx and sf config files +.sf +.sfdx diff --git a/messages/create.md b/messages/create.md index 6472d9ad..17585698 100644 --- a/messages/create.md +++ b/messages/create.md @@ -82,6 +82,10 @@ This command works with only scratch orgs. This command doesn't work when authorizing an org using the JWT flow if the org is on Hyperforce. +# error.invalidRoleDeveloperName + +Invalid roleDeveloperName: "%s". Must start with a letter and contain only alphanumeric characters or single underscores, with no double or final underscores. + # error.jwtHyperforce.actions - Authorize your Dev Hub with either the `org login web` or `org login sfdx-url` command. You can then successfully use the `org create user` command on scratch orgs that you create with your Dev Hub. diff --git a/src/commands/org/create/user.ts b/src/commands/org/create/user.ts index 19a62299..f290dd2a 100644 --- a/src/commands/org/create/user.ts +++ b/src/commands/org/create/user.ts @@ -197,6 +197,9 @@ export class CreateUserCommand extends SfCommand { } else if (key.toLowerCase() === 'profilename') { // @ts-expect-error standardize profileName casing defaultFields['profileName'] = this.varargs[key]; + } else if (key.toLowerCase() === 'roledevelopername') { + // @ts-expect-error standardize roleDeveloperName casing + defaultFields['roleDeveloperName'] = this.varargs[key]; } else { // @ts-expect-error all other varargs are left "as is" defaultFields[lowerFirstLetter(key)] = this.varargs[key]; @@ -220,6 +223,21 @@ export class CreateUserCommand extends SfCommand { defaultFields.profileId = profile.Id; } + // @ts-expect-error check if "roleDeveloperName" was passed, this needs to become a userRoleId before calling User.create + if (defaultFields['roleDeveloperName']) { + // @ts-expect-error roleDeveloperName is not a valid field on UserFields + const devName = defaultFields['roleDeveloperName'] as string; + if (!/^[a-z](?!.*__)(?!.*_$)\w*$/i.test(devName)) { + throw messages.createError('error.invalidRoleDeveloperName', [devName]); + } + logger.debug(`Querying org for user role name [${devName}]`); + const userRole = await this.flags['target-org'] + .getConnection(this.flags['api-version']) + .singleRecordQuery<{ Id: string }>(`SELECT id FROM userrole WHERE developername='${devName}'`); + // @ts-expect-error userRoleId is an optional field therefore not defined on UserFields + defaultFields['userRoleId'] = userRole.Id; + } + return defaultFields; } @@ -280,7 +298,7 @@ const lowerFirstLetter = (word: string): string => word[0].toLowerCase() + word. * @private */ const stripInvalidAPIFields = (fields: UserFields & Dictionary): UserFields => - omit(fields, ['permsets', 'generatepassword', 'generatePassword', 'profileName']); + omit(fields, ['permsets', 'generatepassword', 'generatePassword', 'profileName', 'roleDeveloperName']); const getNewUserAuthInfo = async ( targetOrgUser: User, diff --git a/test/commands/create.test.ts b/test/commands/create.test.ts index 2d64fa89..a4e2b460 100644 --- a/test/commands/create.test.ts +++ b/test/commands/create.test.ts @@ -157,7 +157,9 @@ describe('org:create:user', () => { } if (readsFile) { - $$.SANDBOXES.CONNECTION.stub(Connection.prototype, 'singleRecordQuery').resolves({ Id: '12345678' }); + $$.SANDBOXES.CONNECTION.stub(Connection.prototype, 'singleRecordQuery') + .withArgs('SELECT id FROM profile WHERE name=\'profileFromArgs\'').resolves({ Id: '12345678' }) + .withArgs('SELECT id FROM userrole WHERE developername=\'roleFromArgs\'').resolves({ Id: '87654321' }) $$.SANDBOX.stub(Logger.prototype, 'debug'); if (typeof readsFile !== 'boolean') { const fsStub = $$.SANDBOX.stub(fs.promises, 'readFile'); @@ -429,5 +431,19 @@ describe('org:create:user', () => { ); } }); + + it('will handle a failed `createUser` call with a InvalidRoleDeveloperName error', async () => { + await prepareStubs({}, true); + try { + await CreateUserCommand.run(['--json', '--target-org', testOrg.username,'roleDeveloperName=_Invalid_Role']); + expect.fail('should have thrown an error'); + } catch (e) { + assert(e instanceof Error); + expect(e.name).to.equal('InvalidRoleDeveloperNameError'); + expect(e.message).to.equal( + 'Invalid roleDeveloperName: "_Invalid_Role". Must start with a letter and contain only alphanumeric characters or single underscores, with no double or final underscores.' + ); + } + }); }); }); diff --git a/test/df17AppBuilding/config/complexUser.json b/test/df17AppBuilding/config/complexUser.json index 20211cd5..04b0fbff 100644 --- a/test/df17AppBuilding/config/complexUser.json +++ b/test/df17AppBuilding/config/complexUser.json @@ -8,5 +8,6 @@ "LanguageLocaleKey": "en_US", "profileName": "Standard Platform User", "permsets": ["VolunteeringApp"], + "roleDeveloperName": "Guide", "generatePassword": true } diff --git a/test/df17AppBuilding/config/project-scratch-def.json b/test/df17AppBuilding/config/project-scratch-def.json index 44f9546d..daf4c8b3 100644 --- a/test/df17AppBuilding/config/project-scratch-def.json +++ b/test/df17AppBuilding/config/project-scratch-def.json @@ -4,6 +4,7 @@ "edition": "Developer", "description": "nuts for plugin-user", "features": ["EnableSetPasswordInApi", "AnalyticsAppEmbedded", "DataMaskUser"], + "language": "en_US", "settings": { "lightningExperienceSettings": { "enableS1DesktopEnabled": true diff --git a/test/df17AppBuilding/force-app/main/default/roles/Guide.role-meta.xml b/test/df17AppBuilding/force-app/main/default/roles/Guide.role-meta.xml new file mode 100644 index 00000000..1be27b37 --- /dev/null +++ b/test/df17AppBuilding/force-app/main/default/roles/Guide.role-meta.xml @@ -0,0 +1,8 @@ + + + Edit + Edit + false + Guide + Edit + \ No newline at end of file