diff --git a/docs/docs/cmd/spe/container/container-list.mdx b/docs/docs/cmd/spe/container/container-list.mdx
new file mode 100644
index 00000000000..247e2534228
--- /dev/null
+++ b/docs/docs/cmd/spe/container/container-list.mdx
@@ -0,0 +1,97 @@
+import Global from '/docs/cmd/_global.mdx';
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+# spe container list
+
+Lists containers of a specific Container Type
+
+## Usage
+
+```sh
+m365 spe container list [options]
+```
+
+## Options
+
+```md definition-list
+`--containerTypeId [containerTypeId]`
+: The Container Type Id of the container instance. Use either `containerTypeId` or `containerTypeName` but not both.
+
+`--containerTypeName [containerTypeName]`
+: The Container Type name of the container instance. Use either `containerTypeId` or `containerTypeName` but not both.
+```
+
+
+
+## Examples
+
+List containers of a specific type by id.
+
+```sh
+m365 spe container list --containerTypeId "91710488-5756-407f-9046-fbe5f0b4de73"
+```
+
+List containers of a specific type by name.
+
+```sh
+m365 spe container list --containerTypeName "trial container"
+```
+
+## Response
+
+
+
+
+ ```json
+ [
+ {
+ "id": "b!ISJs1WRro0y0EWgkUYcktDa0mE8zSlFEqFzqRn70Zwp1CEtDEBZgQICPkRbil_5Z",
+ "displayName": "My File Storage Container",
+ "containerTypeId": "e2756c4d-fa33-4452-9c36-2325686e1082",
+ "createdDateTime": "2021-11-24T15:41:52.347Z"
+ }
+ ]
+ ```
+
+
+
+
+ ```text
+ id displayName containerTypeId createdDateTime
+ ------------------------------------------------------------------ ------------------------- ------------------------------------ ------------------------
+ b!ISJs1WRro0y0EWgkUYcktDa0mE8zSlFEqFzqRn70Zwp1CEtDEBZgQICPkRbil_5Z My File Storage Container e2756c4d-fa33-4452-9c36-2325686e1082 2021-11-24T15:41:52.347Z
+ ```
+
+
+
+
+ ```csv
+ id,displayName,containerTypeId,createdDateTime
+ b!ISJs1WRro0y0EWgkUYcktDa0mE8zSlFEqFzqRn70Zwp1CEtDEBZgQICPkRbil_5Z,My File Storage Container,e2756c4d-fa33-4452-9c36-2325686e1082,2021-11-24T15:41:52.347Z
+ ```
+
+
+
+
+ ```md
+ # spe container list
+
+ Date: 10/06/2024
+
+ ## My File Storage Container
+
+ Property | Value
+ ---------|-------
+ id | b!ISJs1WRro0y0EWgkUYcktDa0mE8zSlFEqFzqRn70Zwp1CEtDEBZgQICPkRbil_5Z
+ displayName | My File Storage Container
+ containerTypeId | e2756c4d-fa33-4452-9c36-2325686e1082
+ createdDateTime | 2021-11-24T15:41:52.347Z
+ ```
+
+
+
+
+## More information
+
+In SharePoint Embedded, all files and documents are stored in Containers. The calling app should be the owning app of the container type.
diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts
index c995ffe5bdb..87bd48d5c66 100644
--- a/docs/src/config/sidebars.ts
+++ b/docs/src/config/sidebars.ts
@@ -1960,6 +1960,15 @@ const sidebars: SidebarsConfig = {
},
{
'SharePoint Embedded (spe)': [
+ {
+ container: [
+ {
+ type: 'doc',
+ label: 'container list',
+ id: 'cmd/spe/container/container-list'
+ }
+ ]
+ },
{
containertype: [
{
diff --git a/src/m365/spe/ContainerProperties.ts b/src/m365/spe/ContainerProperties.ts
new file mode 100644
index 00000000000..b7738cad160
--- /dev/null
+++ b/src/m365/spe/ContainerProperties.ts
@@ -0,0 +1,6 @@
+export interface ContainerProperties {
+ id: string;
+ displayName: string;
+ containerTypeId: string;
+ createdDateTime: string;
+}
\ No newline at end of file
diff --git a/src/m365/spe/ContainerTypeProperties.ts b/src/m365/spe/ContainerTypeProperties.ts
deleted file mode 100644
index 5b077ef73b7..00000000000
--- a/src/m365/spe/ContainerTypeProperties.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-export interface ContainerTypeProperties {
- _ObjectType_?: string;
- AzureSubscriptionId: string;
- ContainerTypeId: string;
- CreationDate: string;
- DisplayName: string;
- ExpiryDate: string;
- IsBillingProfileRequired: boolean;
- OwningAppId: string;
- OwningTenantId: string;
- Region?: string;
- ResourceGroup?: string;
- SPContainerTypeBillingClassification: string;
-}
\ No newline at end of file
diff --git a/src/m365/spe/commands.ts b/src/m365/spe/commands.ts
index ecd05a6e954..ed8e096ffd7 100644
--- a/src/m365/spe/commands.ts
+++ b/src/m365/spe/commands.ts
@@ -1,6 +1,7 @@
const prefix: string = 'spe';
export default {
+ CONTAINER_LIST: `${prefix} container list`,
CONTAINERTYPE_ADD: `${prefix} containertype add`,
CONTAINERTYPE_LIST: `${prefix} containertype list`
};
\ No newline at end of file
diff --git a/src/m365/spe/commands/container/container-list.spec.ts b/src/m365/spe/commands/container/container-list.spec.ts
new file mode 100644
index 00000000000..a5900faf2fa
--- /dev/null
+++ b/src/m365/spe/commands/container/container-list.spec.ts
@@ -0,0 +1,182 @@
+import assert from 'assert';
+import sinon from 'sinon';
+import auth from '../../../../Auth.js';
+import { Logger } from '../../../../cli/Logger.js';
+import request from '../../../../request.js';
+import { telemetry } from '../../../../telemetry.js';
+import { pid } from '../../../../utils/pid.js';
+import { session } from '../../../../utils/session.js';
+import { sinonUtil } from '../../../../utils/sinonUtil.js';
+import commands from '../../commands.js';
+import command from './container-list.js';
+import { spo } from '../../../../utils/spo.js';
+import { CommandError } from '../../../../Command.js';
+import { CommandInfo } from '../../../../cli/CommandInfo.js';
+import { cli } from '../../../../cli/cli.js';
+
+describe(commands.CONTAINER_LIST, () => {
+ let log: string[];
+ let logger: Logger;
+ let loggerLogSpy: sinon.SinonSpy;
+ let commandInfo: CommandInfo;
+
+ const adminUrl = 'https://contoso-admin.sharepoint.com';
+ const containersList = [{
+ "id": "b!ISJs1WRro0y0EWgkUYcktDa0mE8zSlFEqFzqRn70Zwp1CEtDEBZgQICPkRbil_5Z",
+ "displayName": "My File Storage Container",
+ "containerTypeId": "e2756c4d-fa33-4452-9c36-2325686e1082",
+ "createdDateTime": "2021-11-24T15:41:52.347Z"
+ },
+ {
+ "id": "b!NdyMBAJ1FEWHB2hEx0DND2dYRB9gz4JOl4rzl7-DuyPG3Fidzm5TTKkyZW2beare",
+ "displayName": "Trial Container",
+ "containerTypeId": "e2756c4d-fa33-4452-9c36-2325686e1082",
+ "createdDateTime": "2021-11-24T15:41:52.347Z"
+ }];
+
+ const containerTypedata = [{
+ "AzureSubscriptionId": "/Guid(f08575e2-36c4-407f-a891-eabae23f66bc)",
+ "ContainerTypeId": "/Guid(e2756c4d-fa33-4452-9c36-2325686e1082)",
+ "CreationDate": "3/11/2024 2:38:56 PM",
+ "DisplayName": "standard container",
+ "ExpiryDate": "3/11/2028 2:38:56 PM",
+ "IsBillingProfileRequired": true,
+ "OwningAppId": "/Guid(1b3b8660-9a44-4a7c-9c02-657f3ff5d5ac)",
+ "OwningTenantId": "/Guid(e1dd4023-a656-480a-8a0e-c1b1eec51e1d)",
+ "Region": "West Europe",
+ "ResourceGroup": "Standard group",
+ "SPContainerTypeBillingClassification": "Standard"
+ },
+ {
+ "AzureSubscriptionId": "/Guid(f08575e2-36c4-407f-a891-eabae23f66bc)",
+ "ContainerTypeId": "/Guid(e2756c4d-fa33-4452-9c36-2325686e1082)",
+ "CreationDate": "3/11/2024 2:38:56 PM",
+ "DisplayName": "trial container",
+ "ExpiryDate": "3/11/2028 2:38:56 PM",
+ "IsBillingProfileRequired": true,
+ "OwningAppId": "/Guid(1b3b8660-9a44-4a7c-9c02-657f3ff5d5ac)",
+ "OwningTenantId": "/Guid(e1dd4023-a656-480a-8a0e-c1b1eec51e1d)",
+ "Region": "West Europe",
+ "ResourceGroup": "Standard group",
+ "SPContainerTypeBillingClassification": "Standard"
+ }];
+
+ before(() => {
+ sinon.stub(auth, 'restoreAuth').resolves();
+ sinon.stub(telemetry, 'trackEvent').returns();
+ sinon.stub(pid, 'getProcessName').returns('');
+ sinon.stub(session, 'getId').returns('');
+ sinon.stub(spo, 'getSpoAdminUrl').resolves(adminUrl);
+ sinon.stub(spo, 'ensureFormDigest').resolves({ FormDigestValue: 'abc', FormDigestTimeoutSeconds: 1800, FormDigestExpiresAt: new Date(), WebFullUrl: 'https://contoso.sharepoint.com' });
+ auth.connection.active = true;
+ auth.connection.spoUrl = 'https://contoso.sharepoint.com';
+ commandInfo = cli.getCommandInfo(command);
+ });
+
+ beforeEach(() => {
+ log = [];
+ logger = {
+ log: async (msg: string) => {
+ log.push(msg);
+ },
+ logRaw: async (msg: string) => {
+ log.push(msg);
+ },
+ logToStderr: async (msg: string) => {
+ log.push(msg);
+ }
+ };
+ loggerLogSpy = sinon.spy(logger, 'log');
+ });
+
+ afterEach(() => {
+ sinonUtil.restore([
+ request.get,
+ request.post,
+ spo.getSpoAdminUrl,
+ spo.getAllContainerTypes
+ ]);
+ });
+
+ after(() => {
+ sinon.restore();
+ auth.connection.active = false;
+ auth.connection.spoUrl = undefined;
+ });
+
+ it('has correct name', () => {
+ assert.strictEqual(command.name, commands.CONTAINER_LIST);
+ });
+
+ it('has a description', () => {
+ assert.notStrictEqual(command.description, null);
+ });
+
+ it('defines correct properties for the default output', () => {
+ assert.deepStrictEqual(command.defaultProperties(), ['id', 'displayName', 'containerTypeId', 'createdDateTime']);
+ });
+
+ it('fails validation if the containerTypeId is not a valid guid', async () => {
+ const actual = await command.validate({ options: { containerTypeId: 'abc' } }, commandInfo);
+ assert.notStrictEqual(actual, true);
+ });
+
+ it('passes validation if valid containerTypeId is specified', async () => {
+ const actual = await command.validate({ options: { containerTypeId: "e2756c4d-fa33-4452-9c36-2325686e1082" } }, commandInfo);
+ assert.strictEqual(actual, true);
+ });
+
+ it('retrieves list of container type by id', async () => {
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === 'https://graph.microsoft.com/v1.0/storage/fileStorage/containers?$filter=containerTypeId eq e2756c4d-fa33-4452-9c36-2325686e1082') {
+ return { "value": containersList };
+ }
+
+ throw 'Invalid request';
+ });
+
+ await command.action(logger, { options: { containerTypeId: "e2756c4d-fa33-4452-9c36-2325686e1082", debug: true } });
+ assert(loggerLogSpy.calledWith(containersList));
+ });
+
+ it('retrieves list of container type by name', async () => {
+ sinon.stub(spo, 'getAllContainerTypes').resolves(containerTypedata);
+
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === 'https://graph.microsoft.com/v1.0/storage/fileStorage/containers?$filter=containerTypeId eq e2756c4d-fa33-4452-9c36-2325686e1082') {
+ return { "value": containersList };
+ }
+
+ throw 'Invalid request';
+ });
+
+ await command.action(logger, { options: { containerTypeName: "standard container", debug: true } });
+ assert(loggerLogSpy.calledWith(containersList));
+ });
+
+ it('throws an error when service principal is not found', async () => {
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === 'https://graph.microsoft.com/v1.0/storage/fileStorage/containers?$filter=containerTypeId eq e2756c4d-fa33-4452-9c36-2325686e1086') {
+ return [];
+ }
+
+ throw 'Invalid request';
+ });
+
+ sinon.stub(spo, 'getAllContainerTypes').resolves(containerTypedata);
+
+ await assert.rejects(command.action(logger, { options: { containerTypeName: "nonexisting container", debug: true } }),
+ new CommandError(`Container type with name nonexisting container not found`));
+ });
+
+ it('correctly handles error when retrieving containers', async () => {
+ const error = 'An error has occurred';
+ sinon.stub(spo, 'getAllContainerTypes').rejects(new Error(error));
+
+ await assert.rejects(command.action(logger, {
+ options: {
+ debug: true
+ }
+ }), new CommandError('An error has occurred'));
+ });
+});
\ No newline at end of file
diff --git a/src/m365/spe/commands/container/container-list.ts b/src/m365/spe/commands/container/container-list.ts
new file mode 100644
index 00000000000..3149436ab36
--- /dev/null
+++ b/src/m365/spe/commands/container/container-list.ts
@@ -0,0 +1,118 @@
+import { Logger } from '../../../../cli/Logger.js';
+import GlobalOptions from '../../../../GlobalOptions.js';
+import { formatting } from '../../../../utils/formatting.js';
+import { odata } from '../../../../utils/odata.js';
+import { validation } from '../../../../utils/validation.js';
+import GraphCommand from '../../../base/GraphCommand.js';
+import commands from '../../commands.js';
+import { ContainerProperties } from '../../ContainerProperties.js';
+import { ContainerTypeProperties, spo } from '../../../../utils/spo.js';
+
+interface CommandArgs {
+ options: Options;
+}
+
+export interface Options extends GlobalOptions {
+ containerTypeId?: string;
+ containerTypeName?: string;
+}
+
+class SpeContainerListCommand extends GraphCommand {
+ public get name(): string {
+ return commands.CONTAINER_LIST;
+ }
+
+ public get description(): string {
+ return 'Lists all Container Types';
+ }
+
+ public defaultProperties(): string[] | undefined {
+ return ['id', 'displayName', 'containerTypeId', 'createdDateTime'];
+ }
+
+ constructor() {
+ super();
+
+ this.#initTelemetry();
+ this.#initOptions();
+ this.#initValidators();
+ this.#initOptionSets();
+ this.#initTypes();
+ }
+
+ #initTelemetry(): void {
+ this.telemetry.push((args: CommandArgs) => {
+ Object.assign(this.telemetryProperties, {
+ containerTypeId: typeof args.options.containerTypeId !== 'undefined',
+ containerTypeName: typeof args.options.containerTypeName !== 'undefined'
+ });
+ });
+ }
+
+ #initOptions(): void {
+ this.options.unshift(
+ {
+ option: '--containerTypeId [containerTypeId]'
+ },
+ {
+ option: '--containerTypeName [containerTypeName]'
+ }
+ );
+ }
+
+ #initValidators(): void {
+ this.validators.push(
+ async (args: CommandArgs) => {
+ if (args.options.containerTypeId && !validation.isValidGuid(args.options.containerTypeId as string)) {
+ return `${args.options.containerTypeId} is not a valid GUID`;
+ }
+
+ return true;
+ }
+ );
+ }
+
+ #initOptionSets(): void {
+ this.optionSets.push({ options: ['containerTypeId', 'containerTypeName'] });
+ }
+
+ #initTypes(): void {
+ this.types.string.push('containerTypeId', 'containerTypeName');
+ }
+
+ public async commandAction(logger: Logger, args: CommandArgs): Promise {
+ try {
+ if (this.verbose) {
+ await logger.logToStderr(`Retrieving list of Containers...`);
+ }
+
+ const containerTypeId = await this.getContainerTypeId(logger, args.options);
+ const allContainers = await odata.getAllItems(`${this.resource}/v1.0/storage/fileStorage/containers?$filter=containerTypeId eq ${formatting.encodeQueryParameter(containerTypeId)}`);
+ await logger.log(allContainers);
+ }
+ catch (err: any) {
+ this.handleRejectedPromise(err);
+ }
+ }
+
+ private async getContainerTypeId(logger: Logger, options: Options): Promise {
+ if (options.containerTypeId) {
+ return options.containerTypeId;
+ }
+
+ const spoAdminUrl = await spo.getSpoAdminUrl(logger, this.debug);
+ const containerTypes: ContainerTypeProperties[] = await spo.getAllContainerTypes(spoAdminUrl, logger, this.debug);
+
+ // Get id of the container type by name
+ const containerType: ContainerTypeProperties | undefined = containerTypes.find(c => c.DisplayName === options.containerTypeName);
+ if (!containerType) {
+ throw new Error(`Container type with name ${options.containerTypeName} not found`);
+ }
+
+ // The value is returned as "/Guid(073269af-f1d2-042d-2ef5-5bdd6ac83115)/". We need to extract the GUID from it.
+ const containerTypeValue = containerType.ContainerTypeId.toString();
+ return containerTypeValue.substring(containerTypeValue.indexOf('(') + 1, containerTypeValue.lastIndexOf(')'));
+ }
+}
+
+export default new SpeContainerListCommand();
\ No newline at end of file
diff --git a/src/m365/spe/commands/containertype/containertype-list.spec.ts b/src/m365/spe/commands/containertype/containertype-list.spec.ts
index 1d161c62fe7..cb21ddc29ed 100644
--- a/src/m365/spe/commands/containertype/containertype-list.spec.ts
+++ b/src/m365/spe/commands/containertype/containertype-list.spec.ts
@@ -11,42 +11,38 @@ import commands from '../../commands.js';
import command from './containertype-list.js';
import { spo } from '../../../../utils/spo.js';
import { CommandError } from '../../../../Command.js';
-import config from '../../../../config.js';
+
+const containerTypedata = [{
+ "AzureSubscriptionId": "/Guid(f08575e2-36c4-407f-a891-eabae23f66bc)",
+ "ContainerTypeId": "/Guid(c33cfee5-c9b6-0a2a-02ee-060693a57f37)",
+ "CreationDate": "3/11/2024 2:38:56 PM",
+ "DisplayName": "standard container",
+ "ExpiryDate": "3/11/2028 2:38:56 PM",
+ "IsBillingProfileRequired": true,
+ "OwningAppId": "/Guid(1b3b8660-9a44-4a7c-9c02-657f3ff5d5ac)",
+ "OwningTenantId": "/Guid(e1dd4023-a656-480a-8a0e-c1b1eec51e1d)",
+ "Region": "West Europe",
+ "ResourceGroup": "Standard group",
+ "SPContainerTypeBillingClassification": "Standard"
+},
+{
+ "AzureSubscriptionId": "/Guid(f08575e2-36c4-407f-a891-eabae23f66bc)",
+ "ContainerTypeId": "/Guid(c33cfee5-c9b6-0a2a-02ee-060693a57f37)",
+ "CreationDate": "3/11/2024 2:38:56 PM",
+ "DisplayName": "trial container",
+ "ExpiryDate": "3/11/2028 2:38:56 PM",
+ "IsBillingProfileRequired": true,
+ "OwningAppId": "/Guid(1b3b8660-9a44-4a7c-9c02-657f3ff5d5ac)",
+ "OwningTenantId": "/Guid(e1dd4023-a656-480a-8a0e-c1b1eec51e1d)",
+ "Region": "West Europe",
+ "ResourceGroup": "Standard group",
+ "SPContainerTypeBillingClassification": "Standard"
+}];
describe(commands.CONTAINERTYPE_LIST, () => {
let log: string[];
let logger: Logger;
let loggerLogSpy: sinon.SinonSpy;
- const containerTypedata = [{
- "_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.SPContainerTypeProperties",
- "ApplicationRedirectUrl": null,
- "AzureSubscriptionId": "/Guid(00000000-0000-0000-0000-000000000000)/",
- "ContainerTypeId": "/Guid(073269af-f1d2-042d-2ef5-5bdd6ac83115)/",
- "CreationDate": null,
- "DisplayName": "test1",
- "ExpiryDate": null,
- "IsBillingProfileRequired": true,
- "OwningAppId": "/Guid(df4085cc-9a38-4255-badc-5c5225610475)/",
- "OwningTenantId": "/Guid(00000000-0000-0000-0000-000000000000)/",
- "Region": null,
- "ResourceGroup": null,
- "SPContainerTypeBillingClassification": 0
- },
- {
- "_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.SPContainerTypeProperties",
- "ApplicationRedirectUrl": null,
- "AzureSubscriptionId": "/Guid(00000000-0000-0000-0000-000000000000)/",
- "ContainerTypeId": "/Guid(880ab3bd-5b68-01d4-3744-01a7656cf2ba)/",
- "CreationDate": null,
- "DisplayName": "test1",
- "ExpiryDate": null,
- "IsBillingProfileRequired": true,
- "OwningAppId": "/Guid(50785fde-3082-47ac-a36d-06282ac5c7da)/",
- "OwningTenantId": "/Guid(00000000-0000-0000-0000-000000000000)/",
- "Region": null,
- "ResourceGroup": null,
- "SPContainerTypeBillingClassification": 0
- }];
before(() => {
sinon.stub(auth, 'restoreAuth').resolves();
@@ -76,7 +72,8 @@ describe(commands.CONTAINERTYPE_LIST, () => {
afterEach(() => {
sinonUtil.restore([
- request.post
+ request.post,
+ spo.getAllContainerTypes
]);
});
@@ -98,81 +95,20 @@ describe(commands.CONTAINERTYPE_LIST, () => {
assert.deepStrictEqual(command.defaultProperties(), ['ContainerTypeId', 'DisplayName', 'OwningAppId']);
});
- it('correctly handles random API error', async () => {
- sinon.stub(request, 'post').rejects(new Error('An error has occurred'));
-
- await assert.rejects(command.action(logger, { options: { debug: true } } as any), new CommandError("An error has occurred"));
- });
-
- it('retrieves list of Container Type', async () => {
- sinon.stub(request, 'post').callsFake(async (opts) => {
- if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) {
- if (opts.headers &&
- opts.headers['X-RequestDigest'] &&
- opts.headers['X-RequestDigest'] === 'abc' &&
- opts.data === `1`) {
- return JSON.stringify([
- {
- "SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.24817.12005", "ErrorInfo": null, "TraceCorrelationId": "2d63d39f-3016-0000-a532-30514e76ae73"
- }, 46, {
- "IsNull": false
- }, 47, containerTypedata
- ]);
- }
- }
-
- throw 'Invalid request';
- });
-
- await command.action(logger, { options: {} });
- assert(loggerLogSpy.calledWith(containerTypedata));
- });
-
- it('retrieves list of Container Type (debug)', async () => {
- sinon.stub(request, 'post').callsFake(async (opts) => {
- if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) {
- if (opts.headers &&
- opts.headers['X-RequestDigest'] &&
- opts.headers['X-RequestDigest'] === 'abc' &&
- opts.data === `1`) {
- return JSON.stringify([
- {
- "SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.24817.12005", "ErrorInfo": null, "TraceCorrelationId": "2d63d39f-3016-0000-a532-30514e76ae73"
- }, 46, {
- "IsNull": false
- }, 47, containerTypedata
- ]);
- }
- }
-
- throw 'Invalid request';
- });
-
+ it('retrieves list of container type', async () => {
+ sinon.stub(spo, 'getAllContainerTypes').resolves(containerTypedata);
await command.action(logger, { options: { debug: true } });
assert(loggerLogSpy.calledWith(containerTypedata));
});
- it('correctly handles error when retrieving Container Types', async () => {
- sinon.stub(request, 'post').callsFake(async (opts) => {
- if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) {
- if (opts.headers &&
- opts.headers['X-RequestDigest'] &&
- opts.headers['X-RequestDigest'] === 'abc') {
+ it('correctly handles error when retrieving container types', async () => {
+ const error = 'An error has occurred';
+ sinon.stub(spo, 'getAllContainerTypes').rejects(new Error(error));
- return JSON.stringify([
- {
- "SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.7324.1200", "ErrorInfo": {
- "ErrorMessage": "An error has occurred.", "ErrorValue": null, "TraceCorrelationId": "e13c489e-2026-5000-8242-7ec96d02ba1d", "ErrorCode": -1, "ErrorTypeName": "SPException"
- }, "TraceCorrelationId": "e13c489e-2026-5000-8242-7ec96d02ba1d"
- }
- ]);
- }
+ await assert.rejects(command.action(logger, {
+ options: {
+ debug: true
}
-
- throw 'Invalid request';
- });
-
- await assert.rejects(command.action(logger, { options: { debug: true } } as any),
- new CommandError("An error has occurred."));
+ }), new CommandError('An error has occurred'));
});
-});
+});
\ No newline at end of file
diff --git a/src/m365/spe/commands/containertype/containertype-list.ts b/src/m365/spe/commands/containertype/containertype-list.ts
index b55527b9a9e..97e0f573a20 100644
--- a/src/m365/spe/commands/containertype/containertype-list.ts
+++ b/src/m365/spe/commands/containertype/containertype-list.ts
@@ -1,10 +1,7 @@
import { Logger } from '../../../../cli/Logger.js';
-import config from '../../../../config.js';
-import request, { CliRequestOptions } from '../../../../request.js';
-import { ClientSvcResponse, ClientSvcResponseContents, FormDigestInfo, spo } from '../../../../utils/spo.js';
import SpoCommand from '../../../base/SpoCommand.js';
import commands from '../../commands.js';
-import { ContainerTypeProperties } from '../../ContainerTypeProperties.js';
+import { ContainerTypeProperties, spo } from '../../../../utils/spo.js';
class SpeContainertypeListCommand extends SpoCommand {
@@ -28,36 +25,13 @@ class SpeContainertypeListCommand extends SpoCommand {
await logger.logToStderr(`Retrieving list of Container types...`);
}
- const allContainerTypes = await this.getAllContainerTypes(spoAdminUrl, logger);
+ const allContainerTypes: ContainerTypeProperties[] = await spo.getAllContainerTypes(spoAdminUrl, logger, this.debug);
await logger.log(allContainerTypes);
}
catch (err: any) {
this.handleRejectedPromise(err);
}
}
-
- private async getAllContainerTypes(spoAdminUrl: string, logger: Logger): Promise {
- const formDigestInfo: FormDigestInfo = await spo.ensureFormDigest(spoAdminUrl, logger, undefined, this.debug);
-
- const requestOptions: CliRequestOptions = {
- url: `${spoAdminUrl}/_vti_bin/client.svc/ProcessQuery`,
- headers: {
- 'X-RequestDigest': formDigestInfo.FormDigestValue
- },
- data: `1`
- };
-
- const res: string = await request.post(requestOptions);
- const json: ClientSvcResponse = JSON.parse(res);
- const response: ClientSvcResponseContents = json[0];
-
- if (response.ErrorInfo) {
- throw response.ErrorInfo.ErrorMessage;
- }
-
- const containerTypes: ContainerTypeProperties[] = json[json.length - 1];
- return containerTypes;
- }
}
export default new SpeContainertypeListCommand();
\ No newline at end of file
diff --git a/src/utils/spo.spec.ts b/src/utils/spo.spec.ts
index f9b7b914885..adbf41a71d0 100644
--- a/src/utils/spo.spec.ts
+++ b/src/utils/spo.spec.ts
@@ -88,6 +88,37 @@ const copyJobInfo = {
]
};
+const containerTypedata = [{
+ "_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.SPContainerTypeProperties",
+ "ApplicationRedirectUrl": null,
+ "AzureSubscriptionId": "/Guid(00000000-0000-0000-0000-000000000000)/",
+ "ContainerTypeId": "/Guid(073269af-f1d2-042d-2ef5-5bdd6ac83115)/",
+ "CreationDate": null,
+ "DisplayName": "test1",
+ "ExpiryDate": null,
+ "IsBillingProfileRequired": true,
+ "OwningAppId": "/Guid(df4085cc-9a38-4255-badc-5c5225610475)/",
+ "OwningTenantId": "/Guid(00000000-0000-0000-0000-000000000000)/",
+ "Region": null,
+ "ResourceGroup": null,
+ "SPContainerTypeBillingClassification": 0
+},
+{
+ "_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.SPContainerTypeProperties",
+ "ApplicationRedirectUrl": null,
+ "AzureSubscriptionId": "/Guid(00000000-0000-0000-0000-000000000000)/",
+ "ContainerTypeId": "/Guid(880ab3bd-5b68-01d4-3744-01a7656cf2ba)/",
+ "CreationDate": null,
+ "DisplayName": "test1",
+ "ExpiryDate": null,
+ "IsBillingProfileRequired": true,
+ "OwningAppId": "/Guid(50785fde-3082-47ac-a36d-06282ac5c7da)/",
+ "OwningTenantId": "/Guid(00000000-0000-0000-0000-000000000000)/",
+ "Region": null,
+ "ResourceGroup": null,
+ "SPContainerTypeBillingClassification": 0
+}];
+
describe('utils/spo', () => {
let logger: Logger;
let log: string[];
@@ -3137,7 +3168,6 @@ describe('utils/spo', () => {
});
await spo.getSiteAdminPropertiesByUrl('https://contoso.sharepoint.com/sites/sales', false, logger, true);
-
assert.deepStrictEqual(postStub.firstCall.args[0].data, { url: 'https://contoso.sharepoint.com/sites/sales', includeDetail: false });
});
@@ -3154,7 +3184,59 @@ describe('utils/spo', () => {
});
await spo.getSiteAdminPropertiesByUrl('https://contoso.sharepoint.com/sites/sales', true, logger, true);
-
assert.deepStrictEqual(postStub.firstCall.args[0].data, { url: 'https://contoso.sharepoint.com/sites/sales', includeDetail: true });
});
+
+ it('retrieves list of Container Type', async () => {
+ sinon.stub(spo, 'getSpoAdminUrl').resolves('https://contoso-admin.sharepoint.com');
+ sinon.stub(spo, 'ensureFormDigest').resolves({ FormDigestValue: 'abc', FormDigestTimeoutSeconds: 1800, FormDigestExpiresAt: new Date(), WebFullUrl: 'https://contoso.sharepoint.com' });
+
+ sinon.stub(request, 'post').callsFake(async (opts) => {
+ if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) {
+ if (opts.headers &&
+ opts.headers['X-RequestDigest'] &&
+ opts.headers['X-RequestDigest'] === 'abc' &&
+ opts.data === `1`) {
+ return JSON.stringify([
+ {
+ "SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.24817.12005", "ErrorInfo": null, "TraceCorrelationId": "2d63d39f-3016-0000-a532-30514e76ae73"
+ }, 46, {
+ "IsNull": false
+ }, 47, containerTypedata
+ ]);
+ }
+ }
+
+ throw 'Invalid request';
+ });
+
+ const containerTypeList = await spo.getAllContainerTypes('https://contoso-admin.sharepoint.com', logger, true);
+ assert.deepEqual(containerTypeList, containerTypedata);
+ });
+
+ it('correctly throws error when retrieving container types', async () => {
+ sinon.stub(spo, 'getSpoAdminUrl').resolves('https://contoso-admin.sharepoint.com');
+ sinon.stub(spo, 'ensureFormDigest').resolves({ FormDigestValue: 'abc', FormDigestTimeoutSeconds: 1800, FormDigestExpiresAt: new Date(), WebFullUrl: 'https://contoso.sharepoint.com' });
+
+ sinon.stub(request, 'post').callsFake(async (opts) => {
+ if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) {
+ if (opts.headers &&
+ opts.headers['X-RequestDigest'] &&
+ opts.headers['X-RequestDigest'] === 'abc') {
+
+ return JSON.stringify([
+ {
+ "SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.7324.1200", "ErrorInfo": {
+ "ErrorMessage": "An error has occurred", "ErrorValue": null, "TraceCorrelationId": "e13c489e-2026-5000-8242-7ec96d02ba1d", "ErrorCode": -1, "ErrorTypeName": "SPException"
+ }, "TraceCorrelationId": "e13c489e-2026-5000-8242-7ec96d02ba1d"
+ }
+ ]);
+ }
+ }
+
+ throw 'Invalid request';
+ });
+
+ await assert.rejects(spo.getAllContainerTypes('https://contoso-admin.sharepoint.com', logger, true), 'An error occured');
+ });
});
\ No newline at end of file
diff --git a/src/utils/spo.ts b/src/utils/spo.ts
index 9256d3cce4e..e0d7637d2cd 100644
--- a/src/utils/spo.ts
+++ b/src/utils/spo.ts
@@ -89,6 +89,21 @@ export interface User {
UserPrincipalName: string | null;
}
+export interface ContainerTypeProperties {
+ _ObjectType_?: string;
+ AzureSubscriptionId: string;
+ ContainerTypeId: string;
+ CreationDate: string;
+ DisplayName: string;
+ ExpiryDate: string;
+ IsBillingProfileRequired: boolean;
+ OwningAppId: string;
+ OwningTenantId: string;
+ Region?: string;
+ ResourceGroup?: string;
+ SPContainerTypeBillingClassification: string;
+}
+
interface CreateFileCopyJobsOptions {
nameConflictBehavior?: CreateFileCopyJobsNameConflictBehavior;
newName?: string;
@@ -293,6 +308,29 @@ export const spo = {
return context;
},
+ async getAllContainerTypes(spoAdminUrl: string, logger: Logger, verbose: boolean): Promise {
+ const formDigestInfo: FormDigestInfo = await spo.ensureFormDigest(spoAdminUrl, logger, undefined, verbose);
+
+ const requestOptions: CliRequestOptions = {
+ url: `${spoAdminUrl}/_vti_bin/client.svc/ProcessQuery`,
+ headers: {
+ 'X-RequestDigest': formDigestInfo.FormDigestValue
+ },
+ data: `1`
+ };
+
+ const res: string = await request.post(requestOptions);
+ const json: ClientSvcResponse = JSON.parse(res);
+ const response: ClientSvcResponseContents = json[0];
+
+ if (response.ErrorInfo) {
+ throw new Error(response.ErrorInfo.ErrorMessage);
+ }
+
+ const containerTypes: ContainerTypeProperties[] = json[json.length - 1];
+ return containerTypes;
+ },
+
async waitUntilFinished({ operationId, siteUrl, logger, currentContext, debug, verbose }: { operationId: string, siteUrl: string, logger: Logger, currentContext: FormDigestInfo, debug: boolean, verbose: boolean }): Promise {
const resFormDigest = await spo.ensureFormDigest(siteUrl, logger, currentContext, debug);
currentContext = resFormDigest;