using System.Security.Claims; using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Implementation; using SeaHaven.Services.DTOs; using SeaHaven.Services.Exceptions; using SeaHaven.Services.Helpers; using SeaHaven.Services.Implementation; using SeaHaven.Services.Validation; using Xunit; namespace SeaHavenIndustries.Tests; public class WorkOrderAccountScopeTests { private static ApplicationDbContext CreateContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; return new ApplicationDbContext(options); } [Fact] public async Task BoardCreate_WithAccountClaim_StampsAccountId() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 7, "Scoped Co"); var resolver = WorkOrderAccountTestHelpers.Resolver(context); var boardData = new WorkOrderBoardDataService(context); var mutationData = new WorkOrderBoardMutationDataService(context); var boardService = new WorkOrderBoardService(boardData, resolver); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); var create = new WorkOrderBoardCreateService( boardData, mutationData, boardService, audit, new WorkOrderBoardCreateValidation(), resolver); var user = WorkOrderAccountTestHelpers.AccountUser("actor-1", 7, "Dispatcher"); var row = await create.CreateAsync( new WorkOrderBoardCreateRequestDto { WorkOrderType = WorkOrderType.PM, SiteCode = "BK5" }, user, "actor-1"); var wo = await context.workOrders.AsNoTracking().SingleAsync(w => w.Id == row.Id); Assert.Equal(7, wo.AccountId); } [Fact] public async Task BoardCreate_MissingScope_ThrowsForbidden() { await using var context = CreateContext(); var resolver = WorkOrderAccountTestHelpers.Resolver(context); var boardData = new WorkOrderBoardDataService(context); var mutationData = new WorkOrderBoardMutationDataService(context); var boardService = new WorkOrderBoardService(boardData, resolver); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); var create = new WorkOrderBoardCreateService( boardData, mutationData, boardService, audit, new WorkOrderBoardCreateValidation(), resolver); var ex = await Assert.ThrowsAsync(() => create.CreateAsync( new WorkOrderBoardCreateRequestDto { WorkOrderType = WorkOrderType.PM, SiteCode = "BK5" }, WorkOrderAccountTestHelpers.MissingScope(), "actor-1")); Assert.Equal("Forbidden", ex.Code); Assert.Empty(context.workOrders); } [Fact] public async Task BoardCreate_OrgWide_WithUniqueCustomer_StampsAccountId() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 3, "Unique Customer"); var resolver = WorkOrderAccountTestHelpers.Resolver(context); var boardData = new WorkOrderBoardDataService(context); var mutationData = new WorkOrderBoardMutationDataService(context); var boardService = new WorkOrderBoardService(boardData, resolver); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); var create = new WorkOrderBoardCreateService( boardData, mutationData, boardService, audit, new WorkOrderBoardCreateValidation(), resolver); var row = await create.CreateAsync( new WorkOrderBoardCreateRequestDto { WorkOrderType = WorkOrderType.PO, SiteCode = "DAL", Customer = "Unique Customer" }, WorkOrderAccountTestHelpers.OrgWideAdmin(), "admin-1"); var wo = await context.workOrders.AsNoTracking().SingleAsync(w => w.Id == row.Id); Assert.Equal(3, wo.AccountId); Assert.Equal("Unique Customer", wo.Customer); } [Fact] public async Task BoardCreate_OrgWide_UnresolvedCustomer_ThrowsAccountUnresolved() { await using var context = CreateContext(); var resolver = WorkOrderAccountTestHelpers.Resolver(context); var boardData = new WorkOrderBoardDataService(context); var mutationData = new WorkOrderBoardMutationDataService(context); var boardService = new WorkOrderBoardService(boardData, resolver); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); var create = new WorkOrderBoardCreateService( boardData, mutationData, boardService, audit, new WorkOrderBoardCreateValidation(), resolver); var ex = await Assert.ThrowsAsync(() => create.CreateAsync( new WorkOrderBoardCreateRequestDto { WorkOrderType = WorkOrderType.PM, SiteCode = "BK5" }, WorkOrderAccountTestHelpers.OrgWideAdmin(), "admin-1")); Assert.Equal("AccountUnresolved", ex.Code); Assert.Empty(context.workOrders); } [Fact] public async Task Board_ScopedUser_HidesOtherAccountAndNullAccountRows() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A"); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 2, "B"); context.workOrders.AddRange( new WorkOrder { Id = 1, InternalWONumber = "00000000001", AccountId = 1, LifecycleStatus = LifecycleStatus.Scheduled, ScheduledDate = new DateTime(2026, 8, 10), istemplate = false }, new WorkOrder { Id = 2, InternalWONumber = "00000000002", AccountId = 2, LifecycleStatus = LifecycleStatus.Scheduled, ScheduledDate = new DateTime(2026, 8, 11), istemplate = false }, new WorkOrder { Id = 3, InternalWONumber = "00000000003", AccountId = null, LifecycleStatus = LifecycleStatus.Scheduled, ScheduledDate = new DateTime(2026, 8, 12), istemplate = false }); await context.SaveChangesAsync(); var resolver = WorkOrderAccountTestHelpers.Resolver(context); var boardService = new WorkOrderBoardService(new WorkOrderBoardDataService(context), resolver); var scoped = await boardService.GetBoardAsync( new WorkOrderBoardQueryDto { WeekStart = new DateOnly(2026, 8, 10), WeekEnd = new DateOnly(2026, 8, 14) }, WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher"), "disp-1"); Assert.Single(scoped.Scheduled); Assert.Equal(1, scoped.Scheduled[0].Id); var orgWide = await boardService.GetBoardAsync( new WorkOrderBoardQueryDto { WeekStart = new DateOnly(2026, 8, 10), WeekEnd = new DateOnly(2026, 8, 14) }, WorkOrderAccountTestHelpers.OrgWideAdmin(), "admin-1"); Assert.Equal(3, orgWide.Scheduled.Count); } [Fact] public async Task Detail_CrossAccount_ReturnsNull() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A"); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 2, "B"); context.workOrders.Add(new WorkOrder { Id = 10, InternalWONumber = "00000000010", AccountId = 2, LifecycleStatus = LifecycleStatus.Incomplete, istemplate = false }); await context.SaveChangesAsync(); var resolver = WorkOrderAccountTestHelpers.Resolver(context); var boardService = new WorkOrderBoardService(new WorkOrderBoardDataService(context), resolver); var detail = new WorkOrderDetailService( boardService, new WorkOrderDetailDataService(context), new CompletionDocTemplateDataService(context), new UserDataService(context), resolver); var result = await detail.GetDetailAsync( 10, WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher")); Assert.Null(result); } [Fact] public async Task Ingest_UnresolvedCustomer_SkipsCreate() { await using var context = CreateContext(); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); var ingest = new WorkOrderIngestService( new WorkOrderIngestDataService(context), new SyncFieldMergePolicy(fieldLocks), fieldLocks, audit, WorkOrderAccountTestHelpers.Resolver(context)); var result = await ingest.UpsertBatchAsync(new[] { new WorkOrderIngestPayloadDto { ExternalWorkOrderId = "EXT-1", Description = "No account", Customer = "Unknown Customer" } }); Assert.Equal(1, result.Skipped); Assert.Equal(0, result.Created); Assert.Empty(context.workOrders); } [Fact] public async Task Ingest_UniqueCustomer_StampsAccountId() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 9, "Ingest Customer"); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); var ingest = new WorkOrderIngestService( new WorkOrderIngestDataService(context), new SyncFieldMergePolicy(fieldLocks), fieldLocks, audit, WorkOrderAccountTestHelpers.Resolver(context)); var result = await ingest.UpsertBatchAsync(new[] { new WorkOrderIngestPayloadDto { ExternalWorkOrderId = "EXT-9", Description = "Has account", Customer = "Ingest Customer" } }); Assert.Equal(1, result.Created); var wo = Assert.Single(context.workOrders); Assert.Equal(9, wo.AccountId); } [Fact] public async Task ResolveAccountFilter_MissingScope_ThrowsForbidden() { await using var context = CreateContext(); var resolver = WorkOrderAccountTestHelpers.Resolver(context); var ex = Assert.Throws(() => resolver.ResolveAccountFilter(WorkOrderAccountTestHelpers.MissingScope())); Assert.Equal("Forbidden", ex.Code); } }