diff --git a/src/Core/Services/IApplicationCacheService.cs b/src/Core/Services/IApplicationCacheService.cs index 23a58b48f307..bb06b8135481 100644 --- a/src/Core/Services/IApplicationCacheService.cs +++ b/src/Core/Services/IApplicationCacheService.cs @@ -11,9 +11,29 @@ 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(); + /// + /// 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); 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,