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."));
}