mirror of
https://github.com/bitwarden/server
synced 2025-12-06 00:03:34 +00:00
[PM-23845] Update cache service to handle concurrency (#6170)
This commit is contained in:
@@ -0,0 +1,403 @@
|
||||
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));
|
||||
|
||||
}
|
||||
@@ -0,0 +1,541 @@
|
||||
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.Core.Services;
|
||||
using Bit.Core.Services.Implementations;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using GlobalSettings = Bit.Core.Settings.GlobalSettings;
|
||||
|
||||
namespace Bit.Core.Test.Services.Implementations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class FeatureRoutedCacheServiceTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetOrganizationAbilitiesAsync_WhenFeatureIsEnabled_ReturnsFromVNextService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
IDictionary<Guid, OrganizationAbility> expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.GetOrganizationAbilitiesAsync()
|
||||
.Returns(expectedResult);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetOrganizationAbilitiesAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, result);
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.GetOrganizationAbilitiesAsync();
|
||||
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.GetOrganizationAbilitiesAsync();
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetOrganizationAbilitiesAsync_WhenFeatureIsDisabled_ReturnsFromInMemoryService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
IDictionary<Guid, OrganizationAbility> expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(false);
|
||||
sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.GetOrganizationAbilitiesAsync()
|
||||
.Returns(expectedResult);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetOrganizationAbilitiesAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, result);
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.GetOrganizationAbilitiesAsync();
|
||||
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.GetOrganizationAbilitiesAsync();
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetOrganizationAbilityAsync_WhenFeatureIsEnabled_ReturnsFromVNextService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
Guid orgId,
|
||||
OrganizationAbility expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.GetOrganizationAbilityAsync(orgId)
|
||||
.Returns(expectedResult);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetOrganizationAbilityAsync(orgId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, result);
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.GetOrganizationAbilityAsync(orgId);
|
||||
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.GetOrganizationAbilityAsync(orgId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetOrganizationAbilityAsync_WhenFeatureIsDisabled_ReturnsFromInMemoryService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
Guid orgId,
|
||||
OrganizationAbility expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.GetOrganizationAbilityAsync(orgId)
|
||||
.Returns(expectedResult);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetOrganizationAbilityAsync(orgId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, result);
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.GetOrganizationAbilityAsync(orgId);
|
||||
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.GetOrganizationAbilityAsync(orgId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetProviderAbilitiesAsync_WhenFeatureIsEnabled_ReturnsFromVNextService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
IDictionary<Guid, ProviderAbility> expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.GetProviderAbilitiesAsync()
|
||||
.Returns(expectedResult);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetProviderAbilitiesAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, result);
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.GetProviderAbilitiesAsync();
|
||||
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.GetProviderAbilitiesAsync();
|
||||
}
|
||||
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetProviderAbilitiesAsync_WhenFeatureIsDisabled_ReturnsFromInMemoryService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
IDictionary<Guid, ProviderAbility> expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(false);
|
||||
sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.GetProviderAbilitiesAsync()
|
||||
.Returns(expectedResult);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetProviderAbilitiesAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, result);
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.GetProviderAbilitiesAsync();
|
||||
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.GetProviderAbilitiesAsync();
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpsertOrganizationAbilityAsync_WhenFeatureIsEnabled_CallsVNextService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpsertOrganizationAbilityAsync(organization);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.UpsertOrganizationAbilityAsync(organization);
|
||||
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.GetProviderAbilitiesAsync();
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpsertOrganizationAbilityAsync_WhenFeatureIsDisabled_CallsInMemoryService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpsertOrganizationAbilityAsync(organization);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.UpsertOrganizationAbilityAsync(organization);
|
||||
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.GetProviderAbilitiesAsync();
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpsertProviderAbilityAsync_WhenFeatureIsEnabled_CallsVNextService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
Provider provider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpsertProviderAbilityAsync(provider);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.UpsertProviderAbilityAsync(provider);
|
||||
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.UpsertProviderAbilityAsync(provider);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpsertProviderAbilityAsync_WhenFeatureIsDisabled_CallsInMemoryService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
Provider provider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpsertProviderAbilityAsync(provider);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.UpsertProviderAbilityAsync(provider);
|
||||
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.UpsertProviderAbilityAsync(provider);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteOrganizationAbilityAsync_WhenFeatureIsEnabled_CallsVNextService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
Guid organizationId)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.DeleteOrganizationAbilityAsync(organizationId);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.DeleteOrganizationAbilityAsync(organizationId);
|
||||
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.DeleteOrganizationAbilityAsync(organizationId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteOrganizationAbilityAsync_WhenFeatureIsDisabled_CallsInMemoryService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
Guid organizationId)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.DeleteOrganizationAbilityAsync(organizationId);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.DeleteOrganizationAbilityAsync(organizationId);
|
||||
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.DeleteOrganizationAbilityAsync(organizationId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteProviderAbilityAsync_WhenFeatureIsEnabled_CallsVNextService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
Guid providerId)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.DeleteProviderAbilityAsync(providerId);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.DeleteProviderAbilityAsync(providerId);
|
||||
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.DeleteProviderAbilityAsync(providerId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteProviderAbilityAsync_WhenFeatureIsDisabled_CallsInMemoryService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
Guid providerId)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.DeleteProviderAbilityAsync(providerId);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.DeleteProviderAbilityAsync(providerId);
|
||||
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.DeleteProviderAbilityAsync(providerId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BaseUpsertOrganizationAbilityAsync_WhenFeatureIsEnabled_CallsVNextService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.BaseUpsertOrganizationAbilityAsync(organization);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.UpsertOrganizationAbilityAsync(organization);
|
||||
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.UpsertOrganizationAbilityAsync(organization);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BaseUpsertOrganizationAbilityAsync_WhenFeatureIsDisabled_CallsServiceBusCache(
|
||||
Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
var featureService = Substitute.For<IFeatureService>();
|
||||
|
||||
var currentCacheService = CreateCurrentCacheMockService();
|
||||
|
||||
featureService
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(false);
|
||||
|
||||
var sutProvider = Substitute.For<FeatureRoutedCacheService>(
|
||||
featureService,
|
||||
Substitute.For<IVNextInMemoryApplicationCacheService>(),
|
||||
currentCacheService,
|
||||
Substitute.For<IApplicationCacheServiceBusMessaging>());
|
||||
|
||||
// Act
|
||||
await sutProvider.BaseUpsertOrganizationAbilityAsync(organization);
|
||||
|
||||
// Assert
|
||||
await currentCacheService
|
||||
.Received(1)
|
||||
.BaseUpsertOrganizationAbilityAsync(organization);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Our SUT is using a method that is not part of the IVCurrentInMemoryApplicationCacheService,
|
||||
/// so AutoFixture’s auto-created mock won’t work.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static InMemoryServiceBusApplicationCacheService CreateCurrentCacheMockService()
|
||||
{
|
||||
var currentCacheService = Substitute.For<InMemoryServiceBusApplicationCacheService>(
|
||||
Substitute.For<IOrganizationRepository>(),
|
||||
Substitute.For<IProviderRepository>(),
|
||||
new GlobalSettings
|
||||
{
|
||||
ProjectName = "BitwardenTest",
|
||||
ServiceBus = new GlobalSettings.ServiceBusSettings
|
||||
{
|
||||
ConnectionString = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=test;SharedAccessKey=test",
|
||||
ApplicationCacheTopicName = "test-topic",
|
||||
ApplicationCacheSubscriptionName = "test-subscription"
|
||||
}
|
||||
});
|
||||
return currentCacheService;
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BaseUpsertOrganizationAbilityAsync_WhenFeatureIsDisabled_AndServiceIsNotServiceBusCache_ThrowsException(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => sutProvider.Sut.BaseUpsertOrganizationAbilityAsync(organization));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
ExpectedErrorMessage,
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
private static string ExpectedErrorMessage
|
||||
{
|
||||
get => "Expected inMemoryApplicationCacheService to be of type InMemoryServiceBusApplicationCacheService";
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BaseDeleteOrganizationAbilityAsync_WhenFeatureIsEnabled_CallsVNextService(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
Guid organizationId)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.BaseDeleteOrganizationAbilityAsync(organizationId);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IVNextInMemoryApplicationCacheService>()
|
||||
.Received(1)
|
||||
.DeleteOrganizationAbilityAsync(organizationId);
|
||||
|
||||
await sutProvider.GetDependency<IVCurrentInMemoryApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.DeleteOrganizationAbilityAsync(organizationId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task BaseDeleteOrganizationAbilityAsync_WhenFeatureIsDisabled_CallsServiceBusCache(
|
||||
Guid organizationId)
|
||||
{
|
||||
// Arrange
|
||||
var featureService = Substitute.For<IFeatureService>();
|
||||
|
||||
var currentCacheService = CreateCurrentCacheMockService();
|
||||
|
||||
featureService
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(false);
|
||||
|
||||
var sutProvider = Substitute.For<FeatureRoutedCacheService>(
|
||||
featureService,
|
||||
Substitute.For<IVNextInMemoryApplicationCacheService>(),
|
||||
currentCacheService,
|
||||
Substitute.For<IApplicationCacheServiceBusMessaging>());
|
||||
|
||||
// Act
|
||||
await sutProvider.BaseDeleteOrganizationAbilityAsync(organizationId);
|
||||
|
||||
// Assert
|
||||
await currentCacheService
|
||||
.Received(1)
|
||||
.BaseDeleteOrganizationAbilityAsync(organizationId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task
|
||||
BaseDeleteOrganizationAbilityAsync_WhenFeatureIsDisabled_AndServiceIsNotServiceBusCache_ThrowsException(
|
||||
SutProvider<FeatureRoutedCacheService> sutProvider,
|
||||
Guid organizationId)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
sutProvider.Sut.BaseDeleteOrganizationAbilityAsync(organizationId));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
ExpectedErrorMessage,
|
||||
ex.Message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user