Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions .impeccable/hook.cache.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":1,"sessions":{"2ba05265-4718-4ce8-9800-abe38414132c":{"updatedAt":1782371098181,"files":{"/Users/waldek/.copilot/copilot-worktrees/cli-microsoft365/waldekmastykarz-effective-goggles/src/m365/purview/commands/retentionevent/retentionevent-add.ts":{"editCount":1,"findings":[]},"/Users/waldek/.copilot/copilot-worktrees/cli-microsoft365/waldekmastykarz-effective-goggles/src/m365/purview/commands/retentionevent/retentionevent-get.ts":{"editCount":1,"findings":[]},"/Users/waldek/.copilot/copilot-worktrees/cli-microsoft365/waldekmastykarz-effective-goggles/src/m365/purview/commands/retentionevent/retentionevent-remove.ts":{"editCount":2,"findings":[]},"/Users/waldek/.copilot/copilot-worktrees/cli-microsoft365/waldekmastykarz-effective-goggles/src/m365/purview/commands/retentioneventtype/retentioneventtype-add.ts":{"editCount":1,"findings":[]},"/Users/waldek/.copilot/copilot-worktrees/cli-microsoft365/waldekmastykarz-effective-goggles/src/m365/purview/commands/retentioneventtype/retentioneventtype-get.ts":{"editCount":1,"findings":[]},"/Users/waldek/.copilot/copilot-worktrees/cli-microsoft365/waldekmastykarz-effective-goggles/src/m365/purview/commands/retentioneventtype/retentioneventtype-remove.ts":{"editCount":1,"findings":[]},"/Users/waldek/.copilot/copilot-worktrees/cli-microsoft365/waldekmastykarz-effective-goggles/src/m365/purview/commands/retentioneventtype/retentioneventtype-set.ts":{"editCount":1,"findings":[]},"/Users/waldek/.copilot/copilot-worktrees/cli-microsoft365/waldekmastykarz-effective-goggles/src/m365/purview/commands/retentionevent/retentionevent-add.spec.ts":{"editCount":4,"findings":[]},"/Users/waldek/.copilot/copilot-worktrees/cli-microsoft365/waldekmastykarz-effective-goggles/src/m365/purview/commands/retentionevent/retentionevent-get.spec.ts":{"editCount":3,"findings":[]},"/Users/waldek/.copilot/copilot-worktrees/cli-microsoft365/waldekmastykarz-effective-goggles/src/m365/purview/commands/retentionevent/retentionevent-remove.spec.ts":{"editCount":3,"findings":[]},"/Users/waldek/.copilot/copilot-worktrees/cli-microsoft365/waldekmastykarz-effective-goggles/src/m365/purview/commands/retentioneventtype/retentioneventtype-add.spec.ts":{"editCount":3,"findings":[]},"/Users/waldek/.copilot/copilot-worktrees/cli-microsoft365/waldekmastykarz-effective-goggles/src/m365/purview/commands/retentioneventtype/retentioneventtype-get.spec.ts":{"editCount":3,"findings":[]},"/Users/waldek/.copilot/copilot-worktrees/cli-microsoft365/waldekmastykarz-effective-goggles/src/m365/purview/commands/retentioneventtype/retentioneventtype-remove.spec.ts":{"editCount":3,"findings":[]},"/Users/waldek/.copilot/copilot-worktrees/cli-microsoft365/waldekmastykarz-effective-goggles/src/m365/purview/commands/retentioneventtype/retentioneventtype-set.spec.ts":{"editCount":3,"findings":[]}}}}}
Comment thread
waldekmastykarz marked this conversation as resolved.
Outdated
7 changes: 0 additions & 7 deletions npm-shrinkwrap.json
Comment thread
MartinM85 marked this conversation as resolved.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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));
});
});
108 changes: 33 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,25 @@ 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, {
message: `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 || opts.keywords, {
message: 'Specify assetIds and/or keywords, but at least one.',
params: {
customCode: 'required'
}

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;
Comment thread
waldekmastykarz marked this conversation as resolved.
Outdated
}

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));
});
});
Loading
Loading