diff --git a/public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeInterval.test.tsx b/public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeInterval.test.tsx new file mode 100644 index 00000000000..ef440225088 --- /dev/null +++ b/public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeInterval.test.tsx @@ -0,0 +1,53 @@ +import { validateDaysOfMonth } from './MuteTimingTimeInterval'; + +describe('validateDaysOfMonth', () => { + it('should return true for valid empty value', () => { + expect(validateDaysOfMonth('')).toBe(true); + expect(validateDaysOfMonth(undefined)).toBe(true); + }); + + it('should return true for valid single days', () => { + expect(validateDaysOfMonth('1')).toBe(true); + expect(validateDaysOfMonth('15')).toBe(true); + expect(validateDaysOfMonth('31')).toBe(true); + expect(validateDaysOfMonth('-1')).toBe(true); + expect(validateDaysOfMonth('-30')).toBe(true); + }); + + it('should return true for valid day ranges', () => { + expect(validateDaysOfMonth('1:5')).toBe(true); + expect(validateDaysOfMonth('1:5, 10:15')).toBe(true); + expect(validateDaysOfMonth('1:5, 10, 15, 20:25')).toBe(true); + expect(validateDaysOfMonth('1:5,10:15')).toBe(true); + expect(validateDaysOfMonth('1:5,10,15,20:25')).toBe(true); + expect(validateDaysOfMonth('-30:-1')).toBe(true); + }); + + it('should return true for valid mixed positive and negative days', () => { + expect(validateDaysOfMonth('1, -1')).toBe(true); + expect(validateDaysOfMonth('1:5, -10, -15')).toBe(true); + expect(validateDaysOfMonth('1,-1')).toBe(true); + expect(validateDaysOfMonth('1:5,-10,-15')).toBe(true); + }); + + it('should return error message for invalid format with non-numeric characters', () => { + expect(validateDaysOfMonth('1a')).toBe('Invalid day'); + expect(validateDaysOfMonth('a')).toBe('Invalid day'); + expect(validateDaysOfMonth('1-5')).toBe('Invalid day'); + expect(validateDaysOfMonth('1 5')).toBe('Invalid day'); + expect(validateDaysOfMonth('1..5')).toBe('Invalid day'); + }); + + it('should return error message for out of range days', () => { + expect(validateDaysOfMonth('0')).toBe('Invalid day'); + expect(validateDaysOfMonth('32')).toBe('Invalid day'); + expect(validateDaysOfMonth('-32')).toBe('Invalid day'); + expect(validateDaysOfMonth('-0')).toBe('Invalid day'); + }); + + it('should return error message for mixed valid and invalid days', () => { + expect(validateDaysOfMonth('1, 32')).toBe('Invalid day'); + expect(validateDaysOfMonth('1:5, 15a')).toBe('Invalid day'); + expect(validateDaysOfMonth('1, abc')).toBe('Invalid day'); + }); +}); diff --git a/public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeInterval.tsx b/public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeInterval.tsx index d25c002fe0d..49307778402 100644 --- a/public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeInterval.tsx +++ b/public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeInterval.tsx @@ -72,21 +72,13 @@ export const MuteTimingTimeInterval = () => { - validateArrayField( - value, - (day) => { - const parsedDay = parseInt(day, 10); - return (parsedDay > -31 && parsedDay < 0) || (parsedDay > 0 && parsedDay < 32); - }, - 'Invalid day' - ), + validate: validateDaysOfMonth, })} width={50} // @ts-ignore react-hook-form doesn't handle nested field arrays well @@ -195,6 +187,22 @@ const parseDays = (input: string): string[] => { return uniq(parsedDays); }; +export function validateDaysOfMonth(value: string | undefined) { + return validateArrayField( + value, + (day) => { + // Ensure the value contains ONLY digits with an optional negative sign + // This rejects any non-numeric characters or mixed inputs like "3-10" + if (!/^-?\d+$/.test(day)) { + return false; + } + const parsedDay = parseInt(day, 10); + return (parsedDay > -31 && parsedDay < 0) || (parsedDay > 0 && parsedDay < 32); + }, + 'Invalid day' + ); +} + // parse monday:wednesday to ["monday", "tuesday", "wednesday"] function parseWeekdayRange(input: string): string[] { const [start = '', end = ''] = input.split(':');