From cc65176070a48afe0582f939d2c2325c7ac9cc2e Mon Sep 17 00:00:00 2001 From: pata9 Date: Tue, 21 Apr 2026 11:36:10 +0100 Subject: [PATCH 01/23] introduce sliding cache for point read of sites --- infrastructure/resources/high_load_functions.tf | 4 ++++ infrastructure/resources/http_functions.tf | 4 ++++ .../resources/servicebus_functions.tf | 4 ++++ infrastructure/resources/timer_functions.tf | 4 ++++ infrastructure/resources/variables.tf | 10 ++++++++++ .../ServiceRegistration.cs | 2 ++ .../Nhs.Appointments.Core/Sites/SiteService.cs | 17 ++++++++++++++++- .../Sites/SiteServiceOptions.cs | 10 ++++++++++ 8 files changed, 54 insertions(+), 1 deletion(-) diff --git a/infrastructure/resources/high_load_functions.tf b/infrastructure/resources/high_load_functions.tf index ca2358f48..a6ac18d01 100644 --- a/infrastructure/resources/high_load_functions.tf +++ b/infrastructure/resources/high_load_functions.tf @@ -52,6 +52,8 @@ resource "azurerm_windows_function_app" "nbs_mya_high_load_func_app" { ALLSITES_SLIDING_CACHE_ENABLED = var.allsites_sliding_cache_enabled ALLSITES_CACHE_DURATION_MINUTES = var.allsites_cache_duration_minutes ALLSITES_SLIDING_CACHE_DURATION_MINUTES = var.allsites_sliding_cache_duration_minutes + SITE_CACHE_DURATION_MINUTES = var.site_cache_duration_minutes + SITE_SLIDING_CACHE_DURATION_MINUTES = var.site_sliding_cache_duration_minutes DISABLE_SITE_CACHE = var.disable_site_cache SITE_SUPPORTS_SERVICE_SLIDING_CACHE_ABSOLUTE_EXPIRATION_SECONDS = var.site_supports_service_sliding_cache_absolute_expiration_seconds SITE_SUPPORTS_SERVICE_SLIDING_CACHE_SLIDE_THRESHOLD_SECONDS = var.site_supports_service_sliding_cache_slide_threshold_seconds @@ -205,6 +207,8 @@ resource "azurerm_windows_function_app_slot" "nbs_mya_high_load_func_app_preview ALLSITES_SLIDING_CACHE_ENABLED = var.allsites_sliding_cache_enabled ALLSITES_CACHE_DURATION_MINUTES = var.allsites_cache_duration_minutes ALLSITES_SLIDING_CACHE_DURATION_MINUTES = var.allsites_sliding_cache_duration_minutes + SITE_CACHE_DURATION_MINUTES = var.site_cache_duration_minutes + SITE_SLIDING_CACHE_DURATION_MINUTES = var.site_sliding_cache_duration_minutes DISABLE_SITE_CACHE = var.disable_site_cache SITE_SUPPORTS_SERVICE_SLIDING_CACHE_ABSOLUTE_EXPIRATION_SECONDS = var.site_supports_service_sliding_cache_absolute_expiration_seconds SITE_SUPPORTS_SERVICE_SLIDING_CACHE_SLIDE_THRESHOLD_SECONDS = var.site_supports_service_sliding_cache_slide_threshold_seconds diff --git a/infrastructure/resources/http_functions.tf b/infrastructure/resources/http_functions.tf index 05a5f2640..dd6370e82 100644 --- a/infrastructure/resources/http_functions.tf +++ b/infrastructure/resources/http_functions.tf @@ -76,6 +76,8 @@ resource "azurerm_windows_function_app" "nbs_mya_http_func_app" { ALLSITES_SLIDING_CACHE_ENABLED = var.allsites_sliding_cache_enabled ALLSITES_CACHE_DURATION_MINUTES = var.allsites_cache_duration_minutes ALLSITES_SLIDING_CACHE_DURATION_MINUTES = var.allsites_sliding_cache_duration_minutes + SITE_CACHE_DURATION_MINUTES = var.site_cache_duration_minutes + SITE_SLIDING_CACHE_DURATION_MINUTES = var.site_sliding_cache_duration_minutes DISABLE_SITE_CACHE = var.disable_site_cache SITE_SUPPORTS_SERVICE_SLIDING_CACHE_ABSOLUTE_EXPIRATION_SECONDS = var.site_supports_service_sliding_cache_absolute_expiration_seconds SITE_SUPPORTS_SERVICE_SLIDING_CACHE_SLIDE_THRESHOLD_SECONDS = var.site_supports_service_sliding_cache_slide_threshold_seconds @@ -183,6 +185,8 @@ resource "azurerm_windows_function_app_slot" "nbs_mya_http_func_app_preview" { ALLSITES_SLIDING_CACHE_ENABLED = var.allsites_sliding_cache_enabled ALLSITES_CACHE_DURATION_MINUTES = var.allsites_cache_duration_minutes ALLSITES_SLIDING_CACHE_DURATION_MINUTES = var.allsites_sliding_cache_duration_minutes + SITE_CACHE_DURATION_MINUTES = var.site_cache_duration_minutes + SITE_SLIDING_CACHE_DURATION_MINUTES = var.site_sliding_cache_duration_minutes DISABLE_SITE_CACHE = var.disable_site_cache SITE_SUPPORTS_SERVICE_SLIDING_CACHE_ABSOLUTE_EXPIRATION_SECONDS = var.site_supports_service_sliding_cache_absolute_expiration_seconds SITE_SUPPORTS_SERVICE_SLIDING_CACHE_SLIDE_THRESHOLD_SECONDS = var.site_supports_service_sliding_cache_slide_threshold_seconds diff --git a/infrastructure/resources/servicebus_functions.tf b/infrastructure/resources/servicebus_functions.tf index 3958ab555..d18407b66 100644 --- a/infrastructure/resources/servicebus_functions.tf +++ b/infrastructure/resources/servicebus_functions.tf @@ -54,6 +54,8 @@ resource "azurerm_windows_function_app" "nbs_mya_service_bus_func_app" { ALLSITES_SLIDING_CACHE_ENABLED = var.allsites_sliding_cache_enabled ALLSITES_CACHE_DURATION_MINUTES = var.allsites_cache_duration_minutes ALLSITES_SLIDING_CACHE_DURATION_MINUTES = var.allsites_sliding_cache_duration_minutes + SITE_CACHE_DURATION_MINUTES = var.site_cache_duration_minutes + SITE_SLIDING_CACHE_DURATION_MINUTES = var.site_sliding_cache_duration_minutes DISABLE_SITE_CACHE = var.disable_site_cache CANCEL_A_DATE_RANGE_MAXIMUM_DAYS = var.cancel_a_date_range_maximum_days APPLICATION_NAME = "Service Bus Function App" @@ -185,6 +187,8 @@ resource "azurerm_windows_function_app_slot" "nbs_mya_service_bus_func_app_previ ALLSITES_SLIDING_CACHE_ENABLED = var.allsites_sliding_cache_enabled ALLSITES_CACHE_DURATION_MINUTES = var.allsites_sliding_cache_duration_minutes ALLSITES_SLIDING_CACHE_DURATION_MINUTES = var.allsites_cache_duration_minutes + SITE_CACHE_DURATION_MINUTES = var.site_cache_duration_minutes + SITE_SLIDING_CACHE_DURATION_MINUTES = var.site_sliding_cache_duration_minutes DISABLE_SITE_CACHE = var.disable_site_cache SITE_SUMMARY_DAYS_FORWARD = var.site_summary_days_forward SITE_SUMMARY_DAYS_CHUNK_SIZE = var.site_summary_days_chunk_size diff --git a/infrastructure/resources/timer_functions.tf b/infrastructure/resources/timer_functions.tf index 6e966a1b2..f11074527 100644 --- a/infrastructure/resources/timer_functions.tf +++ b/infrastructure/resources/timer_functions.tf @@ -55,6 +55,8 @@ resource "azurerm_windows_function_app" "nbs_mya_timer_func_app" { ALLSITES_SLIDING_CACHE_ENABLED = var.allsites_sliding_cache_enabled ALLSITES_CACHE_DURATION_MINUTES = var.allsites_cache_duration_minutes ALLSITES_SLIDING_CACHE_DURATION_MINUTES = var.allsites_sliding_cache_duration_minutes + SITE_CACHE_DURATION_MINUTES = var.site_cache_duration_minutes + SITE_SLIDING_CACHE_DURATION_MINUTES = var.site_sliding_cache_duration_minutes DISABLE_SITE_CACHE = var.disable_site_cache SPLUNK_HOST_URL = var.splunk_host_url SPLUNK_HEC_TOKEN = var.splunk_hec_token @@ -195,6 +197,8 @@ resource "azurerm_windows_function_app_slot" "nbs_mya_timer_func_app_preview" { ALLSITES_SLIDING_CACHE_ENABLED = var.allsites_sliding_cache_enabled ALLSITES_CACHE_DURATION_MINUTES = var.allsites_sliding_cache_duration_minutes ALLSITES_SLIDING_CACHE_DURATION_MINUTES = var.allsites_cache_duration_minutes + SITE_CACHE_DURATION_MINUTES = var.site_cache_duration_minutes + SITE_SLIDING_CACHE_DURATION_MINUTES = var.site_sliding_cache_duration_minutes DISABLE_SITE_CACHE = var.disable_site_cache SPLUNK_HOST_URL = var.splunk_host_url SPLUNK_HEC_TOKEN = var.splunk_hec_token diff --git a/infrastructure/resources/variables.tf b/infrastructure/resources/variables.tf index 0e56cd3c8..54e0627c6 100644 --- a/infrastructure/resources/variables.tf +++ b/infrastructure/resources/variables.tf @@ -476,6 +476,16 @@ variable "allsites_sliding_cache_duration_minutes" { default = 20 } +variable "site_cache_duration_minutes" { + type = number + default = 5 +} + +variable "site_sliding_cache_duration_minutes" { + type = number + default = 1 +} + variable "auditor_enable" { type = bool } diff --git a/src/api/Nhs.Appointments.Core/ServiceRegistration.cs b/src/api/Nhs.Appointments.Core/ServiceRegistration.cs index 05b360393..0570b2b24 100644 --- a/src/api/Nhs.Appointments.Core/ServiceRegistration.cs +++ b/src/api/Nhs.Appointments.Core/ServiceRegistration.cs @@ -30,6 +30,8 @@ public static IServiceCollection ConfigureSiteService(this IServiceCollection se opts.AllSitesSlidingCacheEnabled = configuration.GetValue("ALLSITES_SLIDING_CACHE_ENABLED", true); opts.AllSitesCacheDurationMinutes = configuration.GetValue("ALLSITES_CACHE_DURATION_MINUTES", 60); opts.AllSitesSlideCacheDurationMinutes = configuration.GetValue("ALLSITES_SLIDING_CACHE_DURATION_MINUTES", 20); + opts.SiteCacheDurationMinutes = configuration.GetValue("SITE_CACHE_DURATION_MINUTES", 5); + opts.SiteSlideCacheDurationMinutes = configuration.GetValue("SITE_SLIDING_CACHE_DURATION_MINUTES", 1); opts.DisableSiteCache = configuration.GetValue("DISABLE_SITE_CACHE", false); //default 4 hours opts.SiteSupportsServiceSlidingCacheAbsoluteExpirationSeconds = configuration.GetValue("SITE_SUPPORTS_SERVICE_SLIDING_CACHE_ABSOLUTE_EXPIRATION_SECONDS", 14400); diff --git a/src/api/Nhs.Appointments.Core/Sites/SiteService.cs b/src/api/Nhs.Appointments.Core/Sites/SiteService.cs index 9797f8892..fecdbbd76 100644 --- a/src/api/Nhs.Appointments.Core/Sites/SiteService.cs +++ b/src/api/Nhs.Appointments.Core/Sites/SiteService.cs @@ -135,7 +135,22 @@ private static List GetDateStringsInRange(DateOnly from, DateOnly to) public async Task GetSiteByIdAsync(string siteId, string scope = "*") { - var site = await siteStore.GetSiteById(siteId); + Site site; + if (options.Value.DisableSiteCache) + { + site = await siteStore.GetSiteById(siteId); + } + else + { + site = await cacheService.GetLazySlidingCacheValue($"site:{siteId}", + new LazySlideCacheOptions( + async () => + await siteStore.GetSiteById(siteId), + TimeSpan.FromMinutes(options.Value.SiteSlideCacheDurationMinutes), + TimeSpan.FromMinutes(options.Value.SiteCacheDurationMinutes))); + } + + if (site is null || site.isDeleted is true) { return default; diff --git a/src/api/Nhs.Appointments.Core/Sites/SiteServiceOptions.cs b/src/api/Nhs.Appointments.Core/Sites/SiteServiceOptions.cs index 0ae08ced1..58414d708 100644 --- a/src/api/Nhs.Appointments.Core/Sites/SiteServiceOptions.cs +++ b/src/api/Nhs.Appointments.Core/Sites/SiteServiceOptions.cs @@ -45,4 +45,14 @@ public class SiteServiceOptions /// i.e. when SiteSupportsServiceBatchMultiplier = 2, and maxRecords = 50 - then service will fetch data in batches of 100, until 50 valid sites are returned. /// public int SiteSupportsServiceBatchMultiplier { get; set; } + + /// + /// The duration, in minutes, to cache site for. + /// + public int SiteCacheDurationMinutes { get; set; } + + /// + /// The duration, in minutes, to wait before sliding the cache for a site. + /// + public int SiteSlideCacheDurationMinutes { get; set; } } From 35afcea430df5b77ba151ad5de17d1c38696288e Mon Sep 17 00:00:00 2001 From: pata9 Date: Tue, 21 Apr 2026 11:54:06 +0100 Subject: [PATCH 02/23] fix tests --- tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs b/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs index 0e162f976..96e538072 100644 --- a/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs +++ b/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs @@ -27,6 +27,8 @@ public SiteServiceTests() SiteSupportsServiceSlidingCacheSlideThresholdSeconds = 900, SiteSupportsServiceSlidingCacheAbsoluteExpirationSeconds = 14400, SiteSupportsServiceBatchMultiplier = 2, + SiteCacheDurationMinutes = 5, + SiteSlideCacheDurationMinutes = 1, }); var cacheService = new CacheService(_memoryCache.Object, TimeProvider.System); From f34a991fbef4162d4db5d07e1949ef40cc25f695 Mon Sep 17 00:00:00 2001 From: pata9 Date: Tue, 21 Apr 2026 15:01:07 +0100 Subject: [PATCH 03/23] refactor reference group to go through the service layer and use caching --- .../Bookings/ReferenceNumberProvider.cs | 18 +- .../Nhs.Appointments.Core/Sites/ISiteStore.cs | 1 - src/api/Nhs.Appointments.Core/Sites/Site.cs | 6 +- .../Sites/SiteService.cs | 7 + .../Nhs.Appointments.Persistance/SiteStore.cs | 6 - .../BulkImport/SiteBulkImportFeatureSteps.cs | 3 +- .../SiteManagement/GetSiteByIdFeatureSteps.cs | 3 +- .../SiteLocationDependentFeatureSteps.cs | 6 +- .../SiteManagementBaseFeatureSteps.cs | 3 +- .../Auth/PermissionCheckerTests.cs | 12 +- .../ApplyAvailabilityTemplateFunctionTests.cs | 3 +- .../Functions/CancelBookingFunctionTests.cs | 15 +- .../ConfirmProvisionalBookingFunctionTests.cs | 6 +- .../Functions/GetSiteMetaDataFunctionTests.cs | 3 +- .../Functions/GetSitesPreviewFunctionTests.cs | 6 +- .../Functions/GetUserProfileFunctionTests.cs | 4 +- .../Functions/MakeBookingFunctionTests.cs | 6 +- .../QueryAvailabilityByDaysFunctionTests.cs | 9 +- .../QueryAvailabilityByHoursFunctionTests.cs | 9 +- .../QueryAvailabilityBySlotsFunctionTests.cs | 6 +- .../QueryAvailabilityFunctionTests.cs | 33 +- .../QueryBookingByReferenceFunctionTests.cs | 6 +- .../Functions/SetAvailabilityFunctionTests.cs | 3 +- .../SetSiteAccessibilitiesFunctionTests.cs | 3 +- .../Functions/SetSiteDetailsFunctionTests.cs | 3 +- ...SiteInformationForCitizensFunctionTests.cs | 3 +- .../SetSiteReferenceDetailsFunctionTests.cs | 3 +- .../Notifications/BookingNotifierTests.cs | 15 +- .../UserRolesChangedNotifierTests.cs | 21 +- .../SiteStatusDataImportHandlerTests.cs | 18 +- .../BulkImport/UserDataImportHandlerTests.cs | 3 +- .../ReferenceNumberProviderTests.cs | 22 +- .../Reports/SiteReportServiceMockData.cs | 9 +- .../SiteSummary/SiteSummaryTriggerTests.cs | 21 +- .../SiteServiceCacheTestsMockData.cs | 21 +- .../SiteServiceTests.cs | 282 ++++++++++++------ .../SiteStoreTests.cs | 24 +- 37 files changed, 409 insertions(+), 213 deletions(-) diff --git a/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs b/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs index dc759108c..707967ca2 100644 --- a/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs +++ b/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs @@ -9,32 +9,32 @@ public interface IReferenceNumberProvider public class ReferenceNumberProvider : IReferenceNumberProvider { - private readonly ISiteStore _siteStore; + private readonly ISiteService _siteService; private readonly IReferenceNumberDocumentStore _referenceNumberDocumentStore; private readonly TimeProvider _timeProvider; public ReferenceNumberProvider( - ISiteStore siteStore, + ISiteService siteService, IReferenceNumberDocumentStore referenceNumberDocumentStore, TimeProvider timeProvider) { - _siteStore = siteStore; + _siteService = siteService; _referenceNumberDocumentStore = referenceNumberDocumentStore; _timeProvider = timeProvider; } public async Task GetReferenceNumber(string siteId) { - var referenceGroup = await _siteStore.GetReferenceNumberGroup(siteId); - if (referenceGroup == 0) + var site = await _siteService.GetSiteByIdAsync(siteId); + if (site.ReferenceNumberGroup == 0) { - referenceGroup = await _referenceNumberDocumentStore.AssignReferenceGroup(); - await _siteStore.AssignPrefix(siteId, referenceGroup); + var referenceGroup = await _referenceNumberDocumentStore.AssignReferenceGroup(); + await _siteService.AssignPrefix(siteId, referenceGroup); } - var sequence = await _referenceNumberDocumentStore.GetNextSequenceNumber(referenceGroup); + var sequence = await _referenceNumberDocumentStore.GetNextSequenceNumber(site.ReferenceNumberGroup); var now = _timeProvider.GetUtcNow(); var rng = now.Day + now.Second; - return $"{referenceGroup:00}-{rng:00}-{sequence:000000}"; + return $"{site.ReferenceNumberGroup:00}-{rng:00}-{sequence:000000}"; } } diff --git a/src/api/Nhs.Appointments.Core/Sites/ISiteStore.cs b/src/api/Nhs.Appointments.Core/Sites/ISiteStore.cs index 5a3758761..4ea0f4d79 100644 --- a/src/api/Nhs.Appointments.Core/Sites/ISiteStore.cs +++ b/src/api/Nhs.Appointments.Core/Sites/ISiteStore.cs @@ -14,7 +14,6 @@ Task UpdateSiteDetails(string siteId, string name, string addre Task UpdateSiteReferenceDetails(string siteId, string odsCode, string icb, string region); Task AssignPrefix(string site, int prefix); - Task GetReferenceNumberGroup(string site); Task> GetAllSites(); Task SaveSiteAsync(string siteId, string odsCode, string name, string address, string phoneNumber, diff --git a/src/api/Nhs.Appointments.Core/Sites/Site.cs b/src/api/Nhs.Appointments.Core/Sites/Site.cs index 335193b38..09c194cb8 100644 --- a/src/api/Nhs.Appointments.Core/Sites/Site.cs +++ b/src/api/Nhs.Appointments.Core/Sites/Site.cs @@ -18,13 +18,17 @@ public record Site( [JsonProperty("location")] Location location, [JsonProperty("status")] SiteStatus? status, [JsonProperty("isDeleted")] bool? isDeleted, - [JsonProperty("type")] string Type + [JsonProperty("type")] string Type, + int ReferenceNumberGroup ) { public IEnumerable Accessibilities { get; set; } = Accessibilities?.Select(a => new Accessibility(a.Id, a.Value.ToLower())); private Location Location { get; } = location; + [JsonIgnore] + public int ReferenceNumberGroup { get; } = ReferenceNumberGroup; + public Coordinates Coordinates { get diff --git a/src/api/Nhs.Appointments.Core/Sites/SiteService.cs b/src/api/Nhs.Appointments.Core/Sites/SiteService.cs index fecdbbd76..5d775da90 100644 --- a/src/api/Nhs.Appointments.Core/Sites/SiteService.cs +++ b/src/api/Nhs.Appointments.Core/Sites/SiteService.cs @@ -30,6 +30,8 @@ Task SaveSiteAsync(string siteId, string odsCode, string name, Task> GetSitesInIcbAsync(string icb); Task> QuerySitesAsync(SiteFilter[] filters, int maxRecords, bool ignoreCache); Task ToggleSiteSoftDeletionAsync(string siteId); + + Task AssignPrefix(string site, int prefix); } public class SiteService( @@ -239,6 +241,11 @@ public async Task ToggleSiteSoftDeletionAsync(string siteId) return await siteStore.ToggleSiteSoftDeletionAsync(siteId); } + public async Task AssignPrefix(string site, int prefix) + { + await siteStore.AssignPrefix(site, prefix); + } + public async Task> QuerySitesAsync(SiteFilter[] filters, int maxRecords, bool ignoreCache) { diff --git a/src/api/Nhs.Appointments.Persistance/SiteStore.cs b/src/api/Nhs.Appointments.Persistance/SiteStore.cs index 0878a5bfb..00343ec6a 100644 --- a/src/api/Nhs.Appointments.Persistance/SiteStore.cs +++ b/src/api/Nhs.Appointments.Persistance/SiteStore.cs @@ -16,12 +16,6 @@ public async Task GetSiteById(string siteId) public async Task> GetAllSites() => await cosmosStore.RunQueryAsync(sd => sd.DocumentType == "site"); - public async Task GetReferenceNumberGroup(string site) - { - var siteDocument = await cosmosStore.GetDocument(site); - return siteDocument.ReferenceNumberGroup; - } - public async Task UpdateSiteReferenceDetails(string siteId, string odsCode, string icb, string region) { var originalDocument = await GetOrDefault(siteId); diff --git a/tests/Nhs.Appointments.Api.Integration/Scenarios/BulkImport/SiteBulkImportFeatureSteps.cs b/tests/Nhs.Appointments.Api.Integration/Scenarios/BulkImport/SiteBulkImportFeatureSteps.cs index 7a314c7ee..222eeec74 100644 --- a/tests/Nhs.Appointments.Api.Integration/Scenarios/BulkImport/SiteBulkImportFeatureSteps.cs +++ b/tests/Nhs.Appointments.Api.Integration/Scenarios/BulkImport/SiteBulkImportFeatureSteps.cs @@ -63,7 +63,8 @@ public async Task AssertSite(DataTable dataTable) location: new Location("Point", [double.Parse(row.Cells.ElementAt(9).Value), double.Parse(row.Cells.ElementAt(10).Value)]), status: SiteStatus.Online, isDeleted: dataTable.GetBoolRowValueOrDefault(row, "IsDeleted"), - Type: dataTable.GetRowValueOrDefault(row, "Type") + Type: dataTable.GetRowValueOrDefault(row, "Type"), + ReferenceNumberGroup: 0 ); Response.StatusCode.Should().Be(HttpStatusCode.OK); (_, ActualResponse) = diff --git a/tests/Nhs.Appointments.Api.Integration/Scenarios/SiteManagement/GetSiteByIdFeatureSteps.cs b/tests/Nhs.Appointments.Api.Integration/Scenarios/SiteManagement/GetSiteByIdFeatureSteps.cs index 098d7c086..1fcc9372d 100644 --- a/tests/Nhs.Appointments.Api.Integration/Scenarios/SiteManagement/GetSiteByIdFeatureSteps.cs +++ b/tests/Nhs.Appointments.Api.Integration/Scenarios/SiteManagement/GetSiteByIdFeatureSteps.cs @@ -45,7 +45,8 @@ public async Task AssertSite(DataTable dataTable) OutOfTheWayLocation, status: null, isDeleted: dataTable.GetBoolRowValueOrDefault(row, "IsDeleted"), - Type: dataTable.GetRowValueOrDefault(row, "Type") + Type: dataTable.GetRowValueOrDefault(row, "Type"), + ReferenceNumberGroup: 0 ); Response.StatusCode.Should().Be(HttpStatusCode.OK); (_, ActualResponse) = diff --git a/tests/Nhs.Appointments.Api.Integration/Scenarios/SiteManagement/SiteLocationDependentFeatureSteps.cs b/tests/Nhs.Appointments.Api.Integration/Scenarios/SiteManagement/SiteLocationDependentFeatureSteps.cs index 4dc35c4f2..1e4d3775e 100644 --- a/tests/Nhs.Appointments.Api.Integration/Scenarios/SiteManagement/SiteLocationDependentFeatureSteps.cs +++ b/tests/Nhs.Appointments.Api.Integration/Scenarios/SiteManagement/SiteLocationDependentFeatureSteps.cs @@ -99,7 +99,8 @@ public async Task AssertSiteInformation(DataTable dataTable) Coordinates: [double.Parse(row.Cells.ElementAt(8).Value), double.Parse(row.Cells.ElementAt(9).Value)]), status: null, isDeleted: dataTable.GetBoolRowValueOrDefault(row, "IsDeleted"), - Type: dataTable.GetRowValueOrDefault(row, "Type") + Type: dataTable.GetRowValueOrDefault(row, "Type"), + ReferenceNumberGroup: 0 ); Response.StatusCode.Should().Be(HttpStatusCode.OK); @@ -394,7 +395,8 @@ public void AssertSites(DataTable dataTable) ]), status: null, isDeleted: dataTable.GetBoolRowValueOrDefault(row, "IsDeleted"), - Type: dataTable.GetRowValueOrDefault(row, "Type") + Type: dataTable.GetRowValueOrDefault(row, "Type"), + ReferenceNumberGroup: 0 ), Distance: int.Parse(row.Cells.ElementAt(11).Value) )).ToList(); diff --git a/tests/Nhs.Appointments.Api.Integration/Scenarios/SiteManagement/SiteManagementBaseFeatureSteps.cs b/tests/Nhs.Appointments.Api.Integration/Scenarios/SiteManagement/SiteManagementBaseFeatureSteps.cs index 8b5e912d7..60ec0c3c4 100644 --- a/tests/Nhs.Appointments.Api.Integration/Scenarios/SiteManagement/SiteManagementBaseFeatureSteps.cs +++ b/tests/Nhs.Appointments.Api.Integration/Scenarios/SiteManagement/SiteManagementBaseFeatureSteps.cs @@ -77,7 +77,8 @@ public async Task AssertSiteInformation(DataTable dataTable) Coordinates: [double.Parse(row.Cells.ElementAt(8).Value), double.Parse(row.Cells.ElementAt(9).Value)]), status: null, isDeleted: dataTable.GetBoolRowValueOrDefault(row, "IsDeleted"), - Type: dataTable.GetRowValueOrDefault(row, "Type") + Type: dataTable.GetRowValueOrDefault(row, "Type"), + ReferenceNumberGroup: 0 ); Response.StatusCode.Should().Be(HttpStatusCode.OK); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Auth/PermissionCheckerTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Auth/PermissionCheckerTests.cs index 7f9fd2cd4..91759ae19 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Auth/PermissionCheckerTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Auth/PermissionCheckerTests.cs @@ -43,7 +43,8 @@ private Site GenerateSite(string id) => new Location("Location", new[] { 0.0 }), SiteStatus.Online, null, - string.Empty); + string.Empty, + ReferenceNumberGroup: 0); [Fact] public async Task HasPermissions_ReturnsTrue_WhenPermissionAssignedGloballyAndGeneralRequest() @@ -474,7 +475,8 @@ public async Task HasPermissionAsync_ReturnsTrue_WhenUserHasRegionOnlyLevelPermi _siteService.Setup(x => x.GetSitesInRegion(It.IsAny())) .ReturnsAsync(new List { - new("1", "TestSite", "TestAddress", "N", "T1", "R1", "ICB", string.Empty, [], new Location("Test", [0,0]), SiteStatus.Online, null, string.Empty) + new("1", "TestSite", "TestAddress", "N", "T1", "R1", "ICB", string.Empty, [], new Location("Test", [0,0]), SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0) }); var result = await _sut.HasPermissionAsync(userId, ["1"], "TestPermission"); @@ -504,7 +506,8 @@ public async Task GetPermissionsAsync_FiltersPermissionsOnRegion() _siteService.Setup(x => x.GetSitesInRegion(It.IsAny())) .ReturnsAsync(new List { - new("2", "TestSite", "TestAddress", "N", "T1", "R1", "ICB", string.Empty, [], new Location("Test", [0,0]), SiteStatus.Online, null, string.Empty) + new("2", "TestSite", "TestAddress", "N", "T1", "R1", "ICB", string.Empty, [], new Location("Test", [0,0]), SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0) }); var result = await _sut.GetPermissionsAsync(userId, "2"); @@ -556,7 +559,8 @@ public async Task GetPermissionsAsync_FiltersPermissionsOnIcb() _siteService.Setup(x => x.GetSitesInIcbAsync(It.IsAny())) .ReturnsAsync(new List { - new("2", "TestSite", "TestAddress", "N", "T1", "R1", "ICB1", string.Empty, [], new Location("Test", [0,0]), SiteStatus.Online, null, string.Empty) + new("2", "TestSite", "TestAddress", "N", "T1", "R1", "ICB1", string.Empty, [], new Location("Test", [0,0]), SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0) }); var result = await _sut.GetPermissionsAsync(userId, "2"); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/ApplyAvailabilityTemplateFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/ApplyAvailabilityTemplateFunctionTests.cs index 7f355cae4..02a5c4d6c 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/ApplyAvailabilityTemplateFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/ApplyAvailabilityTemplateFunctionTests.cs @@ -106,7 +106,8 @@ public async Task RunAsync_AppliesNewAvailabilityTemplate() new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 )); var sessions = new List diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/CancelBookingFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/CancelBookingFunctionTests.cs index 85d873cf5..c84573478 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/CancelBookingFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/CancelBookingFunctionTests.cs @@ -68,7 +68,8 @@ public async Task RunAsync_ReturnsSuccessResponse_WhenBookingCancelled() new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 )); var request = BuildRequest(bookingRef, site); @@ -103,7 +104,8 @@ public async Task RunAsync_CallsBookingServiceOnce_WhenCancellationReasonIsNull( new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 )); var request = BuildRequest(bookingRef, site); @@ -138,7 +140,8 @@ public async Task RunAsync_ReturnsNotFoundResponse_WhenBookingInvalid() new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 )); var request = BuildRequest(bookingRef, site); @@ -174,7 +177,8 @@ public async Task RunAsync_Fails_WhenServiceReturnsUnexpected() new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 )); var request = BuildRequest(bookingRef, site); @@ -214,7 +218,8 @@ public async Task RunAsync_PassesCancellationReasonToService(string cancellation new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 )); var request = BuildRequest(bookingRef, site, cancellationReason); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/ConfirmProvisionalBookingFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/ConfirmProvisionalBookingFunctionTests.cs index 300284e3f..8c1948eae 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/ConfirmProvisionalBookingFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/ConfirmProvisionalBookingFunctionTests.cs @@ -68,7 +68,8 @@ public async Task RunAsync_CallsConfirmProvisionalBooking_WhenNoChildBookings() new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 )); var request = CreateRequest([]); @@ -106,7 +107,8 @@ public async Task RunAsync_CallsConfirmProvisionalBookingWithChildren(params str new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 )); var request = CreateRequest(relatedBookings); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/GetSiteMetaDataFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/GetSiteMetaDataFunctionTests.cs index 8ddfceb5a..d13d6a2a5 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/GetSiteMetaDataFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/GetSiteMetaDataFunctionTests.cs @@ -60,7 +60,8 @@ public async Task RunAsync_ReturnsInformationForCitizen(string attrId, string at new Location("Test", [123.1, 321.3]), status: SiteStatus.Online, isDeleted: null, - Type: null + Type: null, + ReferenceNumberGroup: 0 )); var request = CreateRequest(); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/GetSitesPreviewFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/GetSitesPreviewFunctionTests.cs index 53b70a3e0..0f582f61b 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/GetSitesPreviewFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/GetSitesPreviewFunctionTests.cs @@ -106,7 +106,8 @@ public async Task RunsAsync_UserFound_GetsUsersSites() new Location("point", [0.1, 10]), status: SiteStatus.Online, isDeleted: null, - Type: null + Type: null, + ReferenceNumberGroup: 0 ); var site2 = new Site( @@ -122,7 +123,8 @@ public async Task RunsAsync_UserFound_GetsUsersSites() new Location("point", [0.1, 10]), status: SiteStatus.Online, isDeleted: null, - Type: null + Type: null, + ReferenceNumberGroup: 0 ); var icbs = new List diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/GetUserProfileFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/GetUserProfileFunctionTests.cs index d26f62da5..bc9c89d76 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/GetUserProfileFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/GetUserProfileFunctionTests.cs @@ -94,10 +94,10 @@ public async Task RunAsync_ReturnsCorrectHasSites(bool hasSites) { new Site("1", "Alpha", "somewhere", "0113 1111111", "odsCode1", "R1", "ICB1", string.Empty, new[] { new Accessibility(Id: "Accessibility 1", Value: "true") }, - new Location("point", new[] { 0.1, 10 }), SiteStatus.Online, null, string.Empty), + new Location("point", new[] { 0.1, 10 }), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0), new Site("2", "Beta", "somewhere else", "0113 222222", "odsCode2", "R2", "ICB2", string.Empty, new[] { new Accessibility(Id: "Accessibility 2", Value: "true") }, - new Location("point", new[] { 0.2, 11 }), SiteStatus.Online, null, string.Empty) + new Location("point", new[] { 0.2, 11 }), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0) }; var result = await _sut.RunAsync(request) as ContentResult; diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/MakeBookingFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/MakeBookingFunctionTests.cs index 4ddcb884e..2c6099070 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/MakeBookingFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/MakeBookingFunctionTests.cs @@ -43,7 +43,7 @@ public async Task RunAsync_ReturnsSuccessResponse_WhenAppointmentIsRequested() _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())).ReturnsAsync( new Site("6877d86e-c2df-4def-8508-e1eccf0ea6ba", "Test Site", "Nowhere", "2929292", "15N", "North", "Test Board", "Information For Citizen 123", Enumerable.Empty(), - new Location("Point", [0, 0]), SiteStatus.Online, null, string.Empty)); + new Location("Point", [0, 0]), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0)); _bookingWriteService.Setup(x => x.MakeBooking(It.IsAny())).ReturnsAsync((true, "TEST01")); var request = CreateRequest("34e990af-5dc9-43a6-8895-b9123216d699", "2077-01-01 10:30", "COVID", "9999999999", @@ -77,7 +77,7 @@ public async Task RunAsync_ReturnsError_WhenAppointmentSlotIsNotAvailable() _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())).ReturnsAsync( new Site("6877d86e-c2df-4def-8508-e1eccf0ea6ba", "Test Site", "Nowhere", "2929292", "15N", "North", "Test Board", "Information For Citizens 123", Enumerable.Empty(), - new Location("Point", [0, 0]), SiteStatus.Online, null, string.Empty)); + new Location("Point", [0, 0]), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0)); var slots = AvailabilityHelper.CreateTestSlots(Date, new TimeOnly(10, 0), new TimeOnly(11, 0), TimeSpan.FromMinutes(5)); @@ -97,7 +97,7 @@ public void RunAsync_InvokesBookingService_WithCorrectDetails() _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())).ReturnsAsync( new Site("6877d86e-c2df-4def-8508-e1eccf0ea6ba", "Test Site", "Nowhere", "2929292", "15N", "North", "Test Board", "Information For Citizens 123", Enumerable.Empty(), - new Location("Point", [0, 0]), SiteStatus.Online, null, string.Empty)); + new Location("Point", [0, 0]), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0)); var slots = AvailabilityHelper.CreateTestSlots(Date, new TimeOnly(10, 0), new TimeOnly(11, 0), TimeSpan.FromMinutes(5)); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityByDaysFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityByDaysFunctionTests.cs index ae35bc147..28214a47c 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityByDaysFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityByDaysFunctionTests.cs @@ -94,7 +94,8 @@ public async Task RunAsync_ReturnsSlots_SingleUser_SingleService() new Location("Coords", [1.234, 5.678]), null, null, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 ) }); _bookingAvailabilityStateService.Setup(x => x.GetAvailableSlots(It.IsAny(), It.IsAny(), It.IsAny())) @@ -153,7 +154,8 @@ public async Task RunAsync_ReturnsSlots_TwoAttendees_SingleService() new Location("Coords", [1.234, 5.678]), null, null, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 ) }); _bookingAvailabilityStateService.Setup(x => x.GetAvailableSlots(It.IsAny(), It.IsAny(), It.IsAny())) @@ -222,7 +224,8 @@ public async Task RunAsync_ReturnsSlots_TwoAttendees_MultipleServices() new Location("Coords", [1.234, 5.678]), null, null, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 ) }); _bookingAvailabilityStateService.Setup(x => x.GetAvailableSlots(It.IsAny(), It.IsAny(), It.IsAny())) diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityByHoursFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityByHoursFunctionTests.cs index 3912547e7..1ddcf716b 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityByHoursFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityByHoursFunctionTests.cs @@ -116,7 +116,8 @@ public async Task RunAsync_ReturnAvailabilityByHours() new Location("Coords", [1.234, 5.678]), null, null, - string.Empty)); + string.Empty, + ReferenceNumberGroup: 0)); _bookingAvailabilityStateService.Setup(x => x.GetAvailableSlots(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(slots); _availableSlotsFilter.Setup(x => x.FilterAvailableSlots(It.IsAny>(), It.IsAny>())) @@ -170,7 +171,8 @@ public async Task RunAsync_DoesNotShowFollowingHour_IfSlotSpillsOver() new Location("Coords", [1.234, 5.678]), null, null, - string.Empty)); + string.Empty, + ReferenceNumberGroup: 0)); _bookingAvailabilityStateService.Setup(x => x.GetAvailableSlots(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(slots); _availableSlotsFilter.Setup(x => x.FilterAvailableSlots(It.IsAny>(), It.IsAny>())) @@ -220,7 +222,8 @@ public async Task RunAsync_ReturnsEmptyHoursArray() new Location("Coords", [1.234, 5.678]), null, null, - string.Empty)); + string.Empty, + ReferenceNumberGroup: 0)); _bookingAvailabilityStateService.Setup(x => x.GetAvailableSlots(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(slots); _availableSlotsFilter.Setup(x => x.FilterAvailableSlots(It.IsAny>(), It.IsAny>())) diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityBySlotsFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityBySlotsFunctionTests.cs index bdb726aac..05ad18618 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityBySlotsFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityBySlotsFunctionTests.cs @@ -111,7 +111,8 @@ public async Task RunAsync_ReturnsEmptySlotArray() new Location("Coords", [1.234, 5.678]), null, null, - string.Empty)); + string.Empty, + ReferenceNumberGroup: 0)); _bookingAvailabilityStateService.Setup(x => x.GetAvailableSlots(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(slots); _availableSlotsFilter.Setup(x => x.FilterAvailableSlots(It.IsAny>(), It.IsAny>())) @@ -165,7 +166,8 @@ public async Task RunAsync_ReturnsAvailabilityBySlots() new Location("Coords", [1.234, 5.678]), null, null, - string.Empty)); + string.Empty, + ReferenceNumberGroup: 0)); _bookingAvailabilityStateService.Setup(x => x.GetAvailableSlots(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(slots); _availableSlotsFilter.Setup(x => x.FilterAvailableSlots(It.IsAny>(), It.IsAny>())) diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityFunctionTests.cs index 69a0261cd..29766f567 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityFunctionTests.cs @@ -84,7 +84,8 @@ public async Task RunAsync_ReturnsSuccessResponse_WhenMultipleSitesAreQueried() new Location("Coords", [1.234, 5.678]), null, null, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 ), new( "34e990af-5dc9-43a6-8895-b9123216d699", @@ -102,7 +103,8 @@ public async Task RunAsync_ReturnsSuccessResponse_WhenMultipleSitesAreQueried() new Location("Coords", [1.234, 5.678]), null, null, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 ) }); @@ -163,7 +165,8 @@ public async Task RunAsync_ReturnsCorrectAvailabilityGrouper_WhenCalledWithConfi new Location("Coords", [1.234, 5.678]), null, null, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 ) }); @@ -210,7 +213,8 @@ public async Task RunAsync_ReturnsResults_ForEachDayInRequest() new Location("Coords", [1.234, 5.678]), null, null, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 ) }); @@ -267,7 +271,8 @@ public async Task RunAsync_RunsJointBookings() new Location("Coords", [1.234, 5.678]), null, null, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 ) }); @@ -322,7 +327,8 @@ public async Task RunAsync_CallsGrouperWithCorrectSlots_ForEachDayInRequest() new Location("Coords", [1.234, 5.678]), null, null, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 ) }); @@ -379,7 +385,8 @@ public async Task RunAsync_CallsBookingAvailabilityStateService() new Location("Coords", [1.234, 5.678]), null, null, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 ) }); @@ -422,7 +429,8 @@ public async Task RunAsync_ReturnsEmptyArray_WhenAllRequestedSites_HaveBeenSoftD new Location("Coords", [1.234, 5.678]), null, true, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 ), new( "34e990af-5dc9-43a6-8895-b9123216d699", @@ -440,7 +448,8 @@ public async Task RunAsync_ReturnsEmptyArray_WhenAllRequestedSites_HaveBeenSoftD new Location("Coords", [1.234, 5.678]), null, true, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 ) }); @@ -492,7 +501,8 @@ public async Task RunAsync_OnlyProcessesActiveSites() new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 ), new( "34e990af-5dc9-43a6-8895-b9123216d699", @@ -510,7 +520,8 @@ public async Task RunAsync_OnlyProcessesActiveSites() new Location("Coords", [1.234, 5.678]), null, true, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 ) }); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryBookingByReferenceFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryBookingByReferenceFunctionTests.cs index f980b82dd..221df0699 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryBookingByReferenceFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryBookingByReferenceFunctionTests.cs @@ -78,7 +78,8 @@ public async Task RunAsync_ReturnsSuccessResponse_AndBookingWhenFound() new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 )); var request = new QueryBookingByReferenceRequest(bookingRef, site); @@ -128,7 +129,8 @@ public async Task RunAsync_ReturnsNotFoundResponse_WhenReferenceAndSiteDoNotMatc new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 )); var request = new QueryBookingByReferenceRequest(bookingRef, "TEST03"); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetAvailabilityFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetAvailabilityFunctionTests.cs index febfcb090..c4a46a882 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetAvailabilityFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetAvailabilityFunctionTests.cs @@ -58,7 +58,8 @@ public async Task InvokeAvailabilityService_WhenSettingAvailability() new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 )); var sessions = new List diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteAccessibilitiesFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteAccessibilitiesFunctionTests.cs index 4fda679b1..3d6f2b899 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteAccessibilitiesFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteAccessibilitiesFunctionTests.cs @@ -61,7 +61,8 @@ public async Task InvokeSiteService_WhenSettingSiteAccessibilities(bool operatio new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 )); var result = await _sut.Invoke(request); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteDetailsFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteDetailsFunctionTests.cs index 40b3e3500..77e9ad952 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteDetailsFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteDetailsFunctionTests.cs @@ -64,7 +64,8 @@ public async Task InvokeSiteService_WhenSettingSiteDetails(bool operationSuccess new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 )); var result = await _sut.Invoke(request); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteInformationForCitizensFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteInformationForCitizensFunctionTests.cs index e8c156d79..8e46189bc 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteInformationForCitizensFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteInformationForCitizensFunctionTests.cs @@ -65,7 +65,8 @@ public async Task InvokeSiteService_WhenSettingInformationForCitizens(bool opera new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 )); var result = await _sut.Invoke(request); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteReferenceDetailsFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteReferenceDetailsFunctionTests.cs index fcd770a0e..28c196cc4 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteReferenceDetailsFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteReferenceDetailsFunctionTests.cs @@ -63,7 +63,8 @@ public async Task InvokeSiteService_WhenSettingSiteReferenceDetails(bool operati new Location("Coords", [1.234, 5.678]), null, false, - string.Empty + string.Empty, + ReferenceNumberGroup: 0 )); var result = await _sut.Invoke(request); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Notifications/BookingNotifierTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Notifications/BookingNotifierTests.cs index 627221f32..f6f6586cf 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Notifications/BookingNotifierTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Notifications/BookingNotifierTests.cs @@ -51,7 +51,8 @@ public async Task PassesValuesToEmailGovNotifyService() new NotificationConfiguration { EmailTemplateId = EmailTemplateId, SmsTemplateId = SmsTemplateId }); _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", - "R1", "ICB1", "Information For Citizens 123", null, null, SiteStatus.Online, null, string.Empty))); + "R1", "ICB1", "Information For Citizens 123", null, null, SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0))); _clinicalServiceProviderMock.Setup(x => x.Get(Service)).ReturnsAsync(clinicalService); _notificationClient.Setup(x => x.SendEmailAsync(Email, EmailTemplateId, It.Is>( dic => @@ -84,7 +85,8 @@ public async Task PassesValuesToSmsGovNotifyService() new NotificationConfiguration { EmailTemplateId = EmailTemplateId, SmsTemplateId = SmsTemplateId }); _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", - "R1", "ICB1", "Information For Citizens 123", null, null, SiteStatus.Online, null, string.Empty))); + "R1", "ICB1", "Information For Citizens 123", null, null, SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0))); _clinicalServiceProviderMock.Setup(x => x.Get(Service)).ReturnsAsync(clinicalService); _notificationClient.Setup(x => x.SendSmsAsync(PhoneNumber, SmsTemplateId, It.Is>( dic => @@ -132,7 +134,8 @@ public async Task SiteLocationIsRenderedCorrectly(string informationForCitizens, new NotificationConfiguration { EmailTemplateId = EmailTemplateId, SmsTemplateId = SmsTemplateId }); _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", - "R1", "ICB1", informationForCitizens, null, null, SiteStatus.Online, null, string.Empty))); + "R1", "ICB1", informationForCitizens, null, null, SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0))); _clinicalServiceProviderMock.Setup(x => x.Get(Service)).ReturnsAsync(clinicalService); _notificationClient.Setup(x => x.SendEmailAsync(Email, EmailTemplateId, It.Is>( dic => @@ -171,7 +174,8 @@ public async Task GetsNameOfSite() new NotificationConfiguration { EmailTemplateId = EmailTemplateId, SmsTemplateId = SmsTemplateId }); _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())).Returns( Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", "R1", "ICB1", "Information For Citizens 123", - Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty))).Verifiable(); + Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0))).Verifiable(); _clinicalServiceProviderMock.Setup(x => x.Get(Service)).ReturnsAsync(clinicalService); _notificationClient.Setup( x => x.SendEmailAsync(Email, EmailTemplateId, It.IsAny>())); @@ -195,7 +199,8 @@ public async Task GetsCorrectNotificationConfiguration() new NotificationConfiguration { EmailTemplateId = EmailTemplateId, SmsTemplateId = SmsTemplateId }); _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())).Returns( Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", "R1", "ICB1", "Information For Citizens 123", - Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty))); + Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0))); _clinicalServiceProviderMock.Setup(x => x.Get(Service)).ReturnsAsync(clinicalService); _notificationClient.Setup( x => x.SendEmailAsync(Email, EmailTemplateId, It.IsAny>())); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Notifications/UserRolesChangedNotifierTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Notifications/UserRolesChangedNotifierTests.cs index 43f495552..c94e469ba 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Notifications/UserRolesChangedNotifierTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Notifications/UserRolesChangedNotifierTests.cs @@ -38,7 +38,8 @@ public async Task PassesValuesToGovNotifyService() ])); _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", - "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty))); + "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0))); _notificationClient.Setup(x => x.SendEmailAsync(Email, TemplateId, It.Is>(dic => @@ -58,7 +59,8 @@ public async Task GetsNameOfSite() ])); _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())).Returns( Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", "R1", "ICB1", "Information For Citizens 123", - Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty))).Verifiable(); + Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0))).Verifiable(); await _sut.Notify(nameof(UserRolesChanged), Email, Site, ["newRole"], ["removedRole"]); _siteService.Verify(); @@ -74,7 +76,8 @@ public async Task GetsNamesOfRoles() ])).Verifiable(); _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", - "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty))); + "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0))); await _sut.Notify(nameof(UserRolesChanged), Email, Site, ["newRole"], ["removedRole"]); _rolesStore.Verify(); @@ -90,7 +93,8 @@ public async Task DoesNotSendNotificationIfNoChanges() ])); _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", - "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty))); + "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0))); await _sut.Notify(nameof(UserRolesChanged), Email, Site, [], []); _notificationClient.Verify(x => @@ -108,7 +112,8 @@ public async Task UsesFriendlyNamesForRoles() ])); _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", - "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty))); + "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0))); _notificationClient.Setup(x => x.SendEmailAsync(Email, TemplateId, It.Is>(dic => @@ -129,7 +134,8 @@ public async Task GetsFriendlyRoleNameWhenIdContainsScopePrefixButDatabaseValues ])); _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", - "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty))); + "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0))); _notificationClient.Setup(x => x.SendEmailAsync(Email, TemplateId, It.Is>(dic => @@ -151,7 +157,8 @@ public async Task GetsCorrectNotificationConfiguration() ])); _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", - "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty))); + "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0))); await _sut.Notify(nameof(UserRolesChanged), Email, Site, ["newRole"], ["removedRole"]); _notificationConfigurationService.Verify(); diff --git a/tests/Nhs.Appointments.Core.UnitTests/BulkImport/SiteStatusDataImportHandlerTests.cs b/tests/Nhs.Appointments.Core.UnitTests/BulkImport/SiteStatusDataImportHandlerTests.cs index 28fa83af9..c71db92bc 100644 --- a/tests/Nhs.Appointments.Core.UnitTests/BulkImport/SiteStatusDataImportHandlerTests.cs +++ b/tests/Nhs.Appointments.Core.UnitTests/BulkImport/SiteStatusDataImportHandlerTests.cs @@ -44,7 +44,8 @@ public async Task CanReadSiteData() new Location("Test", [1.123, 3.321]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new( id2.ToString(), "Test Site 2", @@ -58,7 +59,8 @@ public async Task CanReadSiteData() new Location("Test", [1.123, 3.321]), null, null, - string.Empty) + string.Empty, + ReferenceNumberGroup: 0) }; var input = CsvFileBuilder.BuildInputCsv(Headers, inputRows); @@ -146,7 +148,8 @@ public async Task FailsToMatchSite_ReportsBadData() new Location("Test", [1.123, 3.321]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new( Guid.NewGuid().ToString(), "Test Site 2", @@ -160,7 +163,8 @@ public async Task FailsToMatchSite_ReportsBadData() new Location("Test", [1.123, 3.321]), null, null, - string.Empty) + string.Empty, + ReferenceNumberGroup: 0) }; var input = CsvFileBuilder.BuildInputCsv(Headers, inputRows); @@ -207,7 +211,8 @@ public async Task FailsToUpdateSiteSoftDeletionStatus_ReportsFailure() new Location("Test", [1.123, 3.321]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new( id2.ToString(), "Test Site 2", @@ -221,7 +226,8 @@ public async Task FailsToUpdateSiteSoftDeletionStatus_ReportsFailure() new Location("Test", [1.123, 3.321]), null, null, - string.Empty) + string.Empty, + ReferenceNumberGroup: 0) }; var input = CsvFileBuilder.BuildInputCsv(Headers, inputRows); diff --git a/tests/Nhs.Appointments.Core.UnitTests/BulkImport/UserDataImportHandlerTests.cs b/tests/Nhs.Appointments.Core.UnitTests/BulkImport/UserDataImportHandlerTests.cs index ec0a1ff8b..309326e0f 100644 --- a/tests/Nhs.Appointments.Core.UnitTests/BulkImport/UserDataImportHandlerTests.cs +++ b/tests/Nhs.Appointments.Core.UnitTests/BulkImport/UserDataImportHandlerTests.cs @@ -611,7 +611,8 @@ private List GetSites() foreach (var row in InputRows) { - sites.Add(new Site(row.Split(',')[3], "Test", "Test Address", "07777777777", "ABC123", "Test Region", "ICB", "", [], new Location("Test", [1.0, 60.0]), SiteStatus.Online, null, string.Empty)); + sites.Add(new Site(row.Split(',')[3], "Test", "Test Address", "07777777777", "ABC123", "Test Region", "ICB", "", [], new Location("Test", [1.0, 60.0]), SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0)); } return sites; diff --git a/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs b/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs index 6439a0ea3..fa3b6fff3 100644 --- a/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs +++ b/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs @@ -6,36 +6,41 @@ namespace Nhs.Appointments.Core.UnitTests; public class ReferenceNumberProviderTests { private readonly ReferenceNumberProvider _sut; - private readonly Mock _siteStore = new(); + private readonly Mock _siteService = new(); private readonly Mock _referenceNumberDocumentStore = new(); private readonly Mock _timeProvider = new(); public ReferenceNumberProviderTests() { - _sut = new ReferenceNumberProvider(_siteStore.Object, _referenceNumberDocumentStore.Object, _timeProvider.Object); + _sut = new ReferenceNumberProvider(_siteService.Object, _referenceNumberDocumentStore.Object, _timeProvider.Object); } [Fact] public async Task GetReferenceNumber_AssignsRefGroup_WhenNotCurrentAssigned() { - _siteStore.Setup(x => x.GetReferenceNumberGroup("test")).ReturnsAsync(0); + _siteService.Setup(x => x.GetSiteByIdAsync("test", It.IsAny())).ReturnsAsync(new Site("test", "NAME", "ADDRESS", + "PHONENUMBER", "ODSCODE", "REGION", "ICB", "INFO", + new List(), new Location("",[0, 0]), SiteStatus.Online, false, "TYPE", 0)); _referenceNumberDocumentStore.Setup(x => x.AssignReferenceGroup()).ReturnsAsync(14); await _sut.GetReferenceNumber("test"); _referenceNumberDocumentStore.Verify(x => x.AssignReferenceGroup(), Times.Once()); - _siteStore.Verify(x => x.AssignPrefix("test", 14), Times.Once()); + _siteService.Verify(x => x.AssignPrefix("test", 14), Times.Once()); } [Fact] public async Task GetReferenceNumber_DoesNotAssignsRefGroup_WhenAlreadyAssigned() { - _siteStore.Setup(x => x.GetReferenceNumberGroup("test")).ReturnsAsync(14); + _siteService.Setup(x => x.GetSiteByIdAsync("test", It.IsAny())).ReturnsAsync(new Site( + "test", "NAME", "ADDRESS", "PHONENUMBER", "ODSCODE", "REGION", + "ICB", "INFO", new List(), new Location("", + [0, 0]), SiteStatus.Online, false, "TYPE", 14)); await _sut.GetReferenceNumber("test"); _referenceNumberDocumentStore.Verify(x => x.AssignReferenceGroup(), Times.Never); - _siteStore.Verify(x => x.AssignPrefix("test", It.IsAny()), Times.Never); + _siteService.Verify(x => x.AssignPrefix("test", It.IsAny()), Times.Never); } [Fact] @@ -44,7 +49,10 @@ public async Task GetReferenceNumber_GeneratesCorrectlyFormattedNumber() _timeProvider.Setup(x => x.GetUtcNow()).Returns(new DateTime(2077, 1, 31, 9, 0, 59)); _referenceNumberDocumentStore.Setup(x => x.GetNextSequenceNumber(14)).ReturnsAsync(2345); - _siteStore.Setup(x => x.GetReferenceNumberGroup("test")).ReturnsAsync(14); + _siteService.Setup(x => x.GetSiteByIdAsync("test", It.IsAny())).ReturnsAsync(new Site( + "test", "NAME", "ADDRESS", "PHONENUMBER", "ODSCODE", "REGION", + "ICB", "INFO", new List(), new Location("", + [0, 0]), SiteStatus.Online, false, "TYPE", 14)); var result = await _sut.GetReferenceNumber("test"); result.Should().Be("14-90-002345"); diff --git a/tests/Nhs.Appointments.Core.UnitTests/Reports/SiteReportServiceMockData.cs b/tests/Nhs.Appointments.Core.UnitTests/Reports/SiteReportServiceMockData.cs index e9f1767e9..dd18806d7 100644 --- a/tests/Nhs.Appointments.Core.UnitTests/Reports/SiteReportServiceMockData.cs +++ b/tests/Nhs.Appointments.Core.UnitTests/Reports/SiteReportServiceMockData.cs @@ -29,7 +29,8 @@ public static class SiteReportServiceMockData InformationForCitizens: "", Accessibilities: new List { new("accessibility/access_need_1", "true") }, status: SiteStatus.Online, isDeleted: null, - Type: "GP Practice"), + Type: "GP Practice", + ReferenceNumberGroup: 0), new( Site2Guid.ToString(), "Site 2", @@ -42,7 +43,8 @@ public static class SiteReportServiceMockData InformationForCitizens: "", Accessibilities: new List { new("accessibility/access_need_1", "true") }, status: SiteStatus.Offline, isDeleted: null, - Type: "GP Practice"), + Type: "GP Practice", + ReferenceNumberGroup: 0), new( Site3Guid.ToString(), "Site 3", @@ -55,7 +57,8 @@ public static class SiteReportServiceMockData InformationForCitizens: "", Accessibilities: new List { new("accessibility/access_need_1", "true") }, status: null, isDeleted: null, - Type: "GP Practice") + Type: "GP Practice", + ReferenceNumberGroup: 0) ]; public static readonly ClinicalServiceType[] MockClinicalServices = diff --git a/tests/Nhs.Appointments.Core.UnitTests/Reports/SiteSummary/SiteSummaryTriggerTests.cs b/tests/Nhs.Appointments.Core.UnitTests/Reports/SiteSummary/SiteSummaryTriggerTests.cs index 1b26d31ae..16c0787ff 100644 --- a/tests/Nhs.Appointments.Core.UnitTests/Reports/SiteSummary/SiteSummaryTriggerTests.cs +++ b/tests/Nhs.Appointments.Core.UnitTests/Reports/SiteSummary/SiteSummaryTriggerTests.cs @@ -40,7 +40,8 @@ public async Task When_LastRun_Null() new Location("test", [0.0]), SiteStatus.Online, null, - string.Empty) + string.Empty, + ReferenceNumberGroup: 0) }); await _sut.Trigger(); @@ -78,7 +79,8 @@ public async Task When_LastRun_Finished_DayOld() new Location("test", [0.0]), SiteStatus.Online, null, - string.Empty) + string.Empty, + ReferenceNumberGroup: 0) }); var site = "site-1"; @@ -121,7 +123,8 @@ public async Task When_RunningFullDay_Iterates_AsExpected() new Location("test", [0.0]), null, null, - string.Empty) + string.Empty, + ReferenceNumberGroup: 0) }); var site = "site-1"; @@ -235,7 +238,8 @@ public async Task When_LastRun_NotFinished_DayOld() "INFO", new Accessibility[] { }, new Location("test", [0.0]), - null, null, string.Empty) + null, null, string.Empty, + ReferenceNumberGroup: 0) }); var site = "site-1"; @@ -276,7 +280,8 @@ public async Task When_LastRun_NotFinished_SameDay() "INFO", new Accessibility[] { }, new Location("test", [0.0]), - null, null, string.Empty) + null, null, string.Empty, + ReferenceNumberGroup: 0) }); var site = "site-1"; @@ -338,7 +343,8 @@ public async Task When_Multiple_Sites() "INFO", new Accessibility[] { }, new Location("test", [0.0]), - SiteStatus.Online, null, string.Empty), + SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0), new ( "site-2", "site-name-2", @@ -350,7 +356,8 @@ public async Task When_Multiple_Sites() "INFO", new Accessibility[] { }, new Location("test", [0.0]), - SiteStatus.Online, null, string.Empty) + SiteStatus.Online, null, string.Empty, + ReferenceNumberGroup: 0) }); var site1 = "site-1"; diff --git a/tests/Nhs.Appointments.Core.UnitTests/SiteServiceCacheTestsMockData.cs b/tests/Nhs.Appointments.Core.UnitTests/SiteServiceCacheTestsMockData.cs index 4e5f34827..2892dd66c 100644 --- a/tests/Nhs.Appointments.Core.UnitTests/SiteServiceCacheTestsMockData.cs +++ b/tests/Nhs.Appointments.Core.UnitTests/SiteServiceCacheTestsMockData.cs @@ -18,7 +18,8 @@ public static class SiteServiceCacheTestsMockData InformationForCitizens: "", Accessibilities: new List { new("accessibility/access_need_1", "true") }, status: SiteStatus.Online, isDeleted: false, - Type: string.Empty), + Type: string.Empty, + ReferenceNumberGroup: 0), new( "3ad2deb1-791b-452d-95dc-7090edd97f9a", "Site 2", @@ -31,7 +32,8 @@ public static class SiteServiceCacheTestsMockData InformationForCitizens: "", Accessibilities: new List { new("accessibility/access_need_1", "true") }, status: SiteStatus.Offline, isDeleted: true, - Type: string.Empty), + Type: string.Empty, + ReferenceNumberGroup: 0), new( "0c06c137-2f8a-4334-a594-0632d0407966", "Site 3", @@ -44,7 +46,8 @@ public static class SiteServiceCacheTestsMockData InformationForCitizens: "", Accessibilities: new List { new("accessibility/access_need_1", "true") }, status: SiteStatus.Offline, isDeleted: null, - Type: string.Empty), + Type: string.Empty, + ReferenceNumberGroup: 0), new( "b40d5219-e1c5-4f28-a2c4-d40f68bcde36", "Site 4", @@ -57,7 +60,8 @@ public static class SiteServiceCacheTestsMockData InformationForCitizens: "", Accessibilities: new List { new("accessibility/access_need_1", "true") }, status: SiteStatus.Online, isDeleted: true, - Type: string.Empty) + Type: string.Empty, + ReferenceNumberGroup: 0) ]; public static readonly Site[] MockOnlyNonDeletedSites = @@ -74,7 +78,8 @@ public static class SiteServiceCacheTestsMockData InformationForCitizens: "", Accessibilities: new List { new("accessibility/access_need_1", "true") }, status: SiteStatus.Online, isDeleted: false, - Type: string.Empty), + Type: string.Empty, + ReferenceNumberGroup: 0), new( "0c06c137-2f8a-4334-a594-0632d0407966", "Site 3", @@ -87,7 +92,8 @@ public static class SiteServiceCacheTestsMockData InformationForCitizens: "", Accessibilities: new List { new("accessibility/access_need_1", "true") }, status: SiteStatus.Offline, isDeleted: null, - Type: string.Empty) + Type: string.Empty, + ReferenceNumberGroup: 0) ]; public static Site CreateMockSite(string id, string name) @@ -104,6 +110,7 @@ public static Site CreateMockSite(string id, string name) InformationForCitizens: "", Accessibilities: new List { new("accessibility/access_need_1", "true") }, status: SiteStatus.Offline, isDeleted: null, - Type: string.Empty); + Type: string.Empty, + ReferenceNumberGroup: 0); } } diff --git a/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs b/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs index 96e538072..50a2cc5b1 100644 --- a/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs +++ b/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs @@ -53,7 +53,8 @@ public async Task GetSiteByIdAsync_ReturnsRequestedSite() InformationForCitizens: "", Accessibilities: new List { new Accessibility(Id: "Accessibility 1", Value: "true") }, status: SiteStatus.Online, isDeleted: null, - Type: string.Empty); + Type: string.Empty, + ReferenceNumberGroup: 0); var expectedSite = new Site( Id: siteId, @@ -67,7 +68,8 @@ public async Task GetSiteByIdAsync_ReturnsRequestedSite() InformationForCitizens: "", Accessibilities: new List { new Accessibility(Id: "Accessibility 1", Value: "true") }, status: SiteStatus.Online, isDeleted: null, - Type: string.Empty); + Type: string.Empty, + ReferenceNumberGroup: 0); _siteStore.Setup(x => x.GetSiteById("6877d86e-c2df-4def-8508-e1eccf0ea6ba")).ReturnsAsync(site); var result = await _sut.GetSiteByIdAsync(siteId); @@ -103,7 +105,8 @@ public async Task GetSitesPreview_CachedSitesFound_ReturningCachedSites() InformationForCitizens: "", Accessibilities: new List() {new (Id: "accessibility/access_need_1", Value: "true")}, status: SiteStatus.Online, isDeleted: null, - Type: string.Empty), + Type: string.Empty, + ReferenceNumberGroup: 0), new Site( Id: "ABC02", Name: "Site 2", @@ -116,7 +119,8 @@ public async Task GetSitesPreview_CachedSitesFound_ReturningCachedSites() InformationForCitizens: "", Accessibilities: new List() {new (Id: "accessibility/access_need_1", Value: "false")}, status: SiteStatus.Online, isDeleted: null, - Type: string.Empty) + Type: string.Empty, + ReferenceNumberGroup: 0) }; object outSites = new CacheService.CacheObject>(sites); _memoryCache.Setup(x => x.TryGetValue("sites", out outSites)).Returns(true); @@ -144,7 +148,8 @@ public async Task GetSitesPreview_CachedSitesNotFound_ReturningAllSitesFromSiteS InformationForCitizens: "", Accessibilities: new List() {new (Id: "accessibility/access_need_1", Value: "true")}, status: SiteStatus.Online, isDeleted: null, - Type: string.Empty), + Type: string.Empty, + ReferenceNumberGroup: 0), new Site( Id: "ABC02", Name: "Site 2", @@ -157,7 +162,8 @@ public async Task GetSitesPreview_CachedSitesNotFound_ReturningAllSitesFromSiteS InformationForCitizens: "", Accessibilities: new List() {new (Id: "accessibility/access_need_1", Value: "false")}, status: SiteStatus.Online, isDeleted: null, - Type: string.Empty) + Type: string.Empty, + ReferenceNumberGroup: 0) }; object outSites = null; _memoryCache.Setup(x => x.TryGetValue("sites", out outSites)).Returns(false); @@ -188,7 +194,8 @@ public async Task GetSitesInRegion_CachedSitesNotFound_ReturnsAllSitesFilteredWi InformationForCitizens: "", Accessibilities: new List() {new (Id: "accessibility/access_need_1", Value: "true")}, status: SiteStatus.Online, isDeleted: null, - Type: string.Empty), + Type: string.Empty, + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetSitesInRegionAsync("R1")).ReturnsAsync(sites); @@ -230,7 +237,8 @@ public async Task QuerySitesAsync_FiltersSitesOnAccessNeeds() new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -245,7 +253,8 @@ public async Task QuerySitesAsync_FiltersSitesOnAccessNeeds() new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new("test456", "Test Site 3", string.Empty, @@ -260,7 +269,8 @@ public async Task QuerySitesAsync_FiltersSitesOnAccessNeeds() new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) @@ -298,7 +308,8 @@ public async Task QuerySitesAsync_FiltersSitesOnDistance() new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -309,7 +320,8 @@ public async Task QuerySitesAsync_FiltersSitesOnDistance() new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new("test456", "Test Site 3", string.Empty, @@ -320,7 +332,8 @@ public async Task QuerySitesAsync_FiltersSitesOnDistance() new Location("Point", [1.6610648, 45.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) @@ -364,7 +377,8 @@ public async Task QuerySitesAsync_FiltersSitesOnService() new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -375,7 +389,8 @@ public async Task QuerySitesAsync_FiltersSitesOnService() new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new("test456", "Test Site 3", string.Empty, @@ -386,7 +401,8 @@ public async Task QuerySitesAsync_FiltersSitesOnService() new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) @@ -439,7 +455,8 @@ public async Task QuerySitesAsync_RemovesDuplicates() new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -454,7 +471,8 @@ public async Task QuerySitesAsync_RemovesDuplicates() new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -469,7 +487,8 @@ public async Task QuerySitesAsync_RemovesDuplicates() new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) @@ -525,7 +544,8 @@ public async Task QuerySitesAsync_CorrectlyHandlesMultipleFilters_AndReturnsAllS new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -540,7 +560,8 @@ public async Task QuerySitesAsync_CorrectlyHandlesMultipleFilters_AndReturnsAllS new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new("test456", "Test Site 3", string.Empty, @@ -555,7 +576,8 @@ public async Task QuerySitesAsync_CorrectlyHandlesMultipleFilters_AndReturnsAllS new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) @@ -611,7 +633,8 @@ public async Task QuerySitesAsync_CorrectlyHandlesMultipleFilters_AndFiltersOutI new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -626,7 +649,8 @@ public async Task QuerySitesAsync_CorrectlyHandlesMultipleFilters_AndFiltersOutI new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new("test456", "Test Site 3", string.Empty, @@ -641,7 +665,8 @@ public async Task QuerySitesAsync_CorrectlyHandlesMultipleFilters_AndFiltersOutI new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), new("test654", "Test Site 4", string.Empty, @@ -656,7 +681,8 @@ public async Task QuerySitesAsync_CorrectlyHandlesMultipleFilters_AndFiltersOutI new Location("Point", [-1.6610648, 53.795467]), null, null, - string.Empty), + string.Empty, + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) @@ -699,7 +725,8 @@ public async Task QuerySitesAsync_FiltersBySiteType() new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -710,7 +737,8 @@ public async Task QuerySitesAsync_FiltersBySiteType() new Location("Point", [-1.6610648, 53.795467]), null, null, - "gp PrActiCE"), // Ensure the filter is case insensitive + "gp PrActiCE", + ReferenceNumberGroup: 0), // Ensure the filter is case insensitive new("test456", "Test Site 3", string.Empty, @@ -721,7 +749,8 @@ public async Task QuerySitesAsync_FiltersBySiteType() new Location("Point", [-1.6610648, 53.795467]), null, null, - "PCN Site"), + "PCN Site", + ReferenceNumberGroup: 0), new("test654", "Test Site 4", string.Empty, @@ -732,7 +761,8 @@ public async Task QuerySitesAsync_FiltersBySiteType() new Location("Point", [-1.6610648, 53.795467]), null, null, - "Some other site type"), + "Some other site type", + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) @@ -771,7 +801,8 @@ public async Task QuerySitesAsync_FiltersBySiteType_AndOdsCode() new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -782,7 +813,8 @@ public async Task QuerySitesAsync_FiltersBySiteType_AndOdsCode() new Location("Point", [-1.6610648, 53.795467]), null, null, - "gp PrActiCE"), // Ensure the filter is case insensitive + "gp PrActiCE", + ReferenceNumberGroup: 0), // Ensure the filter is case insensitive new("test456", "Test Site 3", string.Empty, @@ -793,7 +825,8 @@ public async Task QuerySitesAsync_FiltersBySiteType_AndOdsCode() new Location("Point", [-1.6610648, 53.795467]), null, null, - "PCN Site"), + "PCN Site", + ReferenceNumberGroup: 0), new("test654", "Test Site 4", string.Empty, @@ -804,7 +837,8 @@ public async Task QuerySitesAsync_FiltersBySiteType_AndOdsCode() new Location("Point", [-1.6610648, 53.795467]), null, null, - "Some other site type"), + "Some other site type", + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) @@ -843,7 +877,8 @@ public async Task QuerySitesAsync_FiltersBySiteType_AndOdsCode_AndDoesNotReturnA new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -854,7 +889,8 @@ public async Task QuerySitesAsync_FiltersBySiteType_AndOdsCode_AndDoesNotReturnA new Location("Point", [-1.6610648, 53.795467]), null, null, - "gp PrActiCE"), // Ensure the filter is case insensitive + "gp PrActiCE", + ReferenceNumberGroup: 0), // Ensure the filter is case insensitive new("test456", "Test Site 3", string.Empty, @@ -865,7 +901,8 @@ public async Task QuerySitesAsync_FiltersBySiteType_AndOdsCode_AndDoesNotReturnA new Location("Point", [-1.6610648, 53.795467]), null, null, - "PCN Site"), + "PCN Site", + ReferenceNumberGroup: 0), new("test654", "Test Site 4", string.Empty, @@ -876,7 +913,8 @@ public async Task QuerySitesAsync_FiltersBySiteType_AndOdsCode_AndDoesNotReturnA new Location("Point", [-1.6610648, 53.795467]), null, null, - "Some other site type"), + "Some other site type", + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) @@ -914,7 +952,8 @@ public async Task QuerySitesAsync_FiltersByOdsCode() new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -925,7 +964,8 @@ public async Task QuerySitesAsync_FiltersByOdsCode() new Location("Point", [-1.6610648, 53.795467]), null, null, - "gp PrActiCE"), // Ensure the filter is case insensitive + "gp PrActiCE", + ReferenceNumberGroup: 0), // Ensure the filter is case insensitive new("test456", "Test Site 3", string.Empty, @@ -936,7 +976,8 @@ public async Task QuerySitesAsync_FiltersByOdsCode() new Location("Point", [-1.6610648, 53.795467]), null, null, - "PCN Site"), + "PCN Site", + ReferenceNumberGroup: 0), new("test654", "Test Site 4", string.Empty, @@ -947,7 +988,8 @@ public async Task QuerySitesAsync_FiltersByOdsCode() new Location("Point", [-1.6610648, 53.795467]), null, null, - "Some other site type"), + "Some other site type", + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) @@ -997,7 +1039,8 @@ public async Task QuerySitesAsync_FiltersSitesInPriorityOrder_AndStopsFilteringA new Location("Point", [-1.6610648, 53.795467]), null, null, - "GP Practice")); + "GP Practice", + ReferenceNumberGroup: 0)); } sites.Add(new( @@ -1015,7 +1058,8 @@ public async Task QuerySitesAsync_FiltersSitesInPriorityOrder_AndStopsFilteringA new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy")); + "Pharmacy", + ReferenceNumberGroup: 0)); _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); @@ -1053,7 +1097,8 @@ public async Task QuerySitesAsync_ShouldExcludeSiteTypesFromResults() new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -1064,7 +1109,8 @@ public async Task QuerySitesAsync_ShouldExcludeSiteTypesFromResults() new Location("Point", [-1.6610648, 53.795467]), null, null, - "GP Practice"), + "GP Practice", + ReferenceNumberGroup: 0), new("test456", "Test Site 3", string.Empty, @@ -1075,7 +1121,8 @@ public async Task QuerySitesAsync_ShouldExcludeSiteTypesFromResults() new Location("Point", [-1.6610648, 53.795467]), null, null, - "PCN Site"), + "PCN Site", + ReferenceNumberGroup: 0), new("test654", "Test Site 4", string.Empty, @@ -1086,7 +1133,8 @@ public async Task QuerySitesAsync_ShouldExcludeSiteTypesFromResults() new Location("Point", [-1.6610648, 53.795467]), null, null, - "Some other site type"), + "Some other site type", + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) @@ -1120,7 +1168,8 @@ public async Task QuerySitesAsync_CallsAvailabilityStoreForEachSite_WhenSiteSupp new(Id: "accessibility/access_need_1", Value: "true") }, status: SiteStatus.Online, isDeleted: null, - Type: string.Empty), + Type: string.Empty, + ReferenceNumberGroup: 0), Distance: 2858), new(new Site( Id: "6877d86e-c2df-4def-8508-e1eccf0ea6bb", @@ -1137,7 +1186,8 @@ public async Task QuerySitesAsync_CallsAvailabilityStoreForEachSite_WhenSiteSupp new(Id: "accessibility/access_need_1", Value: "false") }, status: SiteStatus.Online, isDeleted: null, - Type: string.Empty), + Type: string.Empty, + ReferenceNumberGroup: 0), Distance: 3573) }; _siteStore.Setup(x => x.GetAllSites()).ReturnsAsync(sites.Select(s => s.Site)); @@ -1208,7 +1258,8 @@ public async Task QuerySitesAsync_CallsAvailabilityStoreForEachSite_WhenSiteSupp new(Id: "accessibility/access_need_1", Value: "true") }, status: SiteStatus.Online, isDeleted: null, - Type: string.Empty), + Type: string.Empty, + ReferenceNumberGroup: 0), Distance: 2858), new(new Site( Id: "6877d86e-c2df-4def-8508-e1eccf0ea6bb", @@ -1225,7 +1276,8 @@ public async Task QuerySitesAsync_CallsAvailabilityStoreForEachSite_WhenSiteSupp new(Id: "accessibility/access_need_1", Value: "false") }, status: SiteStatus.Online, isDeleted: null, - Type: string.Empty), + Type: string.Empty, + ReferenceNumberGroup: 0), Distance: 3573) }; _siteStore.Setup(x => x.GetAllSites()).ReturnsAsync(sites.Select(s => s.Site)); @@ -1315,7 +1367,8 @@ public async Task QuerySitesAsync_CallsAvailabilityStoreForEachSiteBatched_WhenS new(Id: "accessibility/access_need_1", Value: "false") }, status: SiteStatus.Online, isDeleted: null, - Type: string.Empty), + Type: string.Empty, + ReferenceNumberGroup: 0), Distance: 3500+i)); //invalid site results happen to not be cached @@ -1344,7 +1397,8 @@ public async Task QuerySitesAsync_CallsAvailabilityStoreForEachSiteBatched_WhenS new(Id: "accessibility/access_need_1", Value: "false") }, status: SiteStatus.Online, isDeleted: null, - Type: string.Empty), + Type: string.Empty, + ReferenceNumberGroup: 0), Distance: (int)(3700+i))); //valid site results happen to be cached @@ -1438,7 +1492,8 @@ public async Task GetSiteByIdAsync_ReturnsDefault_WhenSiteIsSoftDeleted() InformationForCitizens: "", Accessibilities: new List { new(Id: "Accessibility 1", Value: "true") }, status: SiteStatus.Online, isDeleted: true, - Type: string.Empty); + Type: string.Empty, + ReferenceNumberGroup: 0); _siteStore.Setup(x => x.GetSiteById(siteId)).ReturnsAsync(site); var result = await _sut.GetSiteByIdAsync(siteId); @@ -1509,7 +1564,8 @@ public async Task QuerySitesAsync_FiltersInIndexOrder_WhenPrioritiesAreTheSame() new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy")); + "Pharmacy", + ReferenceNumberGroup: 0)); } sites.Add(new( @@ -1523,7 +1579,8 @@ public async Task QuerySitesAsync_FiltersInIndexOrder_WhenPrioritiesAreTheSame() new Location("Point", [-1.6610648, 53.795467]), null, null, - "GP Practice")); + "GP Practice", + ReferenceNumberGroup: 0)); _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); @@ -1572,7 +1629,8 @@ public async Task QuerySitesAsync_FiltersOnPriority_WhenOnlyOneFilterHasPriority new Location("Point", [-1.6610648, 53.795467]), null, null, - "GP Practice")); + "GP Practice", + ReferenceNumberGroup: 0)); } sites.Add(new( @@ -1590,7 +1648,8 @@ public async Task QuerySitesAsync_FiltersOnPriority_WhenOnlyOneFilterHasPriority new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy")); + "Pharmacy", + ReferenceNumberGroup: 0)); _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); @@ -1635,7 +1694,8 @@ public async Task QuerySitesAsync_OnlyReturnsSiteOnce_WhenItMatchesMultipleFilte new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -1650,7 +1710,8 @@ public async Task QuerySitesAsync_OnlyReturnsSiteOnce_WhenItMatchesMultipleFilte new Location("Point", [-1.6610648, 53.795467]), null, null, - "GP Practice"), + "GP Practice", + ReferenceNumberGroup: 0), new("test456", "Test Site 3", string.Empty, @@ -1661,7 +1722,8 @@ public async Task QuerySitesAsync_OnlyReturnsSiteOnce_WhenItMatchesMultipleFilte new Location("Point", [-1.6610648, 53.795467]), null, null, - "PCN Site"), + "PCN Site", + ReferenceNumberGroup: 0), new("test654", "Test Site 4", string.Empty, @@ -1672,7 +1734,8 @@ public async Task QuerySitesAsync_OnlyReturnsSiteOnce_WhenItMatchesMultipleFilte new Location("Point", [-1.6610648, 53.795467]), null, null, - "Some other site type"), + "Some other site type", + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); @@ -1716,7 +1779,8 @@ public async Task QuerySitesAsync_ReturnsSitesOnlyWhenAllServicesMatch() new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -1731,7 +1795,8 @@ public async Task QuerySitesAsync_ReturnsSitesOnlyWhenAllServicesMatch() new Location("Point", [-1.6610648, 53.795467]), null, null, - "GP Practice"), + "GP Practice", + ReferenceNumberGroup: 0), new("test456", "Test Site 3", string.Empty, @@ -1742,7 +1807,8 @@ public async Task QuerySitesAsync_ReturnsSitesOnlyWhenAllServicesMatch() new Location("Point", [-1.6610648, 53.795467]), null, null, - "PCN Site"), + "PCN Site", + ReferenceNumberGroup: 0), new("test654", "Test Site 4", string.Empty, @@ -1753,7 +1819,8 @@ public async Task QuerySitesAsync_ReturnsSitesOnlyWhenAllServicesMatch() new Location("Point", [-1.6610648, 53.795467]), null, null, - "Some other site type"), + "Some other site type", + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); @@ -1811,7 +1878,8 @@ public async Task QuerySitesAsync_SameOrderedCacheKeyUsedForUnorderedAndDuplicat new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy") + "Pharmacy", + ReferenceNumberGroup: 0) }; _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); @@ -1886,7 +1954,8 @@ public async Task QuerySitesAsync_CachingKeyUsedForFullServiceList() new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -1901,7 +1970,8 @@ public async Task QuerySitesAsync_CachingKeyUsedForFullServiceList() new Location("Point", [-1.6610648, 53.795467]), null, null, - "GP Practice"), + "GP Practice", + ReferenceNumberGroup: 0), new("test456", "Test Site 3", string.Empty, @@ -1912,7 +1982,8 @@ public async Task QuerySitesAsync_CachingKeyUsedForFullServiceList() new Location("Point", [-1.6610648, 53.795467]), null, null, - "PCN Site"), + "PCN Site", + ReferenceNumberGroup: 0), new("test654", "Test Site 4", string.Empty, @@ -1923,7 +1994,8 @@ public async Task QuerySitesAsync_CachingKeyUsedForFullServiceList() new Location("Point", [-1.6610648, 53.795467]), null, null, - "Some other site type"), + "Some other site type", + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); @@ -1975,7 +2047,8 @@ public async Task QuerySitesAsync_WritesToCache_WhenKeyNotPresentInitially() new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -1990,7 +2063,8 @@ public async Task QuerySitesAsync_WritesToCache_WhenKeyNotPresentInitially() new Location("Point", [-1.6610648, 53.795467]), null, null, - "GP Practice"), + "GP Practice", + ReferenceNumberGroup: 0), new("test456", "Test Site 3", string.Empty, @@ -2001,7 +2075,8 @@ public async Task QuerySitesAsync_WritesToCache_WhenKeyNotPresentInitially() new Location("Point", [-1.6610648, 53.795467]), null, null, - "PCN Site"), + "PCN Site", + ReferenceNumberGroup: 0), new("test654", "Test Site 4", string.Empty, @@ -2012,7 +2087,8 @@ public async Task QuerySitesAsync_WritesToCache_WhenKeyNotPresentInitially() new Location("Point", [-1.6610648, 53.795467]), null, null, - "Some other site type"), + "Some other site type", + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); @@ -2064,7 +2140,8 @@ public async Task QuerySitesAsync_WriteToCache_CacheKeyHasUniqueServicesInAlphab new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -2079,7 +2156,8 @@ public async Task QuerySitesAsync_WriteToCache_CacheKeyHasUniqueServicesInAlphab new Location("Point", [-1.6610648, 53.795467]), null, null, - "GP Practice"), + "GP Practice", + ReferenceNumberGroup: 0), new("test456", "Test Site 3", string.Empty, @@ -2090,7 +2168,8 @@ public async Task QuerySitesAsync_WriteToCache_CacheKeyHasUniqueServicesInAlphab new Location("Point", [-1.6610648, 53.795467]), null, null, - "PCN Site"), + "PCN Site", + ReferenceNumberGroup: 0), new("test654", "Test Site 4", string.Empty, @@ -2101,7 +2180,8 @@ public async Task QuerySitesAsync_WriteToCache_CacheKeyHasUniqueServicesInAlphab new Location("Point", [-1.6610648, 53.795467]), null, null, - "Some other site type"), + "Some other site type", + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); @@ -2153,7 +2233,8 @@ public async Task QuerySitesAsync_ReadsFromCache_CacheKeyHasUniqueServicesInAlph new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -2168,7 +2249,8 @@ public async Task QuerySitesAsync_ReadsFromCache_CacheKeyHasUniqueServicesInAlph new Location("Point", [-1.6610648, 53.795467]), null, null, - "GP Practice"), + "GP Practice", + ReferenceNumberGroup: 0), new("test456", "Test Site 3", string.Empty, @@ -2179,7 +2261,8 @@ public async Task QuerySitesAsync_ReadsFromCache_CacheKeyHasUniqueServicesInAlph new Location("Point", [-1.6610648, 53.795467]), null, null, - "PCN Site"), + "PCN Site", + ReferenceNumberGroup: 0), new("test654", "Test Site 4", string.Empty, @@ -2190,7 +2273,8 @@ public async Task QuerySitesAsync_ReadsFromCache_CacheKeyHasUniqueServicesInAlph new Location("Point", [-1.6610648, 53.795467]), null, null, - "Some other site type"), + "Some other site type", + ReferenceNumberGroup: 0), }; _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); @@ -2226,7 +2310,8 @@ public async Task GetAllSites_ReturnsFromBasicCache_WhenOptionsSiteSlidingCache_ new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -2241,7 +2326,8 @@ public async Task GetAllSites_ReturnsFromBasicCache_WhenOptionsSiteSlidingCache_ new Location("Point", [-1.6610648, 53.795467]), null, null, - "GP Practice"), + "GP Practice", + ReferenceNumberGroup: 0), }; object outResult = new CacheService.CacheObject>(sites); @@ -2276,7 +2362,8 @@ public async Task GetAllSites_ReturnsFromBasicCache_WhenOptionsSiteSlidingCache_ new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -2291,7 +2378,8 @@ public async Task GetAllSites_ReturnsFromBasicCache_WhenOptionsSiteSlidingCache_ new Location("Point", [-1.6610648, 53.795467]), null, null, - "GP Practice"), + "GP Practice", + ReferenceNumberGroup: 0), }; object outResult = new CacheService.LazySlideCacheObject(sites, DateTime.UtcNow); @@ -2328,7 +2416,8 @@ public async Task GetAllSites_ReturnsFromStore_WhenOptionsSiteSlidingCache_Disab new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -2343,7 +2432,8 @@ public async Task GetAllSites_ReturnsFromStore_WhenOptionsSiteSlidingCache_Disab new Location("Point", [-1.6610648, 53.795467]), null, null, - "GP Practice"), + "GP Practice", + ReferenceNumberGroup: 0), }; object outResult = null; @@ -2379,7 +2469,8 @@ public async Task GetAllSites_ReturnsFromStore_WhenOptionsSiteSlidingCache_Disab new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -2394,7 +2485,8 @@ public async Task GetAllSites_ReturnsFromStore_WhenOptionsSiteSlidingCache_Disab new Location("Point", [-1.6610648, 53.795467]), null, null, - "GP Practice"), + "GP Practice", + ReferenceNumberGroup: 0), }; object outResult = new CacheService.CacheObject>(null); @@ -2430,7 +2522,8 @@ public async Task GetAllSites_ReturnsFromStore_WhenOptionsSiteSlidingCache_Enabl new Location("Point", [-1.6610648, 53.795467]), null, null, - "Pharmacy"), + "Pharmacy", + ReferenceNumberGroup: 0), new("test321", "Test Site 2", string.Empty, @@ -2445,7 +2538,8 @@ public async Task GetAllSites_ReturnsFromStore_WhenOptionsSiteSlidingCache_Enabl new Location("Point", [-1.6610648, 53.795467]), null, null, - "GP Practice"), + "GP Practice", + ReferenceNumberGroup: 0), }; object outResult = null; diff --git a/tests/Nhs.Appointments.Persistance.UnitTests/SiteStoreTests.cs b/tests/Nhs.Appointments.Persistance.UnitTests/SiteStoreTests.cs index d2fda1ec0..4dc1baabc 100644 --- a/tests/Nhs.Appointments.Persistance.UnitTests/SiteStoreTests.cs +++ b/tests/Nhs.Appointments.Persistance.UnitTests/SiteStoreTests.cs @@ -46,7 +46,8 @@ public async Task UpdateSiteStatusAsync_AddsStatusPropertyToDocument() [], new Location("Coordinates", [-1.75, 52.76]), null, null, - string.Empty)); + string.Empty, + ReferenceNumberGroup: 0)); _siteStore.Setup(x => x.PatchDocument(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((site, reference, patches) => patchOperations = [.. patches]) @@ -81,7 +82,8 @@ public async Task UpdateSiteStatusAsync_UpdatesStatusPropertyOnDocument() [], new Location("Coordinates", [-1.75, 52.76]), SiteStatus.Online, null, - string.Empty)); + string.Empty, + ReferenceNumberGroup: 0)); _siteStore.Setup(x => x.PatchDocument(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((site, reference, patches) => patchOperations = [.. patches]) @@ -116,7 +118,8 @@ public async Task ToggleSiteSoftDeletionAsync_UpdatesSiteSoftDeletionStatus() [], new Location("Coordinates", [-1.75, 52.76]), SiteStatus.Online, false, - string.Empty)); + string.Empty, + ReferenceNumberGroup: 0)); _siteStore.Setup(x => x.PatchDocument(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((site, reference, patches) => patchOperations = [.. patches]) @@ -151,7 +154,8 @@ public async Task ToggleSiteSoftDeletionAsync_AddIsDeletedProperty() [], new Location("Coordinates", [-1.75, 52.76]), SiteStatus.Online, null, - string.Empty)); + string.Empty, + ReferenceNumberGroup: 0)); _siteStore.Setup(x => x.PatchDocument(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((site, reference, patches) => patchOperations = [.. patches]) @@ -211,7 +215,8 @@ public async Task UpdateSiteDetails_FailsToFindSite_WhenItHasBeenSoftDeleted() new Location("Coordinates", [-1.75, 52.76]), SiteStatus.Online, true, - string.Empty)); + string.Empty, + ReferenceNumberGroup: 0)); var result = await _sut.UpdateSiteDetails("some-site-id", "Site Name", "1 Site Lane", "N", (decimal)-0.751, (decimal)50.369); @@ -238,7 +243,8 @@ public async Task UpdateSiteDetails_PatchesSiteDetails_ButNotSiteType() [], new Location("Coordinates", [-1.75, 52.76]), SiteStatus.Online, null, - string.Empty)); + string.Empty, + ReferenceNumberGroup: 0)); _siteStore.Setup(x => x.PatchDocument(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((site, reference, patches) => patchOperations = [.. patches]) @@ -272,7 +278,8 @@ public async Task UpdateSiteDetails_PatchesSiteDetails_AndAddsSiteType() [], new Location("Coordinates", [-1.75, 52.76]), SiteStatus.Online, null, - null)); + null, + ReferenceNumberGroup: 0)); _siteStore.Setup(x => x.PatchDocument(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((site, reference, patches) => patchOperations = [.. patches]) @@ -307,7 +314,8 @@ public async Task UpdateSiteDetails_PatchesSiteDetails_AndReplacesSiteType() [], new Location("Coordinates", [-1.75, 52.76]), SiteStatus.Online, null, - "GP Practice")); + "GP Practice", + ReferenceNumberGroup: 0)); _siteStore.Setup(x => x.PatchDocument(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((site, reference, patches) => patchOperations = [.. patches]) From 311586157072970652a2797a061b08cb4e563bc1 Mon Sep 17 00:00:00 2001 From: pata9 Date: Wed, 22 Apr 2026 10:33:44 +0100 Subject: [PATCH 04/23] refactor to referenceGroup Cache --- .../HttpFunctions/GetSiteFunction.cs | 2 +- .../HttpFunctions/GetSiteMetaDataFunction.cs | 2 +- .../Notifications/UserRolesChangedNotifier.cs | 2 +- .../Bookings/ReferenceNumberProvider.cs | 39 ++++++++++++++----- .../Sites/SiteService.cs | 6 +-- .../ApplyAvailabilityTemplateFunctionTests.cs | 4 +- .../Functions/CancelBookingFunctionTests.cs | 12 +++--- .../ConfirmProvisionalBookingFunctionTests.cs | 6 +-- .../Functions/GetSiteMetaDataFunctionTests.cs | 2 +- .../Functions/MakeBookingFunctionTests.cs | 8 ++-- .../QueryAvailabilityByHoursFunctionTests.cs | 8 ++-- .../QueryAvailabilityBySlotsFunctionTests.cs | 6 +-- .../QueryBookingByReferenceFunctionTests.cs | 8 ++-- .../Functions/SetAvailabilityFunctionTests.cs | 4 +- .../SetSiteAccessibilitiesFunctionTests.cs | 4 +- .../Functions/SetSiteDetailsFunctionTests.cs | 4 +- ...SiteInformationForCitizensFunctionTests.cs | 4 +- .../SetSiteReferenceDetailsFunctionTests.cs | 4 +- .../Notifications/BookingNotifierTests.cs | 10 ++--- .../UserRolesChangedNotifierTests.cs | 14 +++---- .../BulkImport/UserDataImportHandlerTests.cs | 18 ++++----- .../ReferenceNumberProviderTests.cs | 17 +++++--- .../SiteServiceTests.cs | 2 +- 23 files changed, 107 insertions(+), 79 deletions(-) diff --git a/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/GetSiteFunction.cs b/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/GetSiteFunction.cs index be050a6a9..67d44fcb5 100644 --- a/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/GetSiteFunction.cs +++ b/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/GetSiteFunction.cs @@ -47,7 +47,7 @@ public override Task RunAsync( protected override async Task> HandleRequest(SiteBasedResourceRequest request, ILogger logger) { - var site = await siteService.GetSiteByIdAsync(request.Site, request.Scope); + var site = await siteService.GetSiteByIdAsync(request.Site, false, request.Scope); if (site != null) { return ApiResult.Success(site); diff --git a/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/GetSiteMetaDataFunction.cs b/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/GetSiteMetaDataFunction.cs index 5ec6f9077..7160568a4 100644 --- a/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/GetSiteMetaDataFunction.cs +++ b/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/GetSiteMetaDataFunction.cs @@ -53,7 +53,7 @@ protected override async Task> HandleRequest( ILogger logger) { const string scope = "site_details"; - var site = await siteService.GetSiteByIdAsync(request.Site, scope); + var site = await siteService.GetSiteByIdAsync(request.Site, false, scope); if (site != null) { var patientInformation = site.Accessibilities.Any() diff --git a/src/api/Nhs.Appointments.Api/Notifications/UserRolesChangedNotifier.cs b/src/api/Nhs.Appointments.Api/Notifications/UserRolesChangedNotifier.cs index 023b88d26..f332828e5 100644 --- a/src/api/Nhs.Appointments.Api/Notifications/UserRolesChangedNotifier.cs +++ b/src/api/Nhs.Appointments.Api/Notifications/UserRolesChangedNotifier.cs @@ -34,7 +34,7 @@ public async Task Notify(string eventType, string user, string siteId, string[] var rolesAddedNames = GetRoleNames(roles, rolesAdded); var rolesRemovedNames = GetRoleNames(roles, rolesRemoved); - var site = await siteService.GetSiteByIdAsync(siteId, "*"); + var site = await siteService.GetSiteByIdAsync(siteId); var siteName = site == null ? $"Unknown site ({siteId})" : site.Name; var templateValues = new Dictionary diff --git a/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs b/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs index 707967ca2..57db0aab2 100644 --- a/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs +++ b/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs @@ -1,3 +1,4 @@ +using Nhs.Appointments.Core.Caching; using Nhs.Appointments.Core.Sites; namespace Nhs.Appointments.Core.Bookings; @@ -10,32 +11,52 @@ public interface IReferenceNumberProvider public class ReferenceNumberProvider : IReferenceNumberProvider { private readonly ISiteService _siteService; + private readonly ICacheService _cacheService; private readonly IReferenceNumberDocumentStore _referenceNumberDocumentStore; private readonly TimeProvider _timeProvider; public ReferenceNumberProvider( ISiteService siteService, + ICacheService cacheService, IReferenceNumberDocumentStore referenceNumberDocumentStore, TimeProvider timeProvider) { _siteService = siteService; + _cacheService = cacheService; _referenceNumberDocumentStore = referenceNumberDocumentStore; _timeProvider = timeProvider; } public async Task GetReferenceNumber(string siteId) - { - var site = await _siteService.GetSiteByIdAsync(siteId); - if (site.ReferenceNumberGroup == 0) - { - var referenceGroup = await _referenceNumberDocumentStore.AssignReferenceGroup(); - await _siteService.AssignPrefix(siteId, referenceGroup); - } + { + var referenceNumberGroup = + await _cacheService.GetCacheValue( + $"site:referenceNumberGroup:{siteId}", + new CacheOptions( + async () => await GetSitesAssignedReferenceGroup(siteId), + TimeSpan.FromHours(24))); - var sequence = await _referenceNumberDocumentStore.GetNextSequenceNumber(site.ReferenceNumberGroup); + var sequence = await _referenceNumberDocumentStore.GetNextSequenceNumber(referenceNumberGroup); var now = _timeProvider.GetUtcNow(); var rng = now.Day + now.Second; - return $"{site.ReferenceNumberGroup:00}-{rng:00}-{sequence:000000}"; + return $"{referenceNumberGroup:00}-{rng:00}-{sequence:000000}"; + } + + private async Task GetSitesAssignedReferenceGroup(string siteId) + { + var site = await _siteService.GetSiteByIdAsync(siteId, true); + if (!SiteHasNotBeenAssignedReferenceGroup(site)) + { + return site.ReferenceNumberGroup; + } + + var referenceGroup = await _referenceNumberDocumentStore.AssignReferenceGroup(); + await _siteService.AssignPrefix(siteId, referenceGroup); + + return referenceGroup; + } + + private static bool SiteHasNotBeenAssignedReferenceGroup(Site site) => site.ReferenceNumberGroup == 0; } public interface IReferenceNumberDocumentStore diff --git a/src/api/Nhs.Appointments.Core/Sites/SiteService.cs b/src/api/Nhs.Appointments.Core/Sites/SiteService.cs index 5d775da90..5ca8897a5 100644 --- a/src/api/Nhs.Appointments.Core/Sites/SiteService.cs +++ b/src/api/Nhs.Appointments.Core/Sites/SiteService.cs @@ -10,7 +10,7 @@ namespace Nhs.Appointments.Core.Sites; public interface ISiteService { - Task GetSiteByIdAsync(string siteId, string scope = "*"); + Task GetSiteByIdAsync(string siteId, bool ignoreCache = false, string scope = "*"); Task> GetSitesPreview(bool includeDeleted = false); Task> GetAllSites(bool includeDeleted = false, bool ignoreCache = false); Task UpdateAccessibilities(string siteId, IEnumerable accessibilities); @@ -135,10 +135,10 @@ private static List GetDateStringsInRange(DateOnly from, DateOnly to) return result; } - public async Task GetSiteByIdAsync(string siteId, string scope = "*") + public async Task GetSiteByIdAsync(string siteId, bool ignoreCache = false, string scope = "*") { Site site; - if (options.Value.DisableSiteCache) + if (options.Value.DisableSiteCache || ignoreCache) { site = await siteStore.GetSiteById(siteId); } diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/ApplyAvailabilityTemplateFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/ApplyAvailabilityTemplateFunctionTests.cs index 02a5c4d6c..7f674776f 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/ApplyAvailabilityTemplateFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/ApplyAvailabilityTemplateFunctionTests.cs @@ -44,7 +44,7 @@ public ApplyAvailabilityTemplateFunctionTests() [Fact] public async Task RunAsync_ReturnsNotFound_WhenSiteIsNull() { - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(null as Site); var sessions = new List @@ -89,7 +89,7 @@ public async Task RunAsync_AppliesNewAvailabilityTemplate() var userPrincipal = UserDataGenerator.CreateUserPrincipal("test.user3@nhs.net"); _userContextProvider.Setup(x => x.UserPrincipal) .Returns(userPrincipal); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( "test-site", "Test Site", diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/CancelBookingFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/CancelBookingFunctionTests.cs index c84573478..c40fa21b2 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/CancelBookingFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/CancelBookingFunctionTests.cs @@ -51,7 +51,7 @@ public async Task RunAsync_ReturnsSuccessResponse_WhenBookingCancelled() _bookingWriteService.Setup(x => x.CancelBooking(bookingRef, site, CancellationReason.CancelledByCitizen, It.IsAny(), It.IsAny())) .Returns(Task.FromResult(BookingCancellationResult.Success)); - _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( site, "test site", @@ -87,7 +87,7 @@ public async Task RunAsync_CallsBookingServiceOnce_WhenCancellationReasonIsNull( _bookingWriteService.Setup(x => x.CancelBooking(bookingRef, site, CancellationReason.CancelledByCitizen, It.IsAny(), It.IsAny())) .Returns(Task.FromResult(BookingCancellationResult.Success)).Verifiable(Times.Once); - _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( site, "test site", @@ -123,7 +123,7 @@ public async Task RunAsync_ReturnsNotFoundResponse_WhenBookingInvalid() _bookingWriteService.Setup(x => x.CancelBooking(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(BookingCancellationResult.NotFound)); - _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( site, "test site", @@ -160,7 +160,7 @@ public async Task RunAsync_Fails_WhenServiceReturnsUnexpected() _bookingWriteService.Setup(x => x.CancelBooking(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.FromResult((BookingCancellationResult)invalidResultCode)); - _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( site, "test site", @@ -201,7 +201,7 @@ public async Task RunAsync_PassesCancellationReasonToService(string cancellation _bookingWriteService .Setup(x => x.CancelBooking(bookingRef, site, expectedCancellationReason, It.IsAny(), It.IsAny())) .Returns(Task.FromResult(BookingCancellationResult.Success)).Verifiable(); - _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( site, "test site", @@ -236,7 +236,7 @@ public async Task RunAsync_ReturnsNotFound_WhenSiteHasBeenDeleted() var bookingRef = "some-booking"; var site = "TEST01"; - _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny(), It.IsAny())) .ReturnsAsync(null as Site); var request = BuildRequest(bookingRef, site); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/ConfirmProvisionalBookingFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/ConfirmProvisionalBookingFunctionTests.cs index 8c1948eae..5e6326c95 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/ConfirmProvisionalBookingFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/ConfirmProvisionalBookingFunctionTests.cs @@ -51,7 +51,7 @@ public async Task RunAsync_CallsConfirmProvisionalBooking_WhenNoChildBookings() Reference = "booking-ref", Site = "test-site" }); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( "test-site", "test site", @@ -90,7 +90,7 @@ public async Task RunAsync_CallsConfirmProvisionalBookingWithChildren(params str Reference = "booking-ref", Site = "test-site" }); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( "test-site", "test site", @@ -127,7 +127,7 @@ public async Task RunAsync_ReturnsNotFound_WhenSiteHasBeenDeleted() Reference = "booking-ref", Site = "test-site" }); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(null as Site); var request = CreateRequest([]); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/GetSiteMetaDataFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/GetSiteMetaDataFunctionTests.cs index d13d6a2a5..5d3c06244 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/GetSiteMetaDataFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/GetSiteMetaDataFunctionTests.cs @@ -45,7 +45,7 @@ public async Task RunAsync_ReturnsNotFound_WhenRequestedSiteIsNotConfigured() [InlineData("attr_one/test_attr", "Another test", "")] public async Task RunAsync_ReturnsInformationForCitizen(string attrId, string attrVal, string expectedInformation) { - _siteService.Setup(x => x.GetSiteByIdAsync("6877d86e-c2df-4def-8508-e1eccf0ea6ba", "site_details")) + _siteService.Setup(x => x.GetSiteByIdAsync("6877d86e-c2df-4def-8508-e1eccf0ea6ba", It.IsAny(), "site_details")) .ReturnsAsync(new Site ( Id: "6877d86e-c2df-4def-8508-e1eccf0ea6ba", diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/MakeBookingFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/MakeBookingFunctionTests.cs index 2c6099070..2be684605 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/MakeBookingFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/MakeBookingFunctionTests.cs @@ -40,7 +40,7 @@ public async Task RunAsync_ReturnsSuccessResponse_WhenAppointmentIsRequested() { var slots = AvailabilityHelper.CreateTestSlots(Date, new TimeOnly(10, 0), new TimeOnly(11, 0), TimeSpan.FromMinutes(5)); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())).ReturnsAsync( + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync( new Site("6877d86e-c2df-4def-8508-e1eccf0ea6ba", "Test Site", "Nowhere", "2929292", "15N", "North", "Test Board", "Information For Citizen 123", Enumerable.Empty(), new Location("Point", [0, 0]), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0)); @@ -59,7 +59,7 @@ public async Task RunAsync_ReturnsSuccessResponse_WhenAppointmentIsRequested() [Fact] public async Task RunAsync_ReturnsError_WhenSiteDoesNotExist() { - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())).ReturnsAsync((Site)null); + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync((Site)null); var request = CreateRequest("34e990af-5dc9-43a6-8895-b9123216d699", "2077-01-01 09:30", "COVID", "9999999999", "FirstName", "LastName", @@ -74,7 +74,7 @@ public async Task RunAsync_ReturnsError_WhenSiteDoesNotExist() [Fact] public async Task RunAsync_ReturnsError_WhenAppointmentSlotIsNotAvailable() { - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())).ReturnsAsync( + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync( new Site("6877d86e-c2df-4def-8508-e1eccf0ea6ba", "Test Site", "Nowhere", "2929292", "15N", "North", "Test Board", "Information For Citizens 123", Enumerable.Empty(), new Location("Point", [0, 0]), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0)); @@ -94,7 +94,7 @@ public async Task RunAsync_ReturnsError_WhenAppointmentSlotIsNotAvailable() [Fact] public void RunAsync_InvokesBookingService_WithCorrectDetails() { - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())).ReturnsAsync( + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync( new Site("6877d86e-c2df-4def-8508-e1eccf0ea6ba", "Test Site", "Nowhere", "2929292", "15N", "North", "Test Board", "Information For Citizens 123", Enumerable.Empty(), new Location("Point", [0, 0]), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0)); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityByHoursFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityByHoursFunctionTests.cs index 1ddcf716b..b1b56ab9b 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityByHoursFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityByHoursFunctionTests.cs @@ -67,7 +67,7 @@ public async Task RunAsync_ReturnsNotFound_WhenSiteIsInactive() { _featureToggleHelper.Setup(x => x.IsFeatureEnabled(Flags.MultiServiceJointBookings)) .ReturnsAsync(true); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(null as Site); var payload = new AvailabilityQueryByHoursRequest( @@ -99,7 +99,7 @@ public async Task RunAsync_ReturnAvailabilityByHours() _featureToggleHelper.Setup(x => x.IsFeatureEnabled(Flags.MultiServiceJointBookings)) .ReturnsAsync(true); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( "2de5bb57-060f-4cb5-b14d-16587d0c2e8f", "Test Site", @@ -154,7 +154,7 @@ public async Task RunAsync_DoesNotShowFollowingHour_IfSlotSpillsOver() _featureToggleHelper.Setup(x => x.IsFeatureEnabled(Flags.MultiServiceJointBookings)) .ReturnsAsync(true); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( "2de5bb57-060f-4cb5-b14d-16587d0c2e8f", "Test Site", @@ -205,7 +205,7 @@ public async Task RunAsync_ReturnsEmptyHoursArray() _featureToggleHelper.Setup(x => x.IsFeatureEnabled(Flags.MultiServiceJointBookings)) .ReturnsAsync(true); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( "2de5bb57-060f-4cb5-b14d-16587d0c2e8f", "Test Site", diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityBySlotsFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityBySlotsFunctionTests.cs index 05ad18618..4fccedb5e 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityBySlotsFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryAvailabilityBySlotsFunctionTests.cs @@ -68,7 +68,7 @@ public async Task RunAsync_ReturnsNotFound_WhenSiteIsInactive() { _featureToggleHelper.Setup(x => x.IsFeatureEnabled(Flags.MultiServiceJointBookings)) .ReturnsAsync(true); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(null as Site); var payload = new AvailabilityQueryBySlotsRequest( @@ -94,7 +94,7 @@ public async Task RunAsync_ReturnsEmptySlotArray() _featureToggleHelper.Setup(x => x.IsFeatureEnabled(Flags.MultiServiceJointBookings)) .ReturnsAsync(true); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( "2de5bb57-060f-4cb5-b14d-16587d0c2e8f", "Test Site", @@ -149,7 +149,7 @@ public async Task RunAsync_ReturnsAvailabilityBySlots() _featureToggleHelper.Setup(x => x.IsFeatureEnabled(Flags.MultiServiceJointBookings)) .ReturnsAsync(true); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( "2de5bb57-060f-4cb5-b14d-16587d0c2e8f", "Test Site", diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryBookingByReferenceFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryBookingByReferenceFunctionTests.cs index 221df0699..b9c69db32 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryBookingByReferenceFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/QueryBookingByReferenceFunctionTests.cs @@ -61,7 +61,7 @@ public async Task RunAsync_ReturnsSuccessResponse_AndBookingWhenFound() _bookingQueryService.Setup(x => x.GetBookingByReference(It.IsAny())) .ReturnsAsync(booking); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( site, "Test Site", @@ -112,7 +112,7 @@ public async Task RunAsync_ReturnsNotFoundResponse_WhenReferenceAndSiteDoNotMatc _bookingQueryService.Setup(x => x.GetBookingByReference(It.IsAny())) .ReturnsAsync(booking); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( "Test Site", "Test Site", @@ -161,7 +161,7 @@ public async Task ReturnsNotFound_WhenSiteHasBeenSoftDeleted() _bookingQueryService.Setup(x => x.GetBookingByReference(It.IsAny())) .ReturnsAsync(booking); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(null as Site); var request = new QueryBookingByReferenceRequest(bookingRef, "TEST03"); @@ -188,7 +188,7 @@ public async Task ReturnsNotFound_WhenProvisionalBookingNotFound() var result = await _sut.RunAsync(httpRequest) as ContentResult; result.StatusCode.Should().Be(404); - _siteService.Verify(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny()), Times.Never); + _siteService.Verify(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } private static HttpRequest CreateRequest(string site) diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetAvailabilityFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetAvailabilityFunctionTests.cs index c4a46a882..a74a5fd47 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetAvailabilityFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetAvailabilityFunctionTests.cs @@ -41,7 +41,7 @@ public async Task InvokeAvailabilityService_WhenSettingAvailability() var userPrincipal = UserDataGenerator.CreateUserPrincipal("test.user3@nhs.net"); _userContext.Setup(x => x.UserPrincipal) .Returns(userPrincipal); - _siteService.Setup(x => x.GetSiteByIdAsync("test-site", It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync("test-site", It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( "test-site", "Test Site", @@ -92,7 +92,7 @@ public async Task InvokeAvailabilityService_WhenSettingAvailability() [Fact] public async Task DoesNotInvokeAvailabilityService_WhenSiteIsInactive() { - _siteService.Setup(x => x.GetSiteByIdAsync("test-site", It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync("test-site", It.IsAny(), It.IsAny())) .ReturnsAsync(null as Site); var sessions = new List diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteAccessibilitiesFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteAccessibilitiesFunctionTests.cs index 3d6f2b899..cc33d29cd 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteAccessibilitiesFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteAccessibilitiesFunctionTests.cs @@ -47,7 +47,7 @@ public async Task InvokeSiteService_WhenSettingSiteAccessibilities(bool operatio _siteService.Setup(x => x.UpdateAccessibilities(site, accessibilities)) .ReturnsAsync(operationalResult); - _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( site, "test site", @@ -85,7 +85,7 @@ public async Task DoesNotInvokeService_WhenSiteIsInactive() }; var request = new SetSiteAccessibilitiesRequest(site, accessibilities); - _siteService.Setup(x => x.GetSiteByIdAsync("test-site", It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync("test-site", It.IsAny(), It.IsAny())) .ReturnsAsync(null as Site); var result = await _sut.Invoke(request); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteDetailsFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteDetailsFunctionTests.cs index 77e9ad952..855e053ab 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteDetailsFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteDetailsFunctionTests.cs @@ -47,7 +47,7 @@ public async Task InvokeSiteService_WhenSettingSiteDetails(bool operationSuccess _siteService.Setup(x => x.UpdateSiteDetailsAsync(site, name, address, phoneNo, decimal.Parse($"{longitude}"), decimal.Parse($"{latitude}"))) .ReturnsAsync(operationalResult); - _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( site, name, @@ -88,7 +88,7 @@ public async Task DoesNotInvokeService_WhenSiteIsInactive() var latitude = 4.567; var request = new SetSiteDetailsRequest(site, name, address, phoneNo, $"{longitude}", $"{latitude}"); - _siteService.Setup(x => x.GetSiteByIdAsync("test-site", It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync("test-site", It.IsAny(), It.IsAny())) .ReturnsAsync(null as Site); var result = await _sut.Invoke(request); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteInformationForCitizensFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteInformationForCitizensFunctionTests.cs index 8e46189bc..4a79bda76 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteInformationForCitizensFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteInformationForCitizensFunctionTests.cs @@ -48,7 +48,7 @@ public async Task InvokeSiteService_WhenSettingInformationForCitizens(bool opera _userContext.Setup(x => x.UserPrincipal).Returns(userPrincipal); _siteService.Setup(x => x.UpdateInformationForCitizens(It.IsAny(), It.IsAny())) .ReturnsAsync(operationalResult); - _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( site, "Test Site", @@ -84,7 +84,7 @@ public async Task DoesNotInvokeService_WhenSiteIsInactive() var infoForCitizens = "Some information for citizens."; var request = new SetSiteInformationForCitizensRequest(site, infoForCitizens); - _siteService.Setup(x => x.GetSiteByIdAsync("test-site", It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync("test-site", It.IsAny(), It.IsAny())) .ReturnsAsync(null as Site); var result = await _sut.Invoke(request); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteReferenceDetailsFunctionTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteReferenceDetailsFunctionTests.cs index 28c196cc4..dae1be836 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteReferenceDetailsFunctionTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Functions/SetSiteReferenceDetailsFunctionTests.cs @@ -46,7 +46,7 @@ public async Task InvokeSiteService_WhenSettingSiteReferenceDetails(bool operati _siteService.Setup(x => x.UpdateSiteReferenceDetailsAsync(site, odsCode, icb, region)) .ReturnsAsync(operationalResult); - _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(site, It.IsAny(), It.IsAny())) .ReturnsAsync(new Site( site, "Test Site", @@ -84,7 +84,7 @@ public async Task DoesNotInvokeService_WhenSiteIsInactive() var region = "R1"; var request = new SetSiteReferenceDetailsRequest(site, odsCode, icb, region); - _siteService.Setup(x => x.GetSiteByIdAsync("test-site", It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync("test-site", It.IsAny(), It.IsAny())) .ReturnsAsync(null as Site); var result = await _sut.Invoke(request); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Notifications/BookingNotifierTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Notifications/BookingNotifierTests.cs index f6f6586cf..264bfafc1 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Notifications/BookingNotifierTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Notifications/BookingNotifierTests.cs @@ -49,7 +49,7 @@ public async Task PassesValuesToEmailGovNotifyService() _notificationConfigurationService .Setup(x => x.GetNotificationConfigurationsAsync(It.IsAny(), It.IsAny())).ReturnsAsync( new NotificationConfiguration { EmailTemplateId = EmailTemplateId, SmsTemplateId = SmsTemplateId }); - _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", "R1", "ICB1", "Information For Citizens 123", null, null, SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0))); @@ -83,7 +83,7 @@ public async Task PassesValuesToSmsGovNotifyService() _notificationConfigurationService .Setup(x => x.GetNotificationConfigurationsAsync(It.IsAny(), It.IsAny())).ReturnsAsync( new NotificationConfiguration { EmailTemplateId = EmailTemplateId, SmsTemplateId = SmsTemplateId }); - _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", "R1", "ICB1", "Information For Citizens 123", null, null, SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0))); @@ -132,7 +132,7 @@ public async Task SiteLocationIsRenderedCorrectly(string informationForCitizens, _notificationConfigurationService .Setup(x => x.GetNotificationConfigurationsAsync(It.IsAny(), It.IsAny())).ReturnsAsync( new NotificationConfiguration { EmailTemplateId = EmailTemplateId, SmsTemplateId = SmsTemplateId }); - _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", "R1", "ICB1", informationForCitizens, null, null, SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0))); @@ -172,7 +172,7 @@ public async Task GetsNameOfSite() _notificationConfigurationService .Setup(x => x.GetNotificationConfigurationsAsync(It.IsAny(), It.IsAny())).ReturnsAsync( new NotificationConfiguration { EmailTemplateId = EmailTemplateId, SmsTemplateId = SmsTemplateId }); - _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())).Returns( + _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny(), It.IsAny())).Returns( Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0))).Verifiable(); @@ -197,7 +197,7 @@ public async Task GetsCorrectNotificationConfiguration() _notificationConfigurationService .Setup(x => x.GetNotificationConfigurationsAsync(It.IsAny(), It.IsAny())).ReturnsAsync( new NotificationConfiguration { EmailTemplateId = EmailTemplateId, SmsTemplateId = SmsTemplateId }); - _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny())).Returns( + _siteService.Setup(x => x.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns( Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0))); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Notifications/UserRolesChangedNotifierTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Notifications/UserRolesChangedNotifierTests.cs index c94e469ba..6f955bcd6 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Notifications/UserRolesChangedNotifierTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Notifications/UserRolesChangedNotifierTests.cs @@ -36,7 +36,7 @@ public async Task PassesValuesToGovNotifyService() _rolesStore.Setup(x => x.GetRoles()).Returns(Task.FromResult>([ new Role { Id = "newRole", Name = "New Role" }, new Role { Id = "removedRole", Name = "Removed Role" } ])); - _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0))); @@ -57,7 +57,7 @@ public async Task GetsNameOfSite() _rolesStore.Setup(x => x.GetRoles()).Returns(Task.FromResult>([ new Role { Id = "newRole", Name = "New Role" }, new Role { Id = "removedRole", Name = "Removed Role" } ])); - _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())).Returns( + _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny(), It.IsAny())).Returns( Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0))).Verifiable(); @@ -74,7 +74,7 @@ public async Task GetsNamesOfRoles() _rolesStore.Setup(x => x.GetRoles()).Returns(Task.FromResult>([ new Role { Id = "newRole", Name = "New Role" }, new Role { Id = "removedRole", Name = "Removed Role" } ])).Verifiable(); - _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0))); @@ -91,7 +91,7 @@ public async Task DoesNotSendNotificationIfNoChanges() _rolesStore.Setup(x => x.GetRoles()).Returns(Task.FromResult>([ new Role { Id = "newRole", Name = "New Role" }, new Role { Id = "removedRole", Name = "Removed Role" } ])); - _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0))); @@ -110,7 +110,7 @@ public async Task UsesFriendlyNamesForRoles() _rolesStore.Setup(x => x.GetRoles()).Returns(Task.FromResult>([ new Role { Id = "newRole", Name = "New Role" }, new Role { Id = "removedRole", Name = "Removed Role" } ])); - _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0))); @@ -132,7 +132,7 @@ public async Task GetsFriendlyRoleNameWhenIdContainsScopePrefixButDatabaseValues _rolesStore.Setup(x => x.GetRoles()).Returns(Task.FromResult>([ new Role { Id = "newRole", Name = "New Role" }, new Role { Id = "removedRole", Name = "Removed Role" } ])); - _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0))); @@ -155,7 +155,7 @@ public async Task GetsCorrectNotificationConfiguration() _rolesStore.Setup(x => x.GetRoles()).Returns(Task.FromResult>([ new Role { Id = "newRole", Name = "New Role" }, new Role { Id = "removedRole", Name = "Removed Role" } ])); - _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny())) + _siteService.Setup(x => x.GetSiteByIdAsync(It.Is(s => s == Site), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(new Site(Site, "A Clinical Site", "123 Surgery Street", "0113 1111111", "15N", "R1", "ICB1", "Information For Citizens 123", Array.Empty(), new Location("point", [0, 0]), SiteStatus.Online, null, string.Empty, ReferenceNumberGroup: 0))); diff --git a/tests/Nhs.Appointments.Core.UnitTests/BulkImport/UserDataImportHandlerTests.cs b/tests/Nhs.Appointments.Core.UnitTests/BulkImport/UserDataImportHandlerTests.cs index 309326e0f..f920d4472 100644 --- a/tests/Nhs.Appointments.Core.UnitTests/BulkImport/UserDataImportHandlerTests.cs +++ b/tests/Nhs.Appointments.Core.UnitTests/BulkImport/UserDataImportHandlerTests.cs @@ -39,7 +39,7 @@ public async Task CanReadUserData_AndSetsSiteScopedPermissions() var file = new FormFile(stream, 0, stream.Length, "Test", "test.csv"); var sites = GetSites(); - _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), "*")) + _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(sites[0]) .ReturnsAsync(sites[1]) .ReturnsAsync(sites[2]) @@ -77,7 +77,7 @@ public async Task OktaIsDisabled_OktaUsersNotProcessed() var sites = GetSites(); - _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), "*")) + _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(sites[0]) .ReturnsAsync(sites[1]); _userServiceMock.Setup(x => x.UpdateUserRoleAssignmentsAsync(It.IsAny(), It.IsAny(), It.IsAny>(), false)) @@ -108,7 +108,7 @@ public async Task OktaIsEnabled_OktaUsersProcessed() var sites = GetSites(); - _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), "*")) + _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(sites[0]) .ReturnsAsync(sites[1]); _userServiceMock.Setup(x => x.UpdateUserRoleAssignmentsAsync(It.IsAny(), It.IsAny(), It.IsAny>(), false)) @@ -147,7 +147,7 @@ public async Task CanValidateOktaUser() var sites = GetSites(); - _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), "*")) + _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(sites[0]) .ReturnsAsync(sites[1]) .ReturnsAsync(sites[2]) @@ -173,7 +173,7 @@ public async Task ReportsIncorrectSiteId_WhenNotFound() var sites = GetSites(); - _siteServiceMock.Setup(s => s.GetSiteByIdAsync(It.IsAny(), "*")) + _siteServiceMock.Setup(s => s.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(null as Site); _emailWhitelistStore.Setup(x => x.GetWhitelistedEmails()) .ReturnsAsync(["@nhs.net"]); @@ -195,7 +195,7 @@ public async Task ReportsInvalidUserRoles() var sites = GetSites(); - _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), "*")) + _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(sites[0]) .ReturnsAsync(sites[1]) .ReturnsAsync(sites[2]) @@ -276,7 +276,7 @@ public async Task ReadsUserData_AndAddsUserToOkta() var sites = GetSites(); - _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), "*")) + _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(sites[0]) .ReturnsAsync(sites[1]); _userServiceMock.Setup(x => x.UpdateUserRoleAssignmentsAsync(It.IsAny(), It.IsAny(), It.IsAny>(), false)) @@ -312,7 +312,7 @@ public async Task ReadsUserData_AndReportsUnsuccessfulOktaReason_AndDoesntCallUs var sites = GetSites(); - _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), "*")) + _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(sites[0]) .ReturnsAsync(sites[1]); _userServiceMock.Setup(x => x.UpdateUserRoleAssignmentsAsync(It.IsAny(), It.IsAny(), It.IsAny>(), false)) @@ -349,7 +349,7 @@ public async Task ReadsUserData_AndReportsUnsuccesfulOktaCreation_WhenEmailDomai var sites = GetSites(); - _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), "*")) + _siteServiceMock.SetupSequence(s => s.GetSiteByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(sites[0]) .ReturnsAsync(sites[1]); _userServiceMock.Setup(x => x.UpdateUserRoleAssignmentsAsync(It.IsAny(), It.IsAny(), It.IsAny>(), false)) diff --git a/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs b/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs index fa3b6fff3..d9ae21bb5 100644 --- a/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs +++ b/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs @@ -1,4 +1,6 @@ +using Microsoft.Extensions.Caching.Memory; using Nhs.Appointments.Core.Bookings; +using Nhs.Appointments.Core.Caching; using Nhs.Appointments.Core.Sites; namespace Nhs.Appointments.Core.UnitTests; @@ -9,16 +11,18 @@ public class ReferenceNumberProviderTests private readonly Mock _siteService = new(); private readonly Mock _referenceNumberDocumentStore = new(); private readonly Mock _timeProvider = new(); - + private readonly Mock _memoryCache = new(); + private readonly Mock _cacheEntry = new(); public ReferenceNumberProviderTests() { - _sut = new ReferenceNumberProvider(_siteService.Object, _referenceNumberDocumentStore.Object, _timeProvider.Object); + _sut = new ReferenceNumberProvider(_siteService.Object, new CacheService(_memoryCache.Object, TimeProvider.System), _referenceNumberDocumentStore.Object, _timeProvider.Object); } [Fact] public async Task GetReferenceNumber_AssignsRefGroup_WhenNotCurrentAssigned() { - _siteService.Setup(x => x.GetSiteByIdAsync("test", It.IsAny())).ReturnsAsync(new Site("test", "NAME", "ADDRESS", + _memoryCache.Setup(x => x.CreateEntry(It.IsAny())).Returns(_cacheEntry.Object); + _siteService.Setup(x => x.GetSiteByIdAsync("test", It.IsAny(), It.IsAny())).ReturnsAsync(new Site("test", "NAME", "ADDRESS", "PHONENUMBER", "ODSCODE", "REGION", "ICB", "INFO", new List(), new Location("",[0, 0]), SiteStatus.Online, false, "TYPE", 0)); _referenceNumberDocumentStore.Setup(x => x.AssignReferenceGroup()).ReturnsAsync(14); @@ -32,7 +36,8 @@ public async Task GetReferenceNumber_AssignsRefGroup_WhenNotCurrentAssigned() [Fact] public async Task GetReferenceNumber_DoesNotAssignsRefGroup_WhenAlreadyAssigned() { - _siteService.Setup(x => x.GetSiteByIdAsync("test", It.IsAny())).ReturnsAsync(new Site( + _memoryCache.Setup(x => x.CreateEntry(It.IsAny())).Returns(_cacheEntry.Object); + _siteService.Setup(x => x.GetSiteByIdAsync("test", It.IsAny(), It.IsAny())).ReturnsAsync(new Site( "test", "NAME", "ADDRESS", "PHONENUMBER", "ODSCODE", "REGION", "ICB", "INFO", new List(), new Location("", [0, 0]), SiteStatus.Online, false, "TYPE", 14)); @@ -46,15 +51,17 @@ public async Task GetReferenceNumber_DoesNotAssignsRefGroup_WhenAlreadyAssigned( [Fact] public async Task GetReferenceNumber_GeneratesCorrectlyFormattedNumber() { + _memoryCache.Setup(x => x.CreateEntry(It.IsAny())).Returns(_cacheEntry.Object); _timeProvider.Setup(x => x.GetUtcNow()).Returns(new DateTime(2077, 1, 31, 9, 0, 59)); _referenceNumberDocumentStore.Setup(x => x.GetNextSequenceNumber(14)).ReturnsAsync(2345); - _siteService.Setup(x => x.GetSiteByIdAsync("test", It.IsAny())).ReturnsAsync(new Site( + _siteService.Setup(x => x.GetSiteByIdAsync("test", It.IsAny(), It.IsAny())).ReturnsAsync(new Site( "test", "NAME", "ADDRESS", "PHONENUMBER", "ODSCODE", "REGION", "ICB", "INFO", new List(), new Location("", [0, 0]), SiteStatus.Online, false, "TYPE", 14)); var result = await _sut.GetReferenceNumber("test"); + result.Should().Be("14-90-002345"); } } diff --git a/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs b/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs index 50a2cc5b1..4b3cad859 100644 --- a/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs +++ b/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs @@ -84,7 +84,7 @@ public async Task GetSiteByIdAsync_ReturnsDefault_WhenSiteIsNotFound(string scop const string siteId = "9a06bacd-e916-4c10-8263-21451ca751b8"; _siteStore.Setup(x => x.GetSiteById(siteId)).ReturnsAsync((Site)null!); - var result = await _sut.GetSiteByIdAsync(siteId, scope); + var result = await _sut.GetSiteByIdAsync(siteId, true, scope); result.Should().BeNull(); } From 608bbd32c02c09d772b2391544318ca6363aae7f Mon Sep 17 00:00:00 2001 From: pata9 Date: Wed, 22 Apr 2026 10:52:23 +0100 Subject: [PATCH 05/23] add prop to ignore cache on get site and update ui --- .../Functions/HttpFunctions/GetSiteFunction.cs | 2 +- .../Functions/HttpFunctions/SiteBasedResourceFunction.cs | 5 +++-- .../Nhs.Appointments.Api/Models/SiteBasedResourceRequest.cs | 2 +- src/client/src/app/lib/services/appointmentsService.ts | 3 ++- .../app/site/[site]/appointment/[reference]/cancel/page.tsx | 2 +- .../app/site/[site]/availability/cancel/confirmed/page.tsx | 2 +- src/client/src/app/site/[site]/availability/cancel/page.tsx | 2 +- .../[site]/availability/edit-services/confirmation/page.tsx | 2 +- .../[site]/availability/edit-services/confirmed/page.tsx | 2 +- .../availability/edit-services/no-notifications/page.tsx | 2 +- .../src/app/site/[site]/availability/edit-services/page.tsx | 2 +- .../app/site/[site]/availability/edit/confirmation/page.tsx | 2 +- .../src/app/site/[site]/availability/edit/confirmed/page.tsx | 2 +- .../site/[site]/availability/edit/edit-start-time/page.tsx | 2 +- .../site/[site]/availability/edit/no-notifications/page.tsx | 2 +- src/client/src/app/site/[site]/availability/edit/page.tsx | 2 +- .../site/[site]/cancel-day/cancelled-appointments/page.tsx | 2 +- src/client/src/app/site/[site]/cancel-day/confirmed/page.tsx | 2 +- src/client/src/app/site/[site]/cancel-day/page.tsx | 2 +- src/client/src/app/site/[site]/create-availability/page.tsx | 2 +- .../src/app/site/[site]/create-availability/wizard/page.tsx | 2 +- .../edit-accessibilities/edit-accessibilities-page.tsx | 2 +- .../site/[site]/details/edit-details/edit-details-page.tsx | 2 +- .../edit-reference-details/edit-reference-details-page.tsx | 2 +- .../details/edit-site-status/edit-site-status-page.tsx | 2 +- src/client/src/app/site/[site]/details/page.tsx | 2 +- src/client/src/app/site/[site]/details/site-details-page.tsx | 2 +- src/client/src/app/site/[site]/page.tsx | 2 +- src/client/src/app/site/[site]/users/manage/page.tsx | 2 +- src/client/src/app/site/[site]/users/page.tsx | 2 +- src/client/src/app/site/[site]/users/remove/page.tsx | 2 +- .../(nhs-layout)/daily-appointments/page.tsx | 2 +- .../site/[site]/view-availability/(nhs-layout)/layout.tsx | 2 +- .../app/site/[site]/view-availability/(nhs-layout)/page.tsx | 2 +- .../site/[site]/view-availability/(nhs-layout)/week/page.tsx | 2 +- .../(transactional-layout)/week/edit-session/page.tsx | 2 +- .../Validators/SiteBasedResourceRequestValidatorTests.cs | 4 ++-- 37 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/GetSiteFunction.cs b/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/GetSiteFunction.cs index 67d44fcb5..8c2a282e0 100644 --- a/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/GetSiteFunction.cs +++ b/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/GetSiteFunction.cs @@ -47,7 +47,7 @@ public override Task RunAsync( protected override async Task> HandleRequest(SiteBasedResourceRequest request, ILogger logger) { - var site = await siteService.GetSiteByIdAsync(request.Site, false, request.Scope); + var site = await siteService.GetSiteByIdAsync(request.Site, request.IgnoreCache, request.Scope); if (site != null) { return ApiResult.Success(site); diff --git a/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/SiteBasedResourceFunction.cs b/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/SiteBasedResourceFunction.cs index 78f98f77c..d2fa957b1 100644 --- a/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/SiteBasedResourceFunction.cs +++ b/src/api/Nhs.Appointments.Api/Functions/HttpFunctions/SiteBasedResourceFunction.cs @@ -15,14 +15,15 @@ public abstract class SiteBasedResourceFunction(IValidator errors, SiteBasedResourceRequest request)> ReadRequestAsync(HttpRequest req) { var requestedScope = req.Query.Keys.Contains("scope") ? req.Query["scope"].ToString() : "*"; + var requestedIgnoreCache = req.Query.Keys.Contains("ignoreCache") && bool.Parse(req.Query["ignoreCache"]); if (req.Query.Keys.Contains("site")) { var site = req.Query["site"]; - return Task.FromResult((ErrorMessageResponseItem.None, new SiteBasedResourceRequest(site, requestedScope))); + return Task.FromResult((ErrorMessageResponseItem.None, new SiteBasedResourceRequest(site, requestedIgnoreCache, requestedScope))); } var siteId = RestUriHelper.GetResourceIdFromPath(req.Path.ToUriComponent(), "sites"); - return Task.FromResult((ErrorMessageResponseItem.None, new SiteBasedResourceRequest(siteId, requestedScope))); + return Task.FromResult((ErrorMessageResponseItem.None, new SiteBasedResourceRequest(siteId, requestedIgnoreCache, requestedScope))); } } diff --git a/src/api/Nhs.Appointments.Api/Models/SiteBasedResourceRequest.cs b/src/api/Nhs.Appointments.Api/Models/SiteBasedResourceRequest.cs index 3229c5304..500574f14 100644 --- a/src/api/Nhs.Appointments.Api/Models/SiteBasedResourceRequest.cs +++ b/src/api/Nhs.Appointments.Api/Models/SiteBasedResourceRequest.cs @@ -1,3 +1,3 @@ namespace Nhs.Appointments.Api.Models; -public record SiteBasedResourceRequest(string Site, string Scope); +public record SiteBasedResourceRequest(string Site, bool IgnoreCache, string Scope); diff --git a/src/client/src/app/lib/services/appointmentsService.ts b/src/client/src/app/lib/services/appointmentsService.ts index 7bacfa5a5..585be8bb3 100644 --- a/src/client/src/app/lib/services/appointmentsService.ts +++ b/src/client/src/app/lib/services/appointmentsService.ts @@ -141,9 +141,10 @@ export const fetchUsers = async ( export const fetchSite = async ( siteId: string, + ignoreCache: boolean, ): Promise> => appointmentsApi - .get(`sites/${siteId}?scope=*`, { + .get(`sites/${siteId}?ignoreCache=${ignoreCache}&scope=*`, { next: { tags: ['site'] }, }) .then(response => handleBodyResponse(response)); diff --git a/src/client/src/app/site/[site]/appointment/[reference]/cancel/page.tsx b/src/client/src/app/site/[site]/appointment/[reference]/cancel/page.tsx index 9c593fbd6..6e9317dd7 100644 --- a/src/client/src/app/site/[site]/appointment/[reference]/cancel/page.tsx +++ b/src/client/src/app/site/[site]/appointment/[reference]/cancel/page.tsx @@ -24,7 +24,7 @@ const Page = async ({ params }: PageProps) => { await fromServer(assertPermission(siteFromPath, 'booking:cancel')); const [site, booking, clinicalServices] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchBooking(reference, siteFromPath)), fromServer(fetchClinicalServices()), ]); diff --git a/src/client/src/app/site/[site]/availability/cancel/confirmed/page.tsx b/src/client/src/app/site/[site]/availability/cancel/confirmed/page.tsx index b748f3b62..53c0fd897 100644 --- a/src/client/src/app/site/[site]/availability/cancel/confirmed/page.tsx +++ b/src/client/src/app/site/[site]/availability/cancel/confirmed/page.tsx @@ -49,7 +49,7 @@ const Page = async ({ searchParams, params }: PageProps) => { } const [site, clinicalServices] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchClinicalServices()), ]); diff --git a/src/client/src/app/site/[site]/availability/cancel/page.tsx b/src/client/src/app/site/[site]/availability/cancel/page.tsx index 89f7d5c8c..e0bd7b244 100644 --- a/src/client/src/app/site/[site]/availability/cancel/page.tsx +++ b/src/client/src/app/site/[site]/availability/cancel/page.tsx @@ -30,7 +30,7 @@ const Page = async ({ searchParams, params }: PageProps) => { await fromServer(assertPermission(siteFromPath, 'availability:setup')); const [site, clinicalServices] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchClinicalServices()), ]); diff --git a/src/client/src/app/site/[site]/availability/edit-services/confirmation/page.tsx b/src/client/src/app/site/[site]/availability/edit-services/confirmation/page.tsx index e2c29b3e6..32674daa8 100644 --- a/src/client/src/app/site/[site]/availability/edit-services/confirmation/page.tsx +++ b/src/client/src/app/site/[site]/availability/edit-services/confirmation/page.tsx @@ -47,7 +47,7 @@ const Page = async ({ searchParams, params }: PageProps) => { await assertPermission(siteFromPath, 'availability:setup'); const parsedDate = parseToUkDatetime(date); - const site = await fromServer(fetchSite(siteFromPath)); + const site = await fromServer(fetchSite(siteFromPath, false)); const sessionSummary: SessionSummary = JSON.parse(atob(session)); const removedServicesSessionDetails: AvailabilitySession = JSON.parse( atob(removedServicesSession), diff --git a/src/client/src/app/site/[site]/availability/edit-services/confirmed/page.tsx b/src/client/src/app/site/[site]/availability/edit-services/confirmed/page.tsx index 4273ce113..682f1f866 100644 --- a/src/client/src/app/site/[site]/availability/edit-services/confirmed/page.tsx +++ b/src/client/src/app/site/[site]/availability/edit-services/confirmed/page.tsx @@ -45,7 +45,7 @@ const Page = async ({ searchParams, params }: PageProps) => { await fromServer(assertPermission(siteFromPath, 'availability:setup')); const [site, clinicalServices] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchClinicalServices()), ]); diff --git a/src/client/src/app/site/[site]/availability/edit-services/no-notifications/page.tsx b/src/client/src/app/site/[site]/availability/edit-services/no-notifications/page.tsx index 3cb0e97cc..b5c2f1cad 100644 --- a/src/client/src/app/site/[site]/availability/edit-services/no-notifications/page.tsx +++ b/src/client/src/app/site/[site]/availability/edit-services/no-notifications/page.tsx @@ -43,7 +43,7 @@ const Page = async ({ params, searchParams }: PageProps) => { }; const [site, cancelledBookings, clinicalServices] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchBookings(fetchBookingsRequest, ['Cancelled'])), fromServer(fetchClinicalServices()), ]); diff --git a/src/client/src/app/site/[site]/availability/edit-services/page.tsx b/src/client/src/app/site/[site]/availability/edit-services/page.tsx index 198cf5888..1fda3dcf1 100644 --- a/src/client/src/app/site/[site]/availability/edit-services/page.tsx +++ b/src/client/src/app/site/[site]/availability/edit-services/page.tsx @@ -31,7 +31,7 @@ const Page = async ({ searchParams, params }: PageProps) => { await fromServer(assertPermission(siteFromPath, 'availability:setup')); const [site, clinicalServices] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchClinicalServices()), ]); diff --git a/src/client/src/app/site/[site]/availability/edit/confirmation/page.tsx b/src/client/src/app/site/[site]/availability/edit/confirmation/page.tsx index 732c60291..715609648 100644 --- a/src/client/src/app/site/[site]/availability/edit/confirmation/page.tsx +++ b/src/client/src/app/site/[site]/availability/edit/confirmation/page.tsx @@ -45,7 +45,7 @@ const Page = async ({ searchParams, params }: PageProps) => { await assertPermission(siteFromPath, 'availability:setup'); const parsedDate = parseToUkDatetime(date); - const site = await fromServer(fetchSite(siteFromPath)); + const site = await fromServer(fetchSite(siteFromPath, false)); const sessionSummary: SessionSummary = JSON.parse(atob(session)); const newSessionDetails: Session = JSON.parse(atob(sessionToEdit)); const newSessionSummary: SessionSummary = { diff --git a/src/client/src/app/site/[site]/availability/edit/confirmed/page.tsx b/src/client/src/app/site/[site]/availability/edit/confirmed/page.tsx index cda2f5f85..acfcd1f2b 100644 --- a/src/client/src/app/site/[site]/availability/edit/confirmed/page.tsx +++ b/src/client/src/app/site/[site]/availability/edit/confirmed/page.tsx @@ -46,7 +46,7 @@ const Page = async ({ searchParams, params }: PageProps) => { await fromServer(assertPermission(siteFromPath, 'availability:setup')); const [site, clinicalServices] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchClinicalServices()), ]); diff --git a/src/client/src/app/site/[site]/availability/edit/edit-start-time/page.tsx b/src/client/src/app/site/[site]/availability/edit/edit-start-time/page.tsx index b40cca3dd..fd3710fac 100644 --- a/src/client/src/app/site/[site]/availability/edit/edit-start-time/page.tsx +++ b/src/client/src/app/site/[site]/availability/edit/edit-start-time/page.tsx @@ -32,7 +32,7 @@ const Page = async ({ searchParams, params }: PageProps) => { await fromServer(assertPermission(siteFromPath, 'availability:setup')); const parsedDate = parseToUkDatetime(date); - const site = await fromServer(fetchSite(siteFromPath)); + const site = await fromServer(fetchSite(siteFromPath, false)); const existing: SessionSummary = JSON.parse(atob(existingSession)); const updated: AvailabilitySession = JSON.parse(atob(updatedSession)); diff --git a/src/client/src/app/site/[site]/availability/edit/no-notifications/page.tsx b/src/client/src/app/site/[site]/availability/edit/no-notifications/page.tsx index 3cb0e97cc..b5c2f1cad 100644 --- a/src/client/src/app/site/[site]/availability/edit/no-notifications/page.tsx +++ b/src/client/src/app/site/[site]/availability/edit/no-notifications/page.tsx @@ -43,7 +43,7 @@ const Page = async ({ params, searchParams }: PageProps) => { }; const [site, cancelledBookings, clinicalServices] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchBookings(fetchBookingsRequest, ['Cancelled'])), fromServer(fetchClinicalServices()), ]); diff --git a/src/client/src/app/site/[site]/availability/edit/page.tsx b/src/client/src/app/site/[site]/availability/edit/page.tsx index f8fc9eb27..99cfe9735 100644 --- a/src/client/src/app/site/[site]/availability/edit/page.tsx +++ b/src/client/src/app/site/[site]/availability/edit/page.tsx @@ -26,7 +26,7 @@ const Page = async ({ searchParams, params }: PageProps) => { await fromServer(assertPermission(siteFromPath, 'availability:setup')); const parsedDate = parseToUkDatetime(date); - const site = await fromServer(fetchSite(siteFromPath)); + const site = await fromServer(fetchSite(siteFromPath, false)); const sessionSummary: SessionSummary = JSON.parse(atob(session)); diff --git a/src/client/src/app/site/[site]/cancel-day/cancelled-appointments/page.tsx b/src/client/src/app/site/[site]/cancel-day/cancelled-appointments/page.tsx index f061cc054..445b84d76 100644 --- a/src/client/src/app/site/[site]/cancel-day/cancelled-appointments/page.tsx +++ b/src/client/src/app/site/[site]/cancel-day/cancelled-appointments/page.tsx @@ -45,7 +45,7 @@ const Page = async ({ params, searchParams }: PageProps) => { }; const [site, bookings, clinicalServices] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchBookings(fetchBookingsRequest, ['Cancelled'])), fromServer(fetchClinicalServices()), ]); diff --git a/src/client/src/app/site/[site]/cancel-day/confirmed/page.tsx b/src/client/src/app/site/[site]/cancel-day/confirmed/page.tsx index afece712c..822f46c49 100644 --- a/src/client/src/app/site/[site]/cancel-day/confirmed/page.tsx +++ b/src/client/src/app/site/[site]/cancel-day/confirmed/page.tsx @@ -28,7 +28,7 @@ const Page = async ({ searchParams, params }: PageProps) => { notFound(); } - const site = await fromServer(fetchSite(siteFromPath)); + const site = await fromServer(fetchSite(siteFromPath, false)); const dayCancellationSummary: CancelDayResponse = { cancelledBookingCount: cancelledBookingCount ?? 0, diff --git a/src/client/src/app/site/[site]/cancel-day/page.tsx b/src/client/src/app/site/[site]/cancel-day/page.tsx index f468055dc..569f7809f 100644 --- a/src/client/src/app/site/[site]/cancel-day/page.tsx +++ b/src/client/src/app/site/[site]/cancel-day/page.tsx @@ -30,7 +30,7 @@ const Page = async ({ searchParams, params }: PageProps) => { const [daySummary, site, clinicalServices] = await Promise.all([ fromServer(fetchDaySummary(siteFromPath, date)), - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchClinicalServices()), ]); diff --git a/src/client/src/app/site/[site]/create-availability/page.tsx b/src/client/src/app/site/[site]/create-availability/page.tsx index 6c7f58dad..abfc304e0 100644 --- a/src/client/src/app/site/[site]/create-availability/page.tsx +++ b/src/client/src/app/site/[site]/create-availability/page.tsx @@ -18,7 +18,7 @@ const Page = async ({ params }: PageProps) => { const { site: siteFromPath } = { ...(await params) }; const [site, cancelADateRange, sitePermissions] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchFeatureFlag('CancelADateRange')), fromServer(fetchPermissions(siteFromPath)), ]); diff --git a/src/client/src/app/site/[site]/create-availability/wizard/page.tsx b/src/client/src/app/site/[site]/create-availability/wizard/page.tsx index fac9de21e..d893ceeed 100644 --- a/src/client/src/app/site/[site]/create-availability/wizard/page.tsx +++ b/src/client/src/app/site/[site]/create-availability/wizard/page.tsx @@ -23,7 +23,7 @@ const Page = async ({ params, searchParams }: PageProps) => { await fromServer(assertPermission(siteFromPath, 'availability:setup')); const [site, clinicalServices] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchClinicalServices()), ]); diff --git a/src/client/src/app/site/[site]/details/edit-accessibilities/edit-accessibilities-page.tsx b/src/client/src/app/site/[site]/details/edit-accessibilities/edit-accessibilities-page.tsx index 688cf13e3..75add7b20 100644 --- a/src/client/src/app/site/[site]/details/edit-accessibilities/edit-accessibilities-page.tsx +++ b/src/client/src/app/site/[site]/details/edit-accessibilities/edit-accessibilities-page.tsx @@ -17,7 +17,7 @@ const EditAccessibilitiesPage = async ({ site }: Props) => { const accessibilityAccessibilityDefinitions = AccessibilityDefinitions.filter( ad => ad.id.startsWith('accessibility'), ); - const siteDetails = await fromServer(fetchSite(site)); + const siteDetails = await fromServer(fetchSite(site, true)); return ( <> diff --git a/src/client/src/app/site/[site]/details/edit-details/edit-details-page.tsx b/src/client/src/app/site/[site]/details/edit-details/edit-details-page.tsx index 9f979df20..1c4a49c41 100644 --- a/src/client/src/app/site/[site]/details/edit-details/edit-details-page.tsx +++ b/src/client/src/app/site/[site]/details/edit-details/edit-details-page.tsx @@ -8,7 +8,7 @@ type Props = { }; export const EditDetailsPage = async ({ siteId }: Props) => { - const siteDetails = await fromServer(fetchSite(siteId)); + const siteDetails = await fromServer(fetchSite(siteId, true)); return ( <> diff --git a/src/client/src/app/site/[site]/details/edit-reference-details/edit-reference-details-page.tsx b/src/client/src/app/site/[site]/details/edit-reference-details/edit-reference-details-page.tsx index 38a9d2cdb..9c44475b4 100644 --- a/src/client/src/app/site/[site]/details/edit-reference-details/edit-reference-details-page.tsx +++ b/src/client/src/app/site/[site]/details/edit-reference-details/edit-reference-details-page.tsx @@ -12,7 +12,7 @@ type Props = { export const EditReferenceDetailsPage = async ({ siteId }: Props) => { const [siteDetails, wellKnownOdsCodeEntries] = await Promise.all([ - fromServer(fetchSite(siteId)), + fromServer(fetchSite(siteId, true)), fromServer(fetchWellKnownOdsCodeEntries()), ]); diff --git a/src/client/src/app/site/[site]/details/edit-site-status/edit-site-status-page.tsx b/src/client/src/app/site/[site]/details/edit-site-status/edit-site-status-page.tsx index 956651e8d..a0cf8cab1 100644 --- a/src/client/src/app/site/[site]/details/edit-site-status/edit-site-status-page.tsx +++ b/src/client/src/app/site/[site]/details/edit-site-status/edit-site-status-page.tsx @@ -9,7 +9,7 @@ type Props = { }; export const EditSiteStatusPage = async ({ siteId }: Props) => { - const siteDetails = await fromServer(fetchSite(siteId)); + const siteDetails = await fromServer(fetchSite(siteId, true)); const siteStatus = siteDetails.status; const summaryList: SummaryListItem[] = [ diff --git a/src/client/src/app/site/[site]/details/page.tsx b/src/client/src/app/site/[site]/details/page.tsx index c64d5d595..cfb961744 100644 --- a/src/client/src/app/site/[site]/details/page.tsx +++ b/src/client/src/app/site/[site]/details/page.tsx @@ -22,7 +22,7 @@ const Page = async ({ params }: PageProps) => { ); const [site, wellKnownOdsCodeEntries, sitePermissions] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchWellKnownOdsCodeEntries()), fromServer(fetchPermissions(siteFromPath)), ]); diff --git a/src/client/src/app/site/[site]/details/site-details-page.tsx b/src/client/src/app/site/[site]/details/site-details-page.tsx index ee4d39d5a..eef8312e2 100644 --- a/src/client/src/app/site/[site]/details/site-details-page.tsx +++ b/src/client/src/app/site/[site]/details/site-details-page.tsx @@ -23,7 +23,7 @@ const SiteDetailsPage = async ({ }: Props) => { const [accessibilityDefinitions, site] = await Promise.all([ fromServer(fetchAccessibilityDefinitions()), - fromServer(fetchSite(siteId)), + fromServer(fetchSite(siteId, true)), ]); const siteReferenceSummaryData = mapSiteReferenceSummaryData( diff --git a/src/client/src/app/site/[site]/page.tsx b/src/client/src/app/site/[site]/page.tsx index 5fab418ad..db4038033 100644 --- a/src/client/src/app/site/[site]/page.tsx +++ b/src/client/src/app/site/[site]/page.tsx @@ -29,7 +29,7 @@ const Page = async ({ params }: PageProps) => { const [site, wellKnownOdsCodeEntries, sitePermissions, permissionsAtAnySite] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, true)), fromServer(fetchWellKnownOdsCodeEntries()), fromServer(fetchPermissions(siteFromPath)), fromServer(fetchPermissions('*')), diff --git a/src/client/src/app/site/[site]/users/manage/page.tsx b/src/client/src/app/site/[site]/users/manage/page.tsx index 9d8c2c395..5a80a7502 100644 --- a/src/client/src/app/site/[site]/users/manage/page.tsx +++ b/src/client/src/app/site/[site]/users/manage/page.tsx @@ -28,7 +28,7 @@ const AssignRolesPage = async ({ params, searchParams }: UserPageProps) => { const email = userFromParams?.toLowerCase(); const [site, userProfile, roleOptions, userToEdit] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchUserProfile()), fromServer(fetchRoles()), fromServer(fetchUsers(siteFromPath)).then(users => diff --git a/src/client/src/app/site/[site]/users/page.tsx b/src/client/src/app/site/[site]/users/page.tsx index a88c2c146..915b0bdeb 100644 --- a/src/client/src/app/site/[site]/users/page.tsx +++ b/src/client/src/app/site/[site]/users/page.tsx @@ -28,7 +28,7 @@ const Page = async ({ params }: PageProps) => { fromServer(fetchUserProfile()), fromServer(fetchUsers(siteFromPath)), fromServer(fetchRoles()), - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchPermissions(siteFromPath)), ]); diff --git a/src/client/src/app/site/[site]/users/remove/page.tsx b/src/client/src/app/site/[site]/users/remove/page.tsx index 9545c0013..02c395795 100644 --- a/src/client/src/app/site/[site]/users/remove/page.tsx +++ b/src/client/src/app/site/[site]/users/remove/page.tsx @@ -30,7 +30,7 @@ const Page = async ({ params, searchParams }: UserPageProps) => { await fromServer(assertPermission(siteFromPath, 'users:manage')); const [site, users, userProfile] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchUsers(siteFromPath)), fromServer(fetchUserProfile()), ]); diff --git a/src/client/src/app/site/[site]/view-availability/(nhs-layout)/daily-appointments/page.tsx b/src/client/src/app/site/[site]/view-availability/(nhs-layout)/daily-appointments/page.tsx index c23f8b354..7976faa34 100644 --- a/src/client/src/app/site/[site]/view-availability/(nhs-layout)/daily-appointments/page.tsx +++ b/src/client/src/app/site/[site]/view-availability/(nhs-layout)/daily-appointments/page.tsx @@ -50,7 +50,7 @@ const Page = async ({ params, searchParams }: PageProps) => { const [site, bookings, clinicalServices, cancelADateRange, sitePermissions] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchBookings(fetchBookingsRequest, ['Booked', 'Cancelled'])), fromServer(fetchClinicalServices()), fromServer(fetchFeatureFlag('CancelADateRange')), diff --git a/src/client/src/app/site/[site]/view-availability/(nhs-layout)/layout.tsx b/src/client/src/app/site/[site]/view-availability/(nhs-layout)/layout.tsx index 6fa673b24..866dbe007 100644 --- a/src/client/src/app/site/[site]/view-availability/(nhs-layout)/layout.tsx +++ b/src/client/src/app/site/[site]/view-availability/(nhs-layout)/layout.tsx @@ -12,7 +12,7 @@ type LayoutProps = { export default async function Layout({ children, params }: LayoutProps) { const { site: siteFromPath } = { ...(await params) }; - const site = await fromServer(fetchSite(siteFromPath)); + const site = await fromServer(fetchSite(siteFromPath, false)); return ( { const [cancelADateRange, site, sitePermissions] = await Promise.all([ fromServer(fetchFeatureFlag('CancelADateRange')), - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchPermissions(siteFromPath)), ]); diff --git a/src/client/src/app/site/[site]/view-availability/(nhs-layout)/week/page.tsx b/src/client/src/app/site/[site]/view-availability/(nhs-layout)/week/page.tsx index 02d8b0b68..78cad52d0 100644 --- a/src/client/src/app/site/[site]/view-availability/(nhs-layout)/week/page.tsx +++ b/src/client/src/app/site/[site]/view-availability/(nhs-layout)/week/page.tsx @@ -32,7 +32,7 @@ const Page = async ({ searchParams, params }: PageProps) => { const [cancelADateRange, site, sitePermissions] = await Promise.all([ fromServer(fetchFeatureFlag('CancelADateRange')), - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchPermissions(siteFromPath)), ]); diff --git a/src/client/src/app/site/[site]/view-availability/(transactional-layout)/week/edit-session/page.tsx b/src/client/src/app/site/[site]/view-availability/(transactional-layout)/week/edit-session/page.tsx index f56728e86..5f7143005 100644 --- a/src/client/src/app/site/[site]/view-availability/(transactional-layout)/week/edit-session/page.tsx +++ b/src/client/src/app/site/[site]/view-availability/(transactional-layout)/week/edit-session/page.tsx @@ -29,7 +29,7 @@ const Page = async ({ searchParams, params }: PageProps) => { await fromServer(assertPermission(siteFromPath, 'availability:setup')); const [site, clinicalServices] = await Promise.all([ - fromServer(fetchSite(siteFromPath)), + fromServer(fetchSite(siteFromPath, false)), fromServer(fetchClinicalServices()), ]); diff --git a/tests/Nhs.Appointments.Api.UnitTests/Validators/SiteBasedResourceRequestValidatorTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Validators/SiteBasedResourceRequestValidatorTests.cs index 12a48c6d2..50e74648e 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Validators/SiteBasedResourceRequestValidatorTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Validators/SiteBasedResourceRequestValidatorTests.cs @@ -11,7 +11,7 @@ public class SiteBasedRequestValidatorTests [Fact] public void Validate_ReturnsError_WhenSiteIsBlank() { - var testRequest = new SiteBasedResourceRequest("", "*"); + var testRequest = new SiteBasedResourceRequest("", false,"*"); var result = _sut.Validate(testRequest); result.IsValid.Should().BeFalse(); result.Errors.Should().HaveCount(1); @@ -21,7 +21,7 @@ public void Validate_ReturnsError_WhenSiteIsBlank() [Fact] public void Validate_ReturnsSuccess_WhenSiteBasedRequestIsValid() { - var testRequest = new SiteBasedResourceRequest("site", "*"); + var testRequest = new SiteBasedResourceRequest("", false,"*"); var result = _sut.Validate(testRequest); result.IsValid.Should().BeTrue(); result.Errors.Should().HaveCount(0); From e5231d5afb8ba0e053264da7c8402ed7b0f5501c Mon Sep 17 00:00:00 2001 From: pata9 Date: Wed, 22 Apr 2026 10:53:19 +0100 Subject: [PATCH 06/23] add prop to ignore cache on get site and update ui --- .../app/site/[site]/availability/cancel/confirmation/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/src/app/site/[site]/availability/cancel/confirmation/page.tsx b/src/client/src/app/site/[site]/availability/cancel/confirmation/page.tsx index b7a4fa943..a1eaf490f 100644 --- a/src/client/src/app/site/[site]/availability/cancel/confirmation/page.tsx +++ b/src/client/src/app/site/[site]/availability/cancel/confirmation/page.tsx @@ -30,7 +30,7 @@ const Page = async ({ searchParams, params }: PageProps) => { await assertPermission(siteFromPath, 'availability:setup'); const parsedDate = parseToUkDatetime(date); - const site = await fromServer(fetchSite(siteFromPath)); + const site = await fromServer(fetchSite(siteFromPath, false)); const sessionSummary: SessionSummary = JSON.parse(atob(session)); const availabilityRequest: AvailabilityChangeProposalRequest = { from: date, From 3ee288be2b3b76013fa34f2f542ac0015d0ec362 Mon Sep 17 00:00:00 2001 From: pata9 Date: Wed, 22 Apr 2026 11:58:30 +0100 Subject: [PATCH 07/23] add new store with migration to new container --- .../IBookingReferenceNumberDocumentStore.cs | 7 +++ .../Bookings/ReferenceNumberProvider.cs | 6 +- ...ookingReferenceGroupCosmosDocumentStore.cs | 62 +++++++++++++++++++ ... CoreReferenceGroupCosmosDocumentStore.cs} | 46 ++++++++------ ...reReferenceMigrationNumberDocumentStore.cs | 8 +++ .../Models/GeneralCosmosDocument.cs | 6 ++ .../Models/ReferenceGroupDocument.cs | 8 ++- .../ServiceRegistration.cs | 4 +- .../SiteBasedResourceRequestValidatorTests.cs | 2 +- .../ReferenceNumberProviderTests.cs | 2 +- 10 files changed, 125 insertions(+), 26 deletions(-) create mode 100644 src/api/Nhs.Appointments.Core/Bookings/IBookingReferenceNumberDocumentStore.cs create mode 100644 src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs rename src/api/Nhs.Appointments.Persistance/{ReferenceGroupCosmosDocumentStore.cs => CoreReferenceGroupCosmosDocumentStore.cs} (72%) create mode 100644 src/api/Nhs.Appointments.Persistance/ICoreReferenceMigrationNumberDocumentStore.cs diff --git a/src/api/Nhs.Appointments.Core/Bookings/IBookingReferenceNumberDocumentStore.cs b/src/api/Nhs.Appointments.Core/Bookings/IBookingReferenceNumberDocumentStore.cs new file mode 100644 index 000000000..fa533bf8b --- /dev/null +++ b/src/api/Nhs.Appointments.Core/Bookings/IBookingReferenceNumberDocumentStore.cs @@ -0,0 +1,7 @@ +namespace Nhs.Appointments.Core.Bookings; + +public interface IBookingReferenceNumberDocumentStore +{ + Task AssignReferenceGroup(); + Task GetNextSequenceNumber(int prefix); +} diff --git a/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs b/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs index 57db0aab2..3f6eceff9 100644 --- a/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs +++ b/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs @@ -12,12 +12,12 @@ public class ReferenceNumberProvider : IReferenceNumberProvider { private readonly ISiteService _siteService; private readonly ICacheService _cacheService; - private readonly IReferenceNumberDocumentStore _referenceNumberDocumentStore; + private readonly IBookingReferenceNumberDocumentStore _referenceNumberDocumentStore; private readonly TimeProvider _timeProvider; public ReferenceNumberProvider( ISiteService siteService, ICacheService cacheService, - IReferenceNumberDocumentStore referenceNumberDocumentStore, + IBookingReferenceNumberDocumentStore referenceNumberDocumentStore, TimeProvider timeProvider) { _siteService = siteService; @@ -59,7 +59,7 @@ private async Task GetSitesAssignedReferenceGroup(string siteId) private static bool SiteHasNotBeenAssignedReferenceGroup(Site site) => site.ReferenceNumberGroup == 0; } -public interface IReferenceNumberDocumentStore +public interface ICoreReferenceNumberDocumentStore { Task AssignReferenceGroup(); Task GetNextSequenceNumber(int prefix); diff --git a/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs new file mode 100644 index 000000000..9aec46537 --- /dev/null +++ b/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs @@ -0,0 +1,62 @@ +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.Options; +using Nhs.Appointments.Core.Bookings; +using Nhs.Appointments.Persistance.Models; + +namespace Nhs.Appointments.Persistance +{ + public class BookingReferenceGroupCosmosDocumentStore : IBookingReferenceNumberDocumentStore + { + private const string DocumentId = "main"; + private readonly ITypedDocumentCosmosStore _cosmosStore; + private readonly ICoreReferenceMigrationNumberDocumentStore _migrationDocumentStore; + private readonly ReferenceGroupOptions _options; + + public BookingReferenceGroupCosmosDocumentStore( + ITypedDocumentCosmosStore cosmosStore, + ICoreReferenceMigrationNumberDocumentStore migrationDocumentStore, + IOptions options) + { + _cosmosStore = cosmosStore; + _migrationDocumentStore = migrationDocumentStore; + _options = options.Value; + } + + public async Task AssignReferenceGroup() + { + BookingReferenceGroupDocument referenceGroupDocument; + var docType = _cosmosStore.GetDocumentType(); + try + { + referenceGroupDocument = await _cosmosStore.GetByIdAsync("main"); + } + catch(CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + { + var oldDocument = await _migrationDocumentStore.Get(); + + referenceGroupDocument = new BookingReferenceGroupDocument + { + DocumentType = docType, + Id = DocumentId, + Groups = oldDocument.Groups, + }; + + await _cosmosStore.WriteAsync(referenceGroupDocument); + } + + var target = referenceGroupDocument!.Groups.Where(g => g.Prefix > 0).OrderBy(g => g.SiteCount).ThenBy(g => g.Prefix).First(); + var siteCountIncrement = PatchOperation.Increment($"/Groups/{target.Prefix}/SiteCount", 1); + + await _cosmosStore.PatchDocument(docType, DocumentId, siteCountIncrement); + return target.Prefix; + } + + public async Task GetNextSequenceNumber(int prefix) + { + var incrementSequencePatch = PatchOperation.Increment($"/Groups/{prefix}/Sequence", 1); + var docType = _cosmosStore.GetDocumentType(); + var referenceGroupDocument = await _cosmosStore.PatchDocument(docType, DocumentId, incrementSequencePatch); + return referenceGroupDocument.Groups.Single(gr => gr.Prefix == prefix).Sequence; + } + } +} diff --git a/src/api/Nhs.Appointments.Persistance/ReferenceGroupCosmosDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/CoreReferenceGroupCosmosDocumentStore.cs similarity index 72% rename from src/api/Nhs.Appointments.Persistance/ReferenceGroupCosmosDocumentStore.cs rename to src/api/Nhs.Appointments.Persistance/CoreReferenceGroupCosmosDocumentStore.cs index f8e55c6ea..144b5d540 100644 --- a/src/api/Nhs.Appointments.Persistance/ReferenceGroupCosmosDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/CoreReferenceGroupCosmosDocumentStore.cs @@ -5,13 +5,13 @@ namespace Nhs.Appointments.Persistance { - public class ReferenceGroupCosmosDocumentStore : IReferenceNumberDocumentStore + public class CoreCoreReferenceGroupCosmosDocumentStore : ICoreReferenceNumberDocumentStore, ICoreReferenceMigrationNumberDocumentStore { private const string DocumentId = "main"; - private readonly ITypedDocumentCosmosStore _cosmosStore; + private readonly ITypedDocumentCosmosStore _cosmosStore; private readonly ReferenceGroupOptions _options; - public ReferenceGroupCosmosDocumentStore(ITypedDocumentCosmosStore cosmosStore, IOptions options) + public CoreCoreReferenceGroupCosmosDocumentStore(ITypedDocumentCosmosStore cosmosStore, IOptions options) { _cosmosStore = cosmosStore; _options = options.Value; @@ -19,15 +19,35 @@ public ReferenceGroupCosmosDocumentStore(ITypedDocumentCosmosStore AssignReferenceGroup() { - ReferenceGroupDocument referenceGroupDocument; + var docType = _cosmosStore.GetDocumentType(); + CoreReferenceGroupDocument referenceGroupDocument = await Get(); + + var target = referenceGroupDocument!.Groups.Where(g => g.Prefix > 0).OrderBy(g => g.SiteCount).ThenBy(g => g.Prefix).First(); + var siteCountIncrement = PatchOperation.Increment($"/Groups/{target.Prefix}/SiteCount", 1); + + await _cosmosStore.PatchDocument(docType, DocumentId, siteCountIncrement); + return target.Prefix; + } + + public async Task GetNextSequenceNumber(int prefix) + { + var incrementSequencePatch = PatchOperation.Increment($"/Groups/{prefix}/Sequence", 1); + var docType = _cosmosStore.GetDocumentType(); + var referenceGroupDocument = await _cosmosStore.PatchDocument(docType, DocumentId, incrementSequencePatch); + return referenceGroupDocument.Groups.Single(gr => gr.Prefix == prefix).Sequence; + } + + public async Task Get() + { + CoreReferenceGroupDocument referenceGroupDocument; var docType = _cosmosStore.GetDocumentType(); try { - referenceGroupDocument = await _cosmosStore.GetByIdAsync("main"); + referenceGroupDocument = await _cosmosStore.GetByIdAsync("main"); } catch(CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) { - referenceGroupDocument = new ReferenceGroupDocument + referenceGroupDocument = new CoreReferenceGroupDocument { DocumentType = docType, Id = DocumentId, @@ -42,19 +62,7 @@ public async Task AssignReferenceGroup() await _cosmosStore.WriteAsync(referenceGroupDocument); } - var target = referenceGroupDocument!.Groups.Where(g => g.Prefix > 0).OrderBy(g => g.SiteCount).ThenBy(g => g.Prefix).First(); - var siteCountIncrement = PatchOperation.Increment($"/Groups/{target.Prefix}/SiteCount", 1); - - await _cosmosStore.PatchDocument(docType, DocumentId, siteCountIncrement); - return target.Prefix; - } - - public async Task GetNextSequenceNumber(int prefix) - { - var incrementSequencePatch = PatchOperation.Increment($"/Groups/{prefix}/Sequence", 1); - var docType = _cosmosStore.GetDocumentType(); - var referenceGroupDocument = await _cosmosStore.PatchDocument(docType, DocumentId, incrementSequencePatch); - return referenceGroupDocument.Groups.Single(gr => gr.Prefix == prefix).Sequence; + return referenceGroupDocument; } } diff --git a/src/api/Nhs.Appointments.Persistance/ICoreReferenceMigrationNumberDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/ICoreReferenceMigrationNumberDocumentStore.cs new file mode 100644 index 000000000..08f34145b --- /dev/null +++ b/src/api/Nhs.Appointments.Persistance/ICoreReferenceMigrationNumberDocumentStore.cs @@ -0,0 +1,8 @@ +using Nhs.Appointments.Persistance.Models; + +namespace Nhs.Appointments.Persistance; + +public interface ICoreReferenceMigrationNumberDocumentStore +{ + Task Get(); +} diff --git a/src/api/Nhs.Appointments.Persistance/Models/GeneralCosmosDocument.cs b/src/api/Nhs.Appointments.Persistance/Models/GeneralCosmosDocument.cs index 15684c1d4..39ff1299b 100644 --- a/src/api/Nhs.Appointments.Persistance/Models/GeneralCosmosDocument.cs +++ b/src/api/Nhs.Appointments.Persistance/Models/GeneralCosmosDocument.cs @@ -33,6 +33,12 @@ public class CoreDataCosmosDocument : LastUpdatedByCosmosDocument } +[CosmosDocument("booking_reference_data", "docType")] +public class BookingReferenceDataCosmosDocument : LastUpdatedByCosmosDocument +{ + +} + [CosmosDocument("aggregated_data", "date")] public class AggregatedDataCosmosDocument : TypedCosmosDocument { diff --git a/src/api/Nhs.Appointments.Persistance/Models/ReferenceGroupDocument.cs b/src/api/Nhs.Appointments.Persistance/Models/ReferenceGroupDocument.cs index a1e22c0bb..3ed3ed283 100644 --- a/src/api/Nhs.Appointments.Persistance/Models/ReferenceGroupDocument.cs +++ b/src/api/Nhs.Appointments.Persistance/Models/ReferenceGroupDocument.cs @@ -1,7 +1,13 @@ namespace Nhs.Appointments.Persistance.Models; [CosmosDocumentType("reference_group")] -public class ReferenceGroupDocument : CoreDataCosmosDocument +public class CoreReferenceGroupDocument : CoreDataCosmosDocument +{ + public ReferenceGroup[] Groups { get; set; } +} + +[CosmosDocumentType("reference_group")] +public class BookingReferenceGroupDocument : BookingReferenceDataCosmosDocument { public ReferenceGroup[] Groups { get; set; } } diff --git a/src/api/Nhs.Appointments.Persistance/ServiceRegistration.cs b/src/api/Nhs.Appointments.Persistance/ServiceRegistration.cs index 3612756c3..57d352f08 100644 --- a/src/api/Nhs.Appointments.Persistance/ServiceRegistration.cs +++ b/src/api/Nhs.Appointments.Persistance/ServiceRegistration.cs @@ -40,7 +40,9 @@ public static IServiceCollection AddDocumentStores(this IServiceCollection servi .AddScoped() .AddScoped() .AddScoped() - .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() .AddScoped() .AddScoped() .AddScoped() diff --git a/tests/Nhs.Appointments.Api.UnitTests/Validators/SiteBasedResourceRequestValidatorTests.cs b/tests/Nhs.Appointments.Api.UnitTests/Validators/SiteBasedResourceRequestValidatorTests.cs index 50e74648e..80115db09 100644 --- a/tests/Nhs.Appointments.Api.UnitTests/Validators/SiteBasedResourceRequestValidatorTests.cs +++ b/tests/Nhs.Appointments.Api.UnitTests/Validators/SiteBasedResourceRequestValidatorTests.cs @@ -21,7 +21,7 @@ public void Validate_ReturnsError_WhenSiteIsBlank() [Fact] public void Validate_ReturnsSuccess_WhenSiteBasedRequestIsValid() { - var testRequest = new SiteBasedResourceRequest("", false,"*"); + var testRequest = new SiteBasedResourceRequest("test", false,"*"); var result = _sut.Validate(testRequest); result.IsValid.Should().BeTrue(); result.Errors.Should().HaveCount(0); diff --git a/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs b/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs index d9ae21bb5..0850a991f 100644 --- a/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs +++ b/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs @@ -9,7 +9,7 @@ public class ReferenceNumberProviderTests { private readonly ReferenceNumberProvider _sut; private readonly Mock _siteService = new(); - private readonly Mock _referenceNumberDocumentStore = new(); + private readonly Mock _referenceNumberDocumentStore = new(); private readonly Mock _timeProvider = new(); private readonly Mock _memoryCache = new(); private readonly Mock _cacheEntry = new(); From 1bc23fdae25dc76e98066fb1dcd702ac10196601 Mon Sep 17 00:00:00 2001 From: pata9 Date: Wed, 22 Apr 2026 12:03:18 +0100 Subject: [PATCH 08/23] setup infra --- infrastructure/environments/perf-ukw/main.tf | 3 +++ infrastructure/environments/perf/main.tf | 3 +++ infrastructure/environments/prod-ukw/main.tf | 3 +++ infrastructure/environments/prod/main.tf | 3 +++ infrastructure/environments/stag-ukw/main.tf | 3 +++ infrastructure/environments/stag/main.tf | 3 +++ infrastructure/resources/cosmosdb.tf | 16 ++++++++++++++++ infrastructure/resources/variables.tf | 5 +++++ 8 files changed, 39 insertions(+) diff --git a/infrastructure/environments/perf-ukw/main.tf b/infrastructure/environments/perf-ukw/main.tf index 0e6797ad5..0d14e0346 100644 --- a/infrastructure/environments/perf-ukw/main.tf +++ b/infrastructure/environments/perf-ukw/main.tf @@ -121,6 +121,9 @@ module "mya_application_perf" { cosmos_aggregation_autoscale_settings = [{ max_throughput = 10000 }] + cosmos_booking_reference_data_autoscale_settings = [{ + max_throughput = 25000 + }] cosmos_core_autoscale_settings = [{ max_throughput = 25000 }] diff --git a/infrastructure/environments/perf/main.tf b/infrastructure/environments/perf/main.tf index 36d6cdb8e..7201ad22c 100644 --- a/infrastructure/environments/perf/main.tf +++ b/infrastructure/environments/perf/main.tf @@ -128,6 +128,9 @@ module "mya_application_perf" { cosmos_aggregation_autoscale_settings = [{ max_throughput = 10000 }] + cosmos_booking_reference_data_autoscale_settings = [{ + max_throughput = 25000 + }] cosmos_core_autoscale_settings = [{ max_throughput = 25000 }] diff --git a/infrastructure/environments/prod-ukw/main.tf b/infrastructure/environments/prod-ukw/main.tf index 7f83437d2..f0fd28405 100644 --- a/infrastructure/environments/prod-ukw/main.tf +++ b/infrastructure/environments/prod-ukw/main.tf @@ -131,6 +131,9 @@ module "mya_application_prod_ukw" { cosmos_aggregation_autoscale_settings = [{ max_throughput = 10000 }] + cosmos_booking_reference_data_autoscale_settings = [{ + max_throughput = 25000 + }] cosmos_core_autoscale_settings = [{ max_throughput = 25000 }] diff --git a/infrastructure/environments/prod/main.tf b/infrastructure/environments/prod/main.tf index bd2a852bb..29f615ff4 100644 --- a/infrastructure/environments/prod/main.tf +++ b/infrastructure/environments/prod/main.tf @@ -138,6 +138,9 @@ module "mya_application_prod" { cosmos_aggregation_autoscale_settings = [{ max_throughput = 10000 }] + cosmos_booking_reference_data_autoscale_settings = [{ + max_throughput = 25000 + }] cosmos_core_autoscale_settings = [{ max_throughput = 25000 }] diff --git a/infrastructure/environments/stag-ukw/main.tf b/infrastructure/environments/stag-ukw/main.tf index 3ccf80b64..a66dffac8 100644 --- a/infrastructure/environments/stag-ukw/main.tf +++ b/infrastructure/environments/stag-ukw/main.tf @@ -121,6 +121,9 @@ module "mya_application_stag_ukw" { cosmos_aggregation_autoscale_settings = [{ max_throughput = 10000 }] + cosmos_booking_reference_data_autoscale_settings = [{ + max_throughput = 25000 + }] cosmos_core_autoscale_settings = [{ max_throughput = 25000 }] diff --git a/infrastructure/environments/stag/main.tf b/infrastructure/environments/stag/main.tf index c5bd297f4..ccbf8d14d 100644 --- a/infrastructure/environments/stag/main.tf +++ b/infrastructure/environments/stag/main.tf @@ -128,6 +128,9 @@ module "mya_application_stag" { cosmos_aggregation_autoscale_settings = [{ max_throughput = 10000 }] + cosmos_booking_reference_data_autoscale_settings = [{ + max_throughput = 25000 + }] cosmos_core_autoscale_settings = [{ max_throughput = 25000 }] diff --git a/infrastructure/resources/cosmosdb.tf b/infrastructure/resources/cosmosdb.tf index 341edfd89..c604683be 100644 --- a/infrastructure/resources/cosmosdb.tf +++ b/infrastructure/resources/cosmosdb.tf @@ -116,6 +116,22 @@ resource "azurerm_cosmosdb_sql_container" "nbs_mya_core_container" { } } +resource "azurerm_cosmosdb_sql_container" "nbs_mya_booking_reference_container" { + count = var.create_cosmos_db ? 1 : 0 + name = "booking_reference_data" + resource_group_name = local.resource_group_name + account_name = azurerm_cosmosdb_account.nbs_mya_cosmos_db[0].name + database_name = azurerm_cosmosdb_sql_database.nbs_appts_database[0].name + partition_key_paths = ["/docType"] + + dynamic "autoscale_settings" { + for_each = var.cosmos_booking_reference_data_autoscale_settings + content { + max_throughput = autoscale_settings.value["max_throughput"] + } + } +} + resource "azurerm_cosmosdb_sql_container" "nbs_mya_aggregation_container" { count = var.create_cosmos_db ? 1 : 0 name = "aggregated_data" diff --git a/infrastructure/resources/variables.tf b/infrastructure/resources/variables.tf index 54e0627c6..ffe350600 100644 --- a/infrastructure/resources/variables.tf +++ b/infrastructure/resources/variables.tf @@ -194,6 +194,11 @@ variable "cosmos_core_autoscale_settings" { default = [] } +variable "cosmos_booking_reference_data_autoscale_settings" { + type = list(any) + default = [] +} + variable "cosmos_index_autoscale_settings" { type = list(any) default = [] From d6ecdb6ed4052acf680850c927175c6108baf840 Mon Sep 17 00:00:00 2001 From: pata9 Date: Wed, 22 Apr 2026 12:06:01 +0100 Subject: [PATCH 09/23] increase site caching durations --- infrastructure/resources/variables.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/resources/variables.tf b/infrastructure/resources/variables.tf index ffe350600..09b55a5b5 100644 --- a/infrastructure/resources/variables.tf +++ b/infrastructure/resources/variables.tf @@ -483,12 +483,12 @@ variable "allsites_sliding_cache_duration_minutes" { variable "site_cache_duration_minutes" { type = number - default = 5 + default = 20 } variable "site_sliding_cache_duration_minutes" { type = number - default = 1 + default = 10 } variable "auditor_enable" { From 3c3d4a61422730c1c770ace3f79bfa5aa2179b90 Mon Sep 17 00:00:00 2001 From: pata9 Date: Wed, 22 Apr 2026 12:18:50 +0100 Subject: [PATCH 10/23] fix web app test --- .../edit-accessibilities/site-accessibilities-page.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/src/app/site/[site]/details/edit-accessibilities/site-accessibilities-page.test.tsx b/src/client/src/app/site/[site]/details/edit-accessibilities/site-accessibilities-page.test.tsx index fc8dbcebf..0fe8d2e92 100644 --- a/src/client/src/app/site/[site]/details/edit-accessibilities/site-accessibilities-page.test.tsx +++ b/src/client/src/app/site/[site]/details/edit-accessibilities/site-accessibilities-page.test.tsx @@ -79,7 +79,7 @@ describe('Manage Accessibilities Page', () => { permissions: mockPermissions, }); render(jsx); - expect(fetchSite).toHaveBeenCalledWith('TEST'); + expect(fetchSite).toHaveBeenCalledWith('TEST', true); }); it('passes props to form component', async () => { From 737eca8ca705d51bb42ac802c1ca874bdb987752 Mon Sep 17 00:00:00 2001 From: pata9 Date: Wed, 22 Apr 2026 12:34:55 +0100 Subject: [PATCH 11/23] add container and fix tests --- src/api/Nhs.Appointments.Api/FunctionConfigurationExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/Nhs.Appointments.Api/FunctionConfigurationExtensions.cs b/src/api/Nhs.Appointments.Api/FunctionConfigurationExtensions.cs index ef927209c..5392c92e8 100644 --- a/src/api/Nhs.Appointments.Api/FunctionConfigurationExtensions.cs +++ b/src/api/Nhs.Appointments.Api/FunctionConfigurationExtensions.cs @@ -161,6 +161,7 @@ private static async Task SetupCosmosDatabase(CosmosClient cosmosClient) var database = await cosmosClient.CreateDatabaseIfNotExistsAsync(id: "appts"); await database.Database.CreateContainerIfNotExistsAsync(id: "booking_data", partitionKeyPath: "/site"); await database.Database.CreateContainerIfNotExistsAsync(id: "core_data", partitionKeyPath: "/docType"); + await database.Database.CreateContainerIfNotExistsAsync(id: "booking_reference_data", partitionKeyPath: "/docType"); await database.Database.CreateContainerIfNotExistsAsync(id: "index_data", partitionKeyPath: "/docType"); await database.Database.CreateContainerIfNotExistsAsync(id: "audit_data", partitionKeyPath: "/user"); await database.Database.CreateContainerIfNotExistsAsync(id: "aggregated_data", partitionKeyPath: "/date"); From 8f733b9edc71c8c0aa5b6faf5ac193033508b717 Mon Sep 17 00:00:00 2001 From: pata9 Date: Wed, 22 Apr 2026 13:47:42 +0100 Subject: [PATCH 12/23] fix where GetNextSequenceNumber happens before AssignReferenceGroup --- ...ookingReferenceGroupCosmosDocumentStore.cs | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs index 9aec46537..33e5082cb 100644 --- a/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs @@ -1,5 +1,4 @@ using Microsoft.Azure.Cosmos; -using Microsoft.Extensions.Options; using Nhs.Appointments.Core.Bookings; using Nhs.Appointments.Persistance.Models; @@ -10,16 +9,13 @@ public class BookingReferenceGroupCosmosDocumentStore : IBookingReferenceNumberD private const string DocumentId = "main"; private readonly ITypedDocumentCosmosStore _cosmosStore; private readonly ICoreReferenceMigrationNumberDocumentStore _migrationDocumentStore; - private readonly ReferenceGroupOptions _options; public BookingReferenceGroupCosmosDocumentStore( ITypedDocumentCosmosStore cosmosStore, - ICoreReferenceMigrationNumberDocumentStore migrationDocumentStore, - IOptions options) + ICoreReferenceMigrationNumberDocumentStore migrationDocumentStore) { _cosmosStore = cosmosStore; _migrationDocumentStore = migrationDocumentStore; - _options = options.Value; } public async Task AssignReferenceGroup() @@ -32,16 +28,7 @@ public async Task AssignReferenceGroup() } catch(CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) { - var oldDocument = await _migrationDocumentStore.Get(); - - referenceGroupDocument = new BookingReferenceGroupDocument - { - DocumentType = docType, - Id = DocumentId, - Groups = oldDocument.Groups, - }; - - await _cosmosStore.WriteAsync(referenceGroupDocument); + referenceGroupDocument = await MigrateFromOldDocument(); } var target = referenceGroupDocument!.Groups.Where(g => g.Prefix > 0).OrderBy(g => g.SiteCount).ThenBy(g => g.Prefix).First(); @@ -55,8 +42,34 @@ public async Task GetNextSequenceNumber(int prefix) { var incrementSequencePatch = PatchOperation.Increment($"/Groups/{prefix}/Sequence", 1); var docType = _cosmosStore.GetDocumentType(); - var referenceGroupDocument = await _cosmosStore.PatchDocument(docType, DocumentId, incrementSequencePatch); + BookingReferenceGroupDocument referenceGroupDocument; + try + { + referenceGroupDocument = await _cosmosStore.PatchDocument(docType, DocumentId, incrementSequencePatch); + } + catch(CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + { + referenceGroupDocument = await MigrateFromOldDocument(); + } + return referenceGroupDocument.Groups.Single(gr => gr.Prefix == prefix).Sequence; } + + private async Task MigrateFromOldDocument() + { + var docType = _cosmosStore.GetDocumentType(); + var oldDocument = await _migrationDocumentStore.Get(); + + var referenceGroupDocument = new BookingReferenceGroupDocument + { + DocumentType = docType, + Id = DocumentId, + Groups = oldDocument.Groups, + }; + + await _cosmosStore.WriteAsync(referenceGroupDocument); + + return referenceGroupDocument; + } } } From 5be0658be83775006986a6911838a173e3a3530e Mon Sep 17 00:00:00 2001 From: pata9 Date: Wed, 22 Apr 2026 13:49:34 +0100 Subject: [PATCH 13/23] rename document --- .../BookingReferenceGroupCosmosDocumentStore.cs | 8 ++++---- .../CoreReferenceGroupCosmosDocumentStore.cs | 4 ++-- ...e.cs => ICoreReferenceNumberMigrationDocumentStore.cs} | 2 +- .../Nhs.Appointments.Persistance/ServiceRegistration.cs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) rename src/api/Nhs.Appointments.Persistance/{ICoreReferenceMigrationNumberDocumentStore.cs => ICoreReferenceNumberMigrationDocumentStore.cs} (69%) diff --git a/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs index 33e5082cb..c5a147189 100644 --- a/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs @@ -8,14 +8,14 @@ public class BookingReferenceGroupCosmosDocumentStore : IBookingReferenceNumberD { private const string DocumentId = "main"; private readonly ITypedDocumentCosmosStore _cosmosStore; - private readonly ICoreReferenceMigrationNumberDocumentStore _migrationDocumentStore; + private readonly ICoreReferenceNumberMigrationDocumentStore _numberMigrationDocumentStore; public BookingReferenceGroupCosmosDocumentStore( ITypedDocumentCosmosStore cosmosStore, - ICoreReferenceMigrationNumberDocumentStore migrationDocumentStore) + ICoreReferenceNumberMigrationDocumentStore numberMigrationDocumentStore) { _cosmosStore = cosmosStore; - _migrationDocumentStore = migrationDocumentStore; + _numberMigrationDocumentStore = numberMigrationDocumentStore; } public async Task AssignReferenceGroup() @@ -58,7 +58,7 @@ public async Task GetNextSequenceNumber(int prefix) private async Task MigrateFromOldDocument() { var docType = _cosmosStore.GetDocumentType(); - var oldDocument = await _migrationDocumentStore.Get(); + var oldDocument = await _numberMigrationDocumentStore.Get(); var referenceGroupDocument = new BookingReferenceGroupDocument { diff --git a/src/api/Nhs.Appointments.Persistance/CoreReferenceGroupCosmosDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/CoreReferenceGroupCosmosDocumentStore.cs index 144b5d540..cf747463e 100644 --- a/src/api/Nhs.Appointments.Persistance/CoreReferenceGroupCosmosDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/CoreReferenceGroupCosmosDocumentStore.cs @@ -5,13 +5,13 @@ namespace Nhs.Appointments.Persistance { - public class CoreCoreReferenceGroupCosmosDocumentStore : ICoreReferenceNumberDocumentStore, ICoreReferenceMigrationNumberDocumentStore + public class CoreCoreReferenceNumberGroupCosmosDocumentStore : ICoreReferenceNumberDocumentStore, ICoreReferenceNumberMigrationDocumentStore { private const string DocumentId = "main"; private readonly ITypedDocumentCosmosStore _cosmosStore; private readonly ReferenceGroupOptions _options; - public CoreCoreReferenceGroupCosmosDocumentStore(ITypedDocumentCosmosStore cosmosStore, IOptions options) + public CoreCoreReferenceNumberGroupCosmosDocumentStore(ITypedDocumentCosmosStore cosmosStore, IOptions options) { _cosmosStore = cosmosStore; _options = options.Value; diff --git a/src/api/Nhs.Appointments.Persistance/ICoreReferenceMigrationNumberDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/ICoreReferenceNumberMigrationDocumentStore.cs similarity index 69% rename from src/api/Nhs.Appointments.Persistance/ICoreReferenceMigrationNumberDocumentStore.cs rename to src/api/Nhs.Appointments.Persistance/ICoreReferenceNumberMigrationDocumentStore.cs index 08f34145b..2e28db79a 100644 --- a/src/api/Nhs.Appointments.Persistance/ICoreReferenceMigrationNumberDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/ICoreReferenceNumberMigrationDocumentStore.cs @@ -2,7 +2,7 @@ namespace Nhs.Appointments.Persistance; -public interface ICoreReferenceMigrationNumberDocumentStore +public interface ICoreReferenceNumberMigrationDocumentStore { Task Get(); } diff --git a/src/api/Nhs.Appointments.Persistance/ServiceRegistration.cs b/src/api/Nhs.Appointments.Persistance/ServiceRegistration.cs index 57d352f08..8e198024b 100644 --- a/src/api/Nhs.Appointments.Persistance/ServiceRegistration.cs +++ b/src/api/Nhs.Appointments.Persistance/ServiceRegistration.cs @@ -41,8 +41,8 @@ public static IServiceCollection AddDocumentStores(this IServiceCollection servi .AddScoped() .AddScoped() .AddScoped() - .AddScoped() - .AddScoped() + .AddScoped() + .AddScoped() .AddScoped() .AddScoped() .AddScoped() From 5536a72bc8a2481aafb22061a23cc2aa9ea3247a Mon Sep 17 00:00:00 2001 From: pata9 Date: Wed, 22 Apr 2026 16:12:02 +0100 Subject: [PATCH 14/23] make reference documents individual documents --- ...ookingReferenceGroupCosmosDocumentStore.cs | 61 +++++++++++++------ .../CoreReferenceGroupCosmosDocumentStore.cs | 2 +- .../Models/ReferenceGroupDocument.cs | 3 +- 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs index c5a147189..248ef98f3 100644 --- a/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs @@ -6,7 +6,6 @@ namespace Nhs.Appointments.Persistance { public class BookingReferenceGroupCosmosDocumentStore : IBookingReferenceNumberDocumentStore { - private const string DocumentId = "main"; private readonly ITypedDocumentCosmosStore _cosmosStore; private readonly ICoreReferenceNumberMigrationDocumentStore _numberMigrationDocumentStore; @@ -20,56 +19,80 @@ public BookingReferenceGroupCosmosDocumentStore( public async Task AssignReferenceGroup() { - BookingReferenceGroupDocument referenceGroupDocument; + IEnumerable referenceGroupDocuments; var docType = _cosmosStore.GetDocumentType(); try { - referenceGroupDocument = await _cosmosStore.GetByIdAsync("main"); + referenceGroupDocuments = await _cosmosStore.RunQueryAsync(x => x.DocumentType == docType); } catch(CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) { - referenceGroupDocument = await MigrateFromOldDocument(); + referenceGroupDocuments = await MigrateFromAllOldDocument(); } - var target = referenceGroupDocument!.Groups.Where(g => g.Prefix > 0).OrderBy(g => g.SiteCount).ThenBy(g => g.Prefix).First(); - var siteCountIncrement = PatchOperation.Increment($"/Groups/{target.Prefix}/SiteCount", 1); + var target = referenceGroupDocuments.Where(g => g.Id != 0.ToString()).OrderBy(g => g.SiteCount).ThenBy(g => g.Id).First(); + var siteCountIncrement = PatchOperation.Increment($"/SiteCount", 1); - await _cosmosStore.PatchDocument(docType, DocumentId, siteCountIncrement); - return target.Prefix; + await _cosmosStore.PatchDocument(docType, target.Id, siteCountIncrement); + return int.Parse(target.Id); } public async Task GetNextSequenceNumber(int prefix) { - var incrementSequencePatch = PatchOperation.Increment($"/Groups/{prefix}/Sequence", 1); + var incrementSequencePatch = PatchOperation.Increment($"/Sequence", 1); var docType = _cosmosStore.GetDocumentType(); BookingReferenceGroupDocument referenceGroupDocument; try { - referenceGroupDocument = await _cosmosStore.PatchDocument(docType, DocumentId, incrementSequencePatch); + referenceGroupDocument = await _cosmosStore.PatchDocument(docType, prefix.ToString(), incrementSequencePatch); } catch(CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) { - referenceGroupDocument = await MigrateFromOldDocument(); + referenceGroupDocument = await MigrateFromOldDocument(prefix); } - return referenceGroupDocument.Groups.Single(gr => gr.Prefix == prefix).Sequence; + return referenceGroupDocument.Sequence; } - private async Task MigrateFromOldDocument() + private async Task> MigrateFromAllOldDocument() { var docType = _cosmosStore.GetDocumentType(); var oldDocument = await _numberMigrationDocumentStore.Get(); - var referenceGroupDocument = new BookingReferenceGroupDocument + var referenceGroupDocuments = oldDocument.Groups.Select(g => new BookingReferenceGroupDocument { DocumentType = docType, - Id = DocumentId, - Groups = oldDocument.Groups, - }; + Id = g.Prefix.ToString(), + Sequence = g.Prefix, + SiteCount = g.SiteCount, + }).ToList(); + + foreach (var referenceGroupDocument in referenceGroupDocuments) + { + await _cosmosStore.WriteAsync(referenceGroupDocument); + } - await _cosmosStore.WriteAsync(referenceGroupDocument); - return referenceGroupDocument; + return referenceGroupDocuments; + } + + private async Task MigrateFromOldDocument(int prefix) + { + var docType = _cosmosStore.GetDocumentType(); + var oldDocument = await _numberMigrationDocumentStore.Get(); + + var oldReferenceGroup = oldDocument.Groups.Single(x => x.Prefix == prefix); + var newDocument = new BookingReferenceGroupDocument() + { + DocumentType = docType, + Id = oldReferenceGroup.Prefix.ToString(), + Sequence = oldReferenceGroup.Prefix, + SiteCount = oldReferenceGroup.SiteCount, + }; + + await _cosmosStore.WriteAsync(newDocument); + + return newDocument; } } } diff --git a/src/api/Nhs.Appointments.Persistance/CoreReferenceGroupCosmosDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/CoreReferenceGroupCosmosDocumentStore.cs index cf747463e..1094f3374 100644 --- a/src/api/Nhs.Appointments.Persistance/CoreReferenceGroupCosmosDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/CoreReferenceGroupCosmosDocumentStore.cs @@ -20,7 +20,7 @@ public CoreCoreReferenceNumberGroupCosmosDocumentStore(ITypedDocumentCosmosStore public async Task AssignReferenceGroup() { var docType = _cosmosStore.GetDocumentType(); - CoreReferenceGroupDocument referenceGroupDocument = await Get(); + var referenceGroupDocument = await Get(); var target = referenceGroupDocument!.Groups.Where(g => g.Prefix > 0).OrderBy(g => g.SiteCount).ThenBy(g => g.Prefix).First(); var siteCountIncrement = PatchOperation.Increment($"/Groups/{target.Prefix}/SiteCount", 1); diff --git a/src/api/Nhs.Appointments.Persistance/Models/ReferenceGroupDocument.cs b/src/api/Nhs.Appointments.Persistance/Models/ReferenceGroupDocument.cs index 3ed3ed283..04c1bd25d 100644 --- a/src/api/Nhs.Appointments.Persistance/Models/ReferenceGroupDocument.cs +++ b/src/api/Nhs.Appointments.Persistance/Models/ReferenceGroupDocument.cs @@ -9,7 +9,8 @@ public class CoreReferenceGroupDocument : CoreDataCosmosDocument [CosmosDocumentType("reference_group")] public class BookingReferenceGroupDocument : BookingReferenceDataCosmosDocument { - public ReferenceGroup[] Groups { get; set; } + public int SiteCount { get; set; } + public int Sequence { get; set; } } public class ReferenceGroup From 62f091662b42dd824ba04cfcadbc6b3db99b173a Mon Sep 17 00:00:00 2001 From: pata9 Date: Wed, 22 Apr 2026 16:21:52 +0100 Subject: [PATCH 15/23] fix up the logic for copy old --- ...ookingReferenceGroupCosmosDocumentStore.cs | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs index 248ef98f3..f5fabdcc3 100644 --- a/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs @@ -48,7 +48,8 @@ public async Task GetNextSequenceNumber(int prefix) } catch(CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) { - referenceGroupDocument = await MigrateFromOldDocument(prefix); + await MigrateFromAllOldDocument(); + referenceGroupDocument = await _cosmosStore.PatchDocument(docType, prefix.ToString(), incrementSequencePatch); } return referenceGroupDocument.Sequence; @@ -75,24 +76,5 @@ private async Task> MigrateFromAllOld return referenceGroupDocuments; } - - private async Task MigrateFromOldDocument(int prefix) - { - var docType = _cosmosStore.GetDocumentType(); - var oldDocument = await _numberMigrationDocumentStore.Get(); - - var oldReferenceGroup = oldDocument.Groups.Single(x => x.Prefix == prefix); - var newDocument = new BookingReferenceGroupDocument() - { - DocumentType = docType, - Id = oldReferenceGroup.Prefix.ToString(), - Sequence = oldReferenceGroup.Prefix, - SiteCount = oldReferenceGroup.SiteCount, - }; - - await _cosmosStore.WriteAsync(newDocument); - - return newDocument; - } } } From 841ca2f35e87c3a0bf12e99d804b4739a89e0461 Mon Sep 17 00:00:00 2001 From: pata9 Date: Thu, 23 Apr 2026 09:47:52 +0100 Subject: [PATCH 16/23] fix up migration for assign ref group --- .../BookingReferenceGroupCosmosDocumentStore.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs index f5fabdcc3..218ef647e 100644 --- a/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs @@ -21,11 +21,10 @@ public async Task AssignReferenceGroup() { IEnumerable referenceGroupDocuments; var docType = _cosmosStore.GetDocumentType(); - try - { - referenceGroupDocuments = await _cosmosStore.RunQueryAsync(x => x.DocumentType == docType); - } - catch(CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + + referenceGroupDocuments = (await _cosmosStore.RunQueryAsync(x => x.DocumentType == docType)).ToArray(); + + if (!referenceGroupDocuments.Any()) { referenceGroupDocuments = await MigrateFromAllOldDocument(); } @@ -72,7 +71,6 @@ private async Task> MigrateFromAllOld { await _cosmosStore.WriteAsync(referenceGroupDocument); } - return referenceGroupDocuments; } From b9e9d800c2031605f269f2679844190d3aa67022 Mon Sep 17 00:00:00 2001 From: pata9 Date: Thu, 23 Apr 2026 11:24:00 +0100 Subject: [PATCH 17/23] refactoring --- ...cumentStore.cs => IReferenceNumberDocumentStore.cs} | 2 +- .../Bookings/ReferenceNumberProvider.cs | 10 ++-------- .../CoreReferenceGroupCosmosDocumentStore.cs | 6 +++--- ...ntStore.cs => ReferenceGroupCosmosDocumentStore.cs} | 9 +++++++-- .../ServiceRegistration.cs | 5 ++--- .../ReferenceNumberProviderTests.cs | 2 +- 6 files changed, 16 insertions(+), 18 deletions(-) rename src/api/Nhs.Appointments.Core/Bookings/{IBookingReferenceNumberDocumentStore.cs => IReferenceNumberDocumentStore.cs} (71%) rename src/api/Nhs.Appointments.Persistance/{BookingReferenceGroupCosmosDocumentStore.cs => ReferenceGroupCosmosDocumentStore.cs} (91%) diff --git a/src/api/Nhs.Appointments.Core/Bookings/IBookingReferenceNumberDocumentStore.cs b/src/api/Nhs.Appointments.Core/Bookings/IReferenceNumberDocumentStore.cs similarity index 71% rename from src/api/Nhs.Appointments.Core/Bookings/IBookingReferenceNumberDocumentStore.cs rename to src/api/Nhs.Appointments.Core/Bookings/IReferenceNumberDocumentStore.cs index fa533bf8b..a1bbe5494 100644 --- a/src/api/Nhs.Appointments.Core/Bookings/IBookingReferenceNumberDocumentStore.cs +++ b/src/api/Nhs.Appointments.Core/Bookings/IReferenceNumberDocumentStore.cs @@ -1,6 +1,6 @@ namespace Nhs.Appointments.Core.Bookings; -public interface IBookingReferenceNumberDocumentStore +public interface IReferenceNumberDocumentStore { Task AssignReferenceGroup(); Task GetNextSequenceNumber(int prefix); diff --git a/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs b/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs index 3f6eceff9..07f3470dd 100644 --- a/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs +++ b/src/api/Nhs.Appointments.Core/Bookings/ReferenceNumberProvider.cs @@ -12,12 +12,12 @@ public class ReferenceNumberProvider : IReferenceNumberProvider { private readonly ISiteService _siteService; private readonly ICacheService _cacheService; - private readonly IBookingReferenceNumberDocumentStore _referenceNumberDocumentStore; + private readonly IReferenceNumberDocumentStore _referenceNumberDocumentStore; private readonly TimeProvider _timeProvider; public ReferenceNumberProvider( ISiteService siteService, ICacheService cacheService, - IBookingReferenceNumberDocumentStore referenceNumberDocumentStore, + IReferenceNumberDocumentStore referenceNumberDocumentStore, TimeProvider timeProvider) { _siteService = siteService; @@ -58,9 +58,3 @@ private async Task GetSitesAssignedReferenceGroup(string siteId) private static bool SiteHasNotBeenAssignedReferenceGroup(Site site) => site.ReferenceNumberGroup == 0; } - -public interface ICoreReferenceNumberDocumentStore -{ - Task AssignReferenceGroup(); - Task GetNextSequenceNumber(int prefix); -} diff --git a/src/api/Nhs.Appointments.Persistance/CoreReferenceGroupCosmosDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/CoreReferenceGroupCosmosDocumentStore.cs index 1094f3374..37245caea 100644 --- a/src/api/Nhs.Appointments.Persistance/CoreReferenceGroupCosmosDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/CoreReferenceGroupCosmosDocumentStore.cs @@ -5,13 +5,13 @@ namespace Nhs.Appointments.Persistance { - public class CoreCoreReferenceNumberGroupCosmosDocumentStore : ICoreReferenceNumberDocumentStore, ICoreReferenceNumberMigrationDocumentStore + public class CoreReferenceNumberGroupCosmosDocumentStore : IReferenceNumberDocumentStore, ICoreReferenceNumberMigrationDocumentStore { private const string DocumentId = "main"; private readonly ITypedDocumentCosmosStore _cosmosStore; private readonly ReferenceGroupOptions _options; - public CoreCoreReferenceNumberGroupCosmosDocumentStore(ITypedDocumentCosmosStore cosmosStore, IOptions options) + public CoreReferenceNumberGroupCosmosDocumentStore(ITypedDocumentCosmosStore cosmosStore, IOptions options) { _cosmosStore = cosmosStore; _options = options.Value; @@ -43,7 +43,7 @@ public async Task Get() var docType = _cosmosStore.GetDocumentType(); try { - referenceGroupDocument = await _cosmosStore.GetByIdAsync("main"); + referenceGroupDocument = await _cosmosStore.GetByIdAsync(DocumentId); } catch(CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) { diff --git a/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/ReferenceGroupCosmosDocumentStore.cs similarity index 91% rename from src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs rename to src/api/Nhs.Appointments.Persistance/ReferenceGroupCosmosDocumentStore.cs index 218ef647e..e08f0469c 100644 --- a/src/api/Nhs.Appointments.Persistance/BookingReferenceGroupCosmosDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/ReferenceGroupCosmosDocumentStore.cs @@ -4,12 +4,12 @@ namespace Nhs.Appointments.Persistance { - public class BookingReferenceGroupCosmosDocumentStore : IBookingReferenceNumberDocumentStore + public class ReferenceGroupCosmosDocumentStore : IReferenceNumberDocumentStore { private readonly ITypedDocumentCosmosStore _cosmosStore; private readonly ICoreReferenceNumberMigrationDocumentStore _numberMigrationDocumentStore; - public BookingReferenceGroupCosmosDocumentStore( + public ReferenceGroupCosmosDocumentStore( ITypedDocumentCosmosStore cosmosStore, ICoreReferenceNumberMigrationDocumentStore numberMigrationDocumentStore) { @@ -56,6 +56,11 @@ public async Task GetNextSequenceNumber(int prefix) private async Task> MigrateFromAllOldDocument() { + // Acquire a lock + // Check has migration happened already? + // YES - return + // No - migrate + // Release lock var docType = _cosmosStore.GetDocumentType(); var oldDocument = await _numberMigrationDocumentStore.Get(); diff --git a/src/api/Nhs.Appointments.Persistance/ServiceRegistration.cs b/src/api/Nhs.Appointments.Persistance/ServiceRegistration.cs index 8e198024b..919a51975 100644 --- a/src/api/Nhs.Appointments.Persistance/ServiceRegistration.cs +++ b/src/api/Nhs.Appointments.Persistance/ServiceRegistration.cs @@ -40,9 +40,8 @@ public static IServiceCollection AddDocumentStores(this IServiceCollection servi .AddScoped() .AddScoped() .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() + .AddScoped() + .AddScoped() .AddScoped() .AddScoped() .AddScoped() diff --git a/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs b/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs index 0850a991f..d9ae21bb5 100644 --- a/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs +++ b/tests/Nhs.Appointments.Core.UnitTests/ReferenceNumberProviderTests.cs @@ -9,7 +9,7 @@ public class ReferenceNumberProviderTests { private readonly ReferenceNumberProvider _sut; private readonly Mock _siteService = new(); - private readonly Mock _referenceNumberDocumentStore = new(); + private readonly Mock _referenceNumberDocumentStore = new(); private readonly Mock _timeProvider = new(); private readonly Mock _memoryCache = new(); private readonly Mock _cacheEntry = new(); From 297d5244b20e5f5417370d4c4862e759501efc6c Mon Sep 17 00:00:00 2001 From: pata9 Date: Thu, 23 Apr 2026 11:53:02 +0100 Subject: [PATCH 18/23] change reference group to not be lastUpdatedBy --- .../Models/GeneralCosmosDocument.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/Nhs.Appointments.Persistance/Models/GeneralCosmosDocument.cs b/src/api/Nhs.Appointments.Persistance/Models/GeneralCosmosDocument.cs index 39ff1299b..1b28bdc35 100644 --- a/src/api/Nhs.Appointments.Persistance/Models/GeneralCosmosDocument.cs +++ b/src/api/Nhs.Appointments.Persistance/Models/GeneralCosmosDocument.cs @@ -34,7 +34,7 @@ public class CoreDataCosmosDocument : LastUpdatedByCosmosDocument } [CosmosDocument("booking_reference_data", "docType")] -public class BookingReferenceDataCosmosDocument : LastUpdatedByCosmosDocument +public class BookingReferenceDataCosmosDocument : TypedCosmosDocument { } From 05d4107e86bd06ea8d73743b56fb4830ba82abdf Mon Sep 17 00:00:00 2001 From: pata9 Date: Thu, 23 Apr 2026 15:20:39 +0100 Subject: [PATCH 19/23] donut --- .../ReferenceGroupCosmosDocumentStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/Nhs.Appointments.Persistance/ReferenceGroupCosmosDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/ReferenceGroupCosmosDocumentStore.cs index e08f0469c..b6b611d90 100644 --- a/src/api/Nhs.Appointments.Persistance/ReferenceGroupCosmosDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/ReferenceGroupCosmosDocumentStore.cs @@ -68,7 +68,7 @@ private async Task> MigrateFromAllOld { DocumentType = docType, Id = g.Prefix.ToString(), - Sequence = g.Prefix, + Sequence = g.Sequence, SiteCount = g.SiteCount, }).ToList(); From 4a02a3cd71197bacd4b5ad25466d6928612632ed Mon Sep 17 00:00:00 2001 From: pata9 Date: Fri, 24 Apr 2026 11:45:48 +0100 Subject: [PATCH 20/23] make point reads instead of query on db --- .../Availability/IAvailabilityStore.cs | 2 +- .../Sites/SiteService.cs | 2 +- .../AvailabilityDocumentStore.cs | 31 ++----- .../SiteServiceTests.cs | 90 ++++++++++--------- 4 files changed, 55 insertions(+), 70 deletions(-) diff --git a/src/api/Nhs.Appointments.Core/Availability/IAvailabilityStore.cs b/src/api/Nhs.Appointments.Core/Availability/IAvailabilityStore.cs index 1b214bc3f..136591059 100644 --- a/src/api/Nhs.Appointments.Core/Availability/IAvailabilityStore.cs +++ b/src/api/Nhs.Appointments.Core/Availability/IAvailabilityStore.cs @@ -7,7 +7,7 @@ Task ApplyAvailabilityTemplate(string site, DateOnly date, Session[] sessions, A Session sessionToEdit = null); Task> GetDailyAvailability(string site, DateOnly from, DateOnly to); Task CancelSession(string site, DateOnly date, Session session); - Task SiteSupportsAllServicesOnSingleDateInRangeAsync(string siteId, List services, List datesInPeriod); + Task SiteSupportsAllServicesOnSingleDateInRangeAsync(string siteId, List services, DateOnly from, DateOnly until); Task CancelDayAsync(string site, DateOnly date); Task EditSessionsAsync(string site, DateOnly from, DateOnly until, Session sessionMatcher, Session sessionReplacement); Task CancelMultipleSessions(string site, DateOnly from, DateOnly until, Session sessionMatcher = null); diff --git a/src/api/Nhs.Appointments.Core/Sites/SiteService.cs b/src/api/Nhs.Appointments.Core/Sites/SiteService.cs index 5ca8897a5..184c91bd4 100644 --- a/src/api/Nhs.Appointments.Core/Sites/SiteService.cs +++ b/src/api/Nhs.Appointments.Core/Sites/SiteService.cs @@ -436,6 +436,6 @@ private async Task FetchSiteOffersServiceDuringPeriod(string siteId, List< var dateStringsInRange = GetDateStringsInRange(from, until); return await availabilityStore.SiteSupportsAllServicesOnSingleDateInRangeAsync(siteId, services, - dateStringsInRange); + from, until); } } diff --git a/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs index 6f42d0d16..22d08b451 100644 --- a/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs @@ -95,31 +95,14 @@ public async Task CancelSession(string site, DateOnly date, Ses }; } - public async Task SiteSupportsAllServicesOnSingleDateInRangeAsync(string siteId, List services, List datesInPeriod) + public async Task SiteSupportsAllServicesOnSingleDateInRangeAsync(string siteId, List services, DateOnly from, DateOnly until) { - var docType = documentStore.GetDocumentType(); - - var query = @" - SELECT VALUE COUNT(1) - FROM booking_data bd - WHERE ARRAY_CONTAINS(@docIds, bd.id) - AND bd.site = @site - AND bd.docType = @docType - AND ARRAY_LENGTH(SETINTERSECT( - ARRAY( - SELECT VALUE svc FROM session IN bd.sessions JOIN svc IN session.services - ), @services)) = @requestedServiceCount"; - - var queryDef = new QueryDefinition(query) - .WithParameter("@docType", docType) - .WithParameter("@docIds", datesInPeriod) - .WithParameter("@site", siteId) - .WithParameter("@services", services.ToArray()) - .WithParameter("@requestedServiceCount", services.Count); - - var dailyAvailabilityCount = (await documentStore.RunSqlQueryAsync(queryDef)).Single(); - - return dailyAvailabilityCount > 0; + var documents = await GetDailyAvailability(siteId, from, until); + + return documents.Select( + d => d.Sessions.SelectMany( + s => s.Services)) + .All(s => services.All(s.Contains)); } public async Task CancelDayAsync(string site, DateOnly date) diff --git a/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs b/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs index 4b3cad859..c885cc961 100644 --- a/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs +++ b/tests/Nhs.Appointments.Core.UnitTests/SiteServiceTests.cs @@ -281,7 +281,7 @@ public async Task QuerySitesAsync_FiltersSitesOnAccessNeeds() result.Count().Should().Be(2); result.Any(r => r.Site.Id == "test456").Should().BeFalse(); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -344,7 +344,7 @@ public async Task QuerySitesAsync_FiltersSitesOnDistance() result.Count().Should().Be(2); result.Any(r => r.Site.Id == "test456").Should().BeFalse(); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -407,7 +407,7 @@ public async Task QuerySitesAsync_FiltersSitesOnService() _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); - _availabilityStore.SetupSequence(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>())) + _availabilityStore.SetupSequence(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .ReturnsAsync(true) .ReturnsAsync(true) .ReturnsAsync(false); @@ -417,7 +417,7 @@ public async Task QuerySitesAsync_FiltersSitesOnService() result.Count().Should().Be(2); result.Any(r => r.Site.Id == "test456").Should().BeFalse(); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Exactly(3)); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Exactly(3)); } [Fact] @@ -493,14 +493,14 @@ public async Task QuerySitesAsync_RemovesDuplicates() _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); - _availabilityStore.Setup(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>())) + _availabilityStore.Setup(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .ReturnsAsync(true); var result = await _sut.QuerySitesAsync([.. filters], 50, true); result.Count().Should().Be(2); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Exactly(3)); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Exactly(3)); } [Fact] @@ -582,14 +582,14 @@ public async Task QuerySitesAsync_CorrectlyHandlesMultipleFilters_AndReturnsAllS _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); - _availabilityStore.Setup(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>())) + _availabilityStore.Setup(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .ReturnsAsync(true); var result = await _sut.QuerySitesAsync([.. filters], 50, true); result.Count().Should().Be(3); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Exactly(3)); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Exactly(3)); } [Fact] @@ -687,7 +687,7 @@ public async Task QuerySitesAsync_CorrectlyHandlesMultipleFilters_AndFiltersOutI _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); - _availabilityStore.SetupSequence(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>())) + _availabilityStore.SetupSequence(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .ReturnsAsync(true) .ReturnsAsync(true) .ReturnsAsync(true) @@ -697,7 +697,7 @@ public async Task QuerySitesAsync_CorrectlyHandlesMultipleFilters_AndFiltersOutI result.Count().Should().Be(3); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Exactly(4)); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Exactly(4)); } [Fact] @@ -772,7 +772,7 @@ public async Task QuerySitesAsync_FiltersBySiteType() result.Count().Should().Be(1); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -848,7 +848,7 @@ public async Task QuerySitesAsync_FiltersBySiteType_AndOdsCode() result.Count().Should().Be(1); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -924,7 +924,7 @@ public async Task QuerySitesAsync_FiltersBySiteType_AndOdsCode_AndDoesNotReturnA result.Count().Should().Be(0); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -999,7 +999,7 @@ public async Task QuerySitesAsync_FiltersByOdsCode() result.Count().Should().Be(1); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -1069,7 +1069,7 @@ public async Task QuerySitesAsync_FiltersSitesInPriorityOrder_AndStopsFilteringA result.Count().Should().Be(50); result.Any(x => x.Site.Id == "test51").Should().BeFalse(); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -1145,7 +1145,7 @@ public async Task QuerySitesAsync_ShouldExcludeSiteTypesFromResults() result.Count().Should().Be(3); result.Any(r => r.Site.Id == "test321").Should().BeFalse(); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -1191,7 +1191,7 @@ public async Task QuerySitesAsync_CallsAvailabilityStoreForEachSite_WhenSiteSupp Distance: 3573) }; _siteStore.Setup(x => x.GetAllSites()).ReturnsAsync(sites.Select(s => s.Site)); - _availabilityStore.Setup(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>())).ReturnsAsync(true); + _availabilityStore.Setup(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())).ReturnsAsync(true); //set up a cache, but it's for a different date range, so its not used object outResult = new CacheService.LazySlideCacheObject(true, DateTimeOffset.UtcNow); @@ -1217,10 +1217,11 @@ public async Task QuerySitesAsync_CallsAvailabilityStoreForEachSite_WhenSiteSupp var result = await _sut.QuerySitesAsync(filters, 50, false); result.Should().BeEquivalentTo(sites); - var docIds = new List() { "20251003", "20251004", "20251005","20251006","20251007","20251008","20251009","20251010", "20251011", "20251012","20251013","20251014","20251015"}; + var from = new DateOnly(2025, 10, 3); + var to = new DateOnly(2025, 10, 15); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync("6877d86e-c2df-4def-8508-e1eccf0ea6ba", new List { "RSV:Adult" }, docIds), Times.Once); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync("6877d86e-c2df-4def-8508-e1eccf0ea6bb", new List { "RSV:Adult" }, docIds), Times.Once); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync("6877d86e-c2df-4def-8508-e1eccf0ea6ba", new List { "RSV:Adult" }, from, to), Times.Once); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync("6877d86e-c2df-4def-8508-e1eccf0ea6bb", new List { "RSV:Adult" }, from, to), Times.Once); //creates new correct cache for queried date range _memoryCache.Verify(x => x.CreateEntry("LazySlide:site_6877d86e-c2df-4def-8508-e1eccf0ea6ba_supports_RSV:Adult_in_20251003_20251015"), Times.Once); @@ -1305,11 +1306,12 @@ public async Task QuerySitesAsync_CallsAvailabilityStoreForEachSite_WhenSiteSupp var result = await _sut.QuerySitesAsync(filters, 50, false); result.Should().BeEquivalentTo(sites); - var docIds = new List() { "20251003", "20251004", "20251005","20251006","20251007","20251008","20251009","20251010", "20251011", "20251012","20251013","20251014","20251015"}; + var from = new DateOnly(2025, 10, 3); + var to = new DateOnly(2025, 10, 15); //doesn't call the store if cached - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync("6877d86e-c2df-4def-8508-e1eccf0ea6ba", new List { "RSV:Adult" }, docIds), Times.Never); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync("6877d86e-c2df-4def-8508-e1eccf0ea6bb", new List { "RSV:Adult" }, docIds), Times.Never); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync("6877d86e-c2df-4def-8508-e1eccf0ea6ba", new List { "RSV:Adult" }, from, to), Times.Never); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync("6877d86e-c2df-4def-8508-e1eccf0ea6bb", new List { "RSV:Adult" }, from, to), Times.Never); _memoryCache.Verify(x => x.CreateEntry("LazySlide:site_6877d86e-c2df-4def-8508-e1eccf0ea6ba_supports_RSV:Adult_in_20251003_20251015"), Times.Never); _memoryCache.Verify(x => x.CreateEntry("LazySlide:site_6877d86e-c2df-4def-8508-e1eccf0ea6bb_supports_RSV:Adult_in_20251003_20251015"), Times.Never); @@ -1414,7 +1416,7 @@ public async Task QuerySitesAsync_CallsAvailabilityStoreForEachSiteBatched_WhenS for (var i = 1; i < siteCount; i++) { var id = $"{i:00}"; - _availabilityStore.Setup(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync($"6877d86e-c2df-4def-8508-e1eccf0ea6{id}", It.IsAny>(), It.IsAny>())).ReturnsAsync(false); + _availabilityStore.Setup(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync($"6877d86e-c2df-4def-8508-e1eccf0ea6{id}", It.IsAny>(), It.IsAny(), It.IsAny())).ReturnsAsync(false); } var filters = new List @@ -1435,20 +1437,21 @@ public async Task QuerySitesAsync_CallsAvailabilityStoreForEachSiteBatched_WhenS var result = await _sut.QuerySitesAsync(filters, 1, false); result.Single().Site.Id.Should().Be(validSites.First().Site.Id); - - var docIds = new List() { "20251003", "20251004", "20251005", "20251006"}; + + var from = new DateOnly(2025, 10, 3); + var to = new DateOnly(2025, 10, 6); for (var i = 1; i < siteCount; i++) { var id = $"{i:00}"; - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync($"6877d86e-c2df-4def-8508-e1eccf0ea6{id}", new List { "RSV:Adult" }, docIds), Times.Once); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync($"6877d86e-c2df-4def-8508-e1eccf0ea6{id}", new List { "RSV:Adult" }, from, to), Times.Once); } for (var i = 1; i < siteCount; i++) { //since the valid sites were cached, it shouldn't look up via DB var id = $"{i:00}"; - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync($"6877d86e-c2df-4def-8508-e1eccf0ea7{id}", new List { "RSV:Adult" }, docIds), Times.Never); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync($"6877d86e-c2df-4def-8508-e1eccf0ea7{id}", new List { "RSV:Adult" }, from, to), Times.Never); } _logger.Verify(x => x.Log( @@ -1590,7 +1593,7 @@ public async Task QuerySitesAsync_FiltersInIndexOrder_WhenPrioritiesAreTheSame() result.Count().Should().Be(50); result.Any(x => x.Site.Id == "test51").Should().BeFalse(); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -1659,7 +1662,7 @@ public async Task QuerySitesAsync_FiltersOnPriority_WhenOnlyOneFilterHasPriority result.Count().Should().Be(50); result.Any(x => x.Site.Id == "test51").Should().BeFalse(); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -1745,7 +1748,7 @@ public async Task QuerySitesAsync_OnlyReturnsSiteOnce_WhenItMatchesMultipleFilte result.Count().Should().Be(1); result.First().Site.Id.Should().Be("test321"); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -1824,7 +1827,7 @@ public async Task QuerySitesAsync_ReturnsSitesOnlyWhenAllServicesMatch() }; _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); - _availabilityStore.SetupSequence(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>())) + _availabilityStore.SetupSequence(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .ReturnsAsync(true) .ReturnsAsync(false) .ReturnsAsync(true) @@ -1837,7 +1840,7 @@ public async Task QuerySitesAsync_ReturnsSitesOnlyWhenAllServicesMatch() result.Any(x => x.Site.Id == "test321").Should().BeFalse(); _availabilityStore.Verify( - x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(services)), It.IsAny>()), + x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(services)), It.IsAny(), It.IsAny()), Times.Exactly(4)); } @@ -1884,8 +1887,7 @@ public async Task QuerySitesAsync_SameOrderedCacheKeyUsedForUnorderedAndDuplicat _siteStore.Setup(x => x.GetAllSites()) .ReturnsAsync(sites); _availabilityStore.Setup(x => - x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(orderedServices)), - It.IsAny>())) + x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(orderedServices)), It.IsAny(), It.IsAny())) .ReturnsAsync(true); filters.Single().Availability.Services = services1.ToArray(); @@ -1894,32 +1896,32 @@ public async Task QuerySitesAsync_SameOrderedCacheKeyUsedForUnorderedAndDuplicat result1.Count().Should().Be(1); _availabilityStore.Verify( - x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(orderedServices)), It.IsAny>()), + x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(orderedServices)), It.IsAny(), It.IsAny()), Times.Once); _availabilityStore.Verify( - x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(services1)), It.IsAny>()), + x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(services1)), It.IsAny(), It.IsAny()), Times.Never); filters.Single().Availability.Services = services2.ToArray(); var result2 = await _sut.QuerySitesAsync([.. filters], 50, true); result2.Count().Should().Be(1); _availabilityStore.Verify( - x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(services2)), It.IsAny>()), + x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(services2)), It.IsAny(), It.IsAny()), Times.Never); filters.Single().Availability.Services = services3.ToArray(); var result3 = await _sut.QuerySitesAsync([.. filters], 50, true); result3.Count().Should().Be(1); _availabilityStore.Verify( - x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(services3)), It.IsAny>()), + x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(services3)), It.IsAny(), It.IsAny()), Times.Never); filters.Single().Availability.Services = services4.ToArray(); var result4 = await _sut.QuerySitesAsync([.. filters], 50, true); result4.Count().Should().Be(1); _availabilityStore.Verify( - x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(services4)), It.IsAny>()), + x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(services4)), It.IsAny(), It.IsAny()), Times.Never); } @@ -2013,7 +2015,7 @@ public async Task QuerySitesAsync_CachingKeyUsedForFullServiceList() _memoryCache.Verify(x => x.CreateEntry(CacheService.LazySlideCacheKey("site_test456_supports_COVID:5_11_RSV:Adult_in_20250901_20251001")), Times.Never); _memoryCache.Verify(x => x.CreateEntry(CacheService.LazySlideCacheKey("site_test654_supports_COVID:5_11_RSV:Adult_in_20250901_20251001")), Times.Never); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -2106,7 +2108,7 @@ public async Task QuerySitesAsync_WritesToCache_WhenKeyNotPresentInitially() _memoryCache.Verify(x => x.CreateEntry(CacheService.LazySlideCacheKey("site_test456_supports_COVID:5_11_RSV:Adult_in_20250901_20251001")), Times.Once); _memoryCache.Verify(x => x.CreateEntry(CacheService.LazySlideCacheKey("site_test654_supports_COVID:5_11_RSV:Adult_in_20250901_20251001")), Times.Once); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Exactly(4)); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Exactly(4)); } [Fact] @@ -2199,7 +2201,7 @@ public async Task QuerySitesAsync_WriteToCache_CacheKeyHasUniqueServicesInAlphab _memoryCache.Verify(x => x.CreateEntry(CacheService.LazySlideCacheKey("site_test456_supports_COVID:5_11_RSV:Adult_in_20250901_20251001")), Times.Once); _memoryCache.Verify(x => x.CreateEntry(CacheService.LazySlideCacheKey("site_test654_supports_COVID:5_11_RSV:Adult_in_20250901_20251001")), Times.Once); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(expectedServices)), It.IsAny>()), Times.Exactly(4)); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(expectedServices)), It.IsAny(), It.IsAny()), Times.Exactly(4)); } [Fact] @@ -2292,7 +2294,7 @@ public async Task QuerySitesAsync_ReadsFromCache_CacheKeyHasUniqueServicesInAlph _memoryCache.Verify(x => x.CreateEntry(CacheService.LazySlideCacheKey("site_test456_supports_COVID:5_11_RSV:Adult_in_20250901_20251001")), Times.Never); _memoryCache.Verify(x => x.CreateEntry(CacheService.LazySlideCacheKey("site_test654_supports_COVID:5_11_RSV:Adult_in_20250901_20251001")), Times.Never); - _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(expectedServices)), It.IsAny>()), Times.Exactly(0)); + _availabilityStore.Verify(x => x.SiteSupportsAllServicesOnSingleDateInRangeAsync(It.IsAny(), It.Is>(l => l.SequenceEqual(expectedServices)), It.IsAny(), It.IsAny()), Times.Exactly(0)); } [Fact] From 4ade28c65944d903e1e1ab47e1022cc1aa762330 Mon Sep 17 00:00:00 2001 From: pata9 Date: Fri, 24 Apr 2026 12:29:02 +0100 Subject: [PATCH 21/23] business logic corrected --- .../AvailabilityDocumentStore.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs index 22d08b451..74dd675a9 100644 --- a/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs @@ -98,11 +98,8 @@ public async Task CancelSession(string site, DateOnly date, Ses public async Task SiteSupportsAllServicesOnSingleDateInRangeAsync(string siteId, List services, DateOnly from, DateOnly until) { var documents = await GetDailyAvailability(siteId, from, until); - - return documents.Select( - d => d.Sessions.SelectMany( - s => s.Services)) - .All(s => services.All(s.Contains)); + var sessions = documents.SelectMany(x => x.Sessions); + return sessions.Any(session => services.All(service => session.Services.Contains(service))); } public async Task CancelDayAsync(string site, DateOnly date) From fb055713837ca085f9affef8760a4b60ff1b104f Mon Sep 17 00:00:00 2001 From: pata9 Date: Fri, 24 Apr 2026 12:53:40 +0100 Subject: [PATCH 22/23] business logic corrected --- .../Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs index 74dd675a9..204151406 100644 --- a/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs @@ -98,8 +98,8 @@ public async Task CancelSession(string site, DateOnly date, Ses public async Task SiteSupportsAllServicesOnSingleDateInRangeAsync(string siteId, List services, DateOnly from, DateOnly until) { var documents = await GetDailyAvailability(siteId, from, until); - var sessions = documents.SelectMany(x => x.Sessions); - return sessions.Any(session => services.All(service => session.Services.Contains(service))); + var sessions = documents.SelectMany(x => x.Sessions).Select(x => x.Services); + return sessions.Any(sessionServices => services.All(sessionServices.Contains)); } public async Task CancelDayAsync(string site, DateOnly date) From db3475194869367b8d6a9118d5cfef9a1e63c7e5 Mon Sep 17 00:00:00 2001 From: pata9 Date: Fri, 24 Apr 2026 13:12:41 +0100 Subject: [PATCH 23/23] business logic corrected --- .../AvailabilityDocumentStore.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs b/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs index 204151406..c7db5f9c5 100644 --- a/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs +++ b/src/api/Nhs.Appointments.Persistance/AvailabilityDocumentStore.cs @@ -98,8 +98,12 @@ public async Task CancelSession(string site, DateOnly date, Ses public async Task SiteSupportsAllServicesOnSingleDateInRangeAsync(string siteId, List services, DateOnly from, DateOnly until) { var documents = await GetDailyAvailability(siteId, from, until); - var sessions = documents.SelectMany(x => x.Sessions).Select(x => x.Services); - return sessions.Any(sessionServices => services.All(sessionServices.Contains)); + var matchingSessions = documents.Where(document => document.Sessions + .SelectMany(s => s.Services) + .Distinct() + .Count(services.Contains) == services.Count); + + return matchingSessions.Any(); } public async Task CancelDayAsync(string site, DateOnly date)