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
27 changes: 24 additions & 3 deletions src/m365/spe/commands/container/container-activate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ 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-activate.js';
import command, { options } from './container-activate.js';
import { CommandError } from '../../../../Command.js';
import { formatting } from '../../../../utils/formatting.js';
import { CommandInfo } from '../../../../cli/CommandInfo.js';
import { cli } from '../../../../cli/cli.js';

describe(commands.CONTAINER_ACTIVATE, () => {
let log: string[];
let logger: Logger;
let commandInfo: CommandInfo;
let commandOptionsSchema: typeof options;

const containerId = 'b!ISJs1WRro0y0EWgkUYcktDa0mE8zSlFEqFzqRn70Zwp1CEtDEBZgQICPkRbil_5Z';

Expand All @@ -24,6 +28,8 @@ describe(commands.CONTAINER_ACTIVATE, () => {
sinon.stub(pid, 'getProcessName').returns('');
sinon.stub(session, 'getId').returns('');
auth.connection.active = true;
commandInfo = cli.getCommandInfo(command);
commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options;
});

beforeEach(() => {
Expand Down Expand Up @@ -60,6 +66,21 @@ describe(commands.CONTAINER_ACTIVATE, () => {
assert.notStrictEqual(command.description, null);
});

it('fails validation when id is not specified', () => {
const actual = commandOptionsSchema.safeParse({
verbose: true
});
assert.strictEqual(actual.success, false);
});

it('fails validation with unknown options', () => {
const actual = commandOptionsSchema.safeParse({
id: containerId,
unknownOption: 'value'
});
assert.strictEqual(actual.success, false);
});

it('activates container by id', async () => {
const postStub = sinon.stub(request, 'post').callsFake(async (opts) => {
if (opts.url === `https://graph.microsoft.com/v1.0/storage/fileStorage/containers/${formatting.encodeQueryParameter(containerId)}/activate`) {
Expand All @@ -69,7 +90,7 @@ describe(commands.CONTAINER_ACTIVATE, () => {
throw 'Invalid request';
});

await command.action(logger, { options: { id: containerId, verbose: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ id: containerId, verbose: true }) });
Comment thread
MartinM85 marked this conversation as resolved.
assert(postStub.calledOnce);
});

Expand All @@ -94,7 +115,7 @@ describe(commands.CONTAINER_ACTIVATE, () => {
throw 'Invalid request';
});

await assert.rejects(command.action(logger, { options: { id: containerId, verbose: true } } as any),
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ id: containerId, verbose: true }) }),
new CommandError(error.error.message));
});
});
31 changes: 11 additions & 20 deletions src/m365/spe/commands/container/container-activate.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import GlobalOptions from '../../../../GlobalOptions.js';
import { z } from 'zod';
import { Logger } from '../../../../cli/Logger.js';
import { globalOptionsZod } from '../../../../Command.js';
import GraphCommand from '../../../base/GraphCommand.js';
import commands from '../../commands.js';
import request, { CliRequestOptions } from '../../../../request.js';
import { formatting } from '../../../../utils/formatting.js';

export const options = z.strictObject({
...globalOptionsZod.shape,
id: z.string().alias('i')
});

declare type Options = z.infer<typeof options>;

interface CommandArgs {
options: Options;
}

interface Options extends GlobalOptions {
id: string;
}

class SpeContainerActivateCommand extends GraphCommand {
public get name(): string {
return commands.CONTAINER_ACTIVATE;
Expand All @@ -22,21 +26,8 @@ class SpeContainerActivateCommand extends GraphCommand {
return 'Activates a container';
}

constructor() {
super();

this.#initOptions();
this.#initTypes();
}

#initOptions(): void {
this.options.unshift(
{ option: '-i, --id <id>' }
);
}

#initTypes(): void {
this.types.string.push('id');
public get schema(): z.ZodTypeAny {
return options;
}

public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
Expand Down
10 changes: 5 additions & 5 deletions src/m365/spe/commands/container/container-get.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ describe(commands.CONTAINER_GET, () => {
throw 'Invalid GET request: ' + opts.url;
});

await command.action(logger, { options: { name: containerName, containerTypeId: containerTypeId } });
await command.action(logger, { options: commandOptionsSchema.parse({ name: containerName, containerTypeId: containerTypeId }) });
assert.deepStrictEqual(loggerLogSpy.lastCall.args[0], containerResponse);
});

Expand All @@ -197,7 +197,7 @@ describe(commands.CONTAINER_GET, () => {
throw 'Invalid GET request: ' + opts.url;
});

await command.action(logger, { options: { name: containerName, containerTypeName: containerTypeName, verbose: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ name: containerName, containerTypeName: containerTypeName, verbose: true }) });
assert.deepStrictEqual(loggerLogSpy.lastCall.args[0], containerResponse);
});

Expand All @@ -206,7 +206,7 @@ describe(commands.CONTAINER_GET, () => {
value: deletedContainersResponse
});

await assert.rejects(command.action(logger, { options: { name: 'Non-existing container', containerTypeId: containerTypeId } }),
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ name: 'Non-existing container', containerTypeId: containerTypeId }) }),
new CommandError(`The specified container 'Non-existing container' does not exist.`));
});

Expand All @@ -232,7 +232,7 @@ describe(commands.CONTAINER_GET, () => {
});

