sqlinsight-pro/tests/SQLInsight.Connectivity.Tests/SqlConnectionFactoryTests.cs
Claude f8aa0c307b
Some checks are pending
CI / Build og unit tests (push) Waiting to run
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Blocked by required conditions
Fase 0: solution structure, capability model, rule engine, least-privilege script
- .NET 10 solution with Core, Connectivity, Rules, Contracts, Agent, Web and Hub
- Capability model gating collectors and rules by SQL Server version (2000 and later);
  rules that cannot run are reported as not measurable, never as passed
- Connectivity via Microsoft.Data.SqlClient with ODBC fallback for legacy instances,
  hardened monitoring session settings valid back to SQL Server 2000
- Support lifecycle rule (SQLI-VER-001) with lifecycle data shipped as a rule pack
- Version-aware least-privilege permissions script, verified by integration tests
  against SQL Server in Docker (reads monitoring sources, cannot read customer data)
- Forgejo Actions CI, contributing guide, code of conduct, issue templates,
  legacy version notes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E3EExwndy8Z22EK2Qpt6BK
2026-09-29 05:40:49 +00:00

146 lines
5.6 KiB
C#

using System.Data;
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
using System.Security.Authentication;
namespace SQLInsight.Connectivity.Tests;
public class SqlConnectionFactoryTests
{
private static readonly ConnectionTarget Target = new() { Server = "sql01" };
[Fact]
public async Task Auto_falls_back_to_odbc_on_legacy_failure()
{
var odbc = new FakeConnection();
var factory = new SqlConnectionFactory(
_ => new FakeConnection(new AuthenticationException("TLS 1.0 only")),
_ => odbc);
await using var opened = await factory.OpenAsync(Target);
Assert.Equal(ConnectionProvider.Odbc, opened.Provider);
Assert.True(opened.UsedLegacyFallback);
Assert.Equal(SqlConnectionFactory.SessionSettings, odbc.ExecutedCommands.Single());
}
[Fact]
public async Task Auto_does_not_fall_back_on_login_failure()
{
var odbcCreated = false;
var factory = new SqlConnectionFactory(
_ => new FakeConnection(new InvalidOperationException("Login failed for user 'x'.")),
_ =>
{
odbcCreated = true;
return new FakeConnection();
});
await Assert.ThrowsAsync<InvalidOperationException>(() => factory.OpenAsync(Target));
Assert.False(odbcCreated);
}
[Fact]
public async Task Explicit_sqlclient_never_falls_back()
{
var factory = new SqlConnectionFactory(
_ => new FakeConnection(new AuthenticationException("TLS 1.0 only")),
_ => new FakeConnection());
await Assert.ThrowsAsync<AuthenticationException>(
() => factory.OpenAsync(Target with { Provider = ConnectionProvider.SqlClient }));
}
[Fact]
public async Task Session_settings_are_applied_on_open()
{
var sqlClient = new FakeConnection();
var factory = new SqlConnectionFactory(_ => sqlClient, _ => new FakeConnection());
await using var opened = await factory.OpenAsync(Target);
Assert.False(opened.UsedLegacyFallback);
Assert.Equal(SqlConnectionFactory.SessionSettings, sqlClient.ExecutedCommands.Single());
}
[Fact]
public async Task Failed_connection_is_disposed()
{
var failing = new FakeConnection(new InvalidOperationException("Login failed"));
var factory = new SqlConnectionFactory(_ => failing, _ => new FakeConnection());
await Assert.ThrowsAsync<InvalidOperationException>(() => factory.OpenAsync(Target));
Assert.True(failing.IsDisposed);
}
[SuppressMessage("Design", "CA1065", Justification = "Test double; unused members throw.")]
private sealed class FakeConnection(Exception? openFailure = null) : DbConnection
{
private ConnectionState _state = ConnectionState.Closed;
public List<string> ExecutedCommands { get; } = [];
public bool IsDisposed { get; private set; }
[AllowNull]
public override string ConnectionString { get; set; } = string.Empty;
public override string Database => "master";
public override string DataSource => "fake";
public override string ServerVersion => "16.0.0.0";
public override ConnectionState State => _state;
public override void Open()
{
if (openFailure is not null) throw openFailure;
_state = ConnectionState.Open;
}
public override Task OpenAsync(CancellationToken cancellationToken)
{
Open();
return Task.CompletedTask;
}
public override void Close() => _state = ConnectionState.Closed;
public override void ChangeDatabase(string databaseName) => throw new NotSupportedException();
protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) => throw new NotSupportedException();
protected override DbCommand CreateDbCommand() => new FakeCommand(this);
protected override void Dispose(bool disposing)
{
IsDisposed = true;
base.Dispose(disposing);
}
public override ValueTask DisposeAsync()
{
IsDisposed = true;
return base.DisposeAsync();
}
private sealed class FakeCommand(FakeConnection owner) : DbCommand
{
[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; }
protected override DbParameterCollection DbParameterCollection => throw new NotSupportedException();
protected override DbTransaction? DbTransaction { get; set; }
public override int ExecuteNonQuery()
{
owner.ExecutedCommands.Add(CommandText);
return 0;
}
public override Task<int> ExecuteNonQueryAsync(CancellationToken cancellationToken) => Task.FromResult(ExecuteNonQuery());
public override object? ExecuteScalar() => throw new NotSupportedException();
public override void Prepare() { }
public override void Cancel() { }
protected override DbParameter CreateDbParameter() => throw new NotSupportedException();
protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => throw new NotSupportedException();
}
}
}