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
3 changes: 2 additions & 1 deletion .gitignore
Comment thread
waldekmastykarz marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,5 @@ docs/.env.production.local

docs/npm-debug.log*
docs/yarn-debug.log*
docs/yarn-error.log*
docs/yarn-error.log*
.impeccable/
41 changes: 24 additions & 17 deletions src/m365/purview/commands/retentionevent/retentionevent-add.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,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 './retentionevent-add.js';
import command, { options } from './retentionevent-add.js';

describe(commands.RETENTIONEVENT_ADD, () => {
const validDisplayName = "Event display name";
Expand Down Expand Up @@ -86,6 +86,7 @@ describe(commands.RETENTIONEVENT_ADD, () => {
let logger: Logger;
let loggerLogSpy: sinon.SinonSpy;
let commandInfo: CommandInfo;
let commandOptionsSchema: typeof options;

before(() => {
sinon.stub(auth, 'restoreAuth').resolves();
Expand All @@ -98,6 +99,7 @@ describe(commands.RETENTIONEVENT_ADD, () => {
accessToken: 'abc'
};
commandInfo = cli.getCommandInfo(command);
commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options;
});

beforeEach(() => {
Expand Down Expand Up @@ -138,19 +140,24 @@ describe(commands.RETENTIONEVENT_ADD, () => {
assert.notStrictEqual(command.description, null);
});

it('fails validation if date is not a valid ISO date string', async () => {
const actual = await command.validate({ options: { displayName: validDisplayName, eventTypeId: validTypeId, description: validDescription, triggerDateTime: "Not a valid date", assetIds: validAssetIds } }, commandInfo);
assert.notStrictEqual(actual, true);
it('fails validation if date is not a valid ISO date string', () => {
const actual = commandOptionsSchema.safeParse({ displayName: validDisplayName, eventTypeId: validTypeId, description: validDescription, triggerDateTime: "Not a valid date", assetIds: validAssetIds });
assert.strictEqual(actual.success, false);
});

it('fails validation if assetId or keywords is not provided', async () => {
const actual = await command.validate({ options: { displayName: validDisplayName, eventTypeId: validTypeId, description: validDescription, triggerDateTime: validDate } }, commandInfo);
assert.notStrictEqual(actual, true);
it('fails validation if assetId or keywords is not provided', () => {
const actual = commandOptionsSchema.safeParse({ displayName: validDisplayName, eventTypeId: validTypeId, description: validDescription, triggerDateTime: validDate });
assert.strictEqual(actual.success, false);
});

it('passes validation if a correct ISO date string is entered', async () => {
const actual = await command.validate({ options: { displayName: validDisplayName, eventTypeId: validTypeId, description: validDescription, triggerDateTime: validDate, assetIds: validAssetIds } }, commandInfo);
assert.strictEqual(actual, true);
it('passes validation if a correct ISO date string is entered', () => {
const actual = commandOptionsSchema.safeParse({ displayName: validDisplayName, eventTypeId: validTypeId, description: validDescription, triggerDateTime: validDate, assetIds: validAssetIds });
assert.strictEqual(actual.success, true);
});

it('fails validation with unknown options', () => {
const actual = commandOptionsSchema.safeParse({ displayName: validDisplayName, eventTypeId: validTypeId, assetIds: validAssetIds, unknownOption: 'value' });
assert.strictEqual(actual.success, false);
});

it('adds retention event with minimal required parameters and assetIds', async () => {
Expand All @@ -162,7 +169,7 @@ describe(commands.RETENTIONEVENT_ADD, () => {
throw 'Invalid request';
});

await command.action(logger, { options: { displayName: validDisplayName, eventTypeId: validTypeId, assetIds: validAssetIds } });
await command.action(logger, { options: commandOptionsSchema.parse({ displayName: validDisplayName, eventTypeId: validTypeId, assetIds: validAssetIds }) });
assert(loggerLogSpy.calledWith(EventResponse));
});

Expand All @@ -175,7 +182,7 @@ describe(commands.RETENTIONEVENT_ADD, () => {
throw 'Invalid request';
});

await command.action(logger, { options: { displayName: validDisplayName, eventTypeId: validTypeId, keywords: validKeyswords } });
await command.action(logger, { options: commandOptionsSchema.parse({ displayName: validDisplayName, eventTypeId: validTypeId, keywords: validKeyswords }) });
assert(loggerLogSpy.calledWith(EventResponse));
});

Expand All @@ -188,7 +195,7 @@ describe(commands.RETENTIONEVENT_ADD, () => {
throw 'Invalid request';
});

await command.action(logger, { options: { verbose: true, displayName: validDisplayName, eventTypeId: validTypeId, description: validDescription, triggerDateTime: validDate, assetIds: validAssetIds, keywords: validKeyswords } });
await command.action(logger, { options: commandOptionsSchema.parse({ verbose: true, displayName: validDisplayName, eventTypeId: validTypeId, description: validDescription, triggerDateTime: validDate, assetIds: validAssetIds, keywords: validKeyswords }) });
assert(loggerLogSpy.calledWith(EventResponse));
});

Expand All @@ -209,7 +216,7 @@ describe(commands.RETENTIONEVENT_ADD, () => {
throw 'Invalid request';
});

await command.action(logger, { options: { verbose: true, displayName: validDisplayName, eventTypeName: validTypeName, assetIds: validAssetIds } });
await command.action(logger, { options: commandOptionsSchema.parse({ verbose: true, displayName: validDisplayName, eventTypeName: validTypeName, assetIds: validAssetIds }) });
assert(loggerLogSpy.calledWith(EventResponse));
});

Expand All @@ -223,7 +230,7 @@ describe(commands.RETENTIONEVENT_ADD, () => {
});

await assert.rejects(command.action(logger, {
options: { displayName: validDisplayName, eventTypeName: validTypeName, assetIds: validAssetIds }
options: commandOptionsSchema.parse({ displayName: validDisplayName, eventTypeName: validTypeName, assetIds: validAssetIds })
}), new CommandError(`The specified event type '${validTypeName}' does not exist.`));
});

Expand All @@ -236,9 +243,9 @@ describe(commands.RETENTIONEVENT_ADD, () => {
sinon.stub(request, 'post').callsFake(async () => { throw error; });

await assert.rejects(command.action(logger, {
options: {
options: commandOptionsSchema.parse({
displayName: validDisplayName, eventTypeId: validTypeId, assetIds: validAssetIds
}
})
}), new CommandError(error.error.message));
});
});
109 changes: 34 additions & 75 deletions src/m365/purview/commands/retentionevent/retentionevent-add.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import { z } from 'zod';
import { Logger } from '../../../../cli/Logger.js';
import { globalOptionsZod } from '../../../../Command.js';
import GraphCommand from '../../../base/GraphCommand.js';
import GlobalOptions from '../../../../GlobalOptions.js';
import commands from '../../commands.js';
import request, { CliRequestOptions } from '../../../../request.js';
import { validation } from '../../../../utils/validation.js';
import { odata } from '../../../../utils/odata.js';

export const options = z.strictObject({
...globalOptionsZod.shape,
displayName: z.string().alias('n'),
eventTypeId: z.string().optional().alias('i'),
eventTypeName: z.string().optional().alias('e'),
description: z.string().optional().alias('d'),
triggerDateTime: z.string().optional()
.refine(val => val === undefined || validation.isValidISODateTime(val), {
message: 'The triggerDateTime is not a valid ISO date string'
}),
assetIds: z.string().optional().alias('a'),
keywords: z.string().optional().alias('k')
});

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

interface CommandArgs {
options: Options;
}

interface Options extends GlobalOptions {
displayName: string;
eventTypeId?: string;
eventTypeName?: string;
description?: string;
triggerDateTime?: string;
assetIds?: string;
keywords?: string;
}

class PurviewRetentionEventAddCommand extends GraphCommand {
public get name(): string {
return commands.RETENTIONEVENT_ADD;
Expand All @@ -29,74 +36,26 @@ class PurviewRetentionEventAddCommand extends GraphCommand {
return 'Create a retention event';
}

constructor() {
super();

this.#initTelemetry();
this.#initOptions();
this.#initValidators();
this.#initOptionSets();
}

#initOptions(): void {
this.options.unshift(
{
option: '-n, --displayName <displayName>'
},
{
option: '-i, --eventTypeId [eventTypeId]'
},
{
option: '-e, --eventTypeName [eventTypeName]'
},
{
option: '-d, --description [description]'
},
{
option: '--triggerDateTime [triggerDateTime]'
},
{
option: '-a, --assetIds [assetIds]'
},
{
option: '-k, --keywords [keywords]'
}
);
public get schema(): z.ZodType | undefined {
return options;
}

#initValidators(): void {
this.validators.push(
async (args: CommandArgs) => {
if (args.options.triggerDateTime && !validation.isValidISODateTime(args.options.triggerDateTime)) {
return 'The triggerDateTime is not a valid ISO date string';
public getRefinedSchema(schema: typeof options): z.ZodObject<any> | undefined {
return schema
.refine(opts => [opts.eventTypeId, opts.eventTypeName].filter(x => x !== undefined).length === 1, {
error: `Specify either 'eventTypeId' or 'eventTypeName', but not both.`,
params: {
customCode: 'optionSet',
options: ['eventTypeId', 'eventTypeName']
}

if (!args.options.assetIds && !args.options.keywords) {
return 'Specify assetIds and/or keywords, but at least one.';
})
.refine(opts => opts.assetIds !== undefined || opts.keywords !== undefined, {
error: 'Specify assetIds and/or keywords, but at least one.',
params: {
customCode: 'optionSet',
options: ['assetIds', 'keywords']
}

return true;
}
);
}

#initTelemetry(): void {
this.telemetry.push((args: CommandArgs) => {
Object.assign(this.telemetryProperties, {
description: typeof args.options.description !== 'undefined',
triggerDateTime: typeof args.options.triggerDateTime !== 'undefined',
eventTypeId: typeof args.options.eventTypeId !== 'undefined',
eventTypeName: typeof args.options.eventTypeName !== 'undefined',
assetIds: typeof args.options.assetIds !== 'undefined',
keywords: typeof args.options.keywords !== 'undefined'
});
});
}

#initOptionSets(): void {
this.optionSets.push(
{ options: ['eventTypeId', 'eventTypeName'] }
);
}) as any;
}

public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
Expand Down
27 changes: 17 additions & 10 deletions src/m365/purview/commands/retentionevent/retentionevent-get.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,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 './retentionevent-get.js';
import command, { options } from './retentionevent-get.js';

describe(commands.RETENTIONEVENT_GET, () => {
const retentionEventId = 'c37d695e-d581-4ae9-82a0-9364eba4291e';
Expand Down Expand Up @@ -63,6 +63,7 @@ describe(commands.RETENTIONEVENT_GET, () => {
let logger: Logger;
let loggerLogSpy: sinon.SinonSpy;
let commandInfo: CommandInfo;
let commandOptionsSchema: typeof options;

before(() => {
sinon.stub(auth, 'restoreAuth').resolves();
Expand All @@ -75,6 +76,7 @@ describe(commands.RETENTIONEVENT_GET, () => {
accessToken: 'abc'
};
commandInfo = cli.getCommandInfo(command);
commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options;
});

beforeEach(() => {
Expand Down Expand Up @@ -114,14 +116,19 @@ describe(commands.RETENTIONEVENT_GET, () => {
assert.notStrictEqual(command.description, null);
});

it('fails validation if id is not a valid GUID', async () => {
const actual = await command.validate({ options: { id: 'invalid' } }, commandInfo);
assert.notStrictEqual(actual, true);
it('fails validation if id is not a valid GUID', () => {
const actual = commandOptionsSchema.safeParse({ id: 'invalid' });
assert.strictEqual(actual.success, false);
});

it('passes validation if a correct id is entered', async () => {
const actual = await command.validate({ options: { id: retentionEventId } }, commandInfo);
assert.strictEqual(actual, true);
it('passes validation if a correct id is entered', () => {
const actual = commandOptionsSchema.safeParse({ id: retentionEventId });
assert.strictEqual(actual.success, true);
});

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

it('retrieves retention event by specified id', async () => {
Expand All @@ -133,7 +140,7 @@ describe(commands.RETENTIONEVENT_GET, () => {
throw 'Invalid request';
});

await command.action(logger, { options: { id: retentionEventId, verbose: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ id: retentionEventId, verbose: true }) });
assert(loggerLogSpy.calledWith(retentionEventGetResponse));
});

Expand All @@ -148,9 +155,9 @@ describe(commands.RETENTIONEVENT_GET, () => {
});

await assert.rejects(command.action(logger, {
options: {
options: commandOptionsSchema.parse({
id: retentionEventId
}
})
}), new CommandError(errorMessage));
});
});
43 changes: 13 additions & 30 deletions src/m365/purview/commands/retentionevent/retentionevent-get.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { z } from 'zod';
import { Logger } from '../../../../cli/Logger.js';
import GlobalOptions from '../../../../GlobalOptions.js';
import { globalOptionsZod } from '../../../../Command.js';
import request, { CliRequestOptions } from '../../../../request.js';
import { validation } from '../../../../utils/validation.js';
import GraphCommand from '../../../base/GraphCommand.js';
import commands from '../../commands.js';

export const options = z.strictObject({
...globalOptionsZod.shape,
id: z.string().refine(val => validation.isValidGuid(val), {
message: 'The value must be a valid GUID.'
}).alias('i')
});

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

interface CommandArgs {
options: Options;
}

interface Options extends GlobalOptions {
id: string;
}

class PurviewRetentionEventGetCommand extends GraphCommand {
public get name(): string {
return commands.RETENTIONEVENT_GET;
Expand All @@ -22,31 +28,8 @@ class PurviewRetentionEventGetCommand extends GraphCommand {
return 'Retrieve the specified retention event';
}

constructor() {
super();

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

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

#initValidators(): void {
this.validators.push(
async (args: CommandArgs) => {
if (!validation.isValidGuid(args.options.id)) {
return `'${args.options.id}' is not a valid GUID.`;
}

return true;
}
);
public get schema(): z.ZodType | undefined {
return options;
}

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