From aee3db5538faf1889a6d5348e9e28e4c6128824d Mon Sep 17 00:00:00 2001 From: Jimmy Vo Date: Mon, 9 Mar 2026 15:18:18 -0400 Subject: [PATCH 1/2] [PM-33040] Add new interface methods to IApplicationCacheService --- src/Core/Services/IApplicationCacheService.cs | 5 + .../FeatureRoutedCacheService.cs | 22 ++++ .../InMemoryApplicationCacheService.cs | 24 ++++ .../FeatureRoutedCacheServiceTests.cs | 117 ++++++++++++++++++ 4 files changed, 168 insertions(+) diff --git a/src/Core/Services/IApplicationCacheService.cs b/src/Core/Services/IApplicationCacheService.cs index 23a58b48f307..0db9e304719d 100644 --- a/src/Core/Services/IApplicationCacheService.cs +++ b/src/Core/Services/IApplicationCacheService.cs @@ -14,6 +14,11 @@ public interface IApplicationCacheService #nullable disable [Obsolete("We are transitioning to a new cache pattern. Please consult the Admin Console team before using.", false)] Task> GetProviderAbilitiesAsync(); +#nullable enable + Task GetProviderAbilityAsync(Guid providerId); +#nullable disable + Task> GetProviderAbilitiesAsync(IEnumerable providerIds); + Task> GetOrganizationAbilitiesAsync(IEnumerable orgIds); Task UpsertOrganizationAbilityAsync(Organization organization); Task UpsertProviderAbilityAsync(Provider provider); Task DeleteOrganizationAbilityAsync(Guid organizationId); diff --git a/src/Core/Services/Implementations/FeatureRoutedCacheService.cs b/src/Core/Services/Implementations/FeatureRoutedCacheService.cs index abd57a4f3afc..6769b1f0c184 100644 --- a/src/Core/Services/Implementations/FeatureRoutedCacheService.cs +++ b/src/Core/Services/Implementations/FeatureRoutedCacheService.cs @@ -19,6 +19,28 @@ public Task> GetOrganizationAbilitiesAsyn public Task> GetProviderAbilitiesAsync() => inMemoryApplicationCacheService.GetProviderAbilitiesAsync(); + public async Task GetProviderAbilityAsync(Guid providerId) + { + (await GetProviderAbilitiesAsync([providerId])).TryGetValue(providerId, out var providerAbility); + return providerAbility; + } + + public async Task> GetProviderAbilitiesAsync(IEnumerable providerIds) + { + var allProviderAbilities = await inMemoryApplicationCacheService.GetProviderAbilitiesAsync(); + return providerIds + .Where(allProviderAbilities.ContainsKey) + .ToDictionary(id => id, id => allProviderAbilities[id]); + } + + public async Task> GetOrganizationAbilitiesAsync(IEnumerable orgIds) + { + var allOrganizationAbilities = await inMemoryApplicationCacheService.GetOrganizationAbilitiesAsync(); + return orgIds + .Where(allOrganizationAbilities.ContainsKey) + .ToDictionary(id => id, id => allOrganizationAbilities[id]); + } + public Task UpsertOrganizationAbilityAsync(Organization organization) => inMemoryApplicationCacheService.UpsertOrganizationAbilityAsync(organization); diff --git a/src/Core/Services/Implementations/InMemoryApplicationCacheService.cs b/src/Core/Services/Implementations/InMemoryApplicationCacheService.cs index 4062162701cd..0709ad310606 100644 --- a/src/Core/Services/Implementations/InMemoryApplicationCacheService.cs +++ b/src/Core/Services/Implementations/InMemoryApplicationCacheService.cs @@ -49,6 +49,30 @@ public virtual async Task> GetProviderAbiliti return _providerAbilities; } +#nullable enable + public async Task GetProviderAbilityAsync(Guid providerId) + { + (await GetProviderAbilitiesAsync()).TryGetValue(providerId, out var providerAbility); + return providerAbility; + } +#nullable disable + + public async Task> GetProviderAbilitiesAsync(IEnumerable providerIds) + { + var allProviderAbilities = await GetProviderAbilitiesAsync(); + return providerIds + .Where(allProviderAbilities.ContainsKey) + .ToDictionary(id => id, id => allProviderAbilities[id]); + } + + public async Task> GetOrganizationAbilitiesAsync(IEnumerable orgIds) + { + var allOrganizationAbilities = await GetOrganizationAbilitiesAsync(); + return orgIds + .Where(allOrganizationAbilities.ContainsKey) + .ToDictionary(id => id, id => allOrganizationAbilities[id]); + } + public virtual async Task UpsertProviderAbilityAsync(Provider provider) { await InitProviderAbilitiesAsync(); diff --git a/test/Core.Test/Services/Implementations/FeatureRoutedCacheServiceTests.cs b/test/Core.Test/Services/Implementations/FeatureRoutedCacheServiceTests.cs index 01ca333ad722..ede442d511a8 100644 --- a/test/Core.Test/Services/Implementations/FeatureRoutedCacheServiceTests.cs +++ b/test/Core.Test/Services/Implementations/FeatureRoutedCacheServiceTests.cs @@ -79,6 +79,123 @@ await sutProvider.GetDependency() .GetProviderAbilitiesAsync(); } + [Theory, BitAutoData] + public async Task GetProviderAbilityAsync_WhenProviderExists_ReturnsAbility( + SutProvider sutProvider, + ProviderAbility providerAbility) + { + // Arrange + var allAbilities = new Dictionary { [providerAbility.Id] = providerAbility }; + sutProvider.GetDependency() + .GetProviderAbilitiesAsync() + .Returns(allAbilities); + + // Act + var result = await sutProvider.Sut.GetProviderAbilityAsync(providerAbility.Id); + + // Assert + Assert.Equal(providerAbility, result); + } + + [Theory, BitAutoData] + public async Task GetProviderAbilityAsync_WhenProviderDoesNotExist_ReturnsNull( + SutProvider sutProvider, + Guid providerId) + { + // Arrange + sutProvider.GetDependency() + .GetProviderAbilitiesAsync() + .Returns(new Dictionary()); + + // Act + var result = await sutProvider.Sut.GetProviderAbilityAsync(providerId); + + // Assert + Assert.Null(result); + } + + [Theory, BitAutoData] + public async Task GetProviderAbilitiesAsync_ReturnsOnlyMatchingAbilities( + SutProvider sutProvider, + ProviderAbility matchedAbility, + ProviderAbility unmatchedAbility) + { + // Arrange + var allAbilities = new Dictionary + { + [matchedAbility.Id] = matchedAbility, + [unmatchedAbility.Id] = unmatchedAbility + }; + sutProvider.GetDependency() + .GetProviderAbilitiesAsync() + .Returns(allAbilities); + + // Act + var result = await sutProvider.Sut.GetProviderAbilitiesAsync([matchedAbility.Id]); + + // Assert + Assert.Single(result); + Assert.Equal(matchedAbility, result[matchedAbility.Id]); + } + + [Theory, BitAutoData] + public async Task GetProviderAbilitiesAsync_WhenNoIdsMatched_ReturnsEmptyDictionary( + SutProvider sutProvider, + Guid missingProviderId) + { + // Arrange + sutProvider.GetDependency() + .GetProviderAbilitiesAsync() + .Returns(new Dictionary()); + + // Act + var result = await sutProvider.Sut.GetProviderAbilitiesAsync([missingProviderId]); + + // Assert + Assert.Empty(result); + } + + [Theory, BitAutoData] + public async Task GetOrganizationAbilitiesAsync_ReturnsOnlyMatchingAbilities( + SutProvider sutProvider, + OrganizationAbility matchedAbility, + OrganizationAbility unmatchedAbility) + { + // Arrange + var allAbilities = new Dictionary + { + [matchedAbility.Id] = matchedAbility, + [unmatchedAbility.Id] = unmatchedAbility + }; + sutProvider.GetDependency() + .GetOrganizationAbilitiesAsync() + .Returns(allAbilities); + + // Act + var result = await sutProvider.Sut.GetOrganizationAbilitiesAsync([matchedAbility.Id]); + + // Assert + Assert.Single(result); + Assert.Equal(matchedAbility, result[matchedAbility.Id]); + } + + [Theory, BitAutoData] + public async Task GetOrganizationAbilitiesAsync_WhenNoIdsMatched_ReturnsEmptyDictionary( + SutProvider sutProvider, + Guid missingOrgId) + { + // Arrange + sutProvider.GetDependency() + .GetOrganizationAbilitiesAsync() + .Returns(new Dictionary()); + + // Act + var result = await sutProvider.Sut.GetOrganizationAbilitiesAsync([missingOrgId]); + + // Assert + Assert.Empty(result); + } + [Theory, BitAutoData] public async Task UpsertOrganizationAbilityAsync_CallsInMemoryService( SutProvider sutProvider, From cbf6ad19e337132845e12dca040daaa6caf746d4 Mon Sep 17 00:00:00 2001 From: Jimmy Vo Date: Mon, 9 Mar 2026 15:24:57 -0400 Subject: [PATCH 2/2] [PM-33040] Add documentation --- src/Core/Services/IApplicationCacheService.cs | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Core/Services/IApplicationCacheService.cs b/src/Core/Services/IApplicationCacheService.cs index 0db9e304719d..bb06b8135481 100644 --- a/src/Core/Services/IApplicationCacheService.cs +++ b/src/Core/Services/IApplicationCacheService.cs @@ -11,13 +11,28 @@ public interface IApplicationCacheService Task> GetOrganizationAbilitiesAsync(); #nullable enable Task GetOrganizationAbilityAsync(Guid orgId); + /// + /// Gets the cached for the specified provider. + /// + /// The ID of the provider. + /// The if found; otherwise, null. + Task GetProviderAbilityAsync(Guid providerId); #nullable disable [Obsolete("We are transitioning to a new cache pattern. Please consult the Admin Console team before using.", false)] Task> GetProviderAbilitiesAsync(); -#nullable enable - Task GetProviderAbilityAsync(Guid providerId); -#nullable disable + /// + /// Gets cached entries for the specified providers. + /// Provider IDs not found in the cache are silently excluded from the result. + /// + /// The IDs of the providers to look up. + /// A dictionary mapping each found provider ID to its . Task> GetProviderAbilitiesAsync(IEnumerable providerIds); + /// + /// Gets cached entries for the specified organizations. + /// Organization IDs not found in the cache are silently excluded from the result. + /// + /// The IDs of the organizations to look up. + /// A dictionary mapping each found organization ID to its . Task> GetOrganizationAbilitiesAsync(IEnumerable orgIds); Task UpsertOrganizationAbilityAsync(Organization organization); Task UpsertProviderAbilityAsync(Provider provider);