using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Implementation; using SeaHaven.Services.DTOs; using SeaHaven.Services.Implementation; using SeaHaven.Services.Validation; namespace SeaHavenIndustries.Tests; public class WorkOrderBoardCreateRelationalTests { [Fact] public async Task Create_AuditAndFieldLocks_UseGeneratedWorkOrderId() { await using var connection = new SqliteConnection("DataSource=:memory:"); await connection.OpenAsync(); var options = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; await using var context = new SqliteBoardTestDbContext(options); await context.Database.EnsureCreatedAsync(); const string actorId = "actor-1"; context.Users.Add(new ApplicationUser { Id = actorId, UserName = "actor-1", NormalizedUserName = "ACTOR-1", Email = "actor@test.local", NormalizedEmail = "ACTOR@TEST.LOCAL" }); await context.SaveChangesAsync(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context); await WorkOrderAccountTestHelpers.EnsureLocationAsync(context); var boardData = new WorkOrderBoardDataService(context); var mutationData = new WorkOrderBoardMutationDataService(context); var resolver = WorkOrderAccountTestHelpers.Resolver(context); var boardService = new WorkOrderBoardService(boardData, resolver); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); var service = new WorkOrderBoardCreateService( boardData, mutationData, boardService, audit, new WorkOrderBoardCreateValidation(), resolver); var result = await service.CreateAsync(new WorkOrderBoardCreateRequestDto { WorkOrderType = WorkOrderType.PM, SiteCode = "BK5", LocationId = 1, Description = "Relational create" }, WorkOrderAccountTestHelpers.AccountUser(actorId), actorId); Assert.True(result.Id > 0); var logs = await context.WorkOrderAuditLogs.AsNoTracking().ToListAsync(); var locks = await context.WorkOrderFieldLocks.AsNoTracking().ToListAsync(); Assert.NotEmpty(logs); Assert.NotEmpty(locks); Assert.Contains(logs, l => l.Action == "Create"); Assert.All(logs, l => Assert.Equal(result.Id, l.WorkOrderId)); Assert.All(locks, l => Assert.Equal(result.Id, l.WorkOrderId)); Assert.DoesNotContain(logs, l => l.WorkOrderId == 0); Assert.DoesNotContain(locks, l => l.WorkOrderId == 0); } [Fact] public async Task Create_WhenContactPersistenceFails_RollsBackWorkOrder() { 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 SqliteBoardTestDbContext(options); await context.Database.EnsureCreatedAsync(); const string actorId = "actor-1"; context.Users.Add(new ApplicationUser { Id = actorId, UserName = "actor-1", NormalizedUserName = "ACTOR-1", Email = "actor@test.local", NormalizedEmail = "ACTOR@TEST.LOCAL" }); await context.SaveChangesAsync(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context); await WorkOrderAccountTestHelpers.EnsureLocationAsync(context); var boardData = new WorkOrderBoardDataService(context); var mutationData = new WorkOrderBoardMutationDataService(context); var resolver = WorkOrderAccountTestHelpers.Resolver(context); var boardService = new WorkOrderBoardService(boardData, resolver); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); var service = new WorkOrderBoardCreateService( boardData, mutationData, boardService, audit, new WorkOrderBoardCreateValidation(), resolver); await Assert.ThrowsAsync(() => service.CreateAsync(new WorkOrderBoardCreateRequestDto { WorkOrderType = WorkOrderType.PM, SiteCode = "BK5", LocationId = 1, Description = "Should roll back", PocContactId = 999_999 }, WorkOrderAccountTestHelpers.AccountUser(actorId), actorId)); // Fresh context on the same connection so we observe committed state only. await using var verify = new SqliteBoardTestDbContext(options); Assert.Empty(await verify.workOrders.AsNoTracking().ToListAsync()); Assert.Empty(await verify.WorkOrderAuditLogs.AsNoTracking().ToListAsync()); Assert.Empty(await verify.WorkOrderFieldLocks.AsNoTracking().ToListAsync()); Assert.Empty(await verify.WorkOrderContacts.AsNoTracking().ToListAsync()); Assert.Empty(await verify.Dispatches.AsNoTracking().ToListAsync()); } private sealed class SqliteBoardTestDbContext : ApplicationDbContext { public SqliteBoardTestDbContext(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; } } } }