feat: complete Oracle and MariaDB WebView data layer
This commit is contained in:
279
tests/MBN_STOCK_WEBVIEW.Infrastructure.Tests/DatabaseFakes.cs
Normal file
279
tests/MBN_STOCK_WEBVIEW.Infrastructure.Tests/DatabaseFakes.cs
Normal file
@@ -0,0 +1,279 @@
|
||||
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));
|
||||
}
|
||||
Reference in New Issue
Block a user