chore(tests): remove deprecated test files for schedule and webhook triggers

This commit is contained in:
zhsama 2025-10-28 22:42:29 +08:00
parent 71b1af69c5
commit 720480d05e
9 changed files with 0 additions and 1882 deletions

View File

@ -1,247 +0,0 @@
import nodeDefault from '../default'
import type { ScheduleTriggerNodeType } from '../types'
// Mock translation function
const mockT = (key: string, params?: any) => {
if (key.includes('fieldRequired')) return `${params?.field} is required`
if (key.includes('invalidCronExpression')) return 'Invalid cron expression'
if (key.includes('invalidTimezone')) return 'Invalid timezone'
if (key.includes('noValidExecutionTime')) return 'No valid execution time'
if (key.includes('executionTimeCalculationError')) return 'Execution time calculation error'
return key
}
describe('Schedule Trigger Default - Backward Compatibility', () => {
describe('Enhanced Cron Expression Support', () => {
it('should accept enhanced month abbreviations', () => {
const payload: ScheduleTriggerNodeType = {
mode: 'cron',
timezone: 'UTC',
cron_expression: '0 9 1 JAN *', // January 1st at 9 AM
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
it('should accept enhanced day abbreviations', () => {
const payload: ScheduleTriggerNodeType = {
mode: 'cron',
timezone: 'UTC',
cron_expression: '0 15 * * MON', // Every Monday at 3 PM
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
it('should accept predefined expressions', () => {
const predefinedExpressions = ['@daily', '@weekly', '@monthly', '@yearly', '@hourly']
predefinedExpressions.forEach((expr) => {
const payload: ScheduleTriggerNodeType = {
mode: 'cron',
timezone: 'UTC',
cron_expression: expr,
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
})
it('should accept special characters', () => {
const specialExpressions = [
'0 9 ? * 1', // ? wildcard
'0 12 * * 7', // Sunday as 7
'0 15 L * *', // Last day of month
]
specialExpressions.forEach((expr) => {
const payload: ScheduleTriggerNodeType = {
mode: 'cron',
timezone: 'UTC',
cron_expression: expr,
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
})
it('should maintain backward compatibility with legacy expressions', () => {
const legacyExpressions = [
'15 10 1 * *', // Monthly 1st at 10:15
'0 0 * * 0', // Weekly Sunday midnight
'*/5 * * * *', // Every 5 minutes
'0 9-17 * * 1-5', // Business hours weekdays
'30 14 * * 1', // Monday 14:30
'0 0 1,15 * *', // 1st and 15th midnight
]
legacyExpressions.forEach((expr) => {
const payload: ScheduleTriggerNodeType = {
mode: 'cron',
timezone: 'UTC',
cron_expression: expr,
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
})
})
describe('Error Detection and Validation', () => {
it('should detect invalid enhanced syntax', () => {
const invalidExpressions = [
'0 12 * JANUARY *', // Full month name not supported
'0 12 * * MONDAY', // Full day name not supported
'0 12 32 JAN *', // Invalid day with month abbreviation
'@invalid', // Invalid predefined expression
'0 12 1 INVALID *', // Invalid month abbreviation
'0 12 * * INVALID', // Invalid day abbreviation
]
invalidExpressions.forEach((expr) => {
const payload: ScheduleTriggerNodeType = {
mode: 'cron',
timezone: 'UTC',
cron_expression: expr,
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(false)
expect(result.errorMessage).toContain('Invalid cron expression')
})
})
it('should handle execution time calculation errors gracefully', () => {
// Test with an expression that contains invalid date (Feb 30th)
const payload: ScheduleTriggerNodeType = {
mode: 'cron',
timezone: 'UTC',
cron_expression: '0 0 30 2 *', // Feb 30th (invalid date)
}
const result = nodeDefault.checkValid(payload, mockT)
// Should be an invalid expression error since cron-parser detects Feb 30th as invalid
expect(result.isValid).toBe(false)
expect(result.errorMessage).toBe('Invalid cron expression')
})
})
describe('Timezone Integration', () => {
it('should validate with various timezones', () => {
const timezones = ['UTC', 'America/New_York', 'Asia/Tokyo', 'Europe/London']
timezones.forEach((timezone) => {
const payload: ScheduleTriggerNodeType = {
mode: 'cron',
timezone,
cron_expression: '0 12 * * *', // Daily noon
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
})
it('should reject invalid timezones', () => {
const payload: ScheduleTriggerNodeType = {
mode: 'cron',
timezone: 'Invalid/Timezone',
cron_expression: '0 12 * * *',
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(false)
expect(result.errorMessage).toContain('Invalid timezone')
})
})
describe('Visual Mode Compatibility', () => {
it('should maintain visual mode validation', () => {
const payload: ScheduleTriggerNodeType = {
mode: 'visual',
timezone: 'UTC',
frequency: 'daily',
visual_config: {
time: '9:00 AM',
},
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
it('should validate weekly configuration', () => {
const payload: ScheduleTriggerNodeType = {
mode: 'visual',
timezone: 'UTC',
frequency: 'weekly',
visual_config: {
time: '2:30 PM',
weekdays: ['mon', 'wed', 'fri'],
},
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
it('should validate monthly configuration', () => {
const payload: ScheduleTriggerNodeType = {
mode: 'visual',
timezone: 'UTC',
frequency: 'monthly',
visual_config: {
time: '11:30 AM',
monthly_days: [1, 15, 'last'],
},
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
})
describe('Edge Cases and Robustness', () => {
it('should handle empty/whitespace cron expressions', () => {
const emptyExpressions = ['', ' ', '\t\n ']
emptyExpressions.forEach((expr) => {
const payload: ScheduleTriggerNodeType = {
mode: 'cron',
timezone: 'UTC',
cron_expression: expr,
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(false)
expect(result.errorMessage).toMatch(/(Invalid cron expression|required)/)
})
})
it('should validate whitespace-padded expressions', () => {
const payload: ScheduleTriggerNodeType = {
mode: 'cron',
timezone: 'UTC',
cron_expression: ' 0 12 * * * ', // Padded with whitespace
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
})
})

View File

@ -1,128 +0,0 @@
/**
* Schedule Trigger Node Default Tests
*
* Simple test for the Schedule Trigger node default configuration and validation.
* Tests core checkValid functionality following project patterns.
*/
import nodeDefault from '../default'
import type { ScheduleTriggerNodeType } from '../types'
// Mock external dependencies
jest.mock('../utils/cron-parser', () => ({
isValidCronExpression: jest.fn((expr: string) => {
return expr === '0 9 * * 1' // Only this specific expression is valid
}),
}))
jest.mock('../utils/execution-time-calculator', () => ({
getNextExecutionTimes: jest.fn(() => [new Date(Date.now() + 86400000)]),
}))
// Simple mock translation function
const mockT = (key: string, params?: any) => {
if (key.includes('fieldRequired')) return `${params?.field} is required`
if (key.includes('invalidCronExpression')) return 'Invalid cron expression'
if (key.includes('invalidTimezone')) return 'Invalid timezone'
return key
}
describe('Schedule Trigger Node Default', () => {
describe('Basic Configuration', () => {
it('should have correct default value', () => {
expect(nodeDefault.defaultValue.mode).toBe('visual')
expect(nodeDefault.defaultValue.frequency).toBe('daily')
})
it('should have correct metadata for trigger node', () => {
expect(nodeDefault.metaData).toBeDefined()
expect(nodeDefault.metaData.type).toBe('trigger-schedule')
expect(nodeDefault.metaData.sort).toBe(2)
expect(nodeDefault.metaData.isStart).toBe(true)
})
})
describe('Validation - checkValid', () => {
it('should validate successfully with valid visual config', () => {
const payload: ScheduleTriggerNodeType = {
mode: 'visual',
timezone: 'UTC',
frequency: 'daily',
visual_config: {
time: '9:00 AM',
},
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
it('should require mode field', () => {
const payload = {
timezone: 'UTC',
} as ScheduleTriggerNodeType
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(false)
expect(result.errorMessage).toContain('required')
})
it('should require timezone field', () => {
const payload = {
mode: 'visual',
} as ScheduleTriggerNodeType
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(false)
expect(result.errorMessage).toContain('required')
})
it('should validate cron mode with valid expression', () => {
const payload: ScheduleTriggerNodeType = {
mode: 'cron',
timezone: 'UTC',
cron_expression: '0 9 * * 1',
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(true)
})
it('should reject invalid cron expression', () => {
const payload: ScheduleTriggerNodeType = {
mode: 'cron',
timezone: 'UTC',
cron_expression: 'invalid',
}
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(false)
expect(result.errorMessage).toContain('Invalid cron expression')
})
it('should reject invalid timezone', () => {
const payload = {
mode: 'visual',
timezone: 'Invalid/Timezone',
frequency: 'daily',
visual_config: { time: '9:00 AM' },
} as ScheduleTriggerNodeType
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(false)
expect(result.errorMessage).toContain('Invalid timezone')
})
it('should require frequency in visual mode', () => {
const payload = {
mode: 'visual',
timezone: 'UTC',
} as ScheduleTriggerNodeType
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(false)
expect(result.errorMessage).toContain('required')
})
})
})

View File

@ -1,265 +0,0 @@
import React from 'react'
import { fireEvent, render, screen } from '@testing-library/react'
import { useTranslation } from 'react-i18next'
import MonthlyDaysSelector from '../components/monthly-days-selector'
jest.mock('react-i18next')
const mockUseTranslation = useTranslation as jest.MockedFunction<typeof useTranslation>
const mockTranslation = {
t: (key: string) => {
const translations: Record<string, string> = {
'workflow.nodes.triggerSchedule.days': 'Days',
'workflow.nodes.triggerSchedule.lastDay': 'Last',
'workflow.nodes.triggerSchedule.lastDayTooltip': 'Last day of month',
}
return translations[key] || key
},
}
beforeEach(() => {
mockUseTranslation.mockReturnValue(mockTranslation as any)
})
describe('MonthlyDaysSelector', () => {
describe('Single selection', () => {
test('renders with single selected day', () => {
const onChange = jest.fn()
render(
<MonthlyDaysSelector
selectedDays={[15]}
onChange={onChange}
/>,
)
const button15 = screen.getByRole('button', { name: '15' })
expect(button15).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
})
test('calls onChange when day is clicked', () => {
const onChange = jest.fn()
render(
<MonthlyDaysSelector
selectedDays={[15]}
onChange={onChange}
/>,
)
fireEvent.click(screen.getByRole('button', { name: '20' }))
expect(onChange).toHaveBeenCalledWith([15, 20])
})
test('handles last day selection', () => {
const onChange = jest.fn()
render(
<MonthlyDaysSelector
selectedDays={['last']}
onChange={onChange}
/>,
)
const lastButton = screen.getByRole('button', { name: 'Last' })
expect(lastButton).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
})
})
describe('Multi-select functionality', () => {
test('renders with multiple selected days', () => {
const onChange = jest.fn()
render(
<MonthlyDaysSelector
selectedDays={[1, 15, 30]}
onChange={onChange}
/>,
)
expect(screen.getByRole('button', { name: '1' })).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
expect(screen.getByRole('button', { name: '15' })).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
expect(screen.getByRole('button', { name: '30' })).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
})
test('adds day to selection when clicked', () => {
const onChange = jest.fn()
render(
<MonthlyDaysSelector
selectedDays={[1, 15]}
onChange={onChange}
/>,
)
fireEvent.click(screen.getByRole('button', { name: '20' }))
expect(onChange).toHaveBeenCalledWith([1, 15, 20])
})
test('removes day from selection when clicked', () => {
const onChange = jest.fn()
render(
<MonthlyDaysSelector
selectedDays={[1, 15, 20]}
onChange={onChange}
/>,
)
fireEvent.click(screen.getByRole('button', { name: '15' }))
expect(onChange).toHaveBeenCalledWith([1, 20])
})
test('handles last day selection', () => {
const onChange = jest.fn()
render(
<MonthlyDaysSelector
selectedDays={[1, 'last']}
onChange={onChange}
/>,
)
expect(screen.getByRole('button', { name: 'Last' })).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
fireEvent.click(screen.getByRole('button', { name: 'Last' }))
expect(onChange).toHaveBeenCalledWith([1])
})
test('handles empty selection array', () => {
const onChange = jest.fn()
render(
<MonthlyDaysSelector
selectedDays={[]}
onChange={onChange}
/>,
)
fireEvent.click(screen.getByRole('button', { name: '10' }))
expect(onChange).toHaveBeenCalledWith([10])
})
test('supports mixed selection of numbers and last day', () => {
const onChange = jest.fn()
render(
<MonthlyDaysSelector
selectedDays={[5, 15, 'last']}
onChange={onChange}
/>,
)
expect(screen.getByRole('button', { name: '5' })).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
expect(screen.getByRole('button', { name: '15' })).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
expect(screen.getByRole('button', { name: 'Last' })).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
})
})
describe('Component structure', () => {
test('renders all day buttons from 1 to 31', () => {
render(
<MonthlyDaysSelector
selectedDays={[1]}
onChange={jest.fn()}
/>,
)
for (let i = 1; i <= 31; i++)
expect(screen.getByRole('button', { name: i.toString() })).toBeInTheDocument()
})
test('renders last day button', () => {
render(
<MonthlyDaysSelector
selectedDays={[1]}
onChange={jest.fn()}
/>,
)
expect(screen.getByRole('button', { name: 'Last' })).toBeInTheDocument()
})
test('displays correct label', () => {
render(
<MonthlyDaysSelector
selectedDays={[1]}
onChange={jest.fn()}
/>,
)
expect(screen.getByText('Days')).toBeInTheDocument()
})
test('applies correct grid layout', () => {
const { container } = render(
<MonthlyDaysSelector
selectedDays={[1]}
onChange={jest.fn()}
/>,
)
const gridRows = container.querySelectorAll('.grid-cols-7')
expect(gridRows).toHaveLength(5)
})
})
describe('Accessibility', () => {
test('buttons are keyboard accessible', () => {
const onChange = jest.fn()
render(
<MonthlyDaysSelector
selectedDays={[15]}
onChange={onChange}
/>,
)
const button = screen.getByRole('button', { name: '20' })
button.focus()
expect(document.activeElement).toBe(button)
})
test('last day button has tooltip', () => {
render(
<MonthlyDaysSelector
selectedDays={['last']}
onChange={jest.fn()}
/>,
)
expect(screen.getByRole('button', { name: 'Last' })).toBeInTheDocument()
})
test('selected state is visually distinct', () => {
render(
<MonthlyDaysSelector
selectedDays={[15]}
onChange={jest.fn()}
/>,
)
const selectedButton = screen.getByRole('button', { name: '15' })
const unselectedButton = screen.getByRole('button', { name: '16' })
expect(selectedButton).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
expect(unselectedButton).toHaveClass('border-divider-subtle')
})
})
describe('Default behavior', () => {
test('handles interaction correctly', () => {
const onChange = jest.fn()
render(
<MonthlyDaysSelector
selectedDays={[15]}
onChange={onChange}
/>,
)
fireEvent.click(screen.getByRole('button', { name: '20' }))
expect(onChange).toHaveBeenCalledWith([15, 20])
})
})
})

View File

@ -1,226 +0,0 @@
import { getNextExecutionTimes } from '../utils/execution-time-calculator'
import type { ScheduleTriggerNodeType } from '../types'
const createMonthlyConfig = (monthly_days: (number | 'last')[], time = '10:30 AM', timezone = 'UTC'): ScheduleTriggerNodeType => ({
mode: 'visual',
frequency: 'monthly',
visual_config: {
time,
monthly_days,
},
timezone,
})
describe('Monthly Edge Cases', () => {
beforeEach(() => {
jest.useFakeTimers()
jest.setSystemTime(new Date('2024-02-15T08:00:00.000Z'))
})
afterEach(() => {
jest.useRealTimers()
})
describe('31st day selection logic', () => {
test('31st day skips months without 31 days', () => {
const config = createMonthlyConfig([31])
const times = getNextExecutionTimes(config, 5)
const expectedMonths = times.map(date => date.getMonth() + 1)
expect(expectedMonths).not.toContain(2)
expect(expectedMonths).not.toContain(4)
expect(expectedMonths).not.toContain(6)
expect(expectedMonths).not.toContain(9)
expect(expectedMonths).not.toContain(11)
times.forEach((date) => {
expect(date.getDate()).toBe(31)
})
})
test('30th day skips February', () => {
const config = createMonthlyConfig([30])
const times = getNextExecutionTimes(config, 5)
const expectedMonths = times.map(date => date.getMonth() + 1)
expect(expectedMonths).not.toContain(2)
times.forEach((date) => {
expect(date.getDate()).toBe(30)
})
})
test('29th day works in all months', () => {
const config = createMonthlyConfig([29])
const times = getNextExecutionTimes(config, 12)
const months = times.map(date => date.getMonth() + 1)
expect(months).toContain(1)
expect(months).toContain(3)
expect(months).toContain(4)
expect(months).toContain(5)
expect(months).toContain(6)
expect(months).toContain(7)
expect(months).toContain(8)
expect(months).toContain(9)
expect(months).toContain(10)
expect(months).toContain(11)
expect(months).toContain(12)
})
test('29th day skips February in non-leap years', () => {
jest.setSystemTime(new Date('2023-01-15T08:00:00.000Z'))
const config = createMonthlyConfig([29])
const times = getNextExecutionTimes(config, 12)
const februaryExecutions = times.filter(date => date.getMonth() === 1)
expect(februaryExecutions).toHaveLength(0)
})
test('29th day includes February in leap years', () => {
jest.setSystemTime(new Date('2024-01-15T08:00:00.000Z'))
const config = createMonthlyConfig([29])
const times = getNextExecutionTimes(config, 12)
const februaryExecutions = times.filter(date => date.getMonth() === 1)
expect(februaryExecutions).toHaveLength(1)
expect(februaryExecutions[0].getDate()).toBe(29)
})
})
describe('last day vs specific day distinction', () => {
test('31st selection is different from last day in short months', () => {
const config31 = createMonthlyConfig([31])
const configLast = createMonthlyConfig(['last'])
const times31 = getNextExecutionTimes(config31, 12)
const timesLast = getNextExecutionTimes(configLast, 12)
const months31 = times31.map(date => date.getMonth() + 1)
const monthsLast = timesLast.map(date => date.getMonth() + 1)
expect(months31).not.toContain(2)
expect(monthsLast).toContain(2)
expect(months31).not.toContain(4)
expect(monthsLast).toContain(4)
})
test('31st and last day both work correctly in 31-day months', () => {
const config31 = createMonthlyConfig([31])
const configLast = createMonthlyConfig(['last'])
const times31 = getNextExecutionTimes(config31, 5)
const timesLast = getNextExecutionTimes(configLast, 5)
const march31 = times31.find(date => date.getMonth() === 2)
const marchLast = timesLast.find(date => date.getMonth() === 2)
expect(march31?.getDate()).toBe(31)
expect(marchLast?.getDate()).toBe(31)
})
test('mixed selection with 31st and last behaves correctly', () => {
const config = createMonthlyConfig([31, 'last'])
const times = getNextExecutionTimes(config, 12)
const februaryExecutions = times.filter(date => date.getMonth() === 1)
expect(februaryExecutions).toHaveLength(1)
expect(februaryExecutions[0].getDate()).toBe(29)
const marchExecutions = times.filter(date => date.getMonth() === 2)
expect(marchExecutions).toHaveLength(1)
expect(marchExecutions[0].getDate()).toBe(31)
})
test('deduplicates overlapping selections in 31-day months', () => {
const config = createMonthlyConfig([31, 'last'])
const times = getNextExecutionTimes(config, 12)
const monthsWith31Days = [0, 2, 4, 6, 7, 9, 11]
monthsWith31Days.forEach((month) => {
const monthExecutions = times.filter(date => date.getMonth() === month)
expect(monthExecutions.length).toBeLessThanOrEqual(1)
if (monthExecutions.length === 1)
expect(monthExecutions[0].getDate()).toBe(31)
})
})
test('deduplicates overlapping selections in 30-day months', () => {
const config = createMonthlyConfig([30, 'last'])
const times = getNextExecutionTimes(config, 12)
const monthsWith30Days = [3, 5, 8, 10]
monthsWith30Days.forEach((month) => {
const monthExecutions = times.filter(date => date.getMonth() === month)
expect(monthExecutions.length).toBeLessThanOrEqual(1)
if (monthExecutions.length === 1)
expect(monthExecutions[0].getDate()).toBe(30)
})
})
test('handles complex multi-day with last selection', () => {
const config = createMonthlyConfig([15, 30, 31, 'last'])
const times = getNextExecutionTimes(config, 20)
const marchExecutions = times.filter(date => date.getMonth() === 2).sort((a, b) => a.getDate() - b.getDate())
expect(marchExecutions).toHaveLength(3)
expect(marchExecutions.map(d => d.getDate())).toEqual([15, 30, 31])
const aprilExecutions = times.filter(date => date.getMonth() === 3).sort((a, b) => a.getDate() - b.getDate())
expect(aprilExecutions).toHaveLength(2)
expect(aprilExecutions.map(d => d.getDate())).toEqual([15, 30])
})
})
describe('current month offset calculation', () => {
test('skips current month when no valid days exist', () => {
jest.setSystemTime(new Date('2024-02-15T08:00:00.000Z'))
const config = createMonthlyConfig([31])
const times = getNextExecutionTimes(config, 3)
times.forEach((date) => {
expect(date.getMonth()).toBeGreaterThan(1)
})
})
test('includes current month when valid days exist', () => {
jest.setSystemTime(new Date('2024-03-15T08:00:00.000Z'))
const config = createMonthlyConfig([31])
const times = getNextExecutionTimes(config, 3)
const currentMonthExecution = times.find(date => date.getMonth() === 2)
expect(currentMonthExecution).toBeDefined()
expect(currentMonthExecution?.getDate()).toBe(31)
})
})
describe('sorting and deduplication', () => {
test('handles duplicate selections correctly', () => {
const config = createMonthlyConfig([15, 15, 15])
const times = getNextExecutionTimes(config, 5)
const marchExecutions = times.filter(date => date.getMonth() === 2)
expect(marchExecutions).toHaveLength(1)
})
test('sorts multiple days within same month', () => {
jest.setSystemTime(new Date('2024-03-01T08:00:00.000Z'))
const config = createMonthlyConfig([31, 15, 1])
const times = getNextExecutionTimes(config, 5)
const marchExecutions = times.filter(date => date.getMonth() === 2).sort((a, b) => a.getDate() - b.getDate())
expect(marchExecutions.map(d => d.getDate())).toEqual([1, 15, 31])
})
})
})

View File

@ -1,223 +0,0 @@
import { getNextExecutionTimes } from '../utils/execution-time-calculator'
import type { ScheduleTriggerNodeType } from '../types'
const createMonthlyConfig = (monthlyDays: (number | 'last')[], time = '10:30 AM'): ScheduleTriggerNodeType => ({
mode: 'visual',
frequency: 'monthly',
visual_config: {
time,
monthly_days: monthlyDays,
},
timezone: 'UTC',
id: 'test',
type: 'trigger-schedule',
data: {},
position: { x: 0, y: 0 },
})
describe('Monthly Multi-Select Execution Time Calculator', () => {
beforeEach(() => {
jest.useFakeTimers()
jest.setSystemTime(new Date('2024-01-15T08:00:00Z'))
})
afterEach(() => {
jest.useRealTimers()
})
describe('Multi-select functionality', () => {
test('calculates execution times for multiple days in same month', () => {
const config = createMonthlyConfig([1, 15, 30])
const times = getNextExecutionTimes(config, 5)
expect(times).toHaveLength(5)
expect(times[0].getDate()).toBe(15)
expect(times[0].getMonth()).toBe(0)
expect(times[1].getDate()).toBe(30)
expect(times[1].getMonth()).toBe(0)
expect(times[2].getDate()).toBe(1)
expect(times[2].getMonth()).toBe(1)
})
test('handles last day with multiple selections', () => {
const config = createMonthlyConfig([1, 'last'])
const times = getNextExecutionTimes(config, 4)
expect(times[0].getDate()).toBe(31)
expect(times[0].getMonth()).toBe(0)
expect(times[1].getDate()).toBe(1)
expect(times[1].getMonth()).toBe(1)
expect(times[2].getDate()).toBe(29)
expect(times[2].getMonth()).toBe(1)
})
test('skips invalid days in months with fewer days', () => {
const config = createMonthlyConfig([30, 31])
jest.setSystemTime(new Date('2024-01-01T08:00:00Z'))
const times = getNextExecutionTimes(config, 6)
const febTimes = times.filter(t => t.getMonth() === 1)
expect(febTimes.length).toBe(0)
const marchTimes = times.filter(t => t.getMonth() === 2)
expect(marchTimes.length).toBe(2)
expect(marchTimes[0].getDate()).toBe(30)
expect(marchTimes[1].getDate()).toBe(31)
})
test('sorts execution times chronologically', () => {
const config = createMonthlyConfig([25, 5, 15])
const times = getNextExecutionTimes(config, 6)
for (let i = 1; i < times.length; i++)
expect(times[i].getTime()).toBeGreaterThan(times[i - 1].getTime())
})
test('handles single day selection', () => {
const config = createMonthlyConfig([15])
const times = getNextExecutionTimes(config, 3)
expect(times).toHaveLength(3)
expect(times[0].getDate()).toBe(15)
expect(times[1].getDate()).toBe(15)
expect(times[2].getDate()).toBe(15)
for (let i = 1; i < times.length; i++)
expect(times[i].getTime()).toBeGreaterThan(times[i - 1].getTime())
})
})
describe('Single day configuration', () => {
test('supports single day selection', () => {
const config = createMonthlyConfig([15])
const times = getNextExecutionTimes(config, 3)
expect(times).toHaveLength(3)
expect(times[0].getDate()).toBe(15)
expect(times[1].getDate()).toBe(15)
expect(times[2].getDate()).toBe(15)
})
test('supports last day selection', () => {
const config = createMonthlyConfig(['last'])
const times = getNextExecutionTimes(config, 3)
expect(times[0].getDate()).toBe(31)
expect(times[0].getMonth()).toBe(0)
expect(times[1].getDate()).toBe(29)
expect(times[1].getMonth()).toBe(1)
})
test('falls back to day 1 when no configuration provided', () => {
const config: ScheduleTriggerNodeType = {
mode: 'visual',
frequency: 'monthly',
visual_config: {
time: '10:30 AM',
},
timezone: 'UTC',
id: 'test',
type: 'trigger-schedule',
data: {},
position: { x: 0, y: 0 },
}
const times = getNextExecutionTimes(config, 2)
expect(times).toHaveLength(2)
expect(times[0].getDate()).toBe(1)
expect(times[1].getDate()).toBe(1)
})
})
describe('Edge cases', () => {
test('handles empty monthly_days array', () => {
const config = createMonthlyConfig([])
const times = getNextExecutionTimes(config, 2)
expect(times).toHaveLength(2)
expect(times[0].getDate()).toBe(1)
expect(times[1].getDate()).toBe(1)
})
test('handles execution time that has already passed today', () => {
jest.setSystemTime(new Date('2024-01-15T12:00:00Z'))
const config = createMonthlyConfig([15], '10:30 AM')
const times = getNextExecutionTimes(config, 2)
expect(times[0].getMonth()).toBe(1)
expect(times[0].getDate()).toBe(15)
})
test('limits search to reasonable number of months', () => {
const config = createMonthlyConfig([29, 30, 31])
jest.setSystemTime(new Date('2024-03-01T08:00:00Z'))
const times = getNextExecutionTimes(config, 50)
expect(times.length).toBeGreaterThan(0)
expect(times.length).toBeLessThanOrEqual(50)
})
test('handles duplicate days in selection', () => {
const config = createMonthlyConfig([15, 15, 15])
const times = getNextExecutionTimes(config, 4)
const uniqueDates = new Set(times.map(t => t.getTime()))
expect(uniqueDates.size).toBe(times.length)
})
test('correctly handles leap year February', () => {
const config = createMonthlyConfig([29])
jest.setSystemTime(new Date('2024-01-01T08:00:00Z'))
const times = getNextExecutionTimes(config, 3)
expect(times[0].getDate()).toBe(29)
expect(times[0].getMonth()).toBe(0)
expect(times[1].getDate()).toBe(29)
expect(times[1].getMonth()).toBe(1)
})
test('handles non-leap year February', () => {
const config = createMonthlyConfig([29])
jest.setSystemTime(new Date('2023-01-01T08:00:00Z'))
const times = getNextExecutionTimes(config, 3)
expect(times[0].getDate()).toBe(29)
expect(times[0].getMonth()).toBe(0)
expect(times[1].getDate()).toBe(29)
expect(times[1].getMonth()).toBe(2)
})
})
describe('Time handling', () => {
test('respects specified execution time', () => {
const config = createMonthlyConfig([1], '2:45 PM')
const times = getNextExecutionTimes(config, 1)
expect(times[0].getHours()).toBe(14)
expect(times[0].getMinutes()).toBe(45)
})
test('handles AM/PM conversion correctly', () => {
const configAM = createMonthlyConfig([1], '6:30 AM')
const configPM = createMonthlyConfig([1], '6:30 PM')
const timesAM = getNextExecutionTimes(configAM, 1)
const timesPM = getNextExecutionTimes(configPM, 1)
expect(timesAM[0].getHours()).toBe(6)
expect(timesPM[0].getHours()).toBe(18)
})
test('handles 12 AM and 12 PM correctly', () => {
const config12AM = createMonthlyConfig([1], '12:00 AM')
const config12PM = createMonthlyConfig([1], '12:00 PM')
const times12AM = getNextExecutionTimes(config12AM, 1)
const times12PM = getNextExecutionTimes(config12PM, 1)
expect(times12AM[0].getHours()).toBe(0)
expect(times12PM[0].getHours()).toBe(12)
})
})
})

View File

@ -1,163 +0,0 @@
import nodeDefault from '../default'
const mockT = (key: string, options?: any) => {
const translations: Record<string, string> = {
'workflow.errorMsg.fieldRequired': `${options?.field} is required`,
'workflow.nodes.triggerSchedule.monthlyDay': 'Monthly Day',
'workflow.nodes.triggerSchedule.invalidMonthlyDay': 'Invalid monthly day',
'workflow.nodes.triggerSchedule.time': 'Time',
'workflow.nodes.triggerSchedule.invalidTimeFormat': 'Invalid time format',
}
return translations[key] || key
}
describe('Monthly Validation', () => {
describe('Single day validation', () => {
test('validates single day selection', () => {
const config = {
mode: 'visual' as const,
frequency: 'monthly' as const,
visual_config: {
time: '10:30 AM',
monthly_days: [15],
},
timezone: 'UTC',
}
const result = nodeDefault.checkValid(config, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
test('validates last day selection', () => {
const config = {
mode: 'visual' as const,
frequency: 'monthly' as const,
visual_config: {
time: '10:30 AM',
monthly_days: ['last' as const],
},
timezone: 'UTC',
}
const result = nodeDefault.checkValid(config, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
})
describe('Multi-day validation', () => {
test('validates multiple day selection', () => {
const config = {
mode: 'visual' as const,
frequency: 'monthly' as const,
visual_config: {
time: '10:30 AM',
monthly_days: [1, 15, 30],
},
timezone: 'UTC',
}
const result = nodeDefault.checkValid(config, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
test('validates mixed selection with last day', () => {
const config = {
mode: 'visual' as const,
frequency: 'monthly' as const,
visual_config: {
time: '10:30 AM',
monthly_days: [1, 15, 'last' as const],
},
timezone: 'UTC',
}
const result = nodeDefault.checkValid(config, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
test('rejects empty array', () => {
const config = {
mode: 'visual' as const,
frequency: 'monthly' as const,
visual_config: {
time: '10:30 AM',
monthly_days: [],
},
timezone: 'UTC',
}
const result = nodeDefault.checkValid(config, mockT)
expect(result.isValid).toBe(false)
expect(result.errorMessage).toBe('Monthly Day is required')
})
test('rejects invalid day in array', () => {
const config = {
mode: 'visual' as const,
frequency: 'monthly' as const,
visual_config: {
time: '10:30 AM',
monthly_days: [1, 35, 15],
},
timezone: 'UTC',
}
const result = nodeDefault.checkValid(config, mockT)
expect(result.isValid).toBe(false)
expect(result.errorMessage).toBe('Invalid monthly day')
})
})
describe('Edge cases', () => {
test('requires monthly configuration', () => {
const config = {
mode: 'visual' as const,
frequency: 'monthly' as const,
visual_config: {
time: '10:30 AM',
},
timezone: 'UTC',
}
const result = nodeDefault.checkValid(config, mockT)
expect(result.isValid).toBe(false)
expect(result.errorMessage).toBe('Monthly Day is required')
})
test('validates time format along with monthly_days', () => {
const config = {
mode: 'visual' as const,
frequency: 'monthly' as const,
visual_config: {
time: 'invalid-time',
monthly_days: [1, 15],
},
timezone: 'UTC',
}
const result = nodeDefault.checkValid(config, mockT)
expect(result.isValid).toBe(false)
expect(result.errorMessage).toBe('Invalid time format')
})
test('handles very large arrays', () => {
const config = {
mode: 'visual' as const,
frequency: 'monthly' as const,
visual_config: {
time: '10:30 AM',
monthly_days: Array.from({ length: 31 }, (_, i) => i + 1),
},
timezone: 'UTC',
}
const result = nodeDefault.checkValid(config, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
})
})

View File

@ -1,443 +0,0 @@
import { getNextExecutionTimes } from '../utils/execution-time-calculator'
import type { ScheduleTriggerNodeType } from '../types'
const createWeeklyConfig = (
weekdays: string[],
time = '2:30 PM',
timezone = 'UTC',
): ScheduleTriggerNodeType => ({
id: 'test-node',
type: 'schedule-trigger',
mode: 'visual',
frequency: 'weekly',
visual_config: {
time,
weekdays,
},
timezone,
})
describe('Weekly Schedule Time Logic Tests', () => {
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.useRealTimers()
})
describe('Same weekday time comparison logic', () => {
test('should execute today when time has not passed yet', () => {
jest.setSystemTime(new Date('2024-08-28T10:00:00.000Z'))
const config = createWeeklyConfig(['wed'], '2:30 PM', 'UTC')
const times = getNextExecutionTimes(config, 3)
expect(times[0].getDay()).toBe(3)
expect(times[0].getDate()).toBe(28)
expect(times[0].getHours()).toBe(14)
expect(times[0].getMinutes()).toBe(30)
})
test('should skip to next week when time has already passed', () => {
jest.setSystemTime(new Date('2024-08-28T16:00:00.000Z'))
const config = createWeeklyConfig(['wed'], '2:30 PM', 'UTC')
const times = getNextExecutionTimes(config, 3)
expect(times[0].getDay()).toBe(3)
expect(times[0].getDate()).toBe(4)
expect(times[0].getMonth()).toBe(8)
expect(times[0].getHours()).toBe(14)
expect(times[0].getMinutes()).toBe(30)
})
test('should skip to next week when exact time has passed', () => {
jest.setSystemTime(new Date('2024-08-28T14:30:01.000Z'))
const config = createWeeklyConfig(['wed'], '2:30 PM', 'UTC')
const times = getNextExecutionTimes(config, 3)
expect(times[0].getDate()).toBe(4)
expect(times[0].getMonth()).toBe(8)
})
test('should execute today when time is exactly now', () => {
jest.setSystemTime(new Date('2024-08-28T14:30:00.000Z'))
const config = createWeeklyConfig(['wed'], '2:30 PM', 'UTC')
const times = getNextExecutionTimes(config, 3)
expect(times[0].getDate()).toBe(4)
expect(times[0].getMonth()).toBe(8)
})
})
describe('Cross-day scenarios', () => {
test('should handle early morning execution on same day', () => {
jest.setSystemTime(new Date('2024-08-28T02:00:00.000Z'))
const config = createWeeklyConfig(['wed'], '6:00 AM', 'UTC')
const times = getNextExecutionTimes(config, 2)
expect(times[0].getDate()).toBe(28)
expect(times[0].getHours()).toBe(6)
expect(times[1].getDate()).toBe(4)
expect(times[1].getMonth()).toBe(8)
})
test('should handle midnight execution correctly', () => {
jest.setSystemTime(new Date('2024-08-27T23:30:00.000Z'))
const config = createWeeklyConfig(['wed'], '12:00 AM', 'UTC')
const times = getNextExecutionTimes(config, 2)
expect(times[0].getDate()).toBe(28)
expect(times[0].getHours()).toBe(0)
expect(times[1].getDate()).toBe(4)
expect(times[1].getMonth()).toBe(8)
})
test('should handle noon execution correctly', () => {
jest.setSystemTime(new Date('2024-08-28T10:00:00.000Z'))
const config = createWeeklyConfig(['wed'], '12:00 PM', 'UTC')
const times = getNextExecutionTimes(config, 2)
expect(times[0].getDate()).toBe(28)
expect(times[0].getHours()).toBe(12)
})
})
describe('Multiple weekdays with time logic', () => {
test('should respect time for multiple weekdays in same week', () => {
jest.setSystemTime(new Date('2024-08-28T10:00:00.000Z'))
const config = createWeeklyConfig(['wed', 'fri'], '2:00 PM', 'UTC')
const times = getNextExecutionTimes(config, 4)
expect(times[0].getDay()).toBe(3)
expect(times[0].getDate()).toBe(28)
expect(times[1].getDay()).toBe(5)
expect(times[1].getDate()).toBe(30)
expect(times[2].getDay()).toBe(3)
expect(times[2].getDate()).toBe(4)
expect(times[2].getMonth()).toBe(8)
})
test('should skip past weekdays in current week', () => {
jest.setSystemTime(new Date('2024-08-28T16:00:00.000Z'))
const config = createWeeklyConfig(['mon', 'wed'], '2:00 PM', 'UTC')
const times = getNextExecutionTimes(config, 4)
expect(times[0].getDay()).toBe(1)
expect(times[0].getDate()).toBe(2)
expect(times[0].getMonth()).toBe(8)
expect(times[1].getDay()).toBe(3)
expect(times[1].getDate()).toBe(4)
expect(times[1].getMonth()).toBe(8)
})
test('should handle weekend execution correctly', () => {
jest.setSystemTime(new Date('2024-08-31T10:00:00.000Z'))
const config = createWeeklyConfig(['sat', 'sun'], '9:00 AM', 'UTC')
const times = getNextExecutionTimes(config, 4)
expect(times[0].getDay()).toBe(0)
expect(times[0].getDate()).toBe(1)
expect(times[0].getMonth()).toBe(8)
expect(times[1].getDay()).toBe(6)
expect(times[1].getDate()).toBe(7)
expect(times[1].getMonth()).toBe(8)
})
})
describe('Timezone handling with time logic', () => {
test('should respect timezone when checking if time has passed', () => {
jest.setSystemTime(new Date('2024-08-28T14:30:00.000Z'))
const config = createWeeklyConfig(['wed'], '6:00 PM', 'America/New_York')
const times = getNextExecutionTimes(config, 2)
expect(times[0].getDate()).toBe(28)
expect(times[0].getHours()).toBe(18)
})
test('should handle timezone difference when time has passed', () => {
jest.setSystemTime(new Date('2024-08-28T23:00:00.000Z'))
const config = createWeeklyConfig(['wed'], '6:00 PM', 'America/New_York')
const times = getNextExecutionTimes(config, 2)
expect(times[0].getDate()).toBe(4)
expect(times[0].getMonth()).toBe(8)
})
})
describe('Edge cases and boundary conditions', () => {
test('should handle year boundary correctly with time logic', () => {
jest.setSystemTime(new Date('2024-12-31T10:00:00.000Z'))
const config = createWeeklyConfig(['tue'], '2:00 PM', 'UTC')
const times = getNextExecutionTimes(config, 3)
expect(times[0].getDate()).toBe(31)
expect(times[0].getMonth()).toBe(11)
expect(times[0].getFullYear()).toBe(2024)
expect(times[1].getDate()).toBe(7)
expect(times[1].getMonth()).toBe(0)
expect(times[1].getFullYear()).toBe(2025)
})
test('should handle month boundary correctly', () => {
jest.setSystemTime(new Date('2024-08-31T10:00:00.000Z'))
const config = createWeeklyConfig(['sat'], '2:00 PM', 'UTC')
const times = getNextExecutionTimes(config, 2)
expect(times[0].getDate()).toBe(31)
expect(times[0].getMonth()).toBe(7)
expect(times[1].getDate()).toBe(7)
expect(times[1].getMonth()).toBe(8)
})
test('should handle leap year February correctly', () => {
jest.setSystemTime(new Date('2024-02-29T10:00:00.000Z'))
const config = createWeeklyConfig(['thu'], '2:00 PM', 'UTC')
const times = getNextExecutionTimes(config, 2)
expect(times[0].getDate()).toBe(29)
expect(times[0].getMonth()).toBe(1)
expect(times[0].getFullYear()).toBe(2024)
expect(times[1].getDate()).toBe(7)
expect(times[1].getMonth()).toBe(2)
})
test('should handle daylight saving time transitions', () => {
jest.setSystemTime(new Date('2024-03-10T10:00:00.000Z'))
const config = createWeeklyConfig(['sun'], '2:00 AM', 'America/New_York')
const times = getNextExecutionTimes(config, 3)
expect(times.length).toBeGreaterThan(0)
times.forEach((time) => {
expect(time.getDay()).toBe(0)
expect(time.getHours()).toBe(2)
})
})
})
describe('Validation of PR #24641 fix', () => {
test('should correctly calculate weekday offsets (not use index as day offset)', () => {
jest.setSystemTime(new Date('2024-08-28T10:00:00.000Z'))
const config = createWeeklyConfig(['sun'], '2:00 PM', 'UTC')
const times = getNextExecutionTimes(config, 2)
expect(times[0].getDay()).toBe(0)
expect(times[0].getDate()).toBe(1)
expect(times[0].getMonth()).toBe(8)
expect(times[1].getDate()).toBe(8)
expect(times[1].getMonth()).toBe(8)
})
test('should correctly handle multiple weekdays selection', () => {
jest.setSystemTime(new Date('2024-08-26T11:00:00.000Z'))
const config = createWeeklyConfig(['mon', 'wed', 'fri'], '9:00 AM', 'UTC')
const times = getNextExecutionTimes(config, 6)
expect(times[0].getDay()).toBe(3)
expect(times[0].getDate()).toBe(28)
expect(times[1].getDay()).toBe(5)
expect(times[1].getDate()).toBe(30)
expect(times[2].getDay()).toBe(1)
expect(times[2].getDate()).toBe(2)
expect(times[2].getMonth()).toBe(8)
})
test('should prevent infinite loops with invalid weekdays', () => {
jest.setSystemTime(new Date('2024-08-28T10:00:00.000Z'))
const config = createWeeklyConfig(['invalid'], '2:00 PM', 'UTC')
const times = getNextExecutionTimes(config, 3)
expect(times).toEqual([])
})
})
describe('Comprehensive time scenarios for all weekdays', () => {
const weekdays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
test.each(weekdays)('should respect time logic for %s', (weekday) => {
jest.setSystemTime(new Date('2024-08-28T10:00:00.000Z'))
const config = createWeeklyConfig([weekday], '2:00 PM', 'UTC')
const times = getNextExecutionTimes(config, 3)
expect(times.length).toBe(3)
times.forEach((time) => {
expect(time.getHours()).toBe(14)
expect(time.getMinutes()).toBe(0)
})
})
test.each(weekdays)('should handle early morning execution for %s', (weekday) => {
jest.setSystemTime(new Date('2024-08-28T23:00:00.000Z'))
const config = createWeeklyConfig([weekday], '6:00 AM', 'UTC')
const times = getNextExecutionTimes(config, 2)
expect(times.length).toBeGreaterThan(0)
times.forEach((time) => {
expect(time.getHours()).toBe(6)
expect(time.getMinutes()).toBe(0)
})
})
})
describe('Performance and edge cases', () => {
test('should complete execution within reasonable time', () => {
jest.setSystemTime(new Date('2024-08-28T10:00:00.000Z'))
const start = performance.now()
const config = createWeeklyConfig(['mon', 'tue', 'wed', 'thu', 'fri'], '9:00 AM', 'UTC')
const times = getNextExecutionTimes(config, 10)
const end = performance.now()
expect(times.length).toBe(10)
expect(end - start).toBeLessThan(50)
})
test('should handle large count requests efficiently', () => {
jest.setSystemTime(new Date('2024-08-28T10:00:00.000Z'))
const config = createWeeklyConfig(['sun'], '2:00 PM', 'UTC')
const times = getNextExecutionTimes(config, 100)
expect(times.length).toBe(100)
for (let i = 1; i < times.length; i++) {
const timeDiff = times[i].getTime() - times[i - 1].getTime()
const daysDiff = timeDiff / (1000 * 60 * 60 * 24)
expect(daysDiff).toBe(7)
}
})
test('should handle empty weekdays array', () => {
jest.setSystemTime(new Date('2024-08-28T10:00:00.000Z'))
const config = createWeeklyConfig([], '2:00 PM', 'UTC')
const times = getNextExecutionTimes(config, 3)
expect(times).toEqual([])
})
})
describe('Comparison with other frequency modes consistency', () => {
test('should behave consistently with daily mode time logic', () => {
jest.setSystemTime(new Date('2024-08-28T10:00:00.000Z'))
const weeklyConfig = createWeeklyConfig(['wed'], '2:00 PM', 'UTC')
const dailyConfig: ScheduleTriggerNodeType = {
id: 'test-node',
type: 'schedule-trigger',
mode: 'visual',
frequency: 'daily',
visual_config: {
time: '2:00 PM',
},
timezone: 'UTC',
}
const weeklyTimes = getNextExecutionTimes(weeklyConfig, 1)
const dailyTimes = getNextExecutionTimes(dailyConfig, 1)
expect(weeklyTimes[0].getDate()).toBe(28)
expect(dailyTimes[0].getDate()).toBe(28)
expect(weeklyTimes[0].getHours()).toBe(14)
expect(dailyTimes[0].getHours()).toBe(14)
})
test('should behave consistently when time has passed', () => {
jest.setSystemTime(new Date('2024-08-28T16:00:00.000Z'))
const weeklyConfig = createWeeklyConfig(['wed'], '2:00 PM', 'UTC')
const dailyConfig: ScheduleTriggerNodeType = {
id: 'test-node',
type: 'schedule-trigger',
mode: 'visual',
frequency: 'daily',
visual_config: {
time: '2:00 PM',
},
timezone: 'UTC',
}
const weeklyTimes = getNextExecutionTimes(weeklyConfig, 1)
const dailyTimes = getNextExecutionTimes(dailyConfig, 1)
expect(weeklyTimes[0].getDate()).toBe(4)
expect(dailyTimes[0].getDate()).toBe(29)
expect(weeklyTimes[0].getHours()).toBe(14)
expect(dailyTimes[0].getHours()).toBe(14)
})
})
describe('Real-world scenarios', () => {
test('Monday morning meeting scheduled on Monday at 10am should execute today if before 10am', () => {
jest.setSystemTime(new Date('2024-08-26T08:00:00.000Z'))
const config = createWeeklyConfig(['mon'], '10:00 AM', 'UTC')
const times = getNextExecutionTimes(config, 1)
expect(times[0].getDay()).toBe(1)
expect(times[0].getDate()).toBe(26)
expect(times[0].getHours()).toBe(10)
})
test('Friday afternoon report scheduled on Friday at 5pm should wait until next Friday if after 5pm', () => {
jest.setSystemTime(new Date('2024-08-30T18:00:00.000Z'))
const config = createWeeklyConfig(['fri'], '5:00 PM', 'UTC')
const times = getNextExecutionTimes(config, 1)
expect(times[0].getDay()).toBe(5)
expect(times[0].getDate()).toBe(6)
expect(times[0].getMonth()).toBe(8)
expect(times[0].getHours()).toBe(17)
})
test('Weekend cleanup scheduled for Saturday and Sunday should work correctly', () => {
jest.setSystemTime(new Date('2024-08-30T14:00:00.000Z'))
const config = createWeeklyConfig(['sat', 'sun'], '11:00 PM', 'UTC')
const times = getNextExecutionTimes(config, 4)
expect(times[0].getDay()).toBe(6)
expect(times[0].getDate()).toBe(31)
expect(times[0].getHours()).toBe(23)
expect(times[1].getDay()).toBe(0)
expect(times[1].getDate()).toBe(1)
expect(times[1].getMonth()).toBe(8)
expect(times[1].getHours()).toBe(23)
})
})
})

View File

@ -1,106 +0,0 @@
/**
* Webhook Trigger Node Default Tests
*
* Tests for webhook trigger node default configuration and field validation.
* Tests core checkValid functionality following project patterns.
*/
import nodeDefault from '../default'
import type { WebhookTriggerNodeType } from '../types'
// Simple mock translation function
const mockT = (key: string, params?: any) => {
const translations: Record<string, string> = {
'workflow.nodes.triggerWebhook.validation.webhookUrlRequired': 'Webhook URL is required',
'workflow.nodes.triggerWebhook.validation.invalidParameterType': `Invalid parameter type ${params?.type} for ${params?.name}`,
}
if (key.includes('fieldRequired')) return `${params?.field} is required`
return translations[key] || key
}
describe('Webhook Trigger Node Default', () => {
describe('Basic Configuration', () => {
it('should have correct default values for all backend fields', () => {
const defaultValue = nodeDefault.defaultValue
// Core webhook configuration
expect(defaultValue.webhook_url).toBe('')
expect(defaultValue.method).toBe('POST')
expect(defaultValue.content_type).toBe('application/json')
// Response configuration fields
expect(defaultValue.async_mode).toBe(true)
expect(defaultValue.status_code).toBe(200)
expect(defaultValue.response_body).toBe('')
// Parameter arrays
expect(Array.isArray(defaultValue.headers)).toBe(true)
expect(Array.isArray(defaultValue.params)).toBe(true)
expect(Array.isArray(defaultValue.body)).toBe(true)
expect(Array.isArray(defaultValue.variables)).toBe(true)
// Initial arrays should be empty
expect(defaultValue.headers).toHaveLength(0)
expect(defaultValue.params).toHaveLength(0)
expect(defaultValue.body).toHaveLength(0)
expect(defaultValue.variables).toHaveLength(1)
const rawVariable = defaultValue.variables?.[0]
expect(rawVariable?.variable).toBe('_webhook_raw')
expect(rawVariable?.label).toBe('raw')
expect(rawVariable?.value_type).toBe('object')
})
it('should have correct metadata for trigger node', () => {
expect(nodeDefault.metaData).toBeDefined()
expect(nodeDefault.metaData.type).toBe('trigger-webhook')
expect(nodeDefault.metaData.sort).toBe(3)
expect(nodeDefault.metaData.isStart).toBe(true)
})
})
describe('Validation - checkValid', () => {
it('should require webhook_url to be configured', () => {
const payload = nodeDefault.defaultValue as WebhookTriggerNodeType
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(false)
expect(result.errorMessage).toContain('required')
})
it('should validate successfully when webhook_url is provided', () => {
const payload = {
...nodeDefault.defaultValue,
webhook_url: 'https://example.com/webhook',
} as WebhookTriggerNodeType
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(true)
expect(result.errorMessage).toBe('')
})
it('should handle response configuration fields when webhook_url is provided', () => {
const payload = {
...nodeDefault.defaultValue,
webhook_url: 'https://example.com/webhook',
status_code: 404,
response_body: '{"error": "Not found"}',
} as WebhookTriggerNodeType
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(true)
})
it('should handle async_mode field correctly when webhook_url is provided', () => {
const payload = {
...nodeDefault.defaultValue,
webhook_url: 'https://example.com/webhook',
async_mode: false,
} as WebhookTriggerNodeType
const result = nodeDefault.checkValid(payload, mockT)
expect(result.isValid).toBe(true)
})
})
})

View File

@ -1,81 +0,0 @@
import {
createParameterTypeOptions,
getAvailableParameterTypes,
isValidParameterType,
normalizeParameterType,
} from './parameter-type-utils'
describe('Parameter Type Utils', () => {
describe('isValidParameterType', () => {
it('should validate specific array types', () => {
expect(isValidParameterType('array[string]')).toBe(true)
expect(isValidParameterType('array[number]')).toBe(true)
expect(isValidParameterType('array[boolean]')).toBe(true)
expect(isValidParameterType('array[object]')).toBe(true)
})
it('should validate basic types', () => {
expect(isValidParameterType('string')).toBe(true)
expect(isValidParameterType('number')).toBe(true)
expect(isValidParameterType('boolean')).toBe(true)
expect(isValidParameterType('object')).toBe(true)
expect(isValidParameterType('file')).toBe(true)
})
it('should reject invalid types', () => {
expect(isValidParameterType('array')).toBe(false)
expect(isValidParameterType('invalid')).toBe(false)
expect(isValidParameterType('array[invalid]')).toBe(false)
})
})
describe('normalizeParameterType', () => {
it('should normalize valid types', () => {
expect(normalizeParameterType('string')).toBe('string')
expect(normalizeParameterType('array[string]')).toBe('array[string]')
})
it('should migrate legacy array type', () => {
expect(normalizeParameterType('array')).toBe('array[string]')
})
it('should default to string for invalid types', () => {
expect(normalizeParameterType('invalid')).toBe('string')
})
})
describe('getAvailableParameterTypes', () => {
it('should return only string for non-request body', () => {
const types = getAvailableParameterTypes('application/json', false)
expect(types).toEqual(['string'])
})
it('should return all types for application/json', () => {
const types = getAvailableParameterTypes('application/json', true)
expect(types).toContain('string')
expect(types).toContain('number')
expect(types).toContain('boolean')
expect(types).toContain('array[string]')
expect(types).toContain('array[number]')
expect(types).toContain('array[boolean]')
expect(types).toContain('array[object]')
expect(types).toContain('object')
})
it('should include file type for multipart/form-data', () => {
const types = getAvailableParameterTypes('multipart/form-data', true)
expect(types).toContain('file')
})
})
describe('createParameterTypeOptions', () => {
it('should create options with display names', () => {
const options = createParameterTypeOptions('application/json', true)
const stringOption = options.find(opt => opt.value === 'string')
const arrayStringOption = options.find(opt => opt.value === 'array[string]')
expect(stringOption?.name).toBe('String')
expect(arrayStringOption?.name).toBe('Array[String]')
})
})
})