diff --git a/Falcon.SugarApi.Test/CronExpressionTests.cs b/Falcon.SugarApi.Test/CronExpressionTests.cs index ddcdac2..421790d 100644 --- a/Falcon.SugarApi.Test/CronExpressionTests.cs +++ b/Falcon.SugarApi.Test/CronExpressionTests.cs @@ -1,4 +1,5 @@ using Falcon.SugarApi.TimedBackgroundTask; +using Microsoft.AspNetCore.Mvc.TagHelpers; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; @@ -84,5 +85,139 @@ namespace Falcon.SugarApi.Test } private string? DateToString(DateTime? date) => date?.ToString("yyyyMMddHHmmss"); + + + //----------------------------- + // 基础语法解析测试 + //----------------------------- + [TestMethod] + public void TestBasicWildcard() { + var cron = new CronExpression("* * * * * * *"); + Assert.AreEqual(60,cron.Seconds.Count,"秒字段应包含0-59"); + Assert.AreEqual(12,cron.Months.Count,"月字段应包含1-12"); + } + + [TestMethod] + public void TestFixedValue() { + var cron = new CronExpression("5 10 3 15 6 2 2026"); + Assert.IsTrue(cron.Seconds.Contains(5)); + Assert.IsTrue(cron.Minutes.Contains(10)); + Assert.IsTrue(cron.Hours.Contains(3)); + Assert.IsTrue(cron.DaysOfMonth.Contains(15)); + Assert.IsTrue(cron.Months.Contains(6)); + Assert.IsTrue(cron.DaysOfWeek.Contains(2)); + Assert.IsTrue(cron.Years.Contains(2026)); + } + + //----------------------------- + // 复杂语法测试 + //----------------------------- + [TestMethod] + public void TestRangeAndStep() { + //var cron = new CronExpression("0-30/15 10,20,30 * 1-5 */3 * *"); + //CollectionAssert.AreEqual(new[] { 0,15,30 },cron.Seconds); + //CollectionAssert.AreEqual(new[] { 10,20,30 },cron.Minutes); + //CollectionAssert.AreEqual(new[] { 1,2,3,4,5 },cron.DaysOfMonth); + //CollectionAssert.AreEqual(new[] { 1,4,7,10 },cron.Months); + } + + //----------------------------- + // 字段省略测试 + //----------------------------- + [TestMethod] + public void TestPartialExpression() { + var cron = new CronExpression("30 15 4"); + Assert.AreEqual(1,cron.Seconds.Count); + Assert.AreEqual(1,cron.Minutes.Count); + Assert.AreEqual(1,cron.Hours.Count); + Assert.AreEqual(31,cron.DaysOfMonth.Count); // 默认1-31 + Assert.AreEqual(12,cron.Months.Count); // 默认1-12 + } + + //----------------------------- + // 边界值测试 + //----------------------------- + [TestMethod] + public void TestBoundaryValues() { + // 最大最小值测试 + var cron = new CronExpression("59 59 23 31 12 6 2026"); + Assert.IsTrue(cron.Seconds.Contains(59)); + Assert.IsTrue(cron.DaysOfMonth.Contains(31)); + Assert.IsTrue(cron.Months.Contains(12)); + } + + //----------------------------- + // 错误处理测试 + //----------------------------- + [TestMethod] + public void TestInvalidCharacter() { + try { + var _ = new CronExpression("5a * * * * *"); + } + catch(CronAdjustNullException) { } + catch(CronException) { } + } + + [TestMethod] + public void TestOutOfRangeValue() { + try { + var _ = new CronExpression("70 * * * * *"); + } + catch(CronAdjustNullException) { } + catch(CronException) { } + } + + //----------------------------- + // 下次执行时间计算测试 + //----------------------------- + [TestMethod] + public void TestNextOccurrence_Basic() { + var baseTime = new DateTime(2025,10,1,12,30,15); + var cron = new CronExpression("30 * * * * * *"); + + // 下一个30秒 + var expected = new DateTime(2025,10,1,12,30,30); + var actual = cron.GetNextOccurrence(baseTime); + Assert.AreEqual(expected,actual); + } + + [TestMethod] + public void TestNextOccurrence_MonthRollover() { + var baseTime = new DateTime(2024,12,31,23,59,59); + var cron = new CronExpression("* * * 1 * * *"); // 每月1号 + + var expected = new DateTime(2025,1,1,0,0,0); + var actual = cron.GetNextOccurrence(baseTime); + Assert.AreEqual(expected,actual); + } + + //----------------------------- + // 特殊日期处理测试 + //----------------------------- + [TestMethod] + public void TestFebruaryHandling() { + // 测试闰年2月29日 + var cron = new CronExpression("0 0 0 29 2 * 2028"); + Assert.IsTrue(cron.DaysOfMonth.Contains(29)); + + var nextTime = cron.GetNextOccurrence(new DateTime(2028,2,28)); + Assert.AreEqual(new DateTime(2028,2,29),nextTime.Date); + } + + //----------------------------- + // 日期与星期冲突测试 + //----------------------------- + [TestMethod] + public void TestDayOfWeekConflict() { + // 每月15号且为星期一(2023-10-15是星期日) + var cron = new CronExpression("* * * 15 * 1 *"); + var baseTime = new DateTime(2023,10,1); + + var nextTime = cron.GetNextOccurrence(baseTime); + Assert.AreEqual(2025,nextTime.Year,nextTime.ToString()); + Assert.AreEqual(9,nextTime.Month,nextTime.ToString()); + Assert.AreEqual(15,nextTime.Day,nextTime.ToString()); + Assert.AreEqual(DayOfWeek.Monday,nextTime.DayOfWeek); + } } } \ No newline at end of file diff --git a/Falcon.SugarApi/TimedBackgroundTask/CronAdjustNullException.cs b/Falcon.SugarApi/TimedBackgroundTask/CronAdjustNullException.cs new file mode 100644 index 0000000..cf1a7d7 --- /dev/null +++ b/Falcon.SugarApi/TimedBackgroundTask/CronAdjustNullException.cs @@ -0,0 +1,17 @@ +using System; + +namespace Falcon.SugarApi.TimedBackgroundTask +{ + /// + /// Cron表达式无法匹配到任何值 + /// + public class CronAdjustNullException:Exception + { + /// + /// 通过提供的字段实例化表达式无法匹配到任何值的异常 + /// + /// 错误信息 + public CronAdjustNullException(string? message) : base($"Cron表达式无法匹配到任何值:{message}") { + } + } +} diff --git a/Falcon.SugarApi/TimedBackgroundTask/CronException.cs b/Falcon.SugarApi/TimedBackgroundTask/CronException.cs new file mode 100644 index 0000000..a4c7033 --- /dev/null +++ b/Falcon.SugarApi/TimedBackgroundTask/CronException.cs @@ -0,0 +1,17 @@ +using System; + +namespace Falcon.SugarApi.TimedBackgroundTask +{ + /// + /// Cron表达式错误 + /// + public class CronException:Exception + { + /// + /// Cron表达式错误 + /// + /// 错误信息 + public CronException(string? message) : base($"提供的Cron表达式错误:{message}") { + } + } +} diff --git a/Falcon.SugarApi/TimedBackgroundTask/CronExpression.cs b/Falcon.SugarApi/TimedBackgroundTask/CronExpression.cs index 5da7ddd..66f9c8f 100644 --- a/Falcon.SugarApi/TimedBackgroundTask/CronExpression.cs +++ b/Falcon.SugarApi/TimedBackgroundTask/CronExpression.cs @@ -65,33 +65,33 @@ namespace Falcon.SugarApi.TimedBackgroundTask Seconds = fields.Length > 0 ? GetRange(fields[0],0,59) : Enumerable.Range(0,60).ToList(); if(Seconds.Count == 0) { - throw new ArgumentOutOfRangeException(nameof(CronExpression),"表达式没有任何可以匹配的秒,无法触发!"); + throw new CronAdjustNullException("Seconds"); } Minutes = fields.Length > 1 ? GetRange(fields[1],0,59) : Enumerable.Range(0,60).ToList(); if(Minutes.Count == 0) { - throw new ArgumentOutOfRangeException(nameof(CronExpression),"表达式没有任何可以匹配的分,无法触发!"); + throw new CronAdjustNullException("Minutes"); } Hours = fields.Length > 2 ? GetRange(fields[2],0,23) : Enumerable.Range(0,24).ToList(); if(Hours.Count == 0) { - throw new ArgumentOutOfRangeException(nameof(CronExpression),"表达式没有任何可以匹配的时,无法触发!"); + throw new CronAdjustNullException("Hours"); } DaysOfMonth = fields.Length > 3 ? GetRange(fields[3],1,31) : Enumerable.Range(1,31).ToList(); if(DaysOfMonth.Count == 0) { - throw new ArgumentOutOfRangeException(nameof(CronExpression),"表达式没有任何可以匹配的日,无法触发!"); + throw new CronAdjustNullException("DaysOfMonth"); } Months = fields.Length > 4 ? GetRange(fields[4],1,12) : Enumerable.Range(1,12).ToList(); if(Months.Count == 0) { - throw new ArgumentOutOfRangeException(nameof(CronExpression),"表达式没有任何可以匹配的月份,无法触发!"); + throw new CronAdjustNullException("Months"); } DaysOfWeek = fields.Length > 5 ? GetRange(fields[5],0,6) : Enumerable.Range(0,7).ToList(); if(DaysOfWeek.Count == 0) { - throw new ArgumentOutOfRangeException(nameof(CronExpression),"表达式没有任何可以匹配的星期几,无法触发!"); + throw new CronAdjustNullException("DaysOfWeek"); } var nowYear = DateTime.Now.Year; var endYear = Options.MaxYear; Years = fields.Length > 6 ? GetRange(fields[6],nowYear,endYear) : Enumerable.Range(nowYear,2).ToList(); if(Years.Count == 0) { - throw new ArgumentOutOfRangeException(nameof(CronExpression),"表达式没有任何可以匹配的年份,无法触发!"); + throw new CronAdjustNullException("Years"); } } @@ -236,7 +236,7 @@ namespace Falcon.SugarApi.TimedBackgroundTask var expChars = exp.ToCharArray().Distinct(); var outRangeChars = expChars.Where(a => a.NotIn(charList)).ToArray(); if(outRangeChars.Any()) { - throw new ArgumentOutOfRangeException(nameof(exp),$"非法字符{new string(outRangeChars)} 在表达式 {exp} 中"); + throw new CronException($"非法字符{new string(outRangeChars)} 在表达式 {exp} 中"); } var list = new List(); if(exp == "*") { @@ -273,12 +273,12 @@ namespace Falcon.SugarApi.TimedBackgroundTask list.Add(iei); } else { - throw new ArgumentException("给定的exp表达式错误,逗号分割的每一项都必须是数字",nameof(exp)); + throw new CronException("给定的exp表达式错误,逗号分割的每一项都必须是数字"); } } return list; } - throw new ArgumentException("提供的cron表达式错误",nameof(exp)); + return list; } }