const stubMultiResults = sinon.stub(cli, 'handleMultipleResultsFound').resolves(deletedContainersResponse.find(c => c.id === containerId)!);
await command.action(logger, { options: { name: containerName, containerTypeId: containerTypeId } });
await command.action(logger, { options: commandOptionsSchema.parse({ name: containerName, containerTypeId: containerTypeId }) });
assert(stubMultiResults.calledOnce);
});

Expand All @@ -245,7 +245,7 @@ describe(commands.CONTAINER_GET, () => {
}
});

await assert.rejects(command.action(logger, { options: { id: containerId } }),
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ id: containerId }) }),
new CommandError(errorMessage));
});
});
45 changes: 34 additions & 11 deletions src/m365/spe/commands/container/container-list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ 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 command, { options } from './container-list.js';
import { CommandError } from '../../../../Command.js';
import { CommandInfo } from '../../../../cli/CommandInfo.js';
import { cli } from '../../../../cli/cli.js';
Expand All @@ -19,6 +19,7 @@ describe(commands.CONTAINER_LIST, () => {
let logger: Logger;
let loggerLogSpy: sinon.SinonSpy;
let commandInfo: CommandInfo;
let commandOptionsSchema: typeof options;

const containersList = [{
"id": "b!ISJs1WRro0y0EWgkUYcktDa0mE8zSlFEqFzqRn70Zwp1CEtDEBZgQICPkRbil_5Z",
Expand All @@ -41,6 +42,7 @@ describe(commands.CONTAINER_LIST, () => {

auth.connection.active = true;
commandInfo = cli.getCommandInfo(command);
commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options;
});

beforeEach(() => {
Expand Down Expand Up @@ -86,14 +88,35 @@ describe(commands.CONTAINER_LIST, () => {
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('fails validation if the containerTypeId is not a valid guid', () => {
const result = commandOptionsSchema.safeParse({ containerTypeId: 'abc' });
assert.strictEqual(result.success, false);
});

Comment thread
MartinM85 marked this conversation as resolved.
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('fails validation when neither containerTypeId nor containerTypeName is specified', () => {
const result = commandOptionsSchema.safeParse({});
assert.strictEqual(result.success, false);
});

it('fails validation when both containerTypeId and containerTypeName are specified', () => {
const result = commandOptionsSchema.safeParse({
containerTypeId: 'e2756c4d-fa33-4452-9c36-2325686e1082',
containerTypeName: 'standard container'
});
assert.strictEqual(result.success, false);
});

it('fails validation when unknown option is specified', () => {
const result = commandOptionsSchema.safeParse({
containerTypeId: 'e2756c4d-fa33-4452-9c36-2325686e1082',
unknownOption: 'value'
});
assert.strictEqual(result.success, false);
});

it('passes validation if valid containerTypeId is specified', () => {
const result = commandOptionsSchema.safeParse({ containerTypeId: "e2756c4d-fa33-4452-9c36-2325686e1082" });
assert.strictEqual(result.success, true);
});

it('retrieves list of container type by id', async () => {
Expand All @@ -105,7 +128,7 @@ describe(commands.CONTAINER_LIST, () => {
throw 'Invalid request';
});

await command.action(logger, { options: { containerTypeId: "e2756c4d-fa33-4452-9c36-2325686e1082", verbose: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ containerTypeId: "e2756c4d-fa33-4452-9c36-2325686e1082", verbose: true }) });
assert(loggerLogSpy.calledWith(containersList));
});

Expand All @@ -118,7 +141,7 @@ describe(commands.CONTAINER_LIST, () => {
throw 'Invalid request';
});

await command.action(logger, { options: { containerTypeName: "standard container", verbose: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ containerTypeName: "standard container", verbose: true }) });
assert(loggerLogSpy.calledWith(containersList));
});

Expand All @@ -128,10 +151,10 @@ describe(commands.CONTAINER_LIST, () => {
sinon.stub(spe, 'getContainerTypeIdByName').rejects(new Error(error));

await assert.rejects(command.action(logger, {
options: {
options: commandOptionsSchema.parse({
containerTypeName: "nonexisting container",
verbose: true
}
})
}), new CommandError('An error has occurred'));
});
});
78 changes: 23 additions & 55 deletions src/m365/spe/commands/container/container-list.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,50 @@
import { z } from 'zod';
import { Logger } from '../../../../cli/Logger.js';
import GlobalOptions from '../../../../GlobalOptions.js';
import { globalOptionsZod } from '../../../../Command.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 { SpeContainer, spe } from '../../../../utils/spe.js';

export const options = z.strictObject({
...globalOptionsZod.shape,
containerTypeId: z.uuid().optional(),
containerTypeName: z.string().optional()
});

declare type Options = z.infer<typeof options>;

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();
return 'Lists containers of a specific Container Type';
}

#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]'
}
);
public get schema(): z.ZodTypeAny {
return options;
}

#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`;
public getRefinedSchema(schema: typeof options): z.ZodObject<any> | undefined {
return schema
.refine(opts => [opts.containerTypeId, opts.containerTypeName].filter(o => o !== undefined).length === 1, {
message: 'Specify one of the following options: containerTypeId, containerTypeName.',
params: {
customCode: 'optionSet',
options: ['containerTypeId', 'containerTypeName']
}

return true;
}
);
}

#initOptionSets(): void {
this.optionSets.push({ options: ['containerTypeId', 'containerTypeName'] });
});
}

#initTypes(): void {
this.types.string.push('containerTypeId', 'containerTypeName');
public defaultProperties(): string[] | undefined {
return ['id', 'displayName', 'containerTypeId', 'createdDateTime'];
}

public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
Expand Down
Loading
Loading