Skip to content

Commit e740b71

Browse files
committed
Adds "aad administrativeunit add" command
1 parent 129c6c7 commit e740b71

File tree

5 files changed

+399
-0
lines changed

5 files changed

+399
-0
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import Global from '/docs/cmd/_global.mdx';
2+
import Tabs from '@theme/Tabs';
3+
import TabItem from '@theme/TabItem';
4+
5+
# aad administrativeunit add
6+
7+
Creates a new administrative unit
8+
9+
## Usage
10+
11+
```sh
12+
m365 aad administrativeunit add [options]
13+
```
14+
15+
## Options
16+
17+
```md definition-list
18+
`-n, --displayname <displayName>`
19+
: Display name for the administrative unit.
20+
21+
`-d, --description [description]`
22+
: Description for the administrative unit.
23+
24+
`--hiddenMembership [hiddenMembership]`
25+
: Indicates whether the administrative unit and its members are hidden.
26+
```
27+
28+
<Global />
29+
30+
## Remarks
31+
32+
:::info
33+
34+
To use this command you must be either **Global Administrator** or **Privileged Role Administrator**.
35+
36+
:::
37+
38+
## Examples
39+
40+
Create an administrative unit with a specific display name
41+
42+
```sh
43+
m365 aad administrativeunit add --displayName 'Marketing Division'
44+
```
45+
46+
Create an administrative unit with a specific display name and description
47+
48+
```sh
49+
m365 aad administrativeunit add --displayName 'Marketing Division' --description 'Marketing department administration'
50+
```
51+
52+
Create a hidden administrative unit with a specific display name
53+
54+
```sh
55+
m365 aad administrativeunit add --displayName 'Marketing Division' --hiddenMembership
56+
```
57+
58+
## Response
59+
60+
<Tabs>
61+
<TabItem value="JSON">
62+
63+
```json
64+
{
65+
"id": "00b45a1b-7632-4e94-a3bd-f06aec976d31",
66+
"deletedDateTime": null,
67+
"displayName": "Marketing Division",
68+
"description": "Marketing department administration",
69+
"membershipRule": null,
70+
"membershipType": null,
71+
"membershipRuleProcessingState": null,
72+
"visibility": null
73+
}
74+
```
75+
76+
</TabItem>
77+
<TabItem value="Text">
78+
79+
```text
80+
deletedDateTime : null
81+
description : Marketing department administration
82+
displayName : Marketing Division
83+
id : 00b45a1b-7632-4e94-a3bd-f06aec976d31
84+
membershipRule : null
85+
membershipRuleProcessingState: null
86+
membershipType : null
87+
visibility : null
88+
```
89+
90+
</TabItem>
91+
<TabItem value="CSV">
92+
93+
```csv
94+
id,displayName,description,visibility
95+
00b45a1b-7632-4e94-a3bd-f06aec976d31,Marketing Division,Marketing department administration,HiddenMembership
96+
```
97+
98+
</TabItem>
99+
<TabItem value="Markdown">
100+
101+
```md
102+
Date: 10/23/2023
103+
104+
## Marketing Division (00b45a1b-7632-4e94-a3bd-f06aec976d31)
105+
106+
Property | Value
107+
---------|-------
108+
id | 00b45a1b-7632-4e94-a3bd-f06aec976d31
109+
displayName | Marketing Division
110+
description | Marketing department administration
111+
visibility | HiddenMembership
112+
```
113+
114+
</TabItem>
115+
</Tabs>

