using Microsoft.Data.SqlClient;
using Xunit;
namespace ScadaLink.ConfigurationDatabase.Tests.Migrations;
///
/// Bundle B2 (#22, #23 M3) integration tests for the AddSiteCallsTable
/// migration: applies the EF migrations to a freshly-created MSSQL test database
/// on the running infra/mssql container and asserts that the resulting
/// SiteCalls table carries the expected columns, primary key, and the
/// two named operational indexes.
///
///
/// Unlike AddAuditLogTable, the SiteCalls table is operational (mutable)
/// state — no partition function, no partition scheme, no DB-role restriction.
/// Standard [PRIMARY] filegroup. Tests pair
/// with Skip.IfNot(...) so the runner reports them as Skipped (not Passed)
/// when MSSQL is unreachable. The fixture applies the migration once at
/// construction time.
///
public class AddSiteCallsTableMigrationTests : IClassFixture
{
private readonly MsSqlMigrationFixture _fixture;
public AddSiteCallsTableMigrationTests(MsSqlMigrationFixture fixture)
{
_fixture = fixture;
}
[SkippableFact]
public async Task AppliesMigration_CreatesSiteCallsTable_WithExpectedColumns()
{
Skip.IfNot(_fixture.Available, _fixture.SkipReason);
var exists = await ScalarAsync(
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES " +
"WHERE TABLE_NAME = 'SiteCalls' AND TABLE_SCHEMA = 'dbo';");
Assert.Equal(1, exists);
// Every required column from SiteCall + IngestedAtUtc. We don't pin types
// here because EF's CreateTable layer already encodes them; the
// entity-config tests cover length / unicode / nullability for the
// value-converted PK column. Just confirm the schema has all twelve.
var expectedColumns = new[]
{
"TrackedOperationId",
"Channel",
"Target",
"SourceSite",
"Status",
"RetryCount",
"LastError",
"HttpStatus",
"CreatedAtUtc",
"UpdatedAtUtc",
"TerminalAtUtc",
"IngestedAtUtc",
};
foreach (var column in expectedColumns)
{
var present = await ScalarAsync(
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS " +
$"WHERE TABLE_NAME = 'SiteCalls' AND COLUMN_NAME = '{column}';");
Assert.True(present == 1, $"Expected SiteCalls.{column} to exist; found {present}.");
}
}
[SkippableFact]
public async Task AppliesMigration_CreatesPK_OnTrackedOperationId()
{
Skip.IfNot(_fixture.Available, _fixture.SkipReason);
// Walk sys.indexes for the table's clustered PK index and confirm its
// single key column is TrackedOperationId. SiteCalls is non-partitioned
// so the PK is a simple single-column clustered index.
var pkColumn = await ScalarAsync(
"SELECT c.name FROM sys.indexes i " +
"INNER JOIN sys.objects o ON i.object_id = o.object_id " +
"INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id " +
"INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id " +
"WHERE o.name = 'SiteCalls' AND i.is_primary_key = 1;");
Assert.Equal("TrackedOperationId", pkColumn);
}
[SkippableFact]
public async Task AppliesMigration_CreatesIndex_Source_Created()
{
Skip.IfNot(_fixture.Available, _fixture.SkipReason);
var count = await ScalarAsync(
"SELECT COUNT(*) FROM sys.indexes i " +
"INNER JOIN sys.objects o ON i.object_id = o.object_id " +
"WHERE o.name = 'SiteCalls' AND i.name = 'IX_SiteCalls_Source_Created';");
Assert.Equal(1, count);
}
[SkippableFact]
public async Task AppliesMigration_CreatesIndex_Status_Updated()
{
Skip.IfNot(_fixture.Available, _fixture.SkipReason);
var count = await ScalarAsync(
"SELECT COUNT(*) FROM sys.indexes i " +
"INNER JOIN sys.objects o ON i.object_id = o.object_id " +
"WHERE o.name = 'SiteCalls' AND i.name = 'IX_SiteCalls_Status_Updated';");
Assert.Equal(1, count);
}
// --- helpers ------------------------------------------------------------
private async Task ScalarAsync(string sql)
{
await using var conn = _fixture.OpenConnection();
await using var cmd = conn.CreateCommand();
cmd.CommandText = sql;
var result = await cmd.ExecuteScalarAsync();
if (result is null || result is DBNull)
{
return default!;
}
return (T)Convert.ChangeType(result, typeof(T) == typeof(string) ? typeof(string) : Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T))!;
}
}