Files
MBN_STOCK_WEBVIEW/tests/MBN_STOCK_WEBVIEW.Infrastructure.Tests/DatabaseFakes.cs

280 lines
8.2 KiB
C#

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<CancellationToken, Task> OpenAsync { get; init; } = _ => Task.CompletedTask;
public Func<CancellationToken, Task<DbDataReader>> ExecuteReaderAsync { get; init; } =
_ => Task.FromResult<DbDataReader>(new DataTable().CreateDataReader());
public bool Disposed { get; set; }
public int? ObservedCommandTimeoutSeconds { get; set; }
public static ConnectionPlan Success(DataTable data) => new()
{
ExecuteReaderAsync = _ => Task.FromResult<DbDataReader>(data.CreateDataReader())
};
}
internal sealed class FakeConnectionFactory : IDatabaseConnectionFactory
{
private readonly Queue<ConnectionPlan> _plans;
public FakeConnectionFactory(params ConnectionPlan[] plans)
{
_plans = new Queue<ConnectionPlan>(plans);
}
public int CreateCount { get; private set; }
public List<DbConnection> 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<DbDataReader> 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<DbParameter> _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));
}