using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations;
using Xunit;
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests.Migrations;
///
/// Idempotency tests for the ResyncLdapGroupMappingSeed migration (#70).
///
/// The original migration did an UNCONDITIONAL InsertData of the
/// Id=5 (SCADA-Viewers → Viewer) seed row. On any pre-existing DB that already
/// carried Id=5 that threw a PK violation and crash-looped startup (dev
/// auto-applies migrations). The fix makes the insert provider-aware and
/// idempotent (SQLite INSERT OR IGNORE / SQL Server guarded
/// IF NOT EXISTS … SET IDENTITY_INSERT … INSERT).
///
/// These tests drive the migration's Up() through the EF migrations SQL
/// pipeline configured for SQLite, so they exercise the exact
/// migrationBuilder.Sql(...) text the migration emits on the SQLite
/// branch — the branch a CI box without an MSSQL container can actually run.
/// The SQL Server branch is the same migrationBuilder.Sql text shape and
/// is exercised by the full-history MSSQL migration fixture
/// () used by the other migration tests.
///
public class ResyncLdapGroupMappingSeedMigrationTests
{
private const string SqliteProvider = "Microsoft.EntityFrameworkCore.Sqlite";
[Fact]
public void Up_OnSqlite_InsertsRow_WhenIdFiveMissing()
{
using var connection = OpenSeededConnection(seedIdFive: false);
ApplyMigrationUp(connection);
Assert.Equal(1, CountId5(connection));
Assert.Equal(("SCADA-Viewers", "Viewer"), ReadId5(connection));
}
[Fact]
public void Up_OnSqlite_IsIdempotent_WhenIdFiveAlreadyExists()
{
// Pre-existing DB already carries Id=5 — the #70 crash scenario.
using var connection = OpenSeededConnection(seedIdFive: true);
// Applying the migration MUST NOT throw on the PK clash...
var ex = Record.Exception(() => ApplyMigrationUp(connection));
Assert.Null(ex);
// ...and MUST NOT duplicate the row.
Assert.Equal(1, CountId5(connection));
Assert.Equal(("SCADA-Viewers", "Viewer"), ReadId5(connection));
}
[Fact]
public void Up_OnSqlite_ReplayingTwice_IsStillIdempotent()
{
using var connection = OpenSeededConnection(seedIdFive: false);
ApplyMigrationUp(connection);
var ex = Record.Exception(() => ApplyMigrationUp(connection));
Assert.Null(ex);
Assert.Equal(1, CountId5(connection));
}
// --- harness -----------------------------------------------------------
///
/// Opens a shared in-memory SQLite connection holding only the columns this
/// migration touches and (optionally) a pre-existing Id=5 row.
///
private static SqliteConnection OpenSeededConnection(bool seedIdFive)
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
using (var create = connection.CreateCommand())
{
// Mirror the production PK shape: Id is the primary key. Re-inserting
// an existing Id raises a UNIQUE/PK violation unless guarded.
create.CommandText =
"CREATE TABLE \"LdapGroupMappings\" (" +
" \"Id\" INTEGER NOT NULL CONSTRAINT \"PK_LdapGroupMappings\" PRIMARY KEY," +
" \"LdapGroupName\" TEXT NOT NULL," +
" \"Role\" TEXT NOT NULL);";
create.ExecuteNonQuery();
}
if (seedIdFive)
{
using var seed = connection.CreateCommand();
seed.CommandText =
"INSERT INTO \"LdapGroupMappings\" (\"Id\", \"LdapGroupName\", \"Role\") " +
"VALUES (5, 'SCADA-Viewers', 'Viewer');";
seed.ExecuteNonQuery();
}
return connection;
}
///
/// Generates the real migration's Up() SQL via the EF migrations
/// pipeline configured for SQLite (so migrationBuilder.ActiveProvider
/// is the SQLite provider and the SQLite branch is taken), then runs every
/// emitted command against the supplied connection.
///
private static void ApplyMigrationUp(SqliteConnection connection)
{
var options = new DbContextOptionsBuilder()
.UseSqlite(connection)
.Options;
using var context = new ScadaBridgeDbContext(options);
var migration = new ResyncLdapGroupMappingSeed { ActiveProvider = SqliteProvider };
var sqlGenerator = context.GetService();
var commands = sqlGenerator.Generate(migration.UpOperations);
foreach (var command in commands)
{
using var cmd = connection.CreateCommand();
cmd.CommandText = command.CommandText;
cmd.ExecuteNonQuery();
}
}
private static int CountId5(SqliteConnection connection)
{
using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT COUNT(*) FROM \"LdapGroupMappings\" WHERE \"Id\" = 5;";
return Convert.ToInt32(cmd.ExecuteScalar());
}
private static (string LdapGroupName, string Role) ReadId5(SqliteConnection connection)
{
using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT \"LdapGroupName\", \"Role\" FROM \"LdapGroupMappings\" WHERE \"Id\" = 5;";
using var reader = cmd.ExecuteReader();
Assert.True(reader.Read());
return (reader.GetString(0), reader.GetString(1));
}
}