Skip to content

Commit 98d5fde

Browse files
committed
Added bulk get method and cleaned up some test stuff.
1 parent d9aef5c commit 98d5fde

5 files changed

Lines changed: 91 additions & 24 deletions

File tree

src/Core/AdminConsole/AbilitiesCache/ExtendedOrganizationAbilityCacheService.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,34 @@ public class ExtendedOrganizationAbilityCacheService(
1818
: IOrganizationAbilityCacheService
1919
{
2020

21-
public async Task<OrganizationAbility?> GetOrganizationAbilityAsync(Guid orgId)
21+
public async Task<OrganizationAbility?> GetOrganizationAbilityAsync(Guid orgId, CancellationToken cancellationToken = default)
2222
{
2323
var cacheKey = BuildCacheKeyForOrganizationAbility(orgId);
2424
return await cache.GetOrSetAsync<OrganizationAbility?>(
2525
cacheKey,
26-
async _ => await organizationRepository.GetAbilityAsync(orgId));
26+
async (_, _) => await organizationRepository.GetAbilityAsync(orgId),
27+
token: cancellationToken);
2728
}
2829

29-
public async Task UpsertOrganizationAbilityAsync(Organization organization)
30+
public async Task<IDictionary<Guid, OrganizationAbility>> GetOrganizationAbilitiesAsync(IEnumerable<Guid> orgIds, CancellationToken cancellationToken = default)
31+
{
32+
var tasks = orgIds.Distinct().Select(async orgId => (orgId, ability: await GetOrganizationAbilityAsync(orgId, cancellationToken)));
33+
var results = await Task.WhenAll(tasks);
34+
return results
35+
.Where(r => r.ability != null)
36+
.ToDictionary(r => r.orgId, r => r.ability!);
37+
}
38+
39+
public async Task UpsertOrganizationAbilityAsync(Organization organization, CancellationToken cancellationToken = default)
3040
{
3141
var cacheKey = BuildCacheKeyForOrganizationAbility(organization.Id);
32-
await cache.SetAsync<OrganizationAbility?>(cacheKey, new OrganizationAbility(organization));
42+
await cache.SetAsync<OrganizationAbility?>(cacheKey, new OrganizationAbility(organization), token: cancellationToken);
3343
}
3444

35-
public async Task DeleteOrganizationAbilityAsync(Guid organizationId)
45+
public async Task DeleteOrganizationAbilityAsync(Guid organizationId, CancellationToken cancellationToken = default)
3646
{
3747
var cacheKey = BuildCacheKeyForOrganizationAbility(organizationId);
38-
await cache.RemoveAsync(cacheKey);
48+
await cache.RemoveAsync(cacheKey, token: cancellationToken);
3949
}
4050

4151
private static string BuildCacheKeyForOrganizationAbility(Guid organizationId)

src/Core/AdminConsole/AbilitiesCache/IOrganizationAbilityCacheService.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ namespace Bit.Core.AdminConsole.AbilitiesCache;
55

66
public interface IOrganizationAbilityCacheService
77
{
8-
Task<OrganizationAbility?> GetOrganizationAbilityAsync(Guid orgId);
9-
Task UpsertOrganizationAbilityAsync(Organization organization);
10-
Task DeleteOrganizationAbilityAsync(Guid organizationId);
8+
Task<OrganizationAbility?> GetOrganizationAbilityAsync(Guid orgId, CancellationToken cancellationToken = default);
9+
Task<IDictionary<Guid, OrganizationAbility>> GetOrganizationAbilitiesAsync(IEnumerable<Guid> orgIds, CancellationToken cancellationToken = default);
10+
Task UpsertOrganizationAbilityAsync(Organization organization, CancellationToken cancellationToken = default);
11+
Task DeleteOrganizationAbilityAsync(Guid organizationId, CancellationToken cancellationToken = default);
1112
}

src/Core/Services/Implementations/FeatureRoutedCacheService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public async Task<IDictionary<Guid, ProviderAbility>> GetProviderAbilitiesAsync(
4040

4141
public async Task<IDictionary<Guid, OrganizationAbility>> GetOrganizationAbilitiesAsync(IEnumerable<Guid> orgIds)
4242
{
43+
if (featureService.IsEnabled(FeatureFlagKeys.OrgAbilityExtendedCache))
44+
{
45+
return await extendedCacheService.GetOrganizationAbilitiesAsync(orgIds);
46+
}
47+
4348
var allOrganizationAbilities = await inMemoryApplicationCacheService.GetOrganizationAbilitiesAsync();
4449
return orgIds
4550
.Distinct()

test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationAbilityCacheTests.cs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
using System.Net;
2-
using Bit.Api.AdminConsole.Models.Request.Organizations;
32
using Bit.Api.Auth.Models.Request.Accounts;
43
using Bit.Api.IntegrationTest.Factories;
54
using Bit.Api.IntegrationTest.Helpers;
5+
using Bit.Api.Models.Request.Organizations;
66
using Bit.Core;
77
using Bit.Core.AdminConsole.Entities;
88
using Bit.Core.Billing.Enums;
@@ -76,22 +76,30 @@ public async Task Put_UpdatesOrganization_CacheReflectsUpdatedValues()
7676
{
7777
// Arrange - setup in InitializeAsync()
7878
await _loginHelper.LoginAsync(_ownerEmail);
79-
var updateRequest = new OrganizationUpdateRequestModel
79+
80+
var cacheService = _factory.GetService<IApplicationCacheService>();
81+
var abilityBefore = await cacheService.GetOrganizationAbilityAsync(_organization.Id);
82+
Assert.NotNull(abilityBefore);
83+
Assert.False(abilityBefore.LimitCollectionCreation);
84+
85+
var updateRequest = new OrganizationCollectionManagementUpdateRequestModel
8086
{
81-
Name = "Updated Cache Test Org",
82-
BillingEmail = "updated-cache@example.com"
87+
LimitCollectionCreation = true,
88+
LimitCollectionDeletion = false,
89+
LimitItemDeletion = false,
90+
AllowAdminAccessToAllCollectionItems = true
8391
};
8492

85-
// Act - update the organization via the HTTP endpoint
86-
var response = await _client.PutAsJsonAsync($"/organizations/{_organization.Id}", updateRequest);
93+
// Act - update collection management settings via the HTTP endpoint
94+
var response = await _client.PutAsJsonAsync(
95+
$"/organizations/{_organization.Id}/collection-management", updateRequest);
8796

88-
// Assert - endpoint succeeded and cache was updated
97+
// Assert - endpoint succeeded and cache reflects the updated value
8998
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
9099

91-
var cacheService = _factory.GetService<IApplicationCacheService>();
92-
var ability = await cacheService.GetOrganizationAbilityAsync(_organization.Id);
93-
Assert.NotNull(ability);
94-
Assert.Equal(_organization.Id, ability.Id);
100+
var abilityAfter = await cacheService.GetOrganizationAbilityAsync(_organization.Id);
101+
Assert.NotNull(abilityAfter);
102+
Assert.True(abilityAfter.LimitCollectionCreation);
95103
}
96104

97105
[Fact]

test/Core.Test/Services/Implementations/FeatureRoutedCacheServiceTests.cs

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,15 @@ public async Task GetProviderAbilitiesAsync_WhenDuplicateIdsProvided_DoesNotThro
208208
}
209209

210210
[Theory, BitAutoData]
211-
public async Task GetOrganizationAbilitiesAsync_ReturnsOnlyMatchingAbilities(
211+
public async Task GetOrganizationAbilitiesAsync_WhenFlagOff_ReturnsFromInMemoryService(
212212
SutProvider<FeatureRoutedCacheService> sutProvider,
213213
OrganizationAbility matchedAbility,
214214
OrganizationAbility unmatchedAbility)
215215
{
216216
// Arrange
217+
sutProvider.GetDependency<IFeatureService>()
218+
.IsEnabled(FeatureFlagKeys.OrgAbilityExtendedCache)
219+
.Returns(false);
217220
var allAbilities = new Dictionary<Guid, OrganizationAbility>
218221
{
219222
[matchedAbility.Id] = matchedAbility,
@@ -229,14 +232,52 @@ public async Task GetOrganizationAbilitiesAsync_ReturnsOnlyMatchingAbilities(
229232
// Assert
230233
Assert.Single(result);
231234
Assert.Equal(matchedAbility, result[matchedAbility.Id]);
235+
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
236+
.Received(1)
237+
.GetOrganizationAbilitiesAsync();
238+
await sutProvider.GetDependency<IOrganizationAbilityCacheService>()
239+
.DidNotReceiveWithAnyArgs()
240+
.GetOrganizationAbilitiesAsync(default);
241+
}
242+
243+
[Theory, BitAutoData]
244+
public async Task GetOrganizationAbilitiesAsync_WhenFlagOn_ReturnsFromExtendedCacheService(
245+
SutProvider<FeatureRoutedCacheService> sutProvider,
246+
OrganizationAbility ability)
247+
{
248+
// Arrange
249+
var orgIds = new[] { ability.Id };
250+
var expectedResult = new Dictionary<Guid, OrganizationAbility> { [ability.Id] = ability };
251+
sutProvider.GetDependency<IFeatureService>()
252+
.IsEnabled(FeatureFlagKeys.OrgAbilityExtendedCache)
253+
.Returns(true);
254+
sutProvider.GetDependency<IOrganizationAbilityCacheService>()
255+
.GetOrganizationAbilitiesAsync(orgIds)
256+
.Returns(expectedResult);
257+
258+
// Act
259+
var result = await sutProvider.Sut.GetOrganizationAbilitiesAsync(orgIds);
260+
261+
// Assert
262+
Assert.Single(result);
263+
Assert.Equal(ability, result[ability.Id]);
264+
await sutProvider.GetDependency<IOrganizationAbilityCacheService>()
265+
.Received(1)
266+
.GetOrganizationAbilitiesAsync(orgIds);
267+
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
268+
.DidNotReceiveWithAnyArgs()
269+
.GetOrganizationAbilitiesAsync();
232270
}
233271

234272
[Theory, BitAutoData]
235-
public async Task GetOrganizationAbilitiesAsync_WhenDuplicateIdsProvided_DoesNotThrowAndReturnsSingleEntry(
273+
public async Task GetOrganizationAbilitiesAsync_WhenFlagOff_WhenDuplicateIdsProvided_DoesNotThrowAndReturnsSingleEntry(
236274
SutProvider<FeatureRoutedCacheService> sutProvider,
237275
OrganizationAbility ability)
238276
{
239277
// Arrange
278+
sutProvider.GetDependency<IFeatureService>()
279+
.IsEnabled(FeatureFlagKeys.OrgAbilityExtendedCache)
280+
.Returns(false);
240281
var allAbilities = new Dictionary<Guid, OrganizationAbility> { [ability.Id] = ability };
241282
sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
242283
.GetOrganizationAbilitiesAsync()
@@ -250,13 +291,15 @@ public async Task GetOrganizationAbilitiesAsync_WhenDuplicateIdsProvided_DoesNot
250291
Assert.Equal(ability, result[ability.Id]);
251292
}
252293

253-
254294
[Theory, BitAutoData]
255-
public async Task GetOrganizationAbilitiesAsync_WhenNoIdsMatched_ReturnsEmptyDictionary(
295+
public async Task GetOrganizationAbilitiesAsync_WhenFlagOff_WhenNoIdsMatched_ReturnsEmptyDictionary(
256296
SutProvider<FeatureRoutedCacheService> sutProvider,
257297
Guid missingOrgId)
258298
{
259299
// Arrange
300+
sutProvider.GetDependency<IFeatureService>()
301+
.IsEnabled(FeatureFlagKeys.OrgAbilityExtendedCache)
302+
.Returns(false);
260303
sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
261304
.GetOrganizationAbilitiesAsync()
262305
.Returns(new Dictionary<Guid, OrganizationAbility>());

0 commit comments

Comments
 (0)