using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using FluentAssertions; using Microsoft.EntityFrameworkCore; using SeaHaven.DataServices.Implementation; using SeaHaven.DataServices.Models; using Xunit; namespace Api.SeaHavenIndustries.Tests; public class VendorCompanyRosterDataServiceTests { private static ApplicationDbContext NewContext(string dbName) { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(dbName) .Options; return new ApplicationDbContext(options); } private static Task<(int companyId, Vendor vendor)> SeedCompanyWithTechnicianAsync( ApplicationDbContext context, string companyName, params string[] technicianNames) => SeedCompanyWithTechnicianAsync(context, companyName, rowVersion: null, technicianNames); private static async Task<(int companyId, Vendor vendor)> SeedCompanyWithTechnicianAsync( ApplicationDbContext context, string companyName, byte[]? rowVersion, params string[] technicianNames) { var company = new VendorCompany { Name = companyName, NormalizedName = companyName.ToLowerInvariant(), RowVersion = rowVersion }; context.VendorCompanies.Add(company); foreach (var name in technicianNames) { context.Vendors.Add(new Vendor { CompanyName = companyName, ContactName = name, IsActive = true, IsDeleted = false, Company = company }); } await context.SaveChangesAsync(); return (company.Id, context.Vendors.First(v => v.CompanyId == company.Id)); } [Fact] public async Task CreateRoster_PersistsCompanyAndTechniciansAtomically() { var dbName = Guid.NewGuid().ToString(); using var context = NewContext(dbName); var service = new VendorCompanyRosterDataService(context); var roster = await service.CreateRosterAsync(new VendorCompanyRosterWriteModel { Name = "Atomic Co", Email = "atomic@example.com", Notes = "shared notes", ActorUserId = "42", Technicians = new List { new() { ContactName = "T1" }, new() { ContactName = "T2" } } }, CancellationToken.None); roster.CompanyId.Should().BeGreaterThan(0); roster.Technicians.Should().HaveCount(2); roster.Notes.Should().Be("shared notes"); var company = context.VendorCompanies.Single(c => c.Id == roster.CompanyId); company.Name.Should().Be("Atomic Co"); context.Vendors.Count(v => v.CompanyId == company.Id).Should().Be(2); context.Vendors.Where(v => v.CompanyId == company.Id).Should().OnlyContain(v => v.CompanyName == "Atomic Co"); } [Fact] public async Task CreateRoster_DuplicateNormalizedName_ThrowsWithoutPersisting() { var dbName = Guid.NewGuid().ToString(); using var context = NewContext(dbName); await SeedCompanyWithTechnicianAsync(context, "Eletrizante", "Existing"); var service = new VendorCompanyRosterDataService(context); var call = () => service.CreateRosterAsync(new VendorCompanyRosterWriteModel { Name = " ELETRIZANTE ", Email = "duplicate@example.com", ActorUserId = "42" }, CancellationToken.None); await call.Should().ThrowAsync(); context.VendorCompanies.Should().ContainSingle(); context.Vendors.Should().ContainSingle(); } [Fact] public async Task GetRoster_ByVendorId_ResolvesCompanyAndAllTechnicians() { var dbName = Guid.NewGuid().ToString(); int companyId, vendorId; using (var seed = NewContext(dbName)) { var (cid, _) = await SeedCompanyWithTechnicianAsync(seed, "Resolve Co", "A", "B"); companyId = cid; vendorId = seed.Vendors.First(v => v.CompanyId == cid && v.ContactName == "A").Id; } using var context = NewContext(dbName); var service = new VendorCompanyRosterDataService(context); var roster = await service.GetRosterAsync(vendorId, null, CancellationToken.None); roster.Should().NotBeNull(); roster!.CompanyId.Should().Be(companyId); roster.Technicians.Select(t => t.ContactName).Should().BeEquivalentTo(new[] { "A", "B" }); } [Fact] public async Task GetRoster_WithMismatchedVendorAndCompanyIds_ReturnsNull() { var dbName = Guid.NewGuid().ToString(); int firstCompanyId, secondVendorId; using (var seed = NewContext(dbName)) { var (firstId, _) = await SeedCompanyWithTechnicianAsync(seed, "First Co", "A"); var (_, secondVendor) = await SeedCompanyWithTechnicianAsync(seed, "Second Co", "B"); firstCompanyId = firstId; secondVendorId = secondVendor.Id; } using var context = NewContext(dbName); var service = new VendorCompanyRosterDataService(context); var roster = await service.GetRosterAsync(secondVendorId, firstCompanyId, CancellationToken.None); roster.Should().BeNull(); } [Fact] public async Task SaveRoster_RenamesCompanyAndTechnicianAndUpdatesNotes() { var dbName = Guid.NewGuid().ToString(); int companyId, techId; using (var seed = NewContext(dbName)) { var (cid, vendor) = await SeedCompanyWithTechnicianAsync(seed, "Old Co", "Old Name"); companyId = cid; techId = vendor.Id; } using var context = NewContext(dbName); var service = new VendorCompanyRosterDataService(context); var roster = await service.SaveRosterAsync(new VendorCompanyRosterWriteModel { CompanyId = companyId, Name = "New Co", Email = "new@example.com", Notes = "updated notes", ActorUserId = "42", Technicians = new List { new() { Id = techId, ContactName = "New Name", IsActive = true } } }, CancellationToken.None); roster.Name.Should().Be("New Co"); roster.Notes.Should().Be("updated notes"); var tech = context.Vendors.Single(v => v.Id == techId); tech.ContactName.Should().Be("New Name"); tech.CompanyName.Should().Be("New Co"); context.VendorCompanies.Single(c => c.Id == companyId).Notes.Should().Be("updated notes"); } [Fact] public async Task SaveRoster_SoftDeletesOmittedTechnicianWithoutOpenWorkOrders() { var dbName = Guid.NewGuid().ToString(); int companyId, keepId, dropId; using (var seed = NewContext(dbName)) { var (cid, _) = await SeedCompanyWithTechnicianAsync(seed, "Drop Co", "Keep", "Drop"); companyId = cid; keepId = seed.Vendors.First(v => v.CompanyId == cid && v.ContactName == "Keep").Id; dropId = seed.Vendors.First(v => v.CompanyId == cid && v.ContactName == "Drop").Id; } using (var act = NewContext(dbName)) { var service = new VendorCompanyRosterDataService(act); await service.SaveRosterAsync(new VendorCompanyRosterWriteModel { CompanyId = companyId, Name = "Drop Co", Email = "drop@example.com", ActorUserId = "42", Technicians = new List { new() { Id = keepId, ContactName = "Keep", IsActive = true } } }, CancellationToken.None); } using var verify = NewContext(dbName); var kept = verify.Vendors.Single(v => v.Id == keepId); kept.IsDeleted.Should().NotBeTrue(); var dropped = verify.Vendors.Single(v => v.Id == dropId); dropped.IsDeleted.Should().BeTrue(); dropped.IsActive.Should().BeFalse(); } [Fact] public async Task SaveRoster_RemovingAllTechniciansLeavesZeroVendorsAndCompanyRemains() { var dbName = Guid.NewGuid().ToString(); int companyId; using (var seed = NewContext(dbName)) { var (cid, _) = await SeedCompanyWithTechnicianAsync(seed, "Empty Co", "T1", "T2"); companyId = cid; } using (var act = NewContext(dbName)) { var service = new VendorCompanyRosterDataService(act); await service.SaveRosterAsync(new VendorCompanyRosterWriteModel { CompanyId = companyId, Name = "Empty Co", Email = "empty@example.com", ActorUserId = "42", Technicians = new List() }, CancellationToken.None); } using var verify = NewContext(dbName); verify.Vendors.Count(v => v.CompanyId == companyId && (v.IsDeleted == null || v.IsDeleted == false)).Should().Be(0); verify.VendorCompanies.Any(c => c.Id == companyId).Should().BeTrue(); } [Fact] public async Task SaveRoster_RemovalBlockedByOpenWorkOrdersFailsWholeSave() { var dbName = Guid.NewGuid().ToString(); int companyId, vendorId; using (var seed = NewContext(dbName)) { var (cid, vendor) = await SeedCompanyWithTechnicianAsync(seed, "Blocked Co", "Busy"); companyId = cid; vendorId = vendor.Id; seed.workOrders.Add(new WorkOrder { Id = 800, LifecycleStatus = LifecycleStatus.Scheduled, Status = "Scheduled" }); seed.Dispatches.Add(new Dispatch { VendorId = vendorId, WorkOrderId = 800, Vendor = vendor }); await seed.SaveChangesAsync(); } using (var act = NewContext(dbName)) { var service = new VendorCompanyRosterDataService(act); var actCall = () => service.SaveRosterAsync(new VendorCompanyRosterWriteModel { CompanyId = companyId, RowVersion = new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, Name = "Blocked Co", Email = "blocked@example.com", ActorUserId = "42", Technicians = new List() }, CancellationToken.None); await actCall.Should().ThrowAsync(); } using var verify = NewContext(dbName); var tech = verify.Vendors.Single(v => v.Id == vendorId); tech.IsDeleted.Should().NotBeTrue(); tech.IsActive.Should().BeTrue(); } [Fact] public async Task GetRoster_ForwardsCancellation() { var dbName = Guid.NewGuid().ToString(); using var context = NewContext(dbName); var service = new VendorCompanyRosterDataService(context); using var cts = new CancellationTokenSource(); cts.Cancel(); var act = () => service.GetRosterAsync(1, null, cts.Token); await act.Should().ThrowAsync(); } [Fact] public async Task SaveRoster_PropagatesCompanyLocationAndPhoneToRetainedTechnician() { var dbName = Guid.NewGuid().ToString(); int companyId, techId; using (var seed = NewContext(dbName)) { var company = new VendorCompany { Name = "Loc Co", NormalizedName = "loc co", CompanyPhone = "(111) 111-0001", Address = "Old Addr", City = "OldCity", State = "OS", Zip = "00001", GoogleMapsUrl = "https://old.example/map" }; seed.VendorCompanies.Add(company); seed.Vendors.Add(new Vendor { CompanyName = "Loc Co", ContactName = "Keeper", IsActive = true, IsDeleted = false, Company = company, CompanyPhone = "(111) 111-0001", Address = "Old Addr", City = "OldCity", State = "OS", Zip = "00001", GoogleMapsUrl = "https://old.example/map" }); await seed.SaveChangesAsync(); companyId = company.Id; techId = seed.Vendors.First(v => v.CompanyId == companyId).Id; } using (var act = NewContext(dbName)) { var service = new VendorCompanyRosterDataService(act); await service.SaveRosterAsync(new VendorCompanyRosterWriteModel { CompanyId = companyId, Name = "Loc Co", CompanyPhone = "(222) 222-0002", Address = "New Addr", City = "NewCity", State = "NS", Zip = "00002", GoogleMapsUrl = "https://new.example/map", ActorUserId = "42", Technicians = new List { new() { Id = techId, ContactName = "Keeper", IsActive = true } } }, CancellationToken.None); } using var verify = NewContext(dbName); var tech = verify.Vendors.Single(v => v.Id == techId); tech.CompanyPhone.Should().Be("(222) 222-0002"); tech.Address.Should().Be("New Addr"); tech.City.Should().Be("NewCity"); tech.State.Should().Be("NS"); tech.Zip.Should().Be("00002"); tech.GoogleMapsUrl.Should().Be("https://new.example/map"); var directory = new VendorDataService(verify); var newCity = await directory.GetDirectoryPagedAsync(1, 10, search: "NewCity"); newCity.Items.Should().ContainSingle(row => row.VendorId == techId); var oldCity = await directory.GetDirectoryPagedAsync(1, 10, search: "OldCity"); oldCity.Items.Should().NotContain(row => row.VendorId == techId); } [Fact] public async Task SaveRoster_ClearsCompanyOwnedFieldsOnRetainedTechnicianWhenCompanyClearsThem() { var dbName = Guid.NewGuid().ToString(); int companyId, techId; using (var seed = NewContext(dbName)) { var company = new VendorCompany { Name = "Clear Co", NormalizedName = "clear co", CompanyPhone = "(333) 333-0003", Address = "Keep Addr", City = "KeepCity", State = "KS", Zip = "00003", GoogleMapsUrl = "https://keep.example/map" }; seed.VendorCompanies.Add(company); seed.Vendors.Add(new Vendor { CompanyName = "Clear Co", ContactName = "Keeper", IsActive = true, IsDeleted = false, Company = company, CompanyPhone = "(333) 333-0003", Address = "Keep Addr", City = "KeepCity", State = "KS", Zip = "00003", GoogleMapsUrl = "https://keep.example/map" }); await seed.SaveChangesAsync(); companyId = company.Id; techId = seed.Vendors.First(v => v.CompanyId == companyId).Id; } using (var act = NewContext(dbName)) { var service = new VendorCompanyRosterDataService(act); await service.SaveRosterAsync(new VendorCompanyRosterWriteModel { CompanyId = companyId, Name = "Clear Co", CompanyPhone = null, Address = null, City = null, State = null, Zip = null, GoogleMapsUrl = null, ActorUserId = "42", Technicians = new List { new() { Id = techId, ContactName = "Keeper", IsActive = true } } }, CancellationToken.None); } using var verify = NewContext(dbName); var tech = verify.Vendors.Single(v => v.Id == techId); tech.CompanyPhone.Should().BeNull(); tech.Address.Should().BeNull(); tech.City.Should().BeNull(); tech.State.Should().BeNull(); tech.Zip.Should().BeNull(); tech.GoogleMapsUrl.Should().BeNull(); } [Fact] public async Task SaveRoster_NewTechnicianReceivesCompanyPhoneAndLocation() { var dbName = Guid.NewGuid().ToString(); int companyId; using (var seed = NewContext(dbName)) { var company = new VendorCompany { Name = "Add Co", NormalizedName = "add co" }; seed.VendorCompanies.Add(company); await seed.SaveChangesAsync(); companyId = company.Id; } int newTechId; using (var act = NewContext(dbName)) { var service = new VendorCompanyRosterDataService(act); await service.SaveRosterAsync(new VendorCompanyRosterWriteModel { CompanyId = companyId, Name = "Add Co", CompanyPhone = "(444) 444-0004", Address = "Add Addr", City = "AddCity", State = "AS", Zip = "00004", GoogleMapsUrl = "https://add.example/map", ActorUserId = "42", Technicians = new List { new() { ContactName = "Fresh", IsActive = true } } }, CancellationToken.None); newTechId = act.Vendors.First(v => v.CompanyId == companyId && v.ContactName == "Fresh").Id; } using var verify = NewContext(dbName); var tech = verify.Vendors.Single(v => v.Id == newTechId); tech.CompanyPhone.Should().Be("(444) 444-0004"); tech.Address.Should().Be("Add Addr"); tech.City.Should().Be("AddCity"); tech.State.Should().Be("AS"); tech.Zip.Should().Be("00004"); tech.GoogleMapsUrl.Should().Be("https://add.example/map"); } [Fact] public async Task CreateRoster_NewTechnicianReceivesCompanyPhoneAndLocation() { var dbName = Guid.NewGuid().ToString(); using var context = NewContext(dbName); var service = new VendorCompanyRosterDataService(context); var roster = await service.CreateRosterAsync(new VendorCompanyRosterWriteModel { Name = "Create Co", CompanyPhone = "(555) 555-0005", Address = "Create Addr", City = "CreateCity", State = "CS", Zip = "00005", GoogleMapsUrl = "https://create.example/map", ActorUserId = "42", Technicians = new List { new() { ContactName = "Brand", IsActive = true } } }, CancellationToken.None); var tech = context.Vendors.Single(v => v.CompanyId == roster.CompanyId); tech.CompanyPhone.Should().Be("(555) 555-0005"); tech.Address.Should().Be("Create Addr"); tech.City.Should().Be("CreateCity"); tech.State.Should().Be("CS"); tech.Zip.Should().Be("00005"); tech.GoogleMapsUrl.Should().Be("https://create.example/map"); } private static byte[] InitialRowVersion() => new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }; [Fact] public async Task AddTechnicians_RenameToTakenName_ThrowsDuplicateNameConflict() { var dbName = Guid.NewGuid().ToString(); int companyId; using (var seed = NewContext(dbName)) { var (cid, _) = await SeedCompanyWithTechnicianAsync(seed, "Gateway Plumbing", InitialRowVersion(), "First"); companyId = cid; await SeedCompanyWithTechnicianAsync(seed, "Harbor Electric", InitialRowVersion(), "Other"); } using (var act = NewContext(dbName)) { var service = new VendorCompanyRosterDataService(act); var call = () => service.AddTechniciansAsync(new VendorCompanyRosterAddWriteModel { CompanyId = companyId, RowVersion = InitialRowVersion(), ActorUserId = "42", CompanyFields = new VendorRosterCompanyFieldsWriteModel { Name = "Harbor Electric" }, AddTechnicians = new List() }, CancellationToken.None); // SH-250: a colliding rename must surface as a stable client conflict, not as an // unhandled DbUpdateException that the controller reports as a 500. await call.Should().ThrowAsync(); } using var verify = NewContext(dbName); verify.VendorCompanies.Single(c => c.Id == companyId).Name.Should().Be("Gateway Plumbing"); } [Fact] public async Task AddTechnicians_RenameToFreeName_Succeeds() { var dbName = Guid.NewGuid().ToString(); int companyId; using (var seed = NewContext(dbName)) { var (cid, _) = await SeedCompanyWithTechnicianAsync(seed, "Gateway Plumbing", InitialRowVersion(), "First"); companyId = cid; } using (var act = NewContext(dbName)) { var service = new VendorCompanyRosterDataService(act); await service.AddTechniciansAsync(new VendorCompanyRosterAddWriteModel { CompanyId = companyId, RowVersion = InitialRowVersion(), ActorUserId = "42", CompanyFields = new VendorRosterCompanyFieldsWriteModel { Name = "Gateway Plumbing & Drain" }, AddTechnicians = new List() }, CancellationToken.None); } using var verify = NewContext(dbName); var company = verify.VendorCompanies.Single(c => c.Id == companyId); company.Name.Should().Be("Gateway Plumbing & Drain"); company.NormalizedName.Should().Be("gateway plumbing & drain"); } [Fact] public async Task AddTechnicians_LeavesAllPreExistingTechniciansIntact() { var dbName = Guid.NewGuid().ToString(); int companyId, firstId, secondId; using (var seed = NewContext(dbName)) { var (cid, _) = await SeedCompanyWithTechnicianAsync(seed, "Add Co", InitialRowVersion(), "First", "Second"); companyId = cid; firstId = seed.Vendors.First(v => v.CompanyId == cid && v.ContactName == "First").Id; secondId = seed.Vendors.First(v => v.CompanyId == cid && v.ContactName == "Second").Id; } using (var act = NewContext(dbName)) { var service = new VendorCompanyRosterDataService(act); var roster = await service.AddTechniciansAsync(new VendorCompanyRosterAddWriteModel { CompanyId = companyId, RowVersion = InitialRowVersion(), ActorUserId = "42", AddTechnicians = new List { new() { ContactName = "Third", Phone = "(312) 555-0100", IsActive = true } } }, CancellationToken.None); roster.Technicians.Select(t => t.ContactName).Should().BeEquivalentTo(new[] { "First", "Second", "Third" }); Convert.ToBase64String(roster.RowVersion!).Should().NotBe(Convert.ToBase64String(InitialRowVersion())); } using var verify = NewContext(dbName); var first = verify.Vendors.Single(v => v.Id == firstId); first.IsDeleted.Should().NotBeTrue(); first.IsActive.Should().BeTrue(); first.ContactName.Should().Be("First"); var second = verify.Vendors.Single(v => v.Id == secondId); second.IsDeleted.Should().NotBeTrue(); second.IsActive.Should().BeTrue(); second.ContactName.Should().Be("Second"); var added = verify.Vendors.Single(v => v.ContactName == "Third"); added.CompanyId.Should().Be(companyId); added.IsActive.Should().BeTrue(); added.Phone.Should().Be("(312) 555-0100"); } [Fact] public async Task AddTechnicians_SucceedsWhenExistingTechniciansHaveOpenWorkOrders() { var dbName = Guid.NewGuid().ToString(); int companyId, busyId; using (var seed = NewContext(dbName)) { var (cid, vendor) = await SeedCompanyWithTechnicianAsync(seed, "Busy Co", InitialRowVersion(), "Busy"); companyId = cid; busyId = vendor.Id; seed.workOrders.Add(new WorkOrder { Id = 900, LifecycleStatus = LifecycleStatus.Scheduled, Status = "Scheduled" }); seed.Dispatches.Add(new Dispatch { VendorId = busyId, WorkOrderId = 900, Vendor = vendor }); await seed.SaveChangesAsync(); } using (var act = NewContext(dbName)) { var service = new VendorCompanyRosterDataService(act); var roster = await service.AddTechniciansAsync(new VendorCompanyRosterAddWriteModel { CompanyId = companyId, RowVersion = InitialRowVersion(), ActorUserId = "42", AddTechnicians = new List { new() { ContactName = "Newcomer", IsActive = true } } }, CancellationToken.None); roster.Technicians.Select(t => t.ContactName).Should().BeEquivalentTo(new[] { "Busy", "Newcomer" }); } using var verify = NewContext(dbName); var busy = verify.Vendors.Single(v => v.Id == busyId); busy.IsDeleted.Should().NotBeTrue(); busy.IsActive.Should().BeTrue(); verify.Vendors.Count(v => v.CompanyId == companyId && (v.IsDeleted == null || v.IsDeleted == false)).Should().Be(2); } [Fact] public async Task AddTechnicians_StaleRowVersion_ThrowsConcurrencyAndLeavesCompanyUnchanged() { var dbName = Guid.NewGuid().ToString(); int companyId; using (var seed = NewContext(dbName)) { var (cid, _) = await SeedCompanyWithTechnicianAsync(seed, "Race Co", InitialRowVersion(), "Original"); companyId = cid; } using (var concurrentWriter = NewContext(dbName)) { var company = concurrentWriter.VendorCompanies.Single(c => c.Id == companyId); company.Notes = "modified concurrently"; await concurrentWriter.SaveChangesAsync(); } using (var act = NewContext(dbName)) { var service = new VendorCompanyRosterDataService(act); var actCall = () => service.AddTechniciansAsync(new VendorCompanyRosterAddWriteModel { CompanyId = companyId, RowVersion = InitialRowVersion(), ActorUserId = "42", AddTechnicians = new List { new() { ContactName = "Should Not Persist", IsActive = true } } }, CancellationToken.None); await actCall.Should().ThrowAsync(); } // The stale save must not clobber the concurrent writer's company state. (The // InMemory provider does not roll back applied inserts when the rowversion-guarded // update fails; on SQL Server the implicit SaveChanges transaction makes the whole // batch atomic — the same mechanism the PUT reconcile relies on.) using var verify = NewContext(dbName); var stored = verify.VendorCompanies.Single(c => c.Id == companyId); stored.Notes.Should().Be("modified concurrently"); stored.RowVersion.Should().NotEqual(InitialRowVersion()); } [Fact] public async Task AddTechnicians_UnknownCompany_ThrowsKeyNotFound() { var dbName = Guid.NewGuid().ToString(); using var context = NewContext(dbName); var service = new VendorCompanyRosterDataService(context); var act = () => service.AddTechniciansAsync(new VendorCompanyRosterAddWriteModel { CompanyId = 404, RowVersion = InitialRowVersion(), ActorUserId = "42", AddTechnicians = new List { new() { ContactName = "Ghost", IsActive = true } } }, CancellationToken.None); await act.Should().ThrowAsync(); context.Vendors.Should().BeEmpty(); context.VendorCompanies.Should().BeEmpty(); } [Fact] public async Task AddTechnicians_WithCompanyFields_UpdatesOnlyProvidedFieldsAndPropagatesToExisting() { var dbName = Guid.NewGuid().ToString(); int companyId, existingId; using (var seed = NewContext(dbName)) { var company = new VendorCompany { Name = "Field Co", NormalizedName = "field co", CompanyPhone = "(111) 111-0001", Email = "keep@example.com", Address = "Old Addr", City = "OldCity", Notes = "keep notes", RowVersion = InitialRowVersion() }; seed.VendorCompanies.Add(company); seed.Vendors.Add(new Vendor { CompanyName = "Field Co", ContactName = "Keeper", IsActive = true, IsDeleted = false, Company = company, CompanyPhone = "(111) 111-0001", Address = "Old Addr", City = "OldCity" }); await seed.SaveChangesAsync(); companyId = company.Id; existingId = seed.Vendors.First(v => v.CompanyId == companyId).Id; } using (var act = NewContext(dbName)) { var service = new VendorCompanyRosterDataService(act); await service.AddTechniciansAsync(new VendorCompanyRosterAddWriteModel { CompanyId = companyId, RowVersion = InitialRowVersion(), ActorUserId = "42", CompanyFields = new VendorRosterCompanyFieldsWriteModel { Name = "Renamed Co", CompanyPhone = "(222) 222-0002", City = "NewCity" }, AddTechnicians = new List { new() { ContactName = "Fresh", IsActive = true } } }, CancellationToken.None); } using var verify = NewContext(dbName); var companyRow = verify.VendorCompanies.Single(c => c.Id == companyId); companyRow.Name.Should().Be("Renamed Co"); companyRow.NormalizedName.Should().Be("renamed co"); companyRow.CompanyPhone.Should().Be("(222) 222-0002"); companyRow.City.Should().Be("NewCity"); companyRow.Email.Should().Be("keep@example.com"); companyRow.Address.Should().Be("Old Addr"); companyRow.Notes.Should().Be("keep notes"); var existing = verify.Vendors.Single(v => v.Id == existingId); existing.IsDeleted.Should().NotBeTrue(); existing.IsActive.Should().BeTrue(); existing.CompanyName.Should().Be("Renamed Co"); existing.CompanyPhone.Should().Be("(222) 222-0002"); existing.City.Should().Be("NewCity"); var added = verify.Vendors.Single(v => v.ContactName == "Fresh"); added.CompanyName.Should().Be("Renamed Co"); added.CompanyPhone.Should().Be("(222) 222-0002"); added.City.Should().Be("NewCity"); } [Fact] public async Task AddTechnicians_ForwardsCancellation() { var dbName = Guid.NewGuid().ToString(); using var context = NewContext(dbName); var service = new VendorCompanyRosterDataService(context); using var cts = new CancellationTokenSource(); cts.Cancel(); var act = () => service.AddTechniciansAsync(new VendorCompanyRosterAddWriteModel { CompanyId = 1, RowVersion = InitialRowVersion(), ActorUserId = "42", AddTechnicians = new List { new() { ContactName = "T" } } }, cts.Token); await act.Should().ThrowAsync(); } }