diff --git a/Api.SeaHavenIndustries.Tests/LocationControllerSitesTests.cs b/Api.SeaHavenIndustries.Tests/LocationControllerSitesTests.cs index dc5fded..b926519 100644 --- a/Api.SeaHavenIndustries.Tests/LocationControllerSitesTests.cs +++ b/Api.SeaHavenIndustries.Tests/LocationControllerSitesTests.cs @@ -47,10 +47,10 @@ public class LocationControllerSitesTests private static async Task SeedSitesAsync(ApplicationDbContext ctx) { ctx.Locations.AddRange( - new Locations { Id = 1, Name = "STL8", City = "St. Louis", State = "MO" }, - new Locations { Id = 2, Name = "BK5", City = "Dallas", State = "TX" }, - new Locations { Id = 3, Name = "IND9", City = "Indianapolis", State = "IN" }, - new Locations { Id = 4, Name = "DFW8", City = "Dallas", State = "TX" }); + new Locations { Id = 1, Name = "STL8", City = "St. Louis", State = "MO", AccountId = 100 }, + new Locations { Id = 2, Name = "BK5", City = "Dallas", State = "TX", AccountId = 100 }, + new Locations { Id = 3, Name = "IND9", City = "Indianapolis", State = "IN", AccountId = 100 }, + new Locations { Id = 4, Name = "DFW8", City = "Dallas", State = "TX", AccountId = 100 }); await ctx.SaveChangesAsync(); } @@ -108,4 +108,44 @@ public class LocationControllerSitesTests data.Should().HaveCount(2); data.Select(s => s.Code).Should().BeEquivalentTo("BK5", "DFW8"); } + + [Fact] + public async Task GetSites_OmitsLocationsWithoutAccountMapping() + { + await using var ctx = NewContext(); + ctx.Locations.AddRange( + new Locations { Id = 1, Name = "STL8", City = "St. Louis", State = "MO", AccountId = 100 }, + new Locations { Id = 2, Name = "UNMAPPED1", City = "Nowhere", State = "ZZ" }); + await ctx.SaveChangesAsync(); + var controller = NewController(ctx); + + var result = await controller.GetSites(); + + var ok = result.Should().BeOfType().Subject; + var data = ok.Value.Should().BeAssignableTo>().Subject; + + data.Should().ContainSingle(); + data[0].Code.Should().Be("STL8"); + + var searchResult = await controller.GetSites("unmapped1"); + + var searchOk = searchResult.Should().BeOfType().Subject; + var searchData = searchOk.Value.Should().BeAssignableTo>().Subject; + + searchData.Should().BeEmpty(); + } + + [Fact] + public async Task GetSites_ForwardsCancellationToken() + { + await using var ctx = NewContext(); + await SeedSitesAsync(ctx); + var controller = NewController(ctx); + using var cancellation = new CancellationTokenSource(); + await cancellation.CancelAsync(); + + var act = () => controller.GetSites(cancellationToken: cancellation.Token); + + await act.Should().ThrowAsync(); + } } diff --git a/Api.SeaHavenIndustries/Controllers/LocationController.cs b/Api.SeaHavenIndustries/Controllers/LocationController.cs index 6d17f9f..5c8bb30 100644 --- a/Api.SeaHavenIndustries/Controllers/LocationController.cs +++ b/Api.SeaHavenIndustries/Controllers/LocationController.cs @@ -27,9 +27,11 @@ namespace Api.SeaHavenIndustries.Controllers } [HttpGet("sites")] - public async Task GetSites(string? search = null) + public async Task GetSites( + string? search = null, + CancellationToken cancellationToken = default) { - var data = await _locationService.GetSiteOptionsAsync(search); + var data = await _locationService.GetSiteOptionsAsync(search, cancellationToken); return Ok(data); } diff --git a/SeaHaven.DataServices/Implementation/LocationDataService.cs b/SeaHaven.DataServices/Implementation/LocationDataService.cs index 48ade48..7a589e7 100644 --- a/SeaHaven.DataServices/Implementation/LocationDataService.cs +++ b/SeaHaven.DataServices/Implementation/LocationDataService.cs @@ -74,9 +74,13 @@ namespace SeaHaven.DataServices.Implementation return (items, totalCount); } - public async Task> GetSiteOptionsAsync(string? search = null) + public async Task> GetSiteOptionsAsync( + string? search = null, + CancellationToken cancellationToken = default) { - var query = _context.Locations.AsNoTracking(); + var query = _context.Locations + .AsNoTracking() + .Where(l => l.AccountId != null); if (!string.IsNullOrWhiteSpace(search)) { @@ -89,7 +93,7 @@ namespace SeaHaven.DataServices.Implementation return await query .OrderBy(l => l.Name) .Select(l => new SiteOptionRow(l.Id, l.Name, l.City, l.State)) - .ToListAsync(); + .ToListAsync(cancellationToken); } public async Task AddAsync(Locations location) diff --git a/SeaHaven.DataServices/Interfaces/ILocationDataService.cs b/SeaHaven.DataServices/Interfaces/ILocationDataService.cs index 1bba950..8e7a51a 100644 --- a/SeaHaven.DataServices/Interfaces/ILocationDataService.cs +++ b/SeaHaven.DataServices/Interfaces/ILocationDataService.cs @@ -17,7 +17,9 @@ namespace SeaHaven.DataServices.Interfaces // For addressbook endpoint - returns simplified DTO Task<(IEnumerable Items, int TotalCount)> GetAddressbookPagedAsync(int page, int pageSize, string? search = null); - Task> GetSiteOptionsAsync(string? search = null); + Task> GetSiteOptionsAsync( + string? search = null, + CancellationToken cancellationToken = default); /// /// Exists is false when the row is missing. AccountId is null when the site has no account. diff --git a/SeaHaven.Services/Implementation/LocationService.cs b/SeaHaven.Services/Implementation/LocationService.cs index 3a9e917..1b301af 100644 --- a/SeaHaven.Services/Implementation/LocationService.cs +++ b/SeaHaven.Services/Implementation/LocationService.cs @@ -70,9 +70,11 @@ namespace SeaHaven.Services.Implementation }; } - public async Task> GetSiteOptionsAsync(string? search = null) + public async Task> GetSiteOptionsAsync( + string? search = null, + CancellationToken cancellationToken = default) { - var rows = await _locationDataService.GetSiteOptionsAsync(search); + var rows = await _locationDataService.GetSiteOptionsAsync(search, cancellationToken); return rows .Select(r => new SiteOptionDTO { diff --git a/SeaHaven.Services/Interfaces/ILocationService.cs b/SeaHaven.Services/Interfaces/ILocationService.cs index 2fa2768..c13ec65 100644 --- a/SeaHaven.Services/Interfaces/ILocationService.cs +++ b/SeaHaven.Services/Interfaces/ILocationService.cs @@ -10,7 +10,9 @@ namespace SeaHaven.Services.Interfaces Task> GetAllLocationsAsync(); Task> GetLocationsByAccountIdAsync(int accountId); Task> GetLocationsPagedAsync(int page, int pageSize, string? search = null); - Task> GetSiteOptionsAsync(string? search = null); + Task> GetSiteOptionsAsync( + string? search = null, + CancellationToken cancellationToken = default); Task CreateLocationAsync(CreateLocationDTO dto, string userId); Task UpdateLocationAsync(int id, UpdateLocationDTO dto, string userId); Task DeleteLocationAsync(int id, string userId); diff --git a/SeaHavenIndustries.Tests/WorkOrderAccountScopeTests.cs b/SeaHavenIndustries.Tests/WorkOrderAccountScopeTests.cs index 067234e..820fde2 100644 --- a/SeaHavenIndustries.Tests/WorkOrderAccountScopeTests.cs +++ b/SeaHavenIndustries.Tests/WorkOrderAccountScopeTests.cs @@ -816,8 +816,10 @@ public class WorkOrderAccountScopeTests => _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) - => _inner.GetSiteOptionsAsync(search); + public Task> GetSiteOptionsAsync( + string? search = null, + CancellationToken cancellationToken = default) + => _inner.GetSiteOptionsAsync(search, cancellationToken); public Task<(bool Exists, int? AccountId)> GetAccountScopeAsync( int locationId,