review update

This commit is contained in:
Michael Kret 2025-01-06 10:12:42 +02:00
parent d187b11044
commit 948aced4a1
6 changed files with 23 additions and 162 deletions

View file

@ -86,8 +86,9 @@ export const calendarFields: INodeProperties[] = [
resource: ['calendar'],
},
},
default: '',
description: 'Start of the interval, if not specified will default to now',
default: '={{ $now.toISO() }}',
description:
'Start of the interval, use <a href="https://docs.n8n.io/code/cookbook/luxon/" target="_blank">expression</a> to set a date, or switch to fixed mode to choose date from widget',
},
{
displayName: 'End Time',
@ -100,8 +101,9 @@ export const calendarFields: INodeProperties[] = [
resource: ['calendar'],
},
},
default: '',
description: 'End of the interval, if not specified will default to Start Time plus 1 hour',
default: "={{ $now.plus(1, 'hour').toISO() }}",
description:
'End of the interval, use <a href="https://docs.n8n.io/code/cookbook/luxon/" target="_blank">expression</a> to set a date, or switch to fixed mode to choose date from widget',
},
{
displayName: 'Options',

View file

@ -114,8 +114,9 @@ export const eventFields: INodeProperties[] = [
resource: ['event'],
},
},
default: '',
description: 'Start time of the event, if not specified will default to now',
default: '={{ $now.toISO() }}',
description:
'Start time of the event, use <a href="https://docs.n8n.io/code/cookbook/luxon/" target="_blank">expression</a> to set a date, or switch to fixed mode to choose date from widget',
},
{
displayName: 'End',
@ -128,8 +129,9 @@ export const eventFields: INodeProperties[] = [
resource: ['event'],
},
},
default: '',
description: 'End time of the event, if not specified will default to start time plus 1 hour',
default: "={{ $now.plus(1, 'hour').toISO() }}",
description:
'End time of the event, use <a href="https://docs.n8n.io/code/cookbook/luxon/" target="_blank">expression</a> to set a date, or switch to fixed mode to choose date from widget',
},
{
displayName: 'Use Default Reminders',

View file

@ -1,3 +1,4 @@
import { DateTime } from 'luxon';
import moment from 'moment-timezone';
import type {
IDataObject,
@ -193,3 +194,8 @@ export function addTimezoneToDate(date: string, timezone: string) {
if (hasTimezone(date)) return date;
return moment.tz(date, timezone).utc().format();
}
export function dateTimeToIso<T>(date: T): string {
if (date instanceof DateTime) return date.toISO();
return date as string;
}

View file

@ -18,6 +18,7 @@ import type { IEvent } from './EventInterface';
import {
addNextOccurrence,
addTimezoneToDate,
dateTimeToIso,
encodeURIComponentOnce,
getCalendars,
getTimezones,
@ -148,10 +149,8 @@ export class GoogleCalendar implements INodeType {
const calendarId = decodeURIComponent(
this.getNodeParameter('calendar', i, '', { extractValue: true }) as string,
);
const timeMin = (this.getNodeParameter('timeMin', i) as string) || moment().format();
const timeMax =
(this.getNodeParameter('timeMax', i) as string) ||
moment(timeMin).add(1, 'hour').format();
const timeMin = dateTimeToIso(this.getNodeParameter('timeMin', i));
const timeMax = dateTimeToIso(this.getNodeParameter('timeMax', i));
const options = this.getNodeParameter('options', i);
const outputFormat = options.outputFormat || 'availability';
const tz = this.getNodeParameter('options.timezone', i, '', {
@ -202,9 +201,8 @@ export class GoogleCalendar implements INodeType {
const calendarId = encodeURIComponentOnce(
this.getNodeParameter('calendar', i, '', { extractValue: true }) as string,
);
const start = (this.getNodeParameter('start', i) as string) || moment().format();
const end =
(this.getNodeParameter('end', i) as string) || moment(start).add(1, 'hour').format();
const start = dateTimeToIso(this.getNodeParameter('start', i));
const end = dateTimeToIso(this.getNodeParameter('end', i));
const useDefaultReminders = this.getNodeParameter('useDefaultReminders', i) as boolean;
const additionalFields = this.getNodeParameter('additionalFields', i);

View file

@ -1,74 +0,0 @@
import type { MockProxy } from 'jest-mock-extended';
import { mock } from 'jest-mock-extended';
import type { INode, IExecuteFunctions } from 'n8n-workflow';
import * as genericFunctions from '../../GenericFunctions';
import { GoogleCalendar } from '../../GoogleCalendar.node';
let googleApiRequestArgs: any[] = [];
const CALENDAR_ID = 'myCalendar';
jest.mock('../../GenericFunctions', () => ({
getTimezones: jest.fn(),
googleApiRequest: jest.fn(async (...args: any[]) => {
googleApiRequestArgs = args;
return { calendars: { [CALENDAR_ID]: { busy: [] } } };
}),
googleApiRequestAllItems: jest.fn(),
addTimezoneToDate: jest.fn(),
addNextOccurrence: jest.fn(),
encodeURIComponentOnce: jest.fn(),
}));
describe('RespondToWebhook Node', () => {
let googleCalendar: GoogleCalendar;
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
beforeEach(() => {
googleCalendar = new GoogleCalendar();
mockExecuteFunctions = mock<IExecuteFunctions>({
getInputData: jest.fn(),
getNode: jest.fn(),
getNodeParameter: jest.fn(),
getTimezone: jest.fn(),
helpers: {
constructExecutionMetaData: jest.fn().mockReturnValue([]),
},
});
});
afterEach(() => {
googleApiRequestArgs = [];
jest.clearAllMocks();
});
describe('Google Calendar > Calendar > Availability', () => {
it('should not have invalid timeMin and timeMax date', async () => {
//pre loop setup
mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }]);
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('calendar');
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('availability');
mockExecuteFunctions.getTimezone.mockReturnValueOnce('Europe/Berlin');
mockExecuteFunctions.getNode.mockReturnValue(mock<INode>({ typeVersion: 1.1 }));
//operation
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(CALENDAR_ID); // calendar
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(''); // timeMin, not set
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(''); // timeMax, not set
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({}); // options
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(''); // options.timezone, default to timezone
await googleCalendar.execute.call(mockExecuteFunctions);
expect(genericFunctions.googleApiRequest).toHaveBeenCalledTimes(1);
const body = googleApiRequestArgs[2] as {
timeMin: string;
timeMax: string;
};
expect(body).toBeDefined();
expect(body.timeMin).not.toEqual('Invalid Date');
expect(body.timeMax).not.toEqual('Invalid Date');
});
});
});

View file

@ -1,73 +0,0 @@
import type { MockProxy } from 'jest-mock-extended';
import { mock } from 'jest-mock-extended';
import type { INode, IExecuteFunctions } from 'n8n-workflow';
import * as genericFunctions from '../../GenericFunctions';
import { GoogleCalendar } from '../../GoogleCalendar.node';
let googleApiRequestArgs: any[] = [];
jest.mock('../../GenericFunctions', () => ({
getTimezones: jest.fn(),
googleApiRequest: jest.fn(async (...args: any[]) => {
googleApiRequestArgs = args;
return {};
}),
googleApiRequestAllItems: jest.fn(),
addTimezoneToDate: jest.fn(),
addNextOccurrence: jest.fn(),
encodeURIComponentOnce: jest.fn(),
}));
describe('RespondToWebhook Node', () => {
let googleCalendar: GoogleCalendar;
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
beforeEach(() => {
googleCalendar = new GoogleCalendar();
mockExecuteFunctions = mock<IExecuteFunctions>({
getInputData: jest.fn(),
getNode: jest.fn(),
getNodeParameter: jest.fn(),
getTimezone: jest.fn(),
helpers: {
constructExecutionMetaData: jest.fn().mockReturnValue([]),
},
});
});
afterEach(() => {
googleApiRequestArgs = [];
jest.clearAllMocks();
});
describe('Google Calendar > Event > Create', () => {
it('should not have invalid start and end date', async () => {
//pre loop setup
mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }]);
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('event');
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('create');
mockExecuteFunctions.getTimezone.mockReturnValueOnce('Europe/Berlin');
mockExecuteFunctions.getNode.mockReturnValue(mock<INode>({ typeVersion: 1.1 }));
//operation
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('myCalendar');
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(''); // start, not set
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(''); // end, not set
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(true); // useDefaultReminders
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({}); // additionalFields
await googleCalendar.execute.call(mockExecuteFunctions);
expect(genericFunctions.googleApiRequest).toHaveBeenCalledTimes(1);
const body = googleApiRequestArgs[2] as {
start: { dateTime: string };
end: { dateTime: string };
};
expect(body).toBeDefined();
expect(body.start.dateTime).not.toEqual('Invalid Date');
expect(body.end.dateTime).not.toEqual('Invalid Date');
});
});
});