Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds "m365 spe container list" command. Closes #6082 #6412

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions docs/docs/cmd/spe/container/container-list.mdx
Original file line number Diff line number Diff line change
@@ -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.
```

<Global />

## 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

<Tabs>
<TabItem value="JSON">

```json
[
{
"id": "b!ISJs1WRro0y0EWgkUYcktDa0mE8zSlFEqFzqRn70Zwp1CEtDEBZgQICPkRbil_5Z",
"displayName": "My File Storage Container",
"containerTypeId": "e2756c4d-fa33-4452-9c36-2325686e1082",
"createdDateTime": "2021-11-24T15:41:52.347Z"
}
]
```

</TabItem>
<TabItem value="Text">

```text
id displayName containerTypeId createdDateTime
------------------------------------------------------------------ ------------------------- ------------------------------------ ------------------------
b!ISJs1WRro0y0EWgkUYcktDa0mE8zSlFEqFzqRn70Zwp1CEtDEBZgQICPkRbil_5Z My File Storage Container e2756c4d-fa33-4452-9c36-2325686e1082 2021-11-24T15:41:52.347Z
```

</TabItem>
<TabItem value="CSV">

```csv
id,displayName,containerTypeId,createdDateTime
b!ISJs1WRro0y0EWgkUYcktDa0mE8zSlFEqFzqRn70Zwp1CEtDEBZgQICPkRbil_5Z,My File Storage Container,e2756c4d-fa33-4452-9c36-2325686e1082,2021-11-24T15:41:52.347Z
```

</TabItem>
<TabItem value="Markdown">

```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
```

</TabItem>
</Tabs>

## 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.
9 changes: 9 additions & 0 deletions docs/src/config/sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1960,6 +1960,15 @@ const sidebars: SidebarsConfig = {
},
{
'SharePoint Embedded (spe)': [
{
container: [
{
type: 'doc',
label: 'container list',
id: 'cmd/spe/container/container-list'
}
]
},
{
containertype: [
{
Expand Down
6 changes: 6 additions & 0 deletions src/m365/spe/ContainerProperties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface ContainerProperties {
id: string;
displayName: string;
containerTypeId: string;
createdDateTime: string;
}
14 changes: 0 additions & 14 deletions src/m365/spe/ContainerTypeProperties.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/m365/spe/commands.ts
Original file line number Diff line number Diff line change
@@ -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`
};
182 changes: 182 additions & 0 deletions src/m365/spe/commands/container/container-list.spec.ts
Original file line number Diff line number Diff line change
@@ -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'));
});
});
Loading