Skip to content

Commit 7dc6b1c

Browse files
committed
Adds 'spe containertype remove' command. Closes pnp#5992
1 parent add4854 commit 7dc6b1c

16 files changed

+729
-11
lines changed

.eslintrc.cjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const dictionary = [
2525
'comm',
2626
'command',
2727
'community',
28-
'containertype',
28+
'container',
2929
'content',
3030
'conversation',
3131
'custom',

docs/docs/cmd/spe/containertype/containertype-add.mdx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,16 @@ m365 spe containertype add [options]
3838

3939
## Remarks
4040

41+
:::info
42+
43+
To use this command you must be either **SharePoint Administrator** or **Global Administrator**.
44+
45+
:::
46+
4147
:::note
42-
You can only create one Trial Container per tenant, a single application registration can only contain one Container and a tenant can contain a maximum of five Containers in total.
48+
49+
You can only create one Trial container type per tenant, a single application registration can only contain one container type and a tenant can contain a maximum of five containers types in total.
50+
4351
:::
4452

4553
## Examples

docs/docs/cmd/spe/containertype/containertype-get.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ m365 containertype get [options]
2424

2525
<Global />
2626

27+
## Remarks
28+
29+
:::info
30+
31+
To use this command you must be either **SharePoint Administrator** or **Global Administrator**.
32+
33+
:::
34+
2735
## Examples
2836

2937
Gets Container Type by id

docs/docs/cmd/spe/containertype/containertype-list.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ m365 spe containertype list [options]
1616

1717
<Global />
1818

19+
## Remarks
20+
21+
:::info
22+
23+
To use this command you must be either **SharePoint Administrator** or **Global Administrator**.
24+
25+
:::
26+
1927
## Examples
2028

2129
Retrieves a list of Container Types created for a SharePoint Embedded Application from the tenant.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import Global from '/docs/cmd/_global.mdx';
2+
3+
# spe containertype remove
4+
5+
Remove a specific container type
6+
7+
## Usage
8+
9+
```sh
10+
m365 spe containertype remove [options]
11+
```
12+
13+
## Options
14+
15+
```md definition-list
16+
`-i, --id [id]`
17+
: The ID of the container type. Specify either `id` or `name` but not both.
18+
19+
`-n, --name [name]`
20+
: The container type name. Specify either `id` or `name` but not both.
21+
22+
`-f, --force`
23+
: Force removal of the container type without confirmation.
24+
```
25+
26+
<Global />
27+
28+
## Remarks
29+
30+
:::info
31+
32+
To use this command you must be either **SharePoint Administrator** or **Global Administrator**.
33+
34+
:::
35+
36+
## Examples
37+
38+
Removes a container type by ID
39+
40+
```sh
41+
m365 spe containertype remove --id 4ec4aefd-4fa3-0e4a-20c3-6e68389e7138
42+
```
43+
44+
Removes a container type by name
45+
46+
```sh
47+
m365 spe containertype remove --name 'My container type'
48+
```
49+
50+
## Response
51+
52+
The command won't return a response on success.

docs/src/config/sidebars.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2136,6 +2136,11 @@ const sidebars: SidebarsConfig = {
21362136
type: 'doc',
21372137
label: 'containertype list',
21382138
id: 'cmd/spe/containertype/containertype-list'
2139+
},
2140+
{
2141+
type: 'doc',
2142+
label: 'containertype remove',
2143+
id: 'cmd/spe/containertype/containertype-remove'
21392144
}
21402145
]
21412146
}

src/m365/spe/commands.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ export default {
66
CONTAINER_LIST: `${prefix} container list`,
77
CONTAINERTYPE_ADD: `${prefix} containertype add`,
88
CONTAINERTYPE_GET: `${prefix} containertype get`,
9-
CONTAINERTYPE_LIST: `${prefix} containertype list`
9+
CONTAINERTYPE_LIST: `${prefix} containertype list`,
10+
CONTAINERTYPE_REMOVE: `${prefix} containertype remove`
1011
};

src/m365/spe/commands/containertype/containertype-add.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export interface Options extends GlobalOptions {
3535
region?: string;
3636
}
3737

