using System.Collections; using System.Data; using System.Data.Common; using System.Diagnostics.CodeAnalysis; using MBN_STOCK_WEBVIEW.Infrastructure; using MMoneyCoderSharp.Data; namespace MBN_STOCK_WEBVIEW.Infrastructure.Tests; internal sealed class ConnectionPlan { public Func OpenAsync { get; init; } = _ => Task.CompletedTask; public Func> ExecuteReaderAsync { get; init; } = _ => Task.FromResult(new DataTable().CreateDataReader()); public bool Disposed { get; set; } public int? ObservedCommandTimeoutSeconds { get; set; } public static ConnectionPlan Success(DataTable data) => new() { ExecuteReaderAsync = _ => Task.FromResult(data.CreateDataReader()) }; } internal sealed class FakeConnectionFactory : IDatabaseConnectionFactory { private readonly Queue _plans; public FakeConnectionFactory(params ConnectionPlan[] plans) { _plans = new Queue(plans); } public int CreateCount { get; private set; } public List CreatedConnections { get; } = []; public DbConnection Create(DataSourceKind source) { CreateCount++; if (!_plans.TryDequeue(out var plan)) { throw new InvalidOperationException("No fake connection plan remains."); } var connection = new FakeDbConnection(plan); CreatedConnections.Add(connection); return connection; } public int GetCommandTimeoutSeconds(DataSourceKind source) => 5; } internal sealed class StubErrorDetector(bool transient) : ITransientDatabaseErrorDetector { public bool IsTransient(DataSourceKind source, Exception exception) => transient; public bool IsTimeout(DataSourceKind source, Exception exception) => exception is TimeoutException; public int? GetProviderErrorCode(DataSourceKind source, Exception exception) => null; } internal sealed class TestDatabaseException(string message) : Exception(message); internal sealed class FakeDbConnection(ConnectionPlan plan) : DbConnection { private ConnectionState _state = ConnectionState.Closed; [AllowNull] public override string ConnectionString { get; set; } = string.Empty; public override string Database => "FakeDatabase"; public override string DataSource => "FakeDataSource"; public override string ServerVersion => "1.0"; public override ConnectionState State => _state; public override void ChangeDatabase(string databaseName) { } public override void Close() => _state = ConnectionState.Closed; public override void Open() => OpenAsync(CancellationToken.None).GetAwaiter().GetResult(); public override async Task OpenAsync(CancellationToken cancellationToken) { await plan.OpenAsync(cancellationToken); _state = ConnectionState.Open; } protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) => throw new NotSupportedException(); protected override DbCommand CreateDbCommand() => new FakeDbCommand(this, plan); protected override void Dispose(bool disposing) { if (disposing) { plan.Disposed = true; _state = ConnectionState.Closed; } base.Dispose(disposing); } } internal sealed class FakeDbCommand(DbConnection connection, ConnectionPlan plan) : DbCommand { private readonly FakeDbParameterCollection _parameters = new(); [AllowNull] public override string CommandText { get; set; } = string.Empty; public override int CommandTimeout { get; set; } public override CommandType CommandType { get; set; } public override bool DesignTimeVisible { get; set; } public override UpdateRowSource UpdatedRowSource { get; set; } protected override DbConnection? DbConnection { get; set; } = connection; protected override DbParameterCollection DbParameterCollection => _parameters; protected override DbTransaction? DbTransaction { get; set; } public override void Cancel() { } public override int ExecuteNonQuery() => throw new NotSupportedException(); public override object? ExecuteScalar() => throw new NotSupportedException(); public override void Prepare() { } protected override DbParameter CreateDbParameter() => new FakeDbParameter(); protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => ExecuteDbDataReaderAsync(behavior, CancellationToken.None).GetAwaiter().GetResult(); protected override Task ExecuteDbDataReaderAsync( CommandBehavior behavior, CancellationToken cancellationToken) { plan.ObservedCommandTimeoutSeconds = CommandTimeout; return plan.ExecuteReaderAsync(cancellationToken); } } internal sealed class FakeDbParameter : DbParameter { public override DbType DbType { get; set; } public override ParameterDirection Direction { get; set; } public override bool IsNullable { get; set; } [AllowNull] public override string ParameterName { get; set; } = string.Empty; public override int Size { get; set; } [AllowNull] public override string SourceColumn { get; set; } = string.Empty; public override bool SourceColumnNullMapping { get; set; } public override object? Value { get; set; } public override void ResetDbType() { } } internal sealed class FakeDbParameterCollection : DbParameterCollection { private readonly List _parameters = []; public override int Count => _parameters.Count; public override object SyncRoot => ((ICollection)_parameters).SyncRoot; public override int Add(object value) { _parameters.Add(RequireParameter(value)); return _parameters.Count - 1; } public override void AddRange(Array values) { foreach (var value in values) { Add(value ?? throw new ArgumentNullException(nameof(values))); } } public override void Clear() => _parameters.Clear(); public override bool Contains(object value) => value is DbParameter parameter && _parameters.Contains(parameter); public override bool Contains(string value) => IndexOf(value) >= 0; public override void CopyTo(Array array, int index) => ((ICollection)_parameters).CopyTo(array, index); public override IEnumerator GetEnumerator() => _parameters.GetEnumerator(); public override int IndexOf(object value) => value is DbParameter parameter ? _parameters.IndexOf(parameter) : -1; public override int IndexOf(string parameterName) => _parameters.FindIndex( parameter => string.Equals( parameter.ParameterName, parameterName, StringComparison.OrdinalIgnoreCase)); public override void Insert(int index, object value) => _parameters.Insert(index, RequireParameter(value)); public override void Remove(object value) { if (value is DbParameter parameter) { _parameters.Remove(parameter); } } public override void RemoveAt(int index) => _parameters.RemoveAt(index); public override void RemoveAt(string parameterName) { var index = IndexOf(parameterName); if (index >= 0) { RemoveAt(index); } } protected override DbParameter GetParameter(int index) => _parameters[index]; protected override DbParameter GetParameter(string parameterName) { var index = IndexOf(parameterName); return index >= 0 ? _parameters[index] : throw new IndexOutOfRangeException(); } protected override void SetParameter(int index, DbParameter value) => _parameters[index] = value; protected override void SetParameter(string parameterName, DbParameter value) { var index = IndexOf(parameterName); if (index >= 0) { _parameters[index] = value; } else { _parameters.Add(value); } } private static DbParameter RequireParameter(object value) => value as DbParameter ?? throw new ArgumentException("A DbParameter is required.", nameof(value)); }