Skip to content
Open
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
15 changes: 12 additions & 3 deletions docs/docs/cmd/spe/container/container-permission-list.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ m365 spe container permission list [options]
## Options

```md definition-list
`-i, --containerId <id>`
: The ID of the SharePoint Embedded Container.
`-i, --containerId [id]`
: The ID of the SharePoint Embedded Container. Specify either `containerId` or `containerName` but not both.

`-n, --containerName [containerName]`
: Display name of the SharePoint Embedded Container. Specify either `containerId` or `containerName` but not both.
```

<Global />
Expand All @@ -42,12 +45,18 @@ m365 spe container permission list [options]

## Examples

Lists Container permissions.
Lists Container permissions by id.

```sh
m365 spe container permission list --containerId "b!ISJs1WRro0y0EWgkUYcktDa0mE8zSlFEqFzqRn70Zwp1CEtDEBZgQICPkRbil_5Z"
```

Lists Container permissions by display name.

```sh
m365 spe container permission list --containerName "My Application Storage Container"
```

## Response

<Tabs>
Expand Down
155 changes: 135 additions & 20 deletions src/m365/spe/commands/container/container-permission-list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,25 @@ import sinon from 'sinon';
import auth from '../../../../Auth.js';
import { Logger } from '../../../../cli/Logger.js';
import { CommandError } from '../../../../Command.js';
import request from '../../../../request.js';
import { telemetry } from '../../../../telemetry.js';
import { pid } from '../../../../utils/pid.js';
import { sinonUtil } from '../../../../utils/sinonUtil.js';
import commands from '../../commands.js';
import command from './container-permission-list.js';
import { formatting } from '../../../../utils/formatting.js';
import { z } from 'zod';
import { spe } from '../../../../utils/spe.js';
import { odata } from '../../../../utils/odata.js';
import { cli } from '../../../../cli/cli.js';

