using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Implementation; namespace SeaHavenIndustries.Tests; // Relational (SQLite) coverage for the uplift queue read. The in-memory provider // evaluates LINQ in memory and cannot prove the query translates to SQL, so this // executes GetPagedAsync against a real relational provider and asserts a dispatch // linked only through DispatchWorkOrders still resolves its work-order context and // closed flag through the effective primary-plus-linked work order. public sealed class UpliftQueueReadRelationalTests { [Fact] public async Task GetPagedAsync_ResolvesWorkOrder_ForDispatchLinkedOnlyThroughDispatchWorkOrders() { await using var connection = new SqliteConnection("DataSource=:memory:"); await connection.OpenAsync(); var options = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; await using var context = new SqliteUpliftTestDbContext(options); await context.Database.EnsureCreatedAsync(); var vendor = new Vendor { CompanyName = "Gateway", IsActive = true }; var workOrder = new WorkOrder { // The queue renders the internal display number; WorkerOrderNumber holds the // CRM external id and must not surface here. InternalWONumber = "WO-LINKED", WorkerOrderNumber = "CRM-EXT-LINKED", SiteCode = "SITE-Z", Service = "Electrical", WorkerOrderTitle = "Repair", LifecycleStatus = LifecycleStatus.Completed }; context.AddRange(vendor, workOrder); await context.SaveChangesAsync(); var dispatch = new Dispatch { VendorId = vendor.Id, DispatchNumber = "DIS-LINKED", Status = "Completed" }; context.Dispatches.Add(dispatch); await context.SaveChangesAsync(); context.DispatchWorkOrders.Add(new DispatchWorkOrder { DispatchId = dispatch.Id, WorkOrderId = workOrder.Id }); context.DispatchUpliftRequests.Add(new DispatchUpliftRequest { DispatchId = dispatch.Id, Status = "Approved", CreatedDate = new DateTime(2026, 3, 1), DecidedAt = new DateTime(2026, 3, 2), RequiredTier = 1, RequestedNTE = 90m, NotificationStatus = "Pending" }); await context.SaveChangesAsync(); var data = new UpliftDataService(context); var (total, items) = await data.GetPagedAsync("Approved", null, 1, 25, CancellationToken.None); Assert.Equal(1, total); var item = Assert.Single(items); Assert.Equal(workOrder.Id, item.WorkOrderId); Assert.Equal("WO-LINKED", item.WorkOrderNumber); Assert.Equal("SITE-Z", item.WorkOrderSiteCode); Assert.Equal("Electrical", item.WorkOrderService); Assert.True(item.WorkOrderClosed); } [Fact] public async Task GetPagedAsync_ResolvesDetailModalContext_PerAccountWorkOrder() { // SH-209: dispatcher (work-order assignee navigation), technician (vendor // contact) and scheduled date must translate to SQL through the effective // work-order subquery, and each row resolves them from its own account's // work order only. await using var connection = new SqliteConnection("DataSource=:memory:"); await connection.OpenAsync(); var options = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; await using var context = new SqliteUpliftTestDbContext(options); await context.Database.EnsureCreatedAsync(); var accountA = new Accounts { Name = "Account A" }; var accountB = new Accounts { Name = "Account B" }; var dispatcherA = new ApplicationUser { Id = "dispatcher-a", FirstName = "Ann", LastName = "Alpha" }; var vendorA = new Vendor { CompanyName = "Vendor A", ContactName = "Tech A", IsActive = true }; var vendorB = new Vendor { CompanyName = "Vendor B", ContactName = "Tech B", IsActive = true }; context.AddRange(accountA, accountB, dispatcherA, vendorA, vendorB); await context.SaveChangesAsync(); var workOrderA = new WorkOrder { InternalWONumber = "WO-A", WorkerOrderTitle = "Repair", AccountId = accountA.Id, AssignTo = dispatcherA.Id, ScheduledDate = new DateTime(2026, 5, 1, 8, 0, 0) }; var workOrderB = new WorkOrder { InternalWONumber = "WO-B", WorkerOrderTitle = "Repair", AccountId = accountB.Id }; context.AddRange(workOrderA, workOrderB); await context.SaveChangesAsync(); var dispatchA = new Dispatch { VendorId = vendorA.Id, WorkOrderId = workOrderA.Id, DispatchNumber = "DIS-A", Status = "Scheduled" }; var dispatchB = new Dispatch { VendorId = vendorB.Id, WorkOrderId = workOrderB.Id, DispatchNumber = "DIS-B", Status = "Scheduled" }; context.Dispatches.AddRange(dispatchA, dispatchB); await context.SaveChangesAsync(); context.DispatchUpliftRequests.AddRange( new DispatchUpliftRequest { DispatchId = dispatchA.Id, Status = "Pending", CreatedDate = new DateTime(2026, 4, 1), RequiredTier = 1, RequestedNTE = 100m, NotificationStatus = "Pending" }, new DispatchUpliftRequest { DispatchId = dispatchB.Id, Status = "Pending", CreatedDate = new DateTime(2026, 4, 2), RequiredTier = 1, RequestedNTE = 200m, NotificationStatus = "Pending" }); await context.SaveChangesAsync(); var data = new UpliftDataService(context); var (total, items) = await data.GetPagedAsync("Pending", null, 1, 25, CancellationToken.None); Assert.Equal(2, total); var rowA = Assert.Single(items, i => i.WorkOrderId == workOrderA.Id); Assert.Equal("Ann", rowA.WorkOrderDispatcherFirstName); Assert.Equal("Alpha", rowA.WorkOrderDispatcherLastName); Assert.Equal("Tech A", rowA.TechnicianName); Assert.Equal(new DateTime(2026, 5, 1, 8, 0, 0), rowA.WorkOrderScheduledDate); var rowB = Assert.Single(items, i => i.WorkOrderId == workOrderB.Id); Assert.Null(rowB.WorkOrderDispatcherFirstName); Assert.Null(rowB.WorkOrderDispatcherLastName); Assert.Equal("Tech B", rowB.TechnicianName); Assert.Null(rowB.WorkOrderScheduledDate); } private sealed class SqliteUpliftTestDbContext : ApplicationDbContext { public SqliteUpliftTestDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); // SQL Server filtered index syntax is invalid on SQLite. foreach (var index in builder.Model.GetEntityTypes().SelectMany(e => e.GetIndexes())) { if (index.GetFilter() != null) index.SetFilter(null); } // SQLite has no rowversion type; treat as plain nullable blobs. 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; } } } }