using System.Security.Claims; using FluentValidation; using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Implementation; using SeaHaven.DataServices.Interfaces; 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); } private static WorkOrderBoardCreateService CreateBoardCreate(ApplicationDbContext context) { 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); return new WorkOrderBoardCreateService( boardData, mutationData, boardService, audit, new WorkOrderBoardCreateValidation(), resolver); } [Fact] public async Task BoardCreate_WithAccountClaim_StampsAccountId() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 7, "Scoped Co"); await WorkOrderAccountTestHelpers.EnsureLocationAsync(context, id: 11, accountId: 7); var create = CreateBoardCreate(context); var user = WorkOrderAccountTestHelpers.AccountUser("actor-1", 7, "Dispatcher"); var row = await create.CreateAsync( new WorkOrderBoardCreateRequestDto { WorkOrderType = WorkOrderType.PM, SiteCode = "BK5", LocationId = 11 }, user, "actor-1"); var wo = await context.workOrders.AsNoTracking().SingleAsync(w => w.Id == row.Id); Assert.Equal(7, wo.AccountId); Assert.Equal(11, wo.LocationId); } [Fact] public async Task BoardCreate_MissingScope_ThrowsForbidden() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A"); await WorkOrderAccountTestHelpers.EnsureLocationAsync(context, id: 11, accountId: 1); var create = CreateBoardCreate(context); var ex = await Assert.ThrowsAsync(() => create.CreateAsync( new WorkOrderBoardCreateRequestDto { WorkOrderType = WorkOrderType.PM, SiteCode = "BK5", LocationId = 11 }, WorkOrderAccountTestHelpers.MissingScope(), "actor-1")); Assert.Equal("Forbidden", ex.Code); Assert.Empty(context.workOrders); } [Fact] public async Task BoardCreate_OrgWide_WithLocationAccount_StampsAccountId() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 3, "Unique Customer"); await WorkOrderAccountTestHelpers.EnsureLocationAsync(context, id: 22, accountId: 3, name: "DAL"); var create = CreateBoardCreate(context); var row = await create.CreateAsync( new WorkOrderBoardCreateRequestDto { WorkOrderType = WorkOrderType.PO, SiteCode = "DAL", LocationId = 22, Customer = "Ignored Client Customer" }, WorkOrderAccountTestHelpers.OrgWideAdmin(), "admin-1"); var wo = await context.workOrders.AsNoTracking().SingleAsync(w => w.Id == row.Id); Assert.Equal(3, wo.AccountId); Assert.Equal(22, wo.LocationId); Assert.Equal("Ignored Client Customer", wo.Customer); } [Fact] public async Task BoardCreate_OrgWide_LocationWithoutAccount_ThrowsAccountUnresolved() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureLocationAsync(context, id: 33, accountId: null); var create = CreateBoardCreate(context); var ex = await Assert.ThrowsAsync(() => create.CreateAsync( new WorkOrderBoardCreateRequestDto { WorkOrderType = WorkOrderType.PM, SiteCode = "BK5", LocationId = 33 }, WorkOrderAccountTestHelpers.OrgWideAdmin(), "admin-1")); Assert.Equal("AccountUnresolved", ex.Code); Assert.Empty(context.workOrders); } [Fact] public async Task BoardCreate_OrgWide_MissingLocation_ThrowsNotFound() { await using var context = CreateContext(); var create = CreateBoardCreate(context); var ex = await Assert.ThrowsAsync(() => create.CreateAsync( new WorkOrderBoardCreateRequestDto { WorkOrderType = WorkOrderType.PM, SiteCode = "BK5", LocationId = 404 }, WorkOrderAccountTestHelpers.OrgWideAdmin(), "admin-1")); Assert.Equal("NotFound", ex.Code); Assert.Empty(context.workOrders); } [Fact] public async Task BoardCreate_MissingLocationId_ThrowsValidation() { await using var context = CreateContext(); var create = CreateBoardCreate(context); await Assert.ThrowsAsync(() => create.CreateAsync( new WorkOrderBoardCreateRequestDto { WorkOrderType = WorkOrderType.PM, SiteCode = "BK5" }, WorkOrderAccountTestHelpers.OrgWideAdmin(), "admin-1")); Assert.Empty(context.workOrders); } [Fact] public async Task BoardCreate_Scoped_LocationOfOtherAccount_ThrowsForbidden() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A"); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 2, "B"); await WorkOrderAccountTestHelpers.EnsureLocationAsync(context, id: 50, accountId: 2); var create = CreateBoardCreate(context); var ex = await Assert.ThrowsAsync(() => create.CreateAsync( new WorkOrderBoardCreateRequestDto { WorkOrderType = WorkOrderType.PM, SiteCode = "BK5", LocationId = 50 }, WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher"), "disp-1")); Assert.Equal("Forbidden", ex.Code); Assert.Empty(context.workOrders); } [Fact] public async Task BoardCreate_OrgWide_ServiceOmitted_Succeeds() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 3, "Org"); await WorkOrderAccountTestHelpers.EnsureLocationAsync(context, id: 60, accountId: 3); var create = CreateBoardCreate(context); var row = await create.CreateAsync( new WorkOrderBoardCreateRequestDto { WorkOrderType = WorkOrderType.PM, SiteCode = "BK5", LocationId = 60 }, WorkOrderAccountTestHelpers.OrgWideAdmin(), "admin-1"); Assert.Null(row.Pm); var wo = await context.workOrders.AsNoTracking().SingleAsync(w => w.Id == row.Id); Assert.Equal(3, wo.AccountId); } [Fact] public async Task BoardCreate_ForwardsCancellationToken_ToLocationAccountLookup() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 7, "Scoped Co"); await WorkOrderAccountTestHelpers.EnsureLocationAsync(context, id: 11, accountId: 7); var locations = new RecordingLocationDataService(new LocationDataService(context)); var resolver = new WorkOrderAccountResolver(new AccountDataService(context), locations); 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); using var cts = new CancellationTokenSource(); await create.CreateAsync( new WorkOrderBoardCreateRequestDto { WorkOrderType = WorkOrderType.PM, SiteCode = "BK5", LocationId = 11 }, WorkOrderAccountTestHelpers.AccountUser("actor-1", 7, "Dispatcher"), "actor-1", cts.Token); Assert.Equal(cts.Token, locations.LastScopeToken); } [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); } [Fact] public async Task LegacyList_ScopedUser_HidesOtherAccountAndNullAccountRows() { await using var context = CreateContext(); await SeedThreeAccountRowsAsync(context); var data = new WorkOrderDataService(context); var scoped = (await data.GetAllWithDetailsAsync(accountId: 1)).ToList(); var dd = await data.GetNonTemplateWorkOrdersWithLocationsAsync(accountId: 1); Assert.Single(scoped); Assert.Equal(1, scoped[0].Id); Assert.Single(dd); Assert.Equal(1, dd[0].Id); } [Fact] public async Task LegacyList_OrgWide_IncludesNullAccountRows() { await using var context = CreateContext(); await SeedThreeAccountRowsAsync(context); var data = new WorkOrderDataService(context); var orgWide = (await data.GetAllWithDetailsAsync(accountId: null)).ToList(); Assert.Equal(3, orgWide.Count); } [Fact] public async Task LegacyDetail_CrossAccount_ReturnsNull() { await using var context = CreateContext(); await SeedThreeAccountRowsAsync(context); var data = new WorkOrderDataService(context); var detail = await data.GetWorkOrderDetailAsync(2, accountId: 1); Assert.Null(detail); } [Fact] public async Task LegacyDetail_SameAccount_ReturnsRow() { await using var context = CreateContext(); await SeedThreeAccountRowsAsync(context); var data = new WorkOrderDataService(context); var detail = await data.GetWorkOrderDetailAsync(1, accountId: 1); Assert.NotNull(detail); Assert.Equal(1, detail!.Id); } [Fact] public async Task LegacyDetail_OrgWide_CanReadNullAccountRow() { await using var context = CreateContext(); await SeedThreeAccountRowsAsync(context); var data = new WorkOrderDataService(context); var detail = await data.GetWorkOrderDetailAsync(3, accountId: null); Assert.NotNull(detail); Assert.Equal(3, detail!.Id); } [Fact] public async Task LegacyFiltered_ScopedUser_HidesOtherAccountAndNullAccountRows() { await using var context = CreateContext(); await SeedThreeAccountRowsAsync(context); var service = CreateLegacyReadService(context); var page = await service.GetFilteredWorkOrdersAsync( Array.Empty(), null, null, null, null, search: "", sort: "", sortby: "", page: 1, pageSize: 50, accountId: 1); Assert.Equal(1, page.TotalCount); Assert.Single(page.Data); Assert.Equal(1, page.Data.First().Id); } private static WorkOrderService CreateLegacyReadService(ApplicationDbContext context) { return new WorkOrderService( new WorkOrderDataService(context), new CommentDataService(context), new UserDataService(context), new QuotesDataService(context), fileStorage: null!, new CreateWorkOrderValidation(), new UpdateWorkOrderValidation(), WorkOrderAccountTestHelpers.Resolver(context)); } private static async Task SeedThreeAccountRowsAsync(ApplicationDbContext context) { 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, istemplate = false }, new WorkOrder { Id = 2, InternalWONumber = "00000000002", AccountId = 2, LifecycleStatus = LifecycleStatus.Scheduled, istemplate = false }, new WorkOrder { Id = 3, InternalWONumber = "00000000003", AccountId = null, LifecycleStatus = LifecycleStatus.Scheduled, istemplate = false }); await context.SaveChangesAsync(); } [Fact] public async Task Comments_CrossAccount_GetAddUpdate_ReturnNotFound() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A"); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 2, "B"); context.Users.Add(new ApplicationUser { Id = "author-1", UserName = "author", FirstName = "Ann", LastName = "Author" }); context.workOrders.Add(new WorkOrder { Id = 20, InternalWONumber = "00000000020", AccountId = 2, LifecycleStatus = LifecycleStatus.Scheduled, istemplate = false }); context.Comments.Add(new Comments { Id = 5, WorkerOrderId = 20, UserId = "author-1", Commenttext = "Secret", CommentType = "General", RecordType = "WorkOrder", CreatedDate = DateTime.UtcNow }); await context.SaveChangesAsync(); var service = new WorkOrderCommentService( new WorkOrderDetailDataService(context), new CommentDataService(context), new UserDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); var scoped = WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher"); Assert.Null(await service.GetCommentsAsync(20, scoped)); var addEx = await Assert.ThrowsAsync(() => service.AddCommentAsync(20, new WorkOrderCommentCreateDto { Text = "Nope" }, scoped, "disp-1")); Assert.Equal("NotFound", addEx.Code); var updateEx = await Assert.ThrowsAsync(() => service.UpdateCommentAsync( 20, 5, new WorkOrderCommentCreateDto { Text = "Nope" }, scoped, "author-1")); Assert.Equal("NotFound", updateEx.Code); } [Fact] public async Task Comments_SameAccount_AddSucceeds() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A"); context.Users.Add(new ApplicationUser { Id = "disp-1", UserName = "disp", FirstName = "Dee", LastName = "Spatch" }); context.workOrders.Add(new WorkOrder { Id = 21, InternalWONumber = "00000000021", AccountId = 1, LifecycleStatus = LifecycleStatus.Scheduled, istemplate = false }); await context.SaveChangesAsync(); var service = new WorkOrderCommentService( new WorkOrderDetailDataService(context), new CommentDataService(context), new UserDataService(context), WorkOrderAccountTestHelpers.Resolver(context)); var scoped = WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher"); var result = await service.AddCommentAsync( 21, new WorkOrderCommentCreateDto { Text = "In scope" }, scoped, "disp-1"); Assert.Equal("In scope", result.Text); Assert.Equal("disp-1", result.AuthorId); } [Fact] public async Task LegacyCommentsByWorkOrder_CrossAccount_ReturnsNull() { await using var context = CreateContext(); await SeedThreeAccountRowsAsync(context); context.Comments.Add(new Comments { WorkerOrderId = 2, Commenttext = "Other account", CreatedDate = DateTime.UtcNow }); await context.SaveChangesAsync(); var service = CreateLegacyReadService(context); var result = await service.GetCommentsByWorkorderIdAsync( 2, WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher")); Assert.Null(result); } [Fact] public async Task CompletionDoc_CrossAccount_EnsureAndUpload_ThrowNotFound() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A"); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 2, "B"); context.workOrders.Add(new WorkOrder { Id = 30, InternalWONumber = "00000000030", AccountId = 2, LifecycleStatus = LifecycleStatus.Scheduled, DocStatus = DocStatus.No, RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }, istemplate = false }); await context.SaveChangesAsync(); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); var service = new WorkOrderCompletionService( new WorkOrderCompletionDataService(context), new CompletionDocTemplateDataService(context), new WorkOrderDetailDataService(context), audit, WorkOrderAccountTestHelpers.Resolver(context)); var scoped = WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher"); var ensureEx = await Assert.ThrowsAsync(() => service.EnsureCanUploadCompletionDocAsync(30, scoped, "disp-1")); Assert.Equal("NotFound", ensureEx.Code); var uploadEx = await Assert.ThrowsAsync(() => service.UploadCompletionDocAsync( 30, new WorkOrderCompletionDocUploadDto { WorkOrderVersion = Convert.ToBase64String(new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }) }, "https://example.com/should-not-stick.pdf", scoped, "disp-1")); Assert.Equal("NotFound", uploadEx.Code); var wo = await context.workOrders.AsNoTracking().SingleAsync(w => w.Id == 30); Assert.Null(wo.SignOffAttachment); Assert.Equal(DocStatus.No, wo.DocStatus); } [Fact] public async Task CompletionDoc_SameAccount_EnsureAllowsUpload() { await using var context = CreateContext(); await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A"); var rowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }; context.workOrders.Add(new WorkOrder { Id = 31, InternalWONumber = "00000000031", AccountId = 1, LifecycleStatus = LifecycleStatus.Scheduled, DocStatus = DocStatus.No, RowVersion = rowVersion, istemplate = false }); await context.SaveChangesAsync(); var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context)); var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks); var service = new WorkOrderCompletionService( new WorkOrderCompletionDataService(context), new CompletionDocTemplateDataService(context), new WorkOrderDetailDataService(context), audit, WorkOrderAccountTestHelpers.Resolver(context)); var scoped = WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher"); await service.EnsureCanUploadCompletionDocAsync(31, scoped, "disp-1"); var result = await service.UploadCompletionDocAsync( 31, new WorkOrderCompletionDocUploadDto { WorkOrderVersion = Convert.ToBase64String(rowVersion) }, "https://example.com/ok.pdf", scoped, "disp-1"); Assert.Equal(DocStatus.Yes, result.DocStatus); Assert.Equal("https://example.com/ok.pdf", result.SignOffAttachment); } [Fact] public async Task LegacyGetComments_ScopedUser_HidesOtherAccountComments() { await using var context = CreateContext(); await SeedThreeAccountRowsAsync(context); context.Comments.AddRange( new Comments { WorkerOrderId = 1, Commenttext = "Account A note", Documents = "a.pdf", CreatedDate = DateTime.UtcNow }, new Comments { WorkerOrderId = 2, Commenttext = "Account B secret", Documents = "b.pdf", CreatedDate = DateTime.UtcNow }, new Comments { WorkerOrderId = 3, Commenttext = "Null account note", CreatedDate = DateTime.UtcNow }); await context.SaveChangesAsync(); var service = CreateLegacyReadService(context); var scoped = await service.GetCommentsAsync( WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher")); Assert.Single(scoped); Assert.Equal("Account A note", scoped.Single().Commenttext); Assert.DoesNotContain(scoped, c => c.Commenttext == "Account B secret"); Assert.DoesNotContain(scoped, c => c.Commenttext == "Null account note"); var orgWide = await service.GetCommentsAsync(WorkOrderAccountTestHelpers.OrgWideAdmin()); Assert.Equal(3, orgWide.Count()); var missingEx = await Assert.ThrowsAsync(() => service.GetCommentsAsync(WorkOrderAccountTestHelpers.MissingScope())); Assert.Equal("Forbidden", missingEx.Code); } private sealed class RecordingLocationDataService : ILocationDataService { private readonly ILocationDataService _inner; public RecordingLocationDataService(ILocationDataService inner) { _inner = inner; } public CancellationToken LastScopeToken { get; private set; } public Task GetByIdAsync(int id) => _inner.GetByIdAsync(id); public Task GetByIdWithDetailsAsync(int id) => _inner.GetByIdWithDetailsAsync(id); public Task> GetAllAsync() => _inner.GetAllAsync(); public Task> GetByAccountIdAsync(int accountId) => _inner.GetByAccountIdAsync(accountId); public Task<(IEnumerable Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null) => _inner.GetPagedAsync(page, pageSize, search); public Task<(IEnumerable Items, int TotalCount)> GetAddressbookPagedAsync(int page, int pageSize, string? search = null) => _inner.GetAddressbookPagedAsync(page, pageSize, search); public Task> GetSiteOptionsAsync( string? search = null, CancellationToken cancellationToken = default) => _inner.GetSiteOptionsAsync(search, cancellationToken); public Task<(bool Exists, int? AccountId)> GetAccountScopeAsync( int locationId, CancellationToken cancellationToken = default) { LastScopeToken = cancellationToken; return _inner.GetAccountScopeAsync(locationId, cancellationToken); } public Task AddAsync(Locations location) => _inner.AddAsync(location); public Task UpdateAsync(Locations location) => _inner.UpdateAsync(location); public Task DeleteAsync(int id) => _inner.DeleteAsync(id); public Task ExistsAsync(int id) => _inner.ExistsAsync(id); public Task CountAsync() => _inner.CountAsync(); public Task<(List Items, int TotalCount)> GetListPagedAsync( int page, int pageSize, string? search, CancellationToken cancellationToken) => _inner.GetListPagedAsync(page, pageSize, search, cancellationToken); public Task GetDetailByIdAsync(int id, CancellationToken cancellationToken) => _inner.GetDetailByIdAsync(id, cancellationToken); public Task GetByIdForUpdateAsync(int id, CancellationToken cancellationToken) => _inner.GetByIdForUpdateAsync(id, cancellationToken); public Task AddAsync(Locations location, CancellationToken cancellationToken) => _inner.AddAsync(location, cancellationToken); public Task UpdateAsync(Locations location, CancellationToken cancellationToken) => _inner.UpdateAsync(location, cancellationToken); public Task DeleteByIdAsync(int id, CancellationToken cancellationToken) => _inner.DeleteByIdAsync(id, cancellationToken); } }