|
| 1 | +using System.Data.Common; |
| 2 | +using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert; |
| 3 | +using static SqlServerSimulator.TestHelpers; |
| 4 | + |
| 5 | +namespace SqlServerSimulator; |
| 6 | + |
| 7 | +/// <summary> |
| 8 | +/// Direct simulator coverage for the <c>DATEPART</c> and <c>DATEADD</c> |
| 9 | +/// scalar functions: keyword resolution (canonical + aliases), per-input-type |
| 10 | +/// extraction / addition semantics, NULL propagation, the cross-type |
| 11 | +/// compatibility rejection (Msg 9810), and the overflow path on DATEADD |
| 12 | +/// (Msg 517). The EF Core wire path is exercised in <c>EFCoreDateTime.cs</c>; |
| 13 | +/// these tests cover SQL-only edges EF Core doesn't reach. |
| 14 | +/// </summary> |
| 15 | +[TestClass] |
| 16 | +public sealed class DatePartTests |
| 17 | +{ |
| 18 | + [TestMethod] |
| 19 | + [DataRow("year", 2024)] |
| 20 | + [DataRow("month", 6)] |
| 21 | + [DataRow("day", 15)] |
| 22 | + [DataRow("dayofyear", 167)] |
| 23 | + [DataRow("quarter", 2)] |
| 24 | + [DataRow("hour", 13)] |
| 25 | + [DataRow("minute", 45)] |
| 26 | + [DataRow("second", 30)] |
| 27 | + [DataRow("millisecond", 500)] |
| 28 | + public void DatePart_OnDateTime2_ReturnsExpectedComponent(string part, int expected) => |
| 29 | + AreEqual(expected, ExecuteScalar($"select datepart({part}, cast('2024-06-15 13:45:30.5' as datetime2(7)))")); |
| 30 | + |
| 31 | + [TestMethod] |
| 32 | + [DataRow("yy", 2024)] |
| 33 | + [DataRow("yyyy", 2024)] |
| 34 | + [DataRow("mm", 6)] |
| 35 | + [DataRow("dd", 15)] |
| 36 | + [DataRow("hh", 13)] |
| 37 | + [DataRow("mi", 45)] |
| 38 | + [DataRow("ss", 30)] |
| 39 | + public void DatePart_AcceptsCommonKeywordAliases(string alias, int expected) => |
| 40 | + AreEqual(expected, ExecuteScalar($"select datepart({alias}, cast('2024-06-15 13:45:30' as datetime2(0)))")); |
| 41 | + |
| 42 | + [TestMethod] |
| 43 | + public void DatePart_OnDate_AcceptsDateParts() => |
| 44 | + AreEqual(2024, ExecuteScalar("select datepart(year, cast('2024-06-15' as date))")); |
| 45 | + |
| 46 | + [TestMethod] |
| 47 | + public void DatePart_OnTime_AcceptsTimeParts() => |
| 48 | + AreEqual(13, ExecuteScalar("select datepart(hour, cast('13:45:30' as time))")); |
| 49 | + |
| 50 | + [TestMethod] |
| 51 | + public void DatePart_OnDateTimeOffset_AcceptsTzOffset() => |
| 52 | + AreEqual(-420, ExecuteScalar("select datepart(tzoffset, cast('2024-06-15 13:45:30 -07:00' as datetimeoffset))")); |
| 53 | + |
| 54 | + [TestMethod] |
| 55 | + public void DatePart_NullInput_ReturnsNullInt() => |
| 56 | + IsInstanceOfType<DBNull>(ExecuteScalar("select datepart(year, cast(null as datetime2))")); |
| 57 | + |
| 58 | + [TestMethod] |
| 59 | + public void DatePart_UnknownKeyword_RaisesMsg155() |
| 60 | + { |
| 61 | + var ex = Throws<DbException>(() => ExecuteScalar("select datepart(badpart, getdate())")); |
| 62 | + AreEqual("'badpart' is not a recognized datepart option.", ex.Message); |
| 63 | + AreEqual("155", ex.Data["HelpLink.EvtID"]); |
| 64 | + } |
| 65 | + |
| 66 | + [TestMethod] |
| 67 | + public void DatePart_HourOnDate_RaisesMsg9810() |
| 68 | + { |
| 69 | + var ex = Throws<DbException>(() => ExecuteScalar("select datepart(hour, cast('2024-06-15' as date))")); |
| 70 | + AreEqual("The datepart hour is not supported by date function datepart for data type date.", ex.Message); |
| 71 | + AreEqual("9810", ex.Data["HelpLink.EvtID"]); |
| 72 | + } |
| 73 | + |
| 74 | + [TestMethod] |
| 75 | + public void DatePart_YearOnTime_RaisesMsg9810() |
| 76 | + { |
| 77 | + var ex = Throws<DbException>(() => ExecuteScalar("select datepart(year, cast('13:45:30' as time))")); |
| 78 | + AreEqual("The datepart year is not supported by date function datepart for data type time.", ex.Message); |
| 79 | + } |
| 80 | + |
| 81 | + [TestMethod] |
| 82 | + public void DateAdd_DayOnDate_PreservesDateType() |
| 83 | + { |
| 84 | + var simulation = new Simulation(); |
| 85 | + _ = simulation.ExecuteNonQuery("create table t (d date)"); |
| 86 | + _ = simulation.ExecuteNonQuery("insert t values ('2024-06-15')"); |
| 87 | + AreEqual(new DateTime(2024, 6, 22), simulation.ExecuteScalar("select dateadd(day, 7, d) from t")); |
| 88 | + } |
| 89 | + |
| 90 | + [TestMethod] |
| 91 | + public void DateAdd_HourOnTime_PreservesTimeType() |
| 92 | + { |
| 93 | + var simulation = new Simulation(); |
| 94 | + _ = simulation.ExecuteNonQuery("create table t (h time(0))"); |
| 95 | + _ = simulation.ExecuteNonQuery("insert t values ('13:45')"); |
| 96 | + AreEqual(new TimeSpan(16, 45, 0), simulation.ExecuteScalar("select dateadd(hour, 3, h) from t")); |
| 97 | + } |
| 98 | + |
| 99 | + [TestMethod] |
| 100 | + public void DateAdd_NegativeN_SubtractsFromValue() => |
| 101 | + AreEqual(new DateTime(2023, 6, 15), ExecuteScalar("select dateadd(year, -1, cast('2024-06-15' as datetime2(0)))")); |
| 102 | + |
| 103 | + [TestMethod] |
| 104 | + public void DateAdd_NullValue_ReturnsTypedNull() => |
| 105 | + IsInstanceOfType<DBNull>(ExecuteScalar("select dateadd(day, 1, cast(null as datetime2))")); |
| 106 | + |
| 107 | + [TestMethod] |
| 108 | + public void DateAdd_HourOnDate_RaisesMsg9810() |
| 109 | + { |
| 110 | + var ex = Throws<DbException>(() => ExecuteScalar("select dateadd(hour, 1, cast('2024-06-15' as date))")); |
| 111 | + AreEqual("The datepart hour is not supported by date function dateadd for data type date.", ex.Message); |
| 112 | + AreEqual("9810", ex.Data["HelpLink.EvtID"]); |
| 113 | + } |
| 114 | + |
| 115 | + [TestMethod] |
| 116 | + public void DateAdd_DayOnTime_RaisesMsg9810() |
| 117 | + { |
| 118 | + var ex = Throws<DbException>(() => ExecuteScalar("select dateadd(day, 1, cast('13:45' as time))")); |
| 119 | + AreEqual("The datepart day is not supported by date function dateadd for data type time.", ex.Message); |
| 120 | + } |
| 121 | + |
| 122 | + [TestMethod] |
| 123 | + public void DateAdd_YearOverflowOnDate_RaisesMsg517() |
| 124 | + { |
| 125 | + var ex = Throws<DbException>(() => ExecuteScalar("select dateadd(year, 100000, cast('2024-06-15' as date))")); |
| 126 | + AreEqual("Adding a value to a 'date' column caused an overflow.", ex.Message); |
| 127 | + AreEqual("517", ex.Data["HelpLink.EvtID"]); |
| 128 | + } |
| 129 | + |
| 130 | + [TestMethod] |
| 131 | + public void DateAdd_TzOffsetOnDateTimeOffset_ShiftsWallClock() |
| 132 | + { |
| 133 | + // DATEADD(tzoffset, +60, ...) preserves the UTC instant but shifts |
| 134 | + // the rendered offset by 60 minutes. |
| 135 | + var v = (DateTimeOffset)ExecuteScalar("select dateadd(tzoffset, 60, cast('2024-06-15 13:00:00 +00:00' as datetimeoffset(0)))")!; |
| 136 | + AreEqual(TimeSpan.FromHours(1), v.Offset); |
| 137 | + AreEqual(new DateTime(2024, 6, 15, 14, 0, 0), v.DateTime); |
| 138 | + } |
| 139 | +} |
0 commit comments