mirror of https://github.com/langgenius/dify.git
chore(tests): remove deprecated test files for schedule and webhook triggers
This commit is contained in:
parent
71b1af69c5
commit
720480d05e
|
|
@ -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('')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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('')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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]')
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue