using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Helpers; using SeaHaven.Services.Implementation; namespace SeaHavenIndustries.Tests; public sealed class WorkOrderServiceRegistryFilterRelationalTests { [Fact] public async Task ServiceDisplayAndFilter_RespectCompletedSnapshotsAndKeepCurrentAliases() { await using var connection = new SqliteConnection("Data Source=:memory:;Foreign Keys=True"); await connection.OpenAsync(); var options = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; await using var context = new SqliteServiceRegistryTestDbContext(options); await context.Database.EnsureCreatedAsync(); context.Services.AddRange( new Service { Id = 10, Name = "Current Active Name", NormalizedName = "CURRENT ACTIVE NAME", Trade = "Electrical", IconKey = "electrical", IsActive = true }, new Service { Id = 20, Name = "Original Inactive Name", NormalizedName = "ORIGINAL INACTIVE NAME", Trade = "Electrical", IconKey = "electrical", IsActive = true }, new Service { Id = 30, Name = "QA Service", NormalizedName = "QA SERVICE", Trade = "Electrical", IconKey = "electrical", IsActive = true }, new Service { Id = 31, Name = "QA Service Extended", NormalizedName = "QA SERVICE EXTENDED", Trade = "Electrical", IconKey = "electrical", IsActive = true }); context.workOrders.AddRange( new WorkOrder { Id = 1, ServiceId = 10, ServiceNameSnapshot = "Original Active Name", Trade = "Original Active Name" }, new WorkOrder { Id = 2, ServiceId = 20, ServiceNameSnapshot = "Historical Inactive Name", Trade = "Historical Inactive Name" }, new WorkOrder { Id = 3, Trade = "Legacy Cooling", Problem = "Legacy Leak" }, new WorkOrder { Id = 4, ServiceId = 10, ServiceNameSnapshot = "Completed Archive Alpha", Trade = "Completed Archive Alpha", LifecycleStatus = LifecycleStatus.Completed }, new WorkOrder { Id = 5, ServiceId = 10, ServiceNameSnapshot = "Completed Archive Beta", Trade = "Completed Archive Beta", Status = "Done" }, new WorkOrder { Id = 6, ServiceId = 30, ServiceNameSnapshot = "QA Service", Trade = "QA Service", LifecycleStatus = LifecycleStatus.Scheduled }, new WorkOrder { Id = 7, ServiceId = 31, ServiceNameSnapshot = "QA Service Extended", Trade = "QA Service Extended", LifecycleStatus = LifecycleStatus.Scheduled }); await context.SaveChangesAsync(); var renamedService = await context.Services.SingleAsync(service => service.Id == 20); renamedService.Name = "Current Inactive Name"; renamedService.NormalizedName = "CURRENT INACTIVE NAME"; await context.SaveChangesAsync(); renamedService.IsActive = false; await context.SaveChangesAsync(); var activeRows = await WorkOrderBoardProjection.ProjectRowsAsync( context, context.workOrders.AsNoTracking().Where(w => w.Id == 1), isUnscheduled: true); var activeDisplay = WorkOrderBoardService.MapRawRow(Assert.Single(activeRows), DateTime.UtcNow); var inactiveRows = await WorkOrderBoardProjection.ProjectRowsAsync( context, context.workOrders.AsNoTracking().Where(w => w.Id == 2), isUnscheduled: true); var inactiveDisplay = WorkOrderBoardService.MapRawRow(Assert.Single(inactiveRows), DateTime.UtcNow); var legacyRows = await WorkOrderBoardProjection.ProjectRowsAsync( context, context.workOrders.AsNoTracking().Where(w => w.Id == 3), isUnscheduled: true); var legacyDisplay = WorkOrderBoardService.MapRawRow(Assert.Single(legacyRows), DateTime.UtcNow); var completedRows = await WorkOrderBoardProjection.ProjectRowsAsync( context, context.workOrders.AsNoTracking().Where(w => w.Id == 4), isUnscheduled: true); var completedDisplay = WorkOrderBoardService.MapRawRow(Assert.Single(completedRows), DateTime.UtcNow); var legacyCompletedRows = await WorkOrderBoardProjection.ProjectRowsAsync( context, context.workOrders.AsNoTracking().Where(w => w.Id == 5), isUnscheduled: true); var legacyCompletedDisplay = WorkOrderBoardService.MapRawRow(Assert.Single(legacyCompletedRows), DateTime.UtcNow); var currentActiveNameMatches = await WorkOrderBoardQueryFilters .ApplyPmTypeFilter(context.workOrders.AsNoTracking(), new[] { "Current Active Name" }) .Select(w => w.Id) .ToListAsync(); var historicalInactiveNameMatches = await WorkOrderBoardQueryFilters .ApplyPmTypeFilter(context.workOrders.AsNoTracking(), new[] { "Historical Inactive Name" }) .Select(w => w.Id) .ToListAsync(); var inactiveRegistryNameMatches = await WorkOrderBoardQueryFilters .ApplyPmTypeFilter(context.workOrders.AsNoTracking(), new[] { "Current Inactive Name" }) .Select(w => w.Id) .ToListAsync(); var completedSnapshotMatches = await WorkOrderBoardQueryFilters .ApplyPmTypeFilter(context.workOrders.AsNoTracking(), new[] { "Completed Archive Alpha" }) .Select(w => w.Id) .ToListAsync(); var legacyCompletedSnapshotMatches = await WorkOrderBoardQueryFilters .ApplyPmTypeFilter(context.workOrders.AsNoTracking(), new[] { "Completed Archive Beta" }) .Select(w => w.Id) .ToListAsync(); var currentNameAliasMatches = await WorkOrderBoardQueryFilters .ApplyPmTypeFilter(context.workOrders.AsNoTracking(), new[] { "Current Active Name" }) .Select(w => w.Id) .OrderBy(id => id) .ToListAsync(); var exactRegistryNameMatches = await WorkOrderBoardQueryFilters .ApplyPmTypeFilter(context.workOrders.AsNoTracking(), new[] { "QA Service" }) .Select(w => w.Id) .OrderBy(id => id) .ToListAsync(); var multipleRegistryNameMatches = await WorkOrderBoardQueryFilters .ApplyPmTypeFilter(context.workOrders.AsNoTracking(), new[] { "QA Service", "QA Service Extended" }) .Select(w => w.Id) .OrderBy(id => id) .ToListAsync(); var legacyTradeMatches = await WorkOrderBoardQueryFilters .ApplyPmTypeFilter(context.workOrders.AsNoTracking(), new[] { "Legacy Cooling" }) .Select(w => w.Id) .ToListAsync(); var legacyProblemMatches = await WorkOrderBoardQueryFilters .ApplyPmTypeFilter(context.workOrders.AsNoTracking(), new[] { "Legacy Leak" }) .Select(w => w.Id) .ToListAsync(); var actual = string.Join("\n", new[] { $"active-display={activeDisplay.Pm}", $"inactive-display={inactiveDisplay.Pm}", $"legacy-display={legacyDisplay.Pm}", $"completed-display={completedDisplay.Pm}", $"legacy-completed-display={legacyCompletedDisplay.Pm}", $"active-filter={string.Join(',', currentActiveNameMatches)}", $"inactive-snapshot-filter={string.Join(',', historicalInactiveNameMatches)}", $"inactive-current-name-filter={string.Join(',', inactiveRegistryNameMatches)}", $"completed-snapshot-filter={string.Join(',', completedSnapshotMatches)}", $"legacy-completed-snapshot-filter={string.Join(',', legacyCompletedSnapshotMatches)}", $"current-name-alias-filter={string.Join(',', currentNameAliasMatches)}", $"exact-registry-filter={string.Join(',', exactRegistryNameMatches)}", $"multiple-registry-filter={string.Join(',', multipleRegistryNameMatches)}", $"legacy-trade-filter={string.Join(',', legacyTradeMatches)}", $"legacy-problem-filter={string.Join(',', legacyProblemMatches)}" }); var expected = string.Join("\n", new[] { "active-display=Current Active Name", "inactive-display=Historical Inactive Name", "legacy-display=Legacy Cooling", "completed-display=Completed Archive Alpha", "legacy-completed-display=Completed Archive Beta", "active-filter=1,4,5", "inactive-snapshot-filter=2", "inactive-current-name-filter=2", "completed-snapshot-filter=4", "legacy-completed-snapshot-filter=5", "current-name-alias-filter=1,4,5", "exact-registry-filter=6", "multiple-registry-filter=6,7", "legacy-trade-filter=3", "legacy-problem-filter=3" }); Assert.True(expected == actual, $"Expected:\n{expected}\nActual:\n{actual}"); } private sealed class SqliteServiceRegistryTestDbContext : ApplicationDbContext { public SqliteServiceRegistryTestDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); // SQL Server filtered indexes and rowversion semantics are not supported by SQLite. foreach (var index in builder.Model.GetEntityTypes().SelectMany(entity => entity.GetIndexes())) { if (index.GetFilter() != null) index.SetFilter(null); } foreach (var entityType in new[] { typeof(WorkOrder), typeof(Dispatch) }) { var property = builder.Entity(entityType).Property("RowVersion").Metadata; property.ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.Never; property.IsConcurrencyToken = false; } } } }