describe(commands.CONTAINER_PERMISSION_LIST, () => {
let log: string[];
let logger: Logger;
let loggerLogSpy: sinon.SinonSpy;
let loggerLogToStderrSpy: sinon.SinonSpy;
let schema: z.ZodTypeAny;

const containerId = "b!ISJs1WRro0y0EWgkUYcktDa0mE8zSlFEqFzqRn70Zwp1CEtDEBZgQICPkRbil_5Z";
const containerName = 'My Application Storage Container';
const containerPermissionResponse = {
"value": [
{
Expand Down Expand Up @@ -66,6 +71,7 @@ describe(commands.CONTAINER_PERMISSION_LIST, () => {
sinon.stub(telemetry, 'trackEvent').resolves();
sinon.stub(pid, 'getProcessName').returns('');
auth.connection.active = true;
schema = command.getSchemaToParse()!;
});

beforeEach(() => {
Expand All @@ -82,12 +88,17 @@ describe(commands.CONTAINER_PERMISSION_LIST, () => {
}
};
loggerLogSpy = sinon.spy(logger, 'log');
loggerLogToStderrSpy = sinon.spy(logger, 'logToStderr');
});

afterEach(() => {
sinonUtil.restore([
request.get
spe.getContainerIdByName,
odata.getAllItems,
cli.handleMultipleResultsFound
]);
loggerLogSpy.restore();
loggerLogToStderrSpy.restore();
});

after(() => {
Expand All @@ -108,13 +119,7 @@ describe(commands.CONTAINER_PERMISSION_LIST, () => {
});

it('correctly lists permissions of a SharePoint Embedded Container', async () => {
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://graph.microsoft.com/v1.0/storage/fileStorage/containers/${formatting.encodeQueryParameter(containerId)}/permissions`) {
return containerPermissionResponse;
}

throw 'Invalid request';
});
sinon.stub(odata, 'getAllItems').resolves(containerPermissionResponse.value);

await command.action(logger, {
options: {
Expand All @@ -127,13 +132,7 @@ describe(commands.CONTAINER_PERMISSION_LIST, () => {
});

it('correctly lists permissions of a SharePoint Embedded Container (TEXT)', async () => {
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://graph.microsoft.com/v1.0/storage/fileStorage/containers/${formatting.encodeQueryParameter(containerId)}/permissions`) {
return containerPermissionResponse;
}

throw 'Invalid request';
});
sinon.stub(odata, 'getAllItems').resolves(containerPermissionResponse.value);

await command.action(logger, {
options: {
Expand All @@ -146,8 +145,99 @@ describe(commands.CONTAINER_PERMISSION_LIST, () => {
assert(loggerLogSpy.calledWith(textOutput));
});

it('correctly lists permissions of a SharePoint Embedded Container by name', async () => {
sinon.stub(odata, 'getAllItems').onFirstCall().resolves([
{
id: containerId,
displayName: containerName
}
]).onSecondCall().resolves(containerPermissionResponse.value);

await command.action(logger, {
options: {
containerName,
debug: true
}
});

assert(loggerLogSpy.calledWith(containerPermissionResponse.value));
});

it('logs progress when resolving container id by name in verbose mode', async () => {
sinon.stub(odata, 'getAllItems').onFirstCall().resolves([
{
id: containerId,
displayName: containerName
}
]).onSecondCall().resolves(containerPermissionResponse.value);

await command.action(logger, {
options: {
containerName,
verbose: true
}
});

assert(loggerLogToStderrSpy.calledWith(`Resolving container id from name '${containerName}'...`));
});

it('fails when container with specified name does not exist', async () => {
sinon.stub(odata, 'getAllItems').resolves([]);

await assert.rejects(
command.action(logger, {
options: {
containerName
}
}),
new CommandError(`The specified container '${containerName}' does not exist.`)
);
});

it('handles multiple containers with same name when resolving id', async () => {
sinon.stub(odata, 'getAllItems').onFirstCall().resolves([
{
id: '1',
displayName: containerName
},
{
id: containerId,
displayName: containerName
}
]).onSecondCall().resolves(containerPermissionResponse.value);
sinon.stub(cli, 'handleMultipleResultsFound').resolves({
id: containerId
});

await command.action(logger, {
options: {
containerName
}
});

assert(loggerLogSpy.calledWith(containerPermissionResponse.value));
});

it('rethrows unexpected errors when resolving container id by name', async () => {
sinon.stub(odata, 'getAllItems').rejects({
error: {
'odata.error': {
message: {
value: 'unexpected error'
}
}
}
});

await assert.rejects(command.action(logger, {
options: {
containerName
}
}), new CommandError('unexpected error'));
});

it('correctly handles error when SharePoint Embedded Container is not found', async () => {
sinon.stub(request, 'get').rejects({
sinon.stub(odata, 'getAllItems').rejects({
error: { 'odata.error': { message: { value: 'Item Not Found.' } } }
});

Expand All @@ -157,12 +247,37 @@ describe(commands.CONTAINER_PERMISSION_LIST, () => {

it('correctly handles error when retrieving permissions of a SharePoint Embedded Container', async () => {
const error = 'An error has occurred';
sinon.stub(request, 'get').rejects(new Error(error));
sinon.stub(odata, 'getAllItems').rejects(new Error(error));

await assert.rejects(command.action(logger, {
options: {
containerId: containerId
}
}), new CommandError(error));
});
});

it('fails validation when neither containerId nor containerName is specified', () => {
const result = schema.safeParse({});
assert.strictEqual(result.success, false);
assert(result.error?.issues.some(issue => issue.message.includes('Specify either containerId or containerName')));
});

it('fails validation when both containerId and containerName are specified', () => {
const result = schema.safeParse({
containerId,
containerName
});
assert.strictEqual(result.success, false);
assert(result.error?.issues.some(issue => issue.message.includes('Specify either containerId or containerName')));
});

it('passes validation when only containerId is specified', () => {
const result = schema.safeParse({ containerId });
assert.strictEqual(result.success, true);
});

it('passes validation when only containerName is specified', () => {
const result = schema.safeParse({ containerName });
assert.strictEqual(result.success, true);
});
});
41 changes: 36 additions & 5 deletions src/m365/spe/commands/container/container-permission-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import { cli } from '../../../../cli/cli.js';
import { z } from 'zod';
import { zod } from '../../../../utils/zod.js';
import { Logger } from '../../../../cli/Logger.js';
import { globalOptionsZod } from '../../../../Command.js';
import { CommandError, globalOptionsZod } from '../../../../Command.js';
import commands from '../../commands.js';
import GraphCommand from '../../../base/GraphCommand.js';
import { odata } from '../../../../utils/odata.js';
import { formatting } from '../../../../utils/formatting.js';
import { spe } from '../../../../utils/spe.js';

const options = globalOptionsZod
.extend({
containerId: zod.alias('i', z.string())
containerId: zod.alias('i', z.string().optional()),
containerName: zod.alias('n', z.string().optional())
})
.strict();
declare type Options = z.infer<typeof options>;
Expand All @@ -36,13 +38,21 @@ class SpeContainerPermissionListCommand extends GraphCommand {
return options;
}

public getRefinedSchema(schema: z.ZodTypeAny): z.ZodEffects<any> | undefined {
return schema.refine((opts: Options) => [opts.containerId, opts.containerName].filter(value => value !== undefined).length === 1, {
message: 'Specify either containerId or containerName, but not both.'
});
}

public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
try {
const containerId = await this.resolveContainerId(args.options, logger);

if (this.verbose) {
await logger.logToStderr(`Retrieving permissions of a SharePoint Embedded Container with id '${args.options.containerId}'...`);
await logger.logToStderr(`Retrieving permissions of a SharePoint Embedded Container with id '${containerId}'...`);
}

const containerPermission = await odata.getAllItems<any>(`${this.resource}/v1.0/storage/fileStorage/containers/${formatting.encodeQueryParameter(args.options.containerId)}/permissions`);
const containerPermission = await odata.getAllItems<any>(`${this.resource}/v1.0/storage/fileStorage/containers/${formatting.encodeQueryParameter(containerId)}/permissions`);

if (!cli.shouldTrimOutput(args.options.output)) {
await logger.log(containerPermission);
Expand All @@ -58,9 +68,30 @@ class SpeContainerPermissionListCommand extends GraphCommand {
}
}
catch (err: any) {
if (err instanceof CommandError) {
throw err;
}
this.handleRejectedODataJsonPromise(err);
}
}

private async resolveContainerId(options: Options, logger: Logger): Promise<string> {
if (options.containerId) {
return options.containerId;
}

if (this.verbose) {
await logger.logToStderr(`Resolving container id from name '${options.containerName}'...`);
}

try {
return await spe.getContainerIdByName(options.containerName!);
}
catch (error: any) {
this.handleRejectedODataJsonPromise(error);
throw error;
}
}
}

export default new SpeContainerPermissionListCommand();
export default new SpeContainerPermissionListCommand();
4 changes: 2 additions & 2 deletions src/m365/spe/commands/container/container-remove.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe(commands.CONTAINER_REMOVE, () => {
sinon.stub(session, 'getId').returns('');

sinon.stub(spe, 'getContainerTypeIdByName').withArgs(containerTypeName).resolves(containerTypeId);
sinon.stub(spe, 'getContainerIdByName').withArgs(containerTypeId, containerName).resolves(containerId);
sinon.stub(spe, 'getContainerIdByNameAndContainerTypeId').withArgs(containerTypeId, containerName).resolves(containerId);

auth.connection.active = true;
commandInfo = cli.getCommandInfo(command);
Expand Down Expand Up @@ -196,4 +196,4 @@ describe(commands.CONTAINER_REMOVE, () => {
await assert.rejects(command.action(logger, { options: { id: containerId, force: true } }),
new CommandError(errorMessage));
});
});
});
4 changes: 2 additions & 2 deletions src/m365/spe/commands/container/container-remove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class SpeContainerRemoveCommand extends GraphCommand {
await logger.logToStderr(`Getting container ID for container with name '${options.name}'...`);
}

return spe.getContainerIdByName(containerTypeId, options.name!);
return spe.getContainerIdByNameAndContainerTypeId(containerTypeId, options.name!);
}

private async getContainerTypeId(options: Options, logger: Logger): Promise<string> {
Expand All @@ -120,4 +120,4 @@ class SpeContainerRemoveCommand extends GraphCommand {
}
}

export default new SpeContainerRemoveCommand();
export default new SpeContainerRemoveCommand();
Loading