using System.Reflection; using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Migrations; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; namespace SeaHavenIndustries.Tests; public class SH221LocationAccountScopeSqlServerTests { private const string LocalDbMaster = @"Server=(localdb)\MSSQLLocalDB;Database=master;Trusted_Connection=True;TrustServerCertificate=True;Connect Timeout=3"; [Fact] public async Task SH221_LocationAccountScope_ApplyOnSqlServer_WhenLocalDbAvailable() { if (!await IsLocalDbAvailableAsync()) return; var dbName = $"SH221LocationAccount_{Guid.NewGuid():N}"; var connectionString = $@"Server=(localdb)\MSSQLLocalDB;Database={dbName};Trusted_Connection=True;TrustServerCertificate=True"; try { await CreateDatabaseAsync(dbName); await using var connection = new SqlConnection(connectionString); await connection.OpenAsync(); await using (var createTables = connection.CreateCommand()) { createTables.CommandText = """ CREATE TABLE Accounts ( Id int NOT NULL IDENTITY PRIMARY KEY ); CREATE TABLE Locations ( Id int NOT NULL IDENTITY PRIMARY KEY ); CREATE TABLE workOrders ( Id int NOT NULL IDENTITY PRIMARY KEY, LocationId int NULL, AccountId int NULL ); SET IDENTITY_INSERT Accounts ON; INSERT INTO Accounts (Id) VALUES (1); SET IDENTITY_INSERT Accounts OFF; SET IDENTITY_INSERT Locations ON; INSERT INTO Locations (Id) VALUES (11); SET IDENTITY_INSERT Locations OFF; INSERT INTO workOrders (LocationId, AccountId) VALUES (11, 1); """; await createTables.ExecuteNonQueryAsync(); } var options = new DbContextOptionsBuilder() .UseSqlServer(connection) .Options; await using var context = new ApplicationDbContext(options); await ApplyMigrationUpAsync(context, new SH221_LocationAccountScope()); await using (var assertCmd = connection.CreateCommand()) { assertCmd.CommandText = """ SELECT c.name FROM sys.columns c WHERE c.object_id = OBJECT_ID(N'Locations') AND c.name = N'AccountId'; """; var column = await assertCmd.ExecuteScalarAsync(); Assert.Equal("AccountId", column); } await using (var indexCmd = connection.CreateCommand()) { indexCmd.CommandText = """ SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID(N'Locations') AND name = N'IX_Locations_AccountId'; """; var index = await indexCmd.ExecuteScalarAsync(); Assert.Equal("IX_Locations_AccountId", index); } await using (var fkCmd = connection.CreateCommand()) { fkCmd.CommandText = """ SELECT name FROM sys.foreign_keys WHERE parent_object_id = OBJECT_ID(N'Locations') AND name = N'FK_Locations_Accounts_AccountId'; """; var fk = await fkCmd.ExecuteScalarAsync(); Assert.Equal("FK_Locations_Accounts_AccountId", fk); } await using (var backfillCmd = connection.CreateCommand()) { backfillCmd.CommandText = "SELECT AccountId FROM Locations WHERE Id = 11;"; var stamped = await backfillCmd.ExecuteScalarAsync(); Assert.Equal(1, Convert.ToInt32(stamped)); } } finally { await DropDatabaseAsync(dbName); } } private static async Task ApplyMigrationUpAsync(ApplicationDbContext context, Migration migration) { var builder = new MigrationBuilder(context.Database.ProviderName!); var up = typeof(Migration).GetMethod("Up", BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new InvalidOperationException("Migration.Up not found."); up.Invoke(migration, [builder]); var sqlGenerator = context.GetService(); var commands = sqlGenerator.Generate(builder.Operations, model: null); foreach (var command in commands) await context.Database.ExecuteSqlRawAsync(command.CommandText); } private static async Task IsLocalDbAvailableAsync() { if (!OperatingSystem.IsWindows()) return false; try { await using var connection = new SqlConnection(LocalDbMaster); await connection.OpenAsync(); return true; } catch (SqlException) { return false; } catch (InvalidOperationException) { return false; } } private static async Task CreateDatabaseAsync(string dbName) { var quoted = QuoteSqlServerIdentifier(dbName); await using var connection = new SqlConnection(LocalDbMaster); await connection.OpenAsync(); await using var command = connection.CreateCommand(); command.CommandText = $"CREATE DATABASE {quoted};"; await command.ExecuteNonQueryAsync(); } private static async Task DropDatabaseAsync(string dbName) { try { var quoted = QuoteSqlServerIdentifier(dbName); await using var connection = new SqlConnection(LocalDbMaster); await connection.OpenAsync(); await using var command = connection.CreateCommand(); command.CommandText = $""" IF DB_ID(@dbName) IS NOT NULL BEGIN ALTER DATABASE {quoted} SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE {quoted}; END """; command.Parameters.AddWithValue("@dbName", dbName); await command.ExecuteNonQueryAsync(); } catch { // Best-effort cleanup for ephemeral LocalDB databases. } } private static string QuoteSqlServerIdentifier(string name) => "[" + name.Replace("]", "]]", StringComparison.Ordinal) + "]"; }