using System.ComponentModel; using System.Reflection; using Microsoft.Data.SqlClient; namespace ZB.MOM.WW.OtOpcUa.ControlPlane.Tests.Harness; /// /// Builds real s for tests. The type has no public constructor, so this goes through its /// internal factory by reflection — the same objects SQL Server failures produce, carrying a real error number. /// public static class SqlExceptionFactory { private const BindingFlags NonPublic = BindingFlags.NonPublic | BindingFlags.Instance; /// A with one error of the given number. /// The SQL Server error number (e.g. 2628, 1205, -2). /// The error text. /// An inner exception (e.g. a for a timeout). /// The exception. public static SqlException Create(int number, string message, Exception? inner = null) { var errorCtor = typeof(SqlError).GetConstructors(NonPublic) .OrderByDescending(c => c.GetParameters().Length) .First(); var args = errorCtor.GetParameters().Select(p => p.Name switch { "infoNumber" => (object?)number, "errorMessage" => message, "server" => "test-sql", "procedure" => string.Empty, "exception" => inner, "errorClass" => (byte)16, _ => p.ParameterType == typeof(string) ? string.Empty : p.ParameterType.IsValueType ? Activator.CreateInstance(p.ParameterType) : null, }).ToArray(); var error = (SqlError)errorCtor.Invoke(args); var collection = (SqlErrorCollection)Activator.CreateInstance(typeof(SqlErrorCollection), nonPublic: true)!; typeof(SqlErrorCollection).GetMethod("Add", NonPublic)!.Invoke(collection, [error]); var create = typeof(SqlException) .GetMethods(BindingFlags.NonPublic | BindingFlags.Static) .Where(m => m.Name == "CreateException") .Select(m => (Method: m, Params: m.GetParameters())) .Where(x => x.Params.Length >= 2 && x.Params[0].ParameterType == typeof(SqlErrorCollection) && x.Params[1].ParameterType == typeof(string)) .OrderBy(x => x.Params.Length) .First(); var createArgs = create.Params.Select((p, i) => i switch { 0 => collection, 1 => (object?)"16.0.0", _ => p.ParameterType == typeof(Exception) ? inner : p.ParameterType.IsValueType ? Activator.CreateInstance(p.ParameterType) : null, }).ToArray(); return (SqlException)create.Method.Invoke(null, createArgs)!; } /// A command timeout as SQL Server reports it: number -2 over a Win32 258 (WAIT_TIMEOUT). /// The exception. public static SqlException Timeout() => Create(-2, "Execution Timeout Expired.", new Win32Exception(258, "The wait operation timed out.")); }