docs/src/config/sidebars.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ const sidebars = {
2626
'cmd/version',
2727
{
2828
'Azure Active Directory (aad)': [
29+
{
30+
administrativeunit: [
31+
{
32+
type: 'doc',
33+
label: 'administrativeunit add',
34+
id: 'cmd/aad/administrativeunit/administrativeunit-add'
35+
}
36+
]
37+
},
2938
{
3039
app: [
3140
{

src/m365/aad/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const prefix: string = 'aad';
22

33
export default {
4+
ADMINISTRATIVEUNIT_ADD: `${prefix} administrativeunit add`,
45
APP_ADD: `${prefix} app add`,
56
APP_GET: `${prefix} app get`,
67
APP_LIST: `${prefix} app list`,
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import assert from 'assert';
2+
import sinon from 'sinon';
3+
import auth from '../../../../Auth.js';
4+
import { Cli } from '../../../../cli/Cli.js';
5+
import { CommandInfo } from '../../../../cli/CommandInfo.js';
6+
import commands from '../../commands.js';
7+
import command from './administrativeunit-add.js';
8+
import { telemetry } from '../../../../telemetry.js';
9+
import { pid } from '../../../../utils/pid.js';
10+
import { session } from '../../../../utils/session.js';
11+
import { sinonUtil } from '../../../../utils/sinonUtil.js';
12+
import request from '../../../../request.js';
13+
import { Logger } from '../../../../cli/Logger.js';
14+
import { CommandError } from '../../../../Command.js';
15+
16+
describe(commands.ADMINISTRATIVEUNIT_ADD, () => {
17+
const administrativeUnitReponse: any = {
18+
id: 'fc33aa61-cf0e-46b6-9506-f633347202ab',
19+
displayName: 'European Division',
20+
description: null,
21+
visibility: null
22+
};
23+
24+
let log: string[];
25+
let logger: Logger;
26+
let loggerLogSpy: sinon.SinonSpy;
27+
let commandInfo: CommandInfo;
28+
29+
before(() => {
30+
sinon.stub(auth, 'restoreAuth').resolves();
31+
sinon.stub(telemetry, 'trackEvent').returns();
32+
sinon.stub(pid, 'getProcessName').returns('');
33+
sinon.stub(session, 'getId').returns('');
34+
auth.service.connected = true;
35+
commandInfo = Cli.getCommandInfo(command);
36+
});
37+
38+
beforeEach(() => {
39+
log = [];
40+
logger = {
41+
log: async (msg: string) => {
42+
log.push(msg);
43+
},
44+
logRaw: async (msg: string) => {
45+
log.push(msg);
46+
},
47+
logToStderr: async (msg: string) => {
48+
log.push(msg);
49+
}
50+
};
51+
loggerLogSpy = sinon.spy(logger, 'log');
52+
(command as any).pollingInterval = 0;
53+
});
54+
55+
afterEach(() => {
56+
sinonUtil.restore([
57+
request.post
58+
]);
59+
});
60+
61+
after(() => {
62+
sinon.restore();
63+
auth.service.connected = false;
64+
});
65+
66+
it('has correct name', () => {
67+
assert.strictEqual(command.name, commands.ADMINISTRATIVEUNIT_ADD);
68+
});
69+
70+
it('has a description', () => {
71+
assert.notStrictEqual(command.description, null);
72+
});
73+
74+
it('creates an administrative unit with a specific display name', async () => {
75+
const postStub = sinon.stub(request, 'post').callsFake(async (opts) => {
76+
if (opts.url === 'https://graph.microsoft.com/v1.0/directory/administrativeUnits') {
77+
return administrativeUnitReponse;
78+
}
79+
80+
throw 'Invalid request';
81+
});
82+
83+
await command.action(logger, { options: { displayName: 'European Division' } });
84+
assert.deepStrictEqual(postStub.lastCall.args[0].data, {
85+
displayName: 'European Division',
86+
description: undefined,
87+
visibility: null
88+
});
89+
assert(loggerLogSpy.calledOnceWith(administrativeUnitReponse));
90+
});
91+
92+
it('creates an administrative unit with a specific display name and description', async () => {
93+
const privateAdministrativeUnitResponse = { ...administrativeUnitReponse };
94+
privateAdministrativeUnitResponse.description = 'European Division Administration';
95+
96+
const postStub = sinon.stub(request, 'post').callsFake(async (opts) => {
97+
if (opts.url === 'https://graph.microsoft.com/v1.0/directory/administrativeUnits') {
98+
return administrativeUnitReponse;
99+
}
100+
101+
throw 'Invalid request';
102+
});
103+
104+
await command.action(logger, { options: { displayName: 'European Division', description: 'European Division Administration' } });
105+
assert.deepStrictEqual(postStub.lastCall.args[0].data, {
106+
displayName: 'European Division',
107+
description: 'European Division Administration',
108+
visibility: null
109+
});
110+
assert(loggerLogSpy.calledOnceWith(administrativeUnitReponse));
111+
});
112+
113+
it('creates a hidden administrative unit with a specific display name and description', async () => {
114+
const privateAdministrativeUnitResponse = { ...administrativeUnitReponse };
115+
privateAdministrativeUnitResponse.description = 'European Division Administration';
116+
privateAdministrativeUnitResponse.visibility = 'HiddenMembership';
117+
118+
const postStub = sinon.stub(request, 'post').callsFake(async (opts) => {
119+
if (opts.url === 'https://graph.microsoft.com/v1.0/directory/administrativeUnits') {
120+
return administrativeUnitReponse;
121+
}
122+
123+
throw 'Invalid request';
124+
});
125+
126+
await command.action(logger, { options: { displayName: 'European Division', description: 'European Division Administration', hiddenMembership: true } });
127+
assert.deepStrictEqual(postStub.lastCall.args[0].data, {
128+
displayName: 'European Division',
129+
description: 'European Division Administration',
130+
visibility: 'HiddenMembership'
131+
});
132+
assert(loggerLogSpy.calledOnceWith(administrativeUnitReponse));
133+
});
134+
135+
it('correctly handles API OData error', async () => {
136+
sinon.stub(request, 'post').rejects({
137+
error: {
138+
'odata.error': {
139+
code: '-1, InvalidOperationException',
140+
message: {
141+
value: 'Invalid request'
142+
}
143+
}
144+
}
145+
});
146+
147+
await assert.rejects(command.action(logger, { options: {} } as any), new CommandError('Invalid request'));
148+
});
149+
150+
it('passes validation when only displayName is specified', async () => {
151+
const actual = await command.validate({ options: { displayName: 'European Division' } }, commandInfo);
152+
assert.strictEqual(actual, true);
153+
});
154+
155+
it('passes validation when the displayName, description and hiddenMembership are specified', async () => {
156+
const actual = await command.validate({ options: { displayName: 'European Division', description: 'European Division Administration', hiddenMembership: true } }, commandInfo);
157+
assert.strictEqual(actual, true);
158+
});
159+
160+
it('supports specifying displayName', () => {
161+
const options = command.options;
162+
let containsOption = false;
163+
options.forEach(o => {
164+
if (o.option.indexOf('--displayName') > -1) {
165+
containsOption = true;
166+
}
167+
});
168+
assert(containsOption);
169+
});
170+
171+
it('supports specifying description', () => {
172+
const options = command.options;
173+
let containsOption = false;
174+
options.forEach(o => {
175+
if (o.option.indexOf('--description') > -1) {
176+
containsOption = true;
177+
}
178+
});
179+
assert(containsOption);
180+
});
181+
182+
it('supports specifying hiddenMembership', () => {
183+
const options = command.options;
184+
let containsOption = false;
185+
options.forEach(o => {
186+
if (o.option.indexOf('--hiddenMembership') > -1) {
187+
containsOption = true;
188+
}
189+
});
190+
assert(containsOption);
191+
});
192+
});

0 commit comments

Comments
 (0)