38-
class SpeContainertypeAddCommand extends SpoCommand {
38+
class SpeContainerTypeAddCommand extends SpoCommand {
3939
public get name(): string {
4040
return commands.CONTAINERTYPE_ADD;
4141
}
@@ -151,7 +151,7 @@ class SpeContainertypeAddCommand extends SpoCommand {
151151
await logger.log(result);
152152
}
153153
catch (err: any) {
154-
this.handleRejectedPromise(err);
154+
this.handleRejectedODataJsonPromise(err);
155155
}
156156
}
157157

@@ -167,4 +167,4 @@ class SpeContainertypeAddCommand extends SpoCommand {
167167
}
168168
}
169169

170-
export default new SpeContainertypeAddCommand();
170+
export default new SpeContainerTypeAddCommand();

src/m365/spe/commands/containertype/containertype-get.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ interface Options extends GlobalOptions {
1616
name?: string;
1717
}
1818

19-
class SpeContainertypeGetCommand extends SpoCommand {
19+
class SpeContainerTypeGetCommand extends SpoCommand {
2020
public get name(): string {
2121
return commands.CONTAINERTYPE_GET;
2222
}
@@ -91,7 +91,7 @@ class SpeContainertypeGetCommand extends SpoCommand {
9191
await logger.log(allContainerTypes);
9292
}
9393
catch (err: any) {
94-
this.handleRejectedPromise(err);
94+
this.handleRejectedODataJsonPromise(err);
9595
}
9696
}
9797

@@ -136,4 +136,4 @@ class SpeContainertypeGetCommand extends SpoCommand {
136136
}
137137
}
138138

139-
export default new SpeContainertypeGetCommand();
139+
export default new SpeContainerTypeGetCommand();

src/m365/spe/commands/containertype/containertype-list.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import SpoCommand from '../../../base/SpoCommand.js';
33
import commands from '../../commands.js';
44
import { ContainerTypeProperties, spo } from '../../../../utils/spo.js';
55

6-
class SpeContainertypeListCommand extends SpoCommand {
6+
class SpeContainerTypeListCommand extends SpoCommand {
77

88
public get name(): string {
99
return commands.CONTAINERTYPE_LIST;
@@ -34,4 +34,4 @@ class SpeContainertypeListCommand extends SpoCommand {
3434
}
3535
}
3636

37-
export default new SpeContainertypeListCommand();
37+
export default new SpeContainerTypeListCommand();
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import assert from 'assert';
2+
import sinon from 'sinon';
3+
import auth from '../../../../Auth.js';
4+
import { CommandInfo } from "../../../../cli/CommandInfo.js";
5+
import { Logger } from '../../../../cli/Logger.js';
6+
import request from '../../../../request.js';
7+
import { telemetry } from '../../../../telemetry.js';
8+
import { pid } from '../../../../utils/pid.js';
9+
import { session } from '../../../../utils/session.js';
10+
import { sinonUtil } from '../../../../utils/sinonUtil.js';
11+
import { cli } from '../../../../cli/cli.js';
12+
import commands from '../../commands.js';
13+
import command from './containertype-remove.js';
14+
import { spe } from '../../../../utils/spe.js';
15+
import { z } from 'zod';
16+
import { CommandError } from '../../../../Command.js';
17+
import config from '../../../../config.js';
18+
import { spo } from '../../../../utils/spo.js';
19+
20+
describe(commands.CONTAINERTYPE_REMOVE, () => {
21+
const spoAdminUrl = 'https://contoso-admin.sharepoint.com';
22+
const containerTypeId = 'c6f08d91-77fa-485f-9369-f246ec0fc19c';
23+
const containerTypeName = 'Container type name';
24+
25+
let log: string[];
26+
let logger: Logger;
27+
let commandInfo: CommandInfo;
28+
let commandOptionsSchema: z.ZodTypeAny;
29+
let confirmationPromptStub: sinon.SinonStub;
30+
31+
before(() => {
32+
sinon.stub(auth, 'restoreAuth').resolves();
33+
sinon.stub(telemetry, 'trackEvent').resolves();
34+
sinon.stub(pid, 'getProcessName').returns('');
35+
sinon.stub(session, 'getId').returns('');
36+
37+
auth.connection.active = true;
38+
auth.connection.spoUrl = spoAdminUrl.replace('-admin.sharepoint.com', '.sharepoint.com');
39+
sinon.stub(spo, 'ensureFormDigest').resolves({ FormDigestValue: 'abc', FormDigestTimeoutSeconds: 1800, FormDigestExpiresAt: new Date(), WebFullUrl: 'https://contoso-admin.sharepoint.com' });
40+
commandInfo = cli.getCommandInfo(command);
41+
commandOptionsSchema = commandInfo.command.getSchemaToParse()!;
42+
});
43+
44+
beforeEach(() => {
45+
log = [];
46+
logger = {
47+
log: async (msg: string) => {
48+
log.push(msg);
49+
},
50+
logRaw: async (msg: string) => {
51+
log.push(msg);
52+
},
53+
logToStderr: async (msg: string) => {
54+
log.push(msg);
55+
}
56+
};
57+
confirmationPromptStub = sinon.stub(cli, 'promptForConfirmation').resolves(false);
58+
});
59+
60+
afterEach(() => {
61+
sinonUtil.restore([
62+
request.post,
63+
spe.getContainerTypeIdByName,
64+
cli.promptForConfirmation
65+
]);
66+
});
67+
68+
after(() => {
69+
sinon.restore();
70+
auth.connection.active = false;
71+
auth.connection.spoUrl = undefined;
72+
});
73+
74+
it('has correct name', () => {
75+
assert.strictEqual(command.name, commands.CONTAINERTYPE_REMOVE);
76+
});
77+
78+
it('has a description', () => {
79+
assert.notStrictEqual(command.description, null);
80+
});
81+
82+
it('fails validation if both id and name options are passed', async () => {
83+
const actual = commandOptionsSchema.safeParse({ id: containerTypeId, name: containerTypeName });
84+
assert.strictEqual(actual.success, false);
85+
});
86+
87+
it('fails validation if neither id nor name options are passed', async () => {
88+
const actual = commandOptionsSchema.safeParse({});
89+
assert.strictEqual(actual.success, false);
90+
});
91+
92+
it('fails validation if id is not a valid GUID', async () => {
93+
const actual = commandOptionsSchema.safeParse({ id: 'invalid' });
94+
assert.strictEqual(actual.success, false);
95+
});
96+
97+
it('passes validation if id is a valid GUID', async () => {
98+
const actual = commandOptionsSchema.safeParse({ id: containerTypeId });
99+
assert.strictEqual(actual.success, true);
100+
});
101+
102+
it('passes validation if name is passed', async () => {
103+
const actual = commandOptionsSchema.safeParse({ name: containerTypeName });
104+
assert.strictEqual(actual.success, true);
105+
});
106+
107+
it('prompts before removing the container type', async () => {
108+
await command.action(logger, { options: { id: containerTypeId } });
109+
assert(confirmationPromptStub.calledOnce);
110+
});
111+
112+
it('aborts removing the container type when prompt is not confirmed', async () => {
113+
const postStub = sinon.stub(request, 'post').resolves([]);
114+
115+
await command.action(logger, { options: { name: containerTypeName } });
116+
assert(postStub.notCalled);
117+
});
118+
119+
it('correctly removes a container type by id', async () => {
120+
const postStub = sinon.stub(request, 'post').callsFake(async (opts) => {
121+
if (opts.url === `${spoAdminUrl}/_vti_bin/client.svc/ProcessQuery`) {
122+
return [
123+
{
124+
SchemaVersion: '15.0.0.0',
125+
LibraryVersion: '16.0.25919.12007',
126+
ErrorInfo: null,
127+
TraceCorrelationId: '864c91a1-f07a-c000-29c0-273ee30d83d8'
128+
},
129+
7,
130+
{
131+
IsNull: false
132+
}
133+
];
134+
}
135+
136+
throw 'Invalid request URL: ' + opts.url;
137+
});
138+
139+
await command.action(logger, { options: { id: containerTypeId, force: true, verbose: true } });
140+
assert.strictEqual(postStub.firstCall.args[0].data, `<Request AddExpandoFieldTypeSuffix="true" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="${config.applicationName}" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009"><Actions><ObjectPath Id="7" ObjectPathId="6" /><Method Name="RemoveSPOContainerType" Id="8" ObjectPathId="6"><Parameters><Parameter TypeId="{b66ab1ca-fd51-44f9-8cfc-01f5c2a21f99}"><Property Name="ContainerTypeId" Type="Guid">{${containerTypeId}}</Property></Parameter></Parameters></Method></Actions><ObjectPaths><Constructor Id="6" TypeId="{268004ae-ef6b-4e9b-8425-127220d84719}" /></ObjectPaths></Request>`);
141+
assert.strictEqual(postStub.lastCall.args[0].headers!['X-RequestDigest'], 'abc');
142+
});
143+
144+
it('correctly removes a container type by name', async () => {
145+
sinon.stub(spe, 'getContainerTypeIdByName').resolves(containerTypeId);
146+
147+
const postStub = sinon.stub(request, 'post').callsFake(async (opts) => {
148+
if (opts.url === `${spoAdminUrl}/_vti_bin/client.svc/ProcessQuery`) {
149+
return [
150+
{
151+
SchemaVersion: '15.0.0.0',
152+
LibraryVersion: '16.0.25919.12007',
153+
ErrorInfo: null,
154+
TraceCorrelationId: '864c91a1-f07a-c000-29c0-273ee30d83d8'
155+
},
156+
7,
157+
{
158+
IsNull: false
159+
}
160+
];
161+
}
162+
163+
throw 'Invalid request URL: ' + opts.url;
164+
});
165+
166+
await command.action(logger, { options: { name: containerTypeName, verbose: true, force: true } });
167+
assert.strictEqual(postStub.firstCall.args[0].data, `<Request AddExpandoFieldTypeSuffix="true" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="${config.applicationName}" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009"><Actions><ObjectPath Id="7" ObjectPathId="6" /><Method Name="RemoveSPOContainerType" Id="8" ObjectPathId="6"><Parameters><Parameter TypeId="{b66ab1ca-fd51-44f9-8cfc-01f5c2a21f99}"><Property Name="ContainerTypeId" Type="Guid">{${containerTypeId}}</Property></Parameter></Parameters></Method></Actions><ObjectPaths><Constructor Id="6" TypeId="{268004ae-ef6b-4e9b-8425-127220d84719}" /></ObjectPaths></Request>`);
168+
assert.strictEqual(postStub.lastCall.args[0].headers!['X-RequestDigest'], 'abc');
169+
});
170+
171+
it('correctly handles error when removing a container type', async () => {
172+
const errorMessage = `Tenant 7d858e1d-a366-48d1-8a15-ce45a733916b cannot delete Container Type ${containerTypeId} as it is a DirectToCustomer Container Type.`;
173+
174+
sinon.stub(request, 'post').callsFake(async (opts) => {
175+
if (opts.url === `${spoAdminUrl}/_vti_bin/client.svc/ProcessQuery`) {
176+
return [
177+
{
178+
SchemaVersion: '15.0.0.0',
179+
LibraryVersion: '16.0.25919.12007',
180+
ErrorInfo: {
181+
ErrorMessage: errorMessage,
182+
ErrorValue: null,
183+
TraceCorrelationId: 'cd4a91a1-6041-c000-29c0-26f4566b5b74',
184+
ErrorCode: -2146232832,
185+
ErrorTypeName: 'Microsoft.SharePoint.SPException'
186+
},
187+
TraceCorrelationId: 'cd4a91a1-6041-c000-29c0-26f4566b5b74'
188+
}
189+
];
190+
}
191+
192+
throw 'Invalid request URL: ' + opts.url;
193+
});
194+
195+
await assert.rejects(command.action(logger, { options: { id: containerTypeId, force: true } }),
196+
new CommandError(errorMessage));
197+
});
198+
});

0 commit comments

Comments
 (0)