You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
403 lines
15 KiB
403 lines
15 KiB
using System.Collections.Concurrent; |
|
using Bit.Core.AdminConsole.AbilitiesCache; |
|
using Bit.Core.AdminConsole.Entities; |
|
using Bit.Core.AdminConsole.Entities.Provider; |
|
using Bit.Core.AdminConsole.Models.Data.Provider; |
|
using Bit.Core.AdminConsole.Repositories; |
|
using Bit.Core.Models.Data.Organizations; |
|
using Bit.Core.Repositories; |
|
using Bit.Test.Common.AutoFixture; |
|
using Bit.Test.Common.AutoFixture.Attributes; |
|
using Microsoft.Extensions.Time.Testing; |
|
using NSubstitute; |
|
using Xunit; |
|
|
|
namespace Bit.Core.Test.AdminConsole.AbilitiesCache; |
|
|
|
[SutProviderCustomize] |
|
public class VNextInMemoryApplicationCacheServiceTests |
|
|
|
{ |
|
[Theory, BitAutoData] |
|
public async Task GetOrganizationAbilitiesAsync_FirstCall_LoadsFromRepository( |
|
ICollection<OrganizationAbility> organizationAbilities, |
|
SutProvider<VNextInMemoryApplicationCacheService> sutProvider) |
|
{ |
|
// Arrange |
|
sutProvider.GetDependency<IOrganizationRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(organizationAbilities); |
|
|
|
// Act |
|
var result = await sutProvider.Sut.GetOrganizationAbilitiesAsync(); |
|
|
|
// Assert |
|
Assert.IsType<ConcurrentDictionary<Guid, OrganizationAbility>>(result); |
|
Assert.Equal(organizationAbilities.Count, result.Count); |
|
foreach (var ability in organizationAbilities) |
|
{ |
|
Assert.True(result.TryGetValue(ability.Id, out var actualAbility)); |
|
Assert.Equal(ability, actualAbility); |
|
} |
|
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).GetManyAbilitiesAsync(); |
|
} |
|
|
|
[Theory, BitAutoData] |
|
public async Task GetOrganizationAbilitiesAsync_SecondCall_UsesCachedValue( |
|
List<OrganizationAbility> organizationAbilities, |
|
SutProvider<VNextInMemoryApplicationCacheService> sutProvider) |
|
{ |
|
// Arrange |
|
sutProvider.GetDependency<IOrganizationRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(organizationAbilities); |
|
|
|
// Act |
|
var firstCall = await sutProvider.Sut.GetOrganizationAbilitiesAsync(); |
|
var secondCall = await sutProvider.Sut.GetOrganizationAbilitiesAsync(); |
|
|
|
// Assert |
|
Assert.Same(firstCall, secondCall); |
|
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).GetManyAbilitiesAsync(); |
|
} |
|
|
|
[Theory, BitAutoData] |
|
public async Task GetOrganizationAbilityAsync_ExistingId_ReturnsAbility( |
|
List<OrganizationAbility> organizationAbilities, |
|
SutProvider<VNextInMemoryApplicationCacheService> sutProvider) |
|
{ |
|
// Arrange |
|
var targetAbility = organizationAbilities.First(); |
|
sutProvider.GetDependency<IOrganizationRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(organizationAbilities); |
|
|
|
// Act |
|
var result = await sutProvider.Sut.GetOrganizationAbilityAsync(targetAbility.Id); |
|
|
|
// Assert |
|
Assert.Equal(targetAbility, result); |
|
} |
|
|
|
[Theory, BitAutoData] |
|
public async Task GetOrganizationAbilityAsync_NonExistingId_ReturnsNull( |
|
List<OrganizationAbility> organizationAbilities, |
|
Guid nonExistingId, |
|
SutProvider<VNextInMemoryApplicationCacheService> sutProvider) |
|
{ |
|
// Arrange |
|
sutProvider.GetDependency<IOrganizationRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(organizationAbilities); |
|
|
|
// Act |
|
var result = await sutProvider.Sut.GetOrganizationAbilityAsync(nonExistingId); |
|
|
|
// Assert |
|
Assert.Null(result); |
|
} |
|
|
|
[Theory, BitAutoData] |
|
public async Task GetProviderAbilitiesAsync_FirstCall_LoadsFromRepository( |
|
List<ProviderAbility> providerAbilities, |
|
SutProvider<VNextInMemoryApplicationCacheService> sutProvider) |
|
{ |
|
// Arrange |
|
sutProvider.GetDependency<IProviderRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(providerAbilities); |
|
|
|
// Act |
|
var result = await sutProvider.Sut.GetProviderAbilitiesAsync(); |
|
|
|
// Assert |
|
Assert.IsType<ConcurrentDictionary<Guid, ProviderAbility>>(result); |
|
Assert.Equal(providerAbilities.Count, result.Count); |
|
foreach (var ability in providerAbilities) |
|
{ |
|
Assert.True(result.TryGetValue(ability.Id, out var actualAbility)); |
|
Assert.Equal(ability, actualAbility); |
|
} |
|
await sutProvider.GetDependency<IProviderRepository>().Received(1).GetManyAbilitiesAsync(); |
|
} |
|
|
|
[Theory, BitAutoData] |
|
public async Task GetProviderAbilitiesAsync_SecondCall_UsesCachedValue( |
|
List<ProviderAbility> providerAbilities, |
|
SutProvider<VNextInMemoryApplicationCacheService> sutProvider) |
|
{ |
|
// Arrange |
|
sutProvider.GetDependency<IProviderRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(providerAbilities); |
|
|
|
// Act |
|
var firstCall = await sutProvider.Sut.GetProviderAbilitiesAsync(); |
|
var secondCall = await sutProvider.Sut.GetProviderAbilitiesAsync(); |
|
|
|
// Assert |
|
Assert.Same(firstCall, secondCall); |
|
await sutProvider.GetDependency<IProviderRepository>().Received(1).GetManyAbilitiesAsync(); |
|
} |
|
|
|
[Theory, BitAutoData] |
|
public async Task UpsertOrganizationAbilityAsync_NewOrganization_AddsToCache( |
|
Organization organization, |
|
List<OrganizationAbility> existingAbilities, |
|
SutProvider<VNextInMemoryApplicationCacheService> sutProvider) |
|
{ |
|
// Arrange |
|
sutProvider.GetDependency<IOrganizationRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(existingAbilities); |
|
await sutProvider.Sut.GetOrganizationAbilitiesAsync(); |
|
|
|
// Act |
|
await sutProvider.Sut.UpsertOrganizationAbilityAsync(organization); |
|
|
|
// Assert |
|
var result = await sutProvider.Sut.GetOrganizationAbilitiesAsync(); |
|
Assert.True(result.ContainsKey(organization.Id)); |
|
Assert.Equal(organization.Id, result[organization.Id].Id); |
|
} |
|
|
|
[Theory, BitAutoData] |
|
public async Task UpsertOrganizationAbilityAsync_ExistingOrganization_UpdatesCache( |
|
Organization organization, |
|
List<OrganizationAbility> existingAbilities, |
|
SutProvider<VNextInMemoryApplicationCacheService> sutProvider) |
|
{ |
|
// Arrange |
|
existingAbilities.Add(new OrganizationAbility { Id = organization.Id }); |
|
sutProvider.GetDependency<IOrganizationRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(existingAbilities); |
|
await sutProvider.Sut.GetOrganizationAbilitiesAsync(); |
|
|
|
// Act |
|
await sutProvider.Sut.UpsertOrganizationAbilityAsync(organization); |
|
|
|
// Assert |
|
var result = await sutProvider.Sut.GetOrganizationAbilitiesAsync(); |
|
Assert.True(result.ContainsKey(organization.Id)); |
|
Assert.Equal(organization.Id, result[organization.Id].Id); |
|
} |
|
|
|
[Theory, BitAutoData] |
|
public async Task UpsertProviderAbilityAsync_NewProvider_AddsToCache( |
|
Provider provider, |
|
List<ProviderAbility> existingAbilities, |
|
SutProvider<VNextInMemoryApplicationCacheService> sutProvider) |
|
{ |
|
// Arrange |
|
sutProvider.GetDependency<IProviderRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(existingAbilities); |
|
await sutProvider.Sut.GetProviderAbilitiesAsync(); |
|
|
|
// Act |
|
await sutProvider.Sut.UpsertProviderAbilityAsync(provider); |
|
|
|
// Assert |
|
var result = await sutProvider.Sut.GetProviderAbilitiesAsync(); |
|
Assert.True(result.ContainsKey(provider.Id)); |
|
Assert.Equal(provider.Id, result[provider.Id].Id); |
|
} |
|
|
|
[Theory, BitAutoData] |
|
public async Task DeleteOrganizationAbilityAsync_ExistingId_RemovesFromCache( |
|
List<OrganizationAbility> organizationAbilities, |
|
SutProvider<VNextInMemoryApplicationCacheService> sutProvider) |
|
{ |
|
// Arrange |
|
var targetAbility = organizationAbilities.First(); |
|
sutProvider.GetDependency<IOrganizationRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(organizationAbilities); |
|
await sutProvider.Sut.GetOrganizationAbilitiesAsync(); |
|
|
|
// Act |
|
await sutProvider.Sut.DeleteOrganizationAbilityAsync(targetAbility.Id); |
|
|
|
// Assert |
|
var result = await sutProvider.Sut.GetOrganizationAbilitiesAsync(); |
|
Assert.False(result.ContainsKey(targetAbility.Id)); |
|
} |
|
|
|
|
|
[Theory, BitAutoData] |
|
public async Task DeleteProviderAbilityAsync_ExistingId_RemovesFromCache( |
|
List<ProviderAbility> providerAbilities, |
|
SutProvider<VNextInMemoryApplicationCacheService> sutProvider) |
|
{ |
|
// Arrange |
|
var targetAbility = providerAbilities.First(); |
|
sutProvider.GetDependency<IProviderRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(providerAbilities); |
|
await sutProvider.Sut.GetProviderAbilitiesAsync(); |
|
|
|
// Act |
|
await sutProvider.Sut.DeleteProviderAbilityAsync(targetAbility.Id); |
|
|
|
// Assert |
|
var result = await sutProvider.Sut.GetProviderAbilitiesAsync(); |
|
Assert.False(result.ContainsKey(targetAbility.Id)); |
|
} |
|
|
|
[Theory, BitAutoData] |
|
public async Task ConcurrentAccess_GetOrganizationAbilities_ThreadSafe( |
|
List<OrganizationAbility> organizationAbilities, |
|
SutProvider<VNextInMemoryApplicationCacheService> sutProvider) |
|
{ |
|
// Arrange |
|
sutProvider.GetDependency<IOrganizationRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(organizationAbilities); |
|
|
|
var results = new ConcurrentBag<IDictionary<Guid, OrganizationAbility>>(); |
|
|
|
const int iterationCount = 100; |
|
|
|
|
|
// Act |
|
await Parallel.ForEachAsync( |
|
Enumerable.Range(0, iterationCount), |
|
async (_, _) => |
|
{ |
|
var result = await sutProvider.Sut.GetOrganizationAbilitiesAsync(); |
|
results.Add(result); |
|
}); |
|
|
|
// Assert |
|
var firstCall = results.First(); |
|
Assert.Equal(iterationCount, results.Count); |
|
Assert.All(results, result => Assert.Same(firstCall, result)); |
|
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).GetManyAbilitiesAsync(); |
|
} |
|
|
|
[Theory, BitAutoData] |
|
public async Task GetOrganizationAbilitiesAsync_AfterRefreshInterval_RefreshesFromRepository( |
|
List<OrganizationAbility> organizationAbilities, |
|
List<OrganizationAbility> updatedAbilities) |
|
{ |
|
// Arrange |
|
var sutProvider = new SutProvider<VNextInMemoryApplicationCacheService>() |
|
.WithFakeTimeProvider() |
|
.Create(); |
|
|
|
sutProvider.GetDependency<IOrganizationRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(organizationAbilities, updatedAbilities); |
|
|
|
var firstCall = await sutProvider.Sut.GetOrganizationAbilitiesAsync(); |
|
|
|
const int pastIntervalInMinutes = 11; |
|
SimulateTimeLapseAfterFirstCall(sutProvider, pastIntervalInMinutes); |
|
|
|
// Act |
|
var secondCall = await sutProvider.Sut.GetOrganizationAbilitiesAsync(); |
|
|
|
// Assert |
|
Assert.NotSame(firstCall, secondCall); |
|
Assert.Equal(updatedAbilities.Count, secondCall.Count); |
|
await sutProvider.GetDependency<IOrganizationRepository>().Received(2).GetManyAbilitiesAsync(); |
|
} |
|
|
|
[Theory, BitAutoData] |
|
public async Task GetProviderAbilitiesAsync_AfterRefreshInterval_RefreshesFromRepository( |
|
List<ProviderAbility> providerAbilities, |
|
List<ProviderAbility> updatedAbilities) |
|
{ |
|
// Arrange |
|
var sutProvider = new SutProvider<VNextInMemoryApplicationCacheService>() |
|
.WithFakeTimeProvider() |
|
.Create(); |
|
|
|
sutProvider.GetDependency<IProviderRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(providerAbilities, updatedAbilities); |
|
|
|
var firstCall = await sutProvider.Sut.GetProviderAbilitiesAsync(); |
|
const int pastIntervalMinutes = 15; |
|
SimulateTimeLapseAfterFirstCall(sutProvider, pastIntervalMinutes); |
|
|
|
// Act |
|
var secondCall = await sutProvider.Sut.GetProviderAbilitiesAsync(); |
|
|
|
// Assert |
|
Assert.NotSame(firstCall, secondCall); |
|
Assert.Equal(updatedAbilities.Count, secondCall.Count); |
|
await sutProvider.GetDependency<IProviderRepository>().Received(2).GetManyAbilitiesAsync(); |
|
} |
|
|
|
public static IEnumerable<object[]> WhenCacheIsWithinIntervalTestCases => |
|
[ |
|
[5, 1], |
|
[10, 1], |
|
]; |
|
|
|
[Theory] |
|
[BitMemberAutoData(nameof(WhenCacheIsWithinIntervalTestCases))] |
|
public async Task GetOrganizationAbilitiesAsync_WhenCacheIsWithinInterval( |
|
int pastIntervalInMinutes, |
|
int expectCacheHit, |
|
List<OrganizationAbility> organizationAbilities) |
|
{ |
|
// Arrange |
|
var sutProvider = new SutProvider<VNextInMemoryApplicationCacheService>() |
|
.WithFakeTimeProvider() |
|
.Create(); |
|
|
|
sutProvider.GetDependency<IOrganizationRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(organizationAbilities); |
|
|
|
var firstCall = await sutProvider.Sut.GetOrganizationAbilitiesAsync(); |
|
|
|
SimulateTimeLapseAfterFirstCall(sutProvider, pastIntervalInMinutes); |
|
|
|
// Act |
|
var secondCall = await sutProvider.Sut.GetOrganizationAbilitiesAsync(); |
|
|
|
// Assert |
|
Assert.Same(firstCall, secondCall); |
|
Assert.Equal(organizationAbilities.Count, secondCall.Count); |
|
await sutProvider.GetDependency<IOrganizationRepository>().Received(expectCacheHit).GetManyAbilitiesAsync(); |
|
} |
|
|
|
[Theory] |
|
[BitMemberAutoData(nameof(WhenCacheIsWithinIntervalTestCases))] |
|
public async Task GetProviderAbilitiesAsync_WhenCacheIsWithinInterval( |
|
int pastIntervalInMinutes, |
|
int expectCacheHit, |
|
List<ProviderAbility> providerAbilities) |
|
{ |
|
// Arrange |
|
var sutProvider = new SutProvider<VNextInMemoryApplicationCacheService>() |
|
.WithFakeTimeProvider() |
|
.Create(); |
|
|
|
sutProvider.GetDependency<IProviderRepository>() |
|
.GetManyAbilitiesAsync() |
|
.Returns(providerAbilities); |
|
|
|
var firstCall = await sutProvider.Sut.GetProviderAbilitiesAsync(); |
|
|
|
SimulateTimeLapseAfterFirstCall(sutProvider, pastIntervalInMinutes); |
|
|
|
// Act |
|
var secondCall = await sutProvider.Sut.GetProviderAbilitiesAsync(); |
|
|
|
// Assert |
|
Assert.Same(firstCall, secondCall); |
|
Assert.Equal(providerAbilities.Count, secondCall.Count); |
|
await sutProvider.GetDependency<IProviderRepository>().Received(expectCacheHit).GetManyAbilitiesAsync(); |
|
} |
|
|
|
private static void SimulateTimeLapseAfterFirstCall(SutProvider<VNextInMemoryApplicationCacheService> sutProvider, int pastIntervalInMinutes) => |
|
sutProvider |
|
.GetDependency<FakeTimeProvider>() |
|
.Advance(TimeSpan.FromMinutes(pastIntervalInMinutes)); |
|
|
|
}
|
|
|