|
| 1 | +using System.Data.Common; |
| 2 | +using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert; |
| 3 | +using static SqlServerSimulator.TestHelpers; |
| 4 | + |
| 5 | +namespace SqlServerSimulator; |
| 6 | + |
| 7 | +[TestClass] |
| 8 | +public sealed class ConvertTests |
| 9 | +{ |
| 10 | + [TestMethod] |
| 11 | + [DataRow("convert(int, '42')", 42)] |
| 12 | + [DataRow("convert(int, 1)", 1)] |
| 13 | + [DataRow("convert(bigint, 1)", 1L)] |
| 14 | + [DataRow("convert(varchar(10), 42)", "42")] |
| 15 | + [DataRow("convert(varchar, 12345)", "12345")] |
| 16 | + public void Convert_Basics(string expression, object expected) => |
| 17 | + AreEqual(expected, ExecuteScalar($"select {expression}")); |
| 18 | + |
| 19 | + [TestMethod] |
| 20 | + public void Convert_DecimalRoundsToTargetScale() => |
| 21 | + AreEqual(3.14m, ExecuteScalar("select convert(decimal(10,2), 3.14)")); |
| 22 | + |
| 23 | + [TestMethod] |
| 24 | + public void Convert_NullSourcePassesThroughTypedNull() => |
| 25 | + IsInstanceOfType<DBNull>(ExecuteScalar("select convert(int, null)")); |
| 26 | + |
| 27 | + /// <summary> |
| 28 | + /// Any NULL style argument propagates the entire result to NULL, even on non-applicable source types. |
| 29 | + /// </summary> |
| 30 | + [TestMethod] |
| 31 | + public void Convert_NullStyleReturnsNull() => |
| 32 | + IsInstanceOfType<DBNull>(ExecuteScalar("select convert(int, '42', null)")); |
| 33 | + |
| 34 | + [TestMethod] |
| 35 | + [DataRow("'abc'")] |
| 36 | + [DataRow("'42.5'")] |
| 37 | + public void Convert_StringParseFailure_StillThrows(string source) |
| 38 | + { |
| 39 | + var ex = Throws<DbException>(() => ExecuteScalar($"select convert(int, {source})")); |
| 40 | + Contains("Conversion failed", ex.Message); |
| 41 | + } |
| 42 | + |
| 43 | + [TestMethod] |
| 44 | + public void Convert_NarrowingOverflow_StillThrows() |
| 45 | + { |
| 46 | + var ex = Throws<DbException>(() => ExecuteScalar("select convert(tinyint, 300)")); |
| 47 | + Contains("Arithmetic overflow", ex.Message); |
| 48 | + } |
| 49 | + |
| 50 | + // --- Date/time × style 0/120/121 ----------------------------------- |
| 51 | + |
| 52 | + [TestMethod] |
| 53 | + [DataRow(0, "Mar 15 2024 10:20AM")] |
| 54 | + [DataRow(120, "2024-03-15 10:20:30")] |
| 55 | + [DataRow(121, "2024-03-15 10:20:30.000")] |
| 56 | + public void Convert_DateTime_StyleOutputs(int style, string expected) => |
| 57 | + AreEqual(expected, ExecuteScalar( |
| 58 | + $"select convert(varchar(30), cast('2024-03-15 10:20:30' as datetime), {style})")); |
| 59 | + |
| 60 | + [TestMethod] |
| 61 | + [DataRow(0, "Mar 15 2024 10:21AM")] |
| 62 | + [DataRow(120, "2024-03-15 10:21:00")] |
| 63 | + [DataRow(121, "2024-03-15 10:21:00.000")] |
| 64 | + public void Convert_SmallDateTime_StyleOutputs(int style, string expected) => |
| 65 | + AreEqual(expected, ExecuteScalar( |
| 66 | + $"select convert(varchar(30), cast('2024-03-15 10:21:00' as smalldatetime), {style})")); |
| 67 | + |
| 68 | + [TestMethod] |
| 69 | + [DataRow(0, "2024-03-15")] |
| 70 | + [DataRow(120, "2024-03-15")] |
| 71 | + [DataRow(121, "2024-03-15")] |
| 72 | + public void Convert_Date_StyleOutputs(int style, string expected) => |
| 73 | + AreEqual(expected, ExecuteScalar( |
| 74 | + $"select convert(varchar(30), cast('2024-03-15' as date), {style})")); |
| 75 | + |
| 76 | + [TestMethod] |
| 77 | + [DataRow(0, "Mar 15 2024 10:20AM")] // legacy form for explicit style 0 |
| 78 | + [DataRow(120, "2024-03-15 10:20:30")] // ISO without fractional |
| 79 | + [DataRow(121, "2024-03-15 10:20:30.1234567")] // precision-respecting |
| 80 | + public void Convert_DateTime2_StyleOutputs(int style, string expected) => |
| 81 | + AreEqual(expected, ExecuteScalar( |
| 82 | + $"select convert(varchar(40), cast('2024-03-15 10:20:30.1234567' as datetime2(7)), {style})")); |
| 83 | + |
| 84 | + [TestMethod] |
| 85 | + public void Convert_DateTime2_Precision0_Style121_OmitsFractional() => |
| 86 | + AreEqual("2024-03-15 10:20:30", ExecuteScalar( |
| 87 | + "select convert(varchar(40), cast('2024-03-15 10:20:30' as datetime2(0)), 121)")); |
| 88 | + |
| 89 | + [TestMethod] |
| 90 | + [DataRow(0, "10:20AM")] |
| 91 | + [DataRow(120, "10:20:30")] |
| 92 | + [DataRow(121, "10:20:30.123")] |
| 93 | + public void Convert_Time_StyleOutputs(int style, string expected) => |
| 94 | + AreEqual(expected, ExecuteScalar( |
| 95 | + $"select convert(varchar(40), cast('10:20:30.123' as time(3)), {style})")); |
| 96 | + |
| 97 | + [TestMethod] |
| 98 | + [DataRow(0, "Mar 15 2024 10:20AM -05:00")] |
| 99 | + [DataRow(120, "2024-03-15 10:20:30 -05:00")] |
| 100 | + [DataRow(121, "2024-03-15 10:20:30.123 -05:00")] |
| 101 | + public void Convert_DateTimeOffset_StyleOutputs(int style, string expected) => |
| 102 | + AreEqual(expected, ExecuteScalar( |
| 103 | + $"select convert(varchar(40), cast('2024-03-15 10:20:30.123 -05:00' as datetimeoffset(3)), {style})")); |
| 104 | + |
| 105 | + [TestMethod] |
| 106 | + public void Convert_NoStyle_MatchesCastDefault() |
| 107 | + { |
| 108 | + // No-style CONVERT mirrors CAST: datetime2 stays in ISO form, even |
| 109 | + // though explicit style 0 would switch it to legacy. |
| 110 | + AreEqual("2024-03-15 10:20:30.1234567", ExecuteScalar( |
| 111 | + "select convert(varchar(40), cast('2024-03-15 10:20:30.1234567' as datetime2(7)))")); |
| 112 | + AreEqual("Mar 15 2024 10:20AM", ExecuteScalar( |
| 113 | + "select convert(varchar(40), cast('2024-03-15 10:20:30' as datetime))")); |
| 114 | + } |
| 115 | + |
| 116 | + [TestMethod] |
| 117 | + public void Convert_NVarcharTarget_StyleStillApplies() => |
| 118 | + AreEqual("2024-03-15 10:20:30.000", ExecuteScalar( |
| 119 | + "select convert(nvarchar(30), cast('2024-03-15 10:20:30' as datetime), 121)")); |
| 120 | + |
| 121 | + // --- Style validation ---------------------------------------------- |
| 122 | + |
| 123 | + [TestMethod] |
| 124 | + [DataRow("datetime", "'2024-03-15 10:00:00'", "999")] |
| 125 | + [DataRow("datetime2", "'2024-03-15 10:00:00'", "999")] |
| 126 | + [DataRow("time", "'10:00:00'", "999")] |
| 127 | + [DataRow("datetimeoffset", "'2024-03-15 10:00:00 +00:00'", "999")] |
| 128 | + [DataRow("date", "'2024-03-15'", "999")] |
| 129 | + [DataRow("smalldatetime", "'2024-03-15 10:00:00'", "999")] |
| 130 | + public void Convert_BadStyle_RaisesMsg281(string typeName, string sourceLiteral, string style) |
| 131 | + { |
| 132 | + var ex = Throws<DbException>(() => ExecuteScalar( |
| 133 | + $"select convert(varchar(30), cast({sourceLiteral} as {typeName}), {style})")); |
| 134 | + AreEqual($"{style} is not a valid style number when converting from {typeName} to a character string.", ex.Message); |
| 135 | + } |
| 136 | + |
| 137 | + [TestMethod] |
| 138 | + public void Convert_StyleOnNonDateSource_SilentlyIgnored() |
| 139 | + { |
| 140 | + // Probe-confirmed: real SQL Server happily accepts a style argument |
| 141 | + // on int → varchar conversions and just ignores it. |
| 142 | + AreEqual("12345", ExecuteScalar("select convert(varchar(30), 12345, 120)")); |
| 143 | + AreEqual(42, ExecuteScalar("select convert(int, '42', 1)")); |
| 144 | + } |
| 145 | + |
| 146 | + [TestMethod] |
| 147 | + public void Convert_StringStyle_RaisesMsg8116() |
| 148 | + { |
| 149 | + var ex = Throws<DbException>(() => ExecuteScalar( |
| 150 | + "select convert(varchar(30), cast('2024-03-15' as datetime), '120')")); |
| 151 | + AreEqual("Argument data type varchar is invalid for argument 3 of convert function.", ex.Message); |
| 152 | + } |
| 153 | + |
| 154 | + // --- TRY_CONVERT --------------------------------------------------- |
| 155 | + |
| 156 | + [TestMethod] |
| 157 | + public void TryConvert_GoodValue_ReturnsValue() => |
| 158 | + AreEqual(42, ExecuteScalar("select try_convert(int, '42')")); |
| 159 | + |
| 160 | + [TestMethod] |
| 161 | + public void TryConvert_NullSource_ReturnsNull() => |
| 162 | + IsInstanceOfType<DBNull>(ExecuteScalar("select try_convert(int, null)")); |
| 163 | + |
| 164 | + [TestMethod] |
| 165 | + [DataRow("try_convert(int, 'abc')")] // Msg 245 |
| 166 | + [DataRow("try_convert(int, '42.5')")] // Msg 245 |
| 167 | + [DataRow("try_convert(tinyint, '300')")] // Msg 244 |
| 168 | + [DataRow("try_convert(int, '99999999999')")] // Msg 248 |
| 169 | + [DataRow("try_convert(uniqueidentifier, 'bad')")] // Msg 8169 |
| 170 | + [DataRow("try_convert(tinyint, 300)")] // Msg 8115 (overflow) |
| 171 | + [DataRow("try_convert(int, 'x', 1)")] // probe-confirmed: ignored style + failure → NULL |
| 172 | + public void TryConvert_ConversionFailure_ReturnsNull(string expression) => |
| 173 | + IsInstanceOfType<DBNull>(ExecuteScalar($"select {expression}")); |
| 174 | + |
| 175 | + [TestMethod] |
| 176 | + public void TryConvert_TruncatesDecimalToInt() => |
| 177 | + AreEqual(42, ExecuteScalar("select try_convert(int, 42.7)")); |
| 178 | + |
| 179 | + [TestMethod] |
| 180 | + public void TryConvert_ExplicitConversionNotAllowed_StillThrows() |
| 181 | + { |
| 182 | + // Msg 529: SQL Server still raises for type pairs where coercion |
| 183 | + // is fundamentally disallowed, even under TRY_CONVERT. |
| 184 | + var ex = Throws<DbException>(() => ExecuteScalar("select try_convert(date, 0)")); |
| 185 | + Contains("Explicit conversion", ex.Message); |
| 186 | + } |
| 187 | + |
| 188 | + [TestMethod] |
| 189 | + public void TryConvert_NullStyle_ReturnsNull() => |
| 190 | + IsInstanceOfType<DBNull>(ExecuteScalar("select try_convert(int, '42', null)")); |
| 191 | + |
| 192 | + // --- Syntax errors ------------------------------------------------- |
| 193 | + |
| 194 | + [TestMethod] |
| 195 | + public void Convert_MissingArgs_RaisesSyntaxError() |
| 196 | + { |
| 197 | + var ex = Throws<DbException>(() => ExecuteScalar("select convert(int)")); |
| 198 | + Contains("syntax", ex.Message); |
| 199 | + } |
| 200 | + |
| 201 | + [TestMethod] |
| 202 | + public void Convert_BadTargetType_RaisesMsg243() |
| 203 | + { |
| 204 | + var ex = Throws<DbException>(() => ExecuteScalar("select convert(notatype, 1)")); |
| 205 | + Contains("notatype", ex.Message); |
| 206 | + } |
| 207 | +} |
0 commit comments