mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 06:03:12 +00:00
189 lines
6.9 KiB
C#
189 lines
6.9 KiB
C#
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<ApplicationDbContext>()
|
|
.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<IMigrationsSqlGenerator>();
|
|
var commands = sqlGenerator.Generate(builder.Operations, model: null);
|
|
foreach (var command in commands)
|
|
await context.Database.ExecuteSqlRawAsync(command.CommandText);
|
|
}
|
|
|
|
private static async Task<bool> 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) + "]";
|
|
}
|