-
Notifications
You must be signed in to change notification settings - Fork 324
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds spo group set command. Closes #3485
- Loading branch information
1 parent
64e5635
commit 7851c9e
Showing
5 changed files
with
587 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# spo group set | ||
|
||
Updates a group in the specified site | ||
|
||
## Usage | ||
|
||
```sh | ||
m365 spo group set [options] | ||
``` | ||
|
||
## Options | ||
|
||
`-u, --webUrl <webUrl>` | ||
: URL of the site where the group is located. | ||
|
||
`-i, --id [id]` | ||
: ID of the group to update. Specify either `id` or `name` but not both. | ||
|
||
`-n, --name [name]` | ||
: Name of the group. Specify either `id` or `name` but not both. | ||
|
||
`--newName [newName]` | ||
: New name for the group. | ||
|
||
`--description [description]` | ||
: The description for the group. | ||
|
||
`--allowMembersEditMembership [allowMembersEditMembership]` | ||
: Who can edit the membership of the group? When `true` members can edit membership, otherwise only owners can do this. | ||
|
||
`--onlyAllowMembersViewMembership [onlyAllowMembersViewMembership]` | ||
: Who can view the membership of the group? When `true` only group members can view the membership, otherwise everyone can. | ||
|
||
`--allowRequestToJoinLeave [allowRequestToJoinLeave]` | ||
: Specify whether to allow users to request membership in this group and allow users to request to leave the group. | ||
|
||
`--autoAcceptRequestToJoinLeave [autoAcceptRequestToJoinLeave]` | ||
: If auto-accept is enabled, users will automatically be added or removed when they make a request. | ||
|
||
`--requestToJoinLeaveEmailSetting [requestToJoinLeaveEmailSetting]` | ||
: All membership requests will be sent to the email address specified. | ||
|
||
`--ownerEmail [ownerEmail]` | ||
: Set user with this email as owner of the group. Specify either `ownerEmail` or `ownerUserName` but not both. | ||
|
||
`--ownerUserName [ownerUserName]` | ||
: Set user with this login name as owner of the group. Specify either `ownerEmail` or `ownerUserName` but not both. | ||
|
||
--8<-- "docs/cmd/_global.md" | ||
|
||
## Examples | ||
|
||
Update group title and description | ||
|
||
```sh | ||
m365 spo group set --webUrl https://contoso.sharepoint.com/sites/project-x --id 18 --newTitle "Project leaders" --description "This group contains all project leaders" | ||
``` | ||
|
||
Update group with membership requests | ||
|
||
```sh | ||
m365 spo group set --webUrl https://contoso.sharepoint.com/sites/project-x --title "Project leaders" --allowRequestToJoinLeave true --requestToJoinLeaveEmailSetting [email protected] | ||
``` | ||
|
||
Sets a specified user as group owner | ||
|
||
```sh | ||
m365 spo group set --webUrl https://contoso.sharepoint.com/sites/project-x --id 18 --ownerEmail [email protected] | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,308 @@ | ||
import * as assert from 'assert'; | ||
import * as sinon from 'sinon'; | ||
import appInsights from '../../../../appInsights'; | ||
import auth from '../../../../Auth'; | ||
import { Cli, Logger } from '../../../../cli'; | ||
import Command, { CommandError } from '../../../../Command'; | ||
import request from '../../../../request'; | ||
import { sinonUtil } from '../../../../utils'; | ||
import commands from '../../commands'; | ||
const command: Command = require('./group-set'); | ||
|
||
const validId = 1; | ||
const validName = "Project leaders"; | ||
const validWebUrl = 'https://contoso.sharepoint.com/sites/project-x'; | ||
const validOwnerEmail = '[email protected]'; | ||
const validOwnerUserName = '[email protected]'; | ||
|
||
const userInfoResponse = { | ||
userPrincipalName: validOwnerUserName | ||
}; | ||
|
||
const ensureUserResponse = { | ||
Id: 3 | ||
}; | ||
|
||
describe(commands.GROUP_SET, () => { | ||
let log: string[]; | ||
let logger: Logger; | ||
|
||
before(() => { | ||
sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.resolve()); | ||
sinon.stub(appInsights, 'trackEvent').callsFake(() => { }); | ||
auth.service.connected = true; | ||
}); | ||
|
||
beforeEach(() => { | ||
log = []; | ||
logger = { | ||
log: (msg: string) => { | ||
log.push(msg); | ||
}, | ||
logRaw: (msg: string) => { | ||
log.push(msg); | ||
}, | ||
logToStderr: (msg: string) => { | ||
log.push(msg); | ||
} | ||
}; | ||
(command as any).items = []; | ||
}); | ||
|
||
afterEach(() => { | ||
sinonUtil.restore([ | ||
request.post, | ||
request.patch, | ||
Cli.executeCommandWithOutput | ||
]); | ||
}); | ||
|
||
after(() => { | ||
sinonUtil.restore([ | ||
auth.restoreAuth, | ||
appInsights.trackEvent | ||
]); | ||
auth.service.connected = false; | ||
}); | ||
|
||
it('has correct name', () => { | ||
assert.strictEqual(command.name, commands.GROUP_SET); | ||
}); | ||
|
||
it('has a description', () => { | ||
assert.notStrictEqual(command.description, null); | ||
}); | ||
|
||
it('defines correct option sets', () => { | ||
const optionSets = command.optionSets(); | ||
assert.deepStrictEqual(optionSets, [['id', 'name']]); | ||
}); | ||
|
||
it('fails validation when group id is not a number', (done) => { | ||
const actual = command.validate({ | ||
options: { | ||
webUrl: validWebUrl, | ||
id: 'invalid id' | ||
} | ||
}); | ||
assert.notStrictEqual(actual, true); | ||
done(); | ||
}); | ||
|
||
it('fails validation when both ownerEmail and ownerUserName are specified', (done) => { | ||
const actual = command.validate({ | ||
options: { | ||
webUrl: validWebUrl, | ||
ownerEmail: validOwnerEmail, | ||
ownerUserName: validOwnerUserName | ||
} | ||
}); | ||
assert.notStrictEqual(actual, true); | ||
done(); | ||
}); | ||
|
||
it('fails validation when invalid boolean is passed as option', (done) => { | ||
const actual = command.validate({ | ||
options: { | ||
webUrl: validWebUrl, | ||
id: validId, | ||
autoAcceptRequestToJoinLeave: 'invalid' | ||
} | ||
}); | ||
assert.notStrictEqual(actual, true); | ||
done(); | ||
}); | ||
|
||
it('fails validation when invalid web URL is passed', (done) => { | ||
const actual = command.validate({ | ||
options: { | ||
webUrl: 'invalid' | ||
} | ||
}); | ||
assert.notStrictEqual(actual, true); | ||
done(); | ||
}); | ||
|
||
it('passes validation when valid options specified', (done) => { | ||
const actual = command.validate({ | ||
options: { | ||
webUrl: validWebUrl, | ||
id: validId, | ||
allowRequestToJoinLeave: 'true' | ||
} | ||
}); | ||
assert.strictEqual(actual, true); | ||
done(); | ||
}); | ||
|
||
it('successfully updates group settings by id', (done) => { | ||
sinon.stub(request, 'patch').callsFake((opts) => { | ||
if (opts.url === `${validWebUrl}/_api/web/sitegroups/GetById(${validId})`) { | ||
return Promise.resolve(); | ||
} | ||
|
||
return Promise.reject('Invalid Request'); | ||
}); | ||
|
||
command.action(logger, { | ||
options: { | ||
webUrl: validWebUrl, | ||
id: validId, | ||
allowRequestToJoinLeave: 'true' | ||
} | ||
}, (err?: any) => { | ||
try { | ||
assert.strictEqual(typeof err, 'undefined', err?.message); | ||
done(); | ||
} | ||
catch (e) { | ||
done(e); | ||
} | ||
}); | ||
}); | ||
|
||
it('successfully updates group settings by name', (done) => { | ||
sinon.stub(request, 'patch').callsFake((opts) => { | ||
if (opts.url === `${validWebUrl}/_api/web/sitegroups/GetByName('${validName}')`) { | ||
return Promise.resolve(); | ||
} | ||
|
||
return Promise.reject('Invalid Request'); | ||
}); | ||
|
||
command.action(logger, { | ||
options: { | ||
webUrl: validWebUrl, | ||
name: validName, | ||
allowRequestToJoinLeave: 'true' | ||
} | ||
}, (err?: any) => { | ||
try { | ||
assert.strictEqual(typeof err, 'undefined', err?.message); | ||
done(); | ||
} | ||
catch (e) { | ||
done(e); | ||
} | ||
}); | ||
}); | ||
|
||
it('successfully updates group owner by ownerEmail', (done) => { | ||
sinon.stub(Cli, 'executeCommandWithOutput').callsFake(() => Promise.resolve({ | ||
stdout: JSON.stringify(userInfoResponse), | ||
stderr: '' | ||
})); | ||
sinon.stub(request, 'patch').callsFake((opts) => { | ||
if (opts.url === `${validWebUrl}/_api/web/sitegroups/GetById(${validId})`) { | ||
return Promise.resolve(); | ||
} | ||
|
||
return Promise.reject('Invalid Request'); | ||
}); | ||
sinon.stub(request, 'post').callsFake((opts) => { | ||
if (opts.url === `${validWebUrl}/_api/web/ensureUser('${userInfoResponse.userPrincipalName}')?$select=Id`) { | ||
return Promise.resolve(ensureUserResponse); | ||
} | ||
|
||
if (opts.url === `${validWebUrl}/_api/web/sitegroups/GetById(${validId})/SetUserAsOwner(${ensureUserResponse.Id})`) { | ||
return Promise.resolve(); | ||
} | ||
|
||
return Promise.reject('Invalid Request'); | ||
}); | ||
|
||
command.action(logger, { | ||
options: { | ||
webUrl: validWebUrl, | ||
id: validId, | ||
ownerEmail: validOwnerEmail | ||
} | ||
}, (err?: any) => { | ||
try { | ||
assert.strictEqual(typeof err, 'undefined', err?.message); | ||
done(); | ||
} | ||
catch (e) { | ||
done(e); | ||
} | ||
}); | ||
}); | ||
|
||
it('successfully updates group owner by ownerEmail', (done) => { | ||
sinon.stub(Cli, 'executeCommandWithOutput').callsFake(() => Promise.resolve({ | ||
stdout: JSON.stringify(userInfoResponse), | ||
stderr: '' | ||
})); | ||
sinon.stub(request, 'patch').callsFake((opts) => { | ||
if (opts.url === `${validWebUrl}/_api/web/sitegroups/GetByName('${validName}')`) { | ||
return Promise.resolve(); | ||
} | ||
|
||
return Promise.reject('Invalid Request'); | ||
}); | ||
sinon.stub(request, 'post').callsFake((opts) => { | ||
if (opts.url === `${validWebUrl}/_api/web/ensureUser('${userInfoResponse.userPrincipalName}')?$select=Id`) { | ||
return Promise.resolve(ensureUserResponse); | ||
} | ||
|
||
if (opts.url === `${validWebUrl}/_api/web/sitegroups/GetByName('${validName}')/SetUserAsOwner(${ensureUserResponse.Id})`) { | ||
return Promise.resolve(); | ||
} | ||
|
||
return Promise.reject('Invalid Request'); | ||
}); | ||
|
||
command.action(logger, { | ||
options: { | ||
webUrl: validWebUrl, | ||
name: validName, | ||
ownerUserName: validOwnerUserName | ||
} | ||
}, (err?: any) => { | ||
try { | ||
assert.strictEqual(typeof err, 'undefined', err?.message); | ||
done(); | ||
} | ||
catch (e) { | ||
done(e); | ||
} | ||
}); | ||
}); | ||
|
||
it('correctly handles random API error', (done) => { | ||
sinon.stub(request, 'patch').callsFake((opts) => { | ||
if (opts.url === `${validWebUrl}/_api/web/sitegroups/GetByName('${validName}')`) { | ||
return Promise.reject('An error has occurred'); | ||
} | ||
|
||
return Promise.reject('Invalid Request'); | ||
}); | ||
|
||
command.action(logger, { | ||
options: { | ||
webUrl: validWebUrl, | ||
name: validName, | ||
autoAcceptRequestToJoinLeave: 'true' | ||
} | ||
}, (err?: any) => { | ||
try { | ||
assert.strictEqual(JSON.stringify(err), JSON.stringify(new CommandError('An error has occurred'))); | ||
done(); | ||
} | ||
catch (e) { | ||
done(e); | ||
} | ||
}); | ||
}); | ||
|
||
it('supports debug mode', () => { | ||
const options = command.options(); | ||
let containsOption = false; | ||
options.forEach(o => { | ||
if (o.option === '--debug') { | ||
containsOption = true; | ||
} | ||
}); | ||
assert(containsOption); | ||
}); | ||
}); |
Oops, something went wrong.