using System.Collections; using System.Data; using System.Data.Common; using Omika.Data.Configuration; using Omika.Data.Connection; namespace Omika.Tests.Connection; public class HfsqlConnectionTests { private static HfsqlOptions CreateOptions() => new() { Driver = "HFSQL Client/Server", Server = "hfsql-serveur", Port = 4900, DatabaseName = "Omika" }; [Fact] public void InitialState_IsClosed() { var connection = new HfsqlConnection(CreateOptions(), new FakeFactory()); Assert.Equal(HfsqlConnectionState.Closed, connection.State); } [Fact] public async Task OpenAsync_Success_SetsOpen() { var connection = new HfsqlConnection(CreateOptions(), new FakeFactory()); await connection.OpenAsync(); Assert.Equal(HfsqlConnectionState.Open, connection.State); } [Fact] public async Task OpenAsync_Failure_SetsFaulted() { var factory = new FakeFactory(throwOnOpen: true); var connection = new HfsqlConnection(CreateOptions(), factory); await Assert.ThrowsAsync(() => connection.OpenAsync()); Assert.Equal(HfsqlConnectionState.Faulted, connection.State); } [Fact] public async Task OpenAsync_Twice_DoesNotCreateSecondConnection() { var factory = new FakeFactory(); var connection = new HfsqlConnection(CreateOptions(), factory); await connection.OpenAsync(); await connection.OpenAsync(); Assert.Equal(HfsqlConnectionState.Open, connection.State); Assert.Equal(1, factory.CreatedCount); } [Fact] public async Task CloseAsync_SetsClosed() { var connection = new HfsqlConnection(CreateOptions(), new FakeFactory()); await connection.OpenAsync(); await connection.CloseAsync(); Assert.Equal(HfsqlConnectionState.Closed, connection.State); } [Fact] public async Task CloseAsync_Twice_DoesNotThrow() { var connection = new HfsqlConnection(CreateOptions(), new FakeFactory()); await connection.OpenAsync(); await connection.CloseAsync(); await connection.CloseAsync(); Assert.Equal(HfsqlConnectionState.Closed, connection.State); } [Fact] public async Task DisposeAsync_ClosesConnection() { var connection = new HfsqlConnection(CreateOptions(), new FakeFactory()); await connection.OpenAsync(); await connection.DisposeAsync(); Assert.Equal(HfsqlConnectionState.Closed, connection.State); } [Fact] public async Task ExecuteScalarAsync_WhenClosed_Throws() { var connection = new HfsqlConnection(CreateOptions(), new FakeFactory()); await Assert.ThrowsAsync( () => connection.ExecuteScalarAsync("SELECT 1")); } [Fact] public async Task ExecuteScalarAsync_WhenOpen_ReturnsValue() { var factory = new FakeFactory(); var connection = new HfsqlConnection(CreateOptions(), factory); await connection.OpenAsync(); var result = await connection.ExecuteScalarAsync("SELECT 1"); Assert.Equal(42, result); } [Fact] public async Task ExecuteNonQueryAsync_WhenOpen_ReturnsAffectedRows() { var factory = new FakeFactory(); var connection = new HfsqlConnection(CreateOptions(), factory); await connection.OpenAsync(); var result = await connection.ExecuteNonQueryAsync("UPDATE T SET X = 1"); Assert.Equal(3, result); } [Fact] public async Task QueryAsync_WhenOpen_ReturnsRows() { var factory = new FakeFactory(); var connection = new HfsqlConnection(CreateOptions(), factory); await connection.OpenAsync(); var rows = await connection.QueryAsync("SELECT * FROM T"); Assert.Single(rows); Assert.Equal("valeur", rows[0]["Col"]); } [Fact] public async Task ExecuteScalarAsync_EmptySql_Throws() { var connection = new HfsqlConnection(CreateOptions(), new FakeFactory()); await connection.OpenAsync(); await Assert.ThrowsAsync( () => connection.ExecuteScalarAsync(" ")); } [Fact] public async Task GetColumnsAsync_WhenOpen_ReturnsColumns() { var factory = new FakeFactory(); var connection = new HfsqlConnection(CreateOptions(), factory); await connection.OpenAsync(); var columns = await connection.GetColumnsAsync("Lieu"); Assert.Equal(2, columns.Count); Assert.Contains(columns, c => c.ColumnName == "LI_PKID"); Assert.Contains(columns, c => c.ColumnName == "LI_CleType"); } [Fact] public async Task GetColumnsAsync_WhenClosed_Throws() { var connection = new HfsqlConnection(CreateOptions(), new FakeFactory()); await Assert.ThrowsAsync( () => connection.GetColumnsAsync("Lieu")); } private sealed class FakeFactory : IHfsqlConnectionFactory { private readonly bool _throwOnOpen; public FakeFactory(bool throwOnOpen = false) { _throwOnOpen = throwOnOpen; } public int CreatedCount { get; private set; } public DbConnection Create(HfsqlOptions options) { CreatedCount++; return new FakeDbConnection(_throwOnOpen); } } private sealed class FakeDbConnection : DbConnection { private readonly bool _throwOnOpen; private ConnectionState _state = ConnectionState.Closed; public FakeDbConnection(bool throwOnOpen) { _throwOnOpen = throwOnOpen; } public override string ConnectionString { get; set; } = string.Empty; public override string Database => "Omika"; public override string DataSource => "fake"; 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() { if (_throwOnOpen) throw new InvalidOperationException("Échec contrôlé de l'ouverture."); _state = ConnectionState.Open; } protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) => throw new NotSupportedException(); protected override DbCommand CreateDbCommand() => new FakeDbCommand(this); public override DataTable GetSchema(string collectionName, string[] restrictionValues) { var table = new DataTable(); table.Columns.Add("COLUMN_NAME", typeof(string)); table.Rows.Add("LI_PKID"); table.Rows.Add("LI_CleType"); return table; } } private sealed class FakeDbCommand : DbCommand { private readonly FakeDbConnection _connection; public FakeDbCommand(FakeDbConnection connection) { _connection = connection; } 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; } = null; protected override DbParameterCollection DbParameterCollection { get; } = null!; protected override DbTransaction? DbTransaction { get; set; } = null; public override void Cancel() { } public override int ExecuteNonQuery() => 3; public override object? ExecuteScalar() => 42; public override void Prepare() { } protected override DbParameter CreateDbParameter() => throw new NotSupportedException(); protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => new FakeDbDataReader(); } private sealed class FakeDbDataReader : DbDataReader { private int _readCount; public override int FieldCount => 1; public override bool HasRows => true; public override bool IsClosed => false; public override int RecordsAffected => 0; public override object this[int ordinal] => "valeur"; public override object this[string name] => "valeur"; public override bool Read() => _readCount++ == 0; public override bool NextResult() => false; public override IEnumerator GetEnumerator() => throw new NotSupportedException(); public override int Depth => 0; public override string GetName(int ordinal) => "Col"; public override int GetOrdinal(string name) => 0; public override string GetDataTypeName(int ordinal) => "string"; public override Type GetFieldType(int ordinal) => typeof(string); public override object GetValue(int ordinal) => "valeur"; public override int GetValues(object[] values) { values[0] = "valeur"; return 1; } public override bool IsDBNull(int ordinal) => false; public override bool GetBoolean(int ordinal) => throw new NotSupportedException(); public override byte GetByte(int ordinal) => throw new NotSupportedException(); public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length) => throw new NotSupportedException(); public override char GetChar(int ordinal) => throw new NotSupportedException(); public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length) => throw new NotSupportedException(); public override DateTime GetDateTime(int ordinal) => throw new NotSupportedException(); public override decimal GetDecimal(int ordinal) => throw new NotSupportedException(); public override double GetDouble(int ordinal) => throw new NotSupportedException(); public override float GetFloat(int ordinal) => throw new NotSupportedException(); public override Guid GetGuid(int ordinal) => throw new NotSupportedException(); public override short GetInt16(int ordinal) => throw new NotSupportedException(); public override int GetInt32(int ordinal) => throw new NotSupportedException(); public override long GetInt64(int ordinal) => throw new NotSupportedException(); public override string GetString(int ordinal) => throw new NotSupportedException(); } }