mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
Chore: document SutProvider and clean up UserServiceTests (#5879)
* UserServiceTests - use builder pattern for SutProvider to reduce boilerplate * SutProvider - add xmldoc
This commit is contained in:
@ -7,13 +7,10 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
@ -21,22 +18,15 @@ using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Fakes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Fido2NetLib;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
@ -179,9 +169,12 @@ public class UserServiceTests
|
||||
[Theory]
|
||||
[BitAutoData(DeviceType.UnknownBrowser, "Unknown Browser")]
|
||||
[BitAutoData(DeviceType.Android, "Android")]
|
||||
public async Task SendNewDeviceVerificationEmailAsync_DeviceMatches(DeviceType deviceType, string deviceTypeName, SutProvider<UserService> sutProvider, User user)
|
||||
public async Task SendNewDeviceVerificationEmailAsync_DeviceMatches(DeviceType deviceType, string deviceTypeName,
|
||||
User user)
|
||||
{
|
||||
SetupFakeTokenProvider(sutProvider, user);
|
||||
var sutProvider = new SutProvider<UserService>()
|
||||
.CreateWithUserServiceCustomizations(user);
|
||||
|
||||
var context = sutProvider.GetDependency<ICurrentContext>();
|
||||
context.DeviceType = deviceType;
|
||||
context.IpAddress = "1.1.1.1";
|
||||
@ -194,9 +187,11 @@ public class UserServiceTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SendNewDeviceVerificationEmailAsync_NullDeviceTypeShouldSendUnkownBrowserType(SutProvider<UserService> sutProvider, User user)
|
||||
public async Task SendNewDeviceVerificationEmailAsync_NullDeviceTypeShouldSendUnkownBrowserType(User user)
|
||||
{
|
||||
SetupFakeTokenProvider(sutProvider, user);
|
||||
var sutProvider = new SutProvider<UserService>()
|
||||
.CreateWithUserServiceCustomizations(user);
|
||||
|
||||
var context = sutProvider.GetDependency<ICurrentContext>();
|
||||
context.DeviceType = null;
|
||||
context.IpAddress = "1.1.1.1";
|
||||
@ -266,76 +261,28 @@ public class UserServiceTests
|
||||
[BitAutoData(true, "bad_test_password", false, ShouldCheck.Password | ShouldCheck.OTP)]
|
||||
public async Task VerifySecretAsync_Works(
|
||||
bool shouldHavePassword, string secret, bool expectedIsVerified, ShouldCheck shouldCheck, // inline theory data
|
||||
SutProvider<UserService> sutProvider, User user) // AutoFixture injected data
|
||||
User user) // AutoFixture injected data
|
||||
{
|
||||
// Arrange
|
||||
var tokenProvider = SetupFakeTokenProvider(sutProvider, user);
|
||||
SetupUserAndDevice(user, shouldHavePassword);
|
||||
|
||||
var sutProvider = new SutProvider<UserService>()
|
||||
.CreateWithUserServiceCustomizations(user);
|
||||
|
||||
// Setup the fake password verification
|
||||
var substitutedUserPasswordStore = Substitute.For<IUserPasswordStore<User>>();
|
||||
substitutedUserPasswordStore
|
||||
sutProvider.GetDependency<IUserPasswordStore<User>>()
|
||||
.GetPasswordHashAsync(user, Arg.Any<CancellationToken>())
|
||||
.Returns((ci) =>
|
||||
{
|
||||
return Task.FromResult("hashed_test_password");
|
||||
});
|
||||
.Returns(Task.FromResult("hashed_test_password"));
|
||||
|
||||
sutProvider.SetDependency<IUserStore<User>>(substitutedUserPasswordStore, "store");
|
||||
|
||||
sutProvider.GetDependency<IPasswordHasher<User>>("passwordHasher")
|
||||
sutProvider.GetDependency<IPasswordHasher<User>>()
|
||||
.VerifyHashedPassword(user, "hashed_test_password", "test_password")
|
||||
.Returns((ci) =>
|
||||
{
|
||||
return PasswordVerificationResult.Success;
|
||||
});
|
||||
.Returns(PasswordVerificationResult.Success);
|
||||
|
||||
// HACK: SutProvider is being weird about not injecting the IPasswordHasher that I configured
|
||||
var sut = new UserService(
|
||||
sutProvider.GetDependency<IUserRepository>(),
|
||||
sutProvider.GetDependency<ICipherRepository>(),
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>(),
|
||||
sutProvider.GetDependency<IOrganizationRepository>(),
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>(),
|
||||
sutProvider.GetDependency<IMailService>(),
|
||||
sutProvider.GetDependency<IPushNotificationService>(),
|
||||
sutProvider.GetDependency<IUserStore<User>>(),
|
||||
sutProvider.GetDependency<IOptions<IdentityOptions>>(),
|
||||
sutProvider.GetDependency<IPasswordHasher<User>>(),
|
||||
sutProvider.GetDependency<IEnumerable<IUserValidator<User>>>(),
|
||||
sutProvider.GetDependency<IEnumerable<IPasswordValidator<User>>>(),
|
||||
sutProvider.GetDependency<ILookupNormalizer>(),
|
||||
sutProvider.GetDependency<IdentityErrorDescriber>(),
|
||||
sutProvider.GetDependency<IServiceProvider>(),
|
||||
sutProvider.GetDependency<ILogger<UserManager<User>>>(),
|
||||
sutProvider.GetDependency<ILicensingService>(),
|
||||
sutProvider.GetDependency<IEventService>(),
|
||||
sutProvider.GetDependency<IApplicationCacheService>(),
|
||||
sutProvider.GetDependency<IDataProtectionProvider>(),
|
||||
sutProvider.GetDependency<IPaymentService>(),
|
||||
sutProvider.GetDependency<IPolicyRepository>(),
|
||||
sutProvider.GetDependency<IPolicyService>(),
|
||||
sutProvider.GetDependency<IFido2>(),
|
||||
sutProvider.GetDependency<ICurrentContext>(),
|
||||
sutProvider.GetDependency<IGlobalSettings>(),
|
||||
sutProvider.GetDependency<IAcceptOrgUserCommand>(),
|
||||
sutProvider.GetDependency<IProviderUserRepository>(),
|
||||
sutProvider.GetDependency<IStripeSyncService>(),
|
||||
new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>(),
|
||||
sutProvider.GetDependency<IFeatureService>(),
|
||||
sutProvider.GetDependency<IPremiumUserBillingService>(),
|
||||
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
|
||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(),
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>(),
|
||||
sutProvider.GetDependency<IDistributedCache>(),
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
);
|
||||
|
||||
var actualIsVerified = await sut.VerifySecretAsync(user, secret);
|
||||
var actualIsVerified = await sutProvider.Sut.VerifySecretAsync(user, secret);
|
||||
|
||||
Assert.Equal(expectedIsVerified, actualIsVerified);
|
||||
|
||||
await tokenProvider
|
||||
await sutProvider.GetDependency<IUserTwoFactorTokenProvider<User>>()
|
||||
.Received(shouldCheck.HasFlag(ShouldCheck.OTP) ? 1 : 0)
|
||||
.ValidateAsync(Arg.Any<string>(), secret, Arg.Any<UserManager<User>>(), user);
|
||||
|
||||
@ -661,26 +608,25 @@ public class UserServiceTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ResendNewDeviceVerificationEmail_SendsToken_Success(
|
||||
SutProvider<UserService> sutProvider, User user)
|
||||
public async Task ResendNewDeviceVerificationEmail_SendsToken_Success(User user)
|
||||
{
|
||||
// Arrange
|
||||
var testPassword = "test_password";
|
||||
var tokenProvider = SetupFakeTokenProvider(sutProvider, user);
|
||||
SetupUserAndDevice(user, true);
|
||||
|
||||
var sutProvider = new SutProvider<UserService>()
|
||||
.CreateWithUserServiceCustomizations(user);
|
||||
|
||||
// Setup the fake password verification
|
||||
var substitutedUserPasswordStore = Substitute.For<IUserPasswordStore<User>>();
|
||||
substitutedUserPasswordStore
|
||||
sutProvider
|
||||
.GetDependency<IUserPasswordStore<User>>()
|
||||
.GetPasswordHashAsync(user, Arg.Any<CancellationToken>())
|
||||
.Returns((ci) =>
|
||||
{
|
||||
return Task.FromResult("hashed_test_password");
|
||||
});
|
||||
|
||||
sutProvider.SetDependency<IUserStore<User>>(substitutedUserPasswordStore, "store");
|
||||
|
||||
sutProvider.GetDependency<IPasswordHasher<User>>("passwordHasher")
|
||||
sutProvider.GetDependency<IPasswordHasher<User>>()
|
||||
.VerifyHashedPassword(user, "hashed_test_password", testPassword)
|
||||
.Returns((ci) =>
|
||||
{
|
||||
@ -695,10 +641,7 @@ public class UserServiceTests
|
||||
context.DeviceType = DeviceType.Android;
|
||||
context.IpAddress = "1.1.1.1";
|
||||
|
||||
// HACK: SutProvider is being weird about not injecting the IPasswordHasher that I configured
|
||||
var sut = RebuildSut(sutProvider);
|
||||
|
||||
await sut.ResendNewDeviceVerificationEmail(user.Email, testPassword);
|
||||
await sutProvider.Sut.ResendNewDeviceVerificationEmail(user.Email, testPassword);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
@ -842,8 +785,15 @@ public class UserServiceTests
|
||||
user.MasterPassword = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IUserTwoFactorTokenProvider<User> SetupFakeTokenProvider(SutProvider<UserService> sutProvider, User user)
|
||||
public static class UserServiceSutProviderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Arranges a fake token provider. Must call as part of a builder pattern that ends in Create(), as it modifies
|
||||
/// the SutProvider build chain.
|
||||
/// </summary>
|
||||
private static SutProvider<UserService> SetFakeTokenProvider(this SutProvider<UserService> sutProvider, User user)
|
||||
{
|
||||
var fakeUserTwoFactorProvider = Substitute.For<IUserTwoFactorTokenProvider<User>>();
|
||||
|
||||
@ -859,8 +809,11 @@ public class UserServiceTests
|
||||
.ValidateAsync(Arg.Any<string>(), "otp_token", Arg.Any<UserManager<User>>(), user)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOptions<IdentityOptions>>()
|
||||
.Value.Returns(new IdentityOptions
|
||||
var fakeIdentityOptions = Substitute.For<IOptions<IdentityOptions>>();
|
||||
|
||||
fakeIdentityOptions
|
||||
.Value
|
||||
.Returns(new IdentityOptions
|
||||
{
|
||||
Tokens = new TokenOptions
|
||||
{
|
||||
@ -874,54 +827,54 @@ public class UserServiceTests
|
||||
}
|
||||
});
|
||||
|
||||
// The above arranging of dependencies is used in the constructor of UserManager
|
||||
// ref: https://github.com/dotnet/aspnetcore/blob/bfeb3bf9005c36b081d1e48725531ee0e15a9dfb/src/Identity/Extensions.Core/src/UserManager.cs#L103-L120
|
||||
// since the constructor of the Sut has ran already (when injected) I need to recreate it to get it to run again
|
||||
sutProvider.Create();
|
||||
sutProvider.SetDependency(fakeIdentityOptions);
|
||||
// Also set the fake provider dependency so that we can retrieve it easily via GetDependency
|
||||
sutProvider.SetDependency(fakeUserTwoFactorProvider);
|
||||
|
||||
return fakeUserTwoFactorProvider;
|
||||
return sutProvider;
|
||||
}
|
||||
|
||||
private IUserService RebuildSut(SutProvider<UserService> sutProvider)
|
||||
/// <summary>
|
||||
/// Properly registers IUserPasswordStore as IUserStore so it's injected when the sut is initialized.
|
||||
/// </summary>
|
||||
/// <param name="sutProvider"></param>
|
||||
/// <returns></returns>
|
||||
private static SutProvider<UserService> SetUserPasswordStore(this SutProvider<UserService> sutProvider)
|
||||
{
|
||||
return new UserService(
|
||||
sutProvider.GetDependency<IUserRepository>(),
|
||||
sutProvider.GetDependency<ICipherRepository>(),
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>(),
|
||||
sutProvider.GetDependency<IOrganizationRepository>(),
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>(),
|
||||
sutProvider.GetDependency<IMailService>(),
|
||||
sutProvider.GetDependency<IPushNotificationService>(),
|
||||
sutProvider.GetDependency<IUserStore<User>>(),
|
||||
sutProvider.GetDependency<IOptions<IdentityOptions>>(),
|
||||
sutProvider.GetDependency<IPasswordHasher<User>>(),
|
||||
sutProvider.GetDependency<IEnumerable<IUserValidator<User>>>(),
|
||||
sutProvider.GetDependency<IEnumerable<IPasswordValidator<User>>>(),
|
||||
sutProvider.GetDependency<ILookupNormalizer>(),
|
||||
sutProvider.GetDependency<IdentityErrorDescriber>(),
|
||||
sutProvider.GetDependency<IServiceProvider>(),
|
||||
sutProvider.GetDependency<ILogger<UserManager<User>>>(),
|
||||
sutProvider.GetDependency<ILicensingService>(),
|
||||
sutProvider.GetDependency<IEventService>(),
|
||||
sutProvider.GetDependency<IApplicationCacheService>(),
|
||||
sutProvider.GetDependency<IDataProtectionProvider>(),
|
||||
sutProvider.GetDependency<IPaymentService>(),
|
||||
sutProvider.GetDependency<IPolicyRepository>(),
|
||||
sutProvider.GetDependency<IPolicyService>(),
|
||||
sutProvider.GetDependency<IFido2>(),
|
||||
sutProvider.GetDependency<ICurrentContext>(),
|
||||
sutProvider.GetDependency<IGlobalSettings>(),
|
||||
sutProvider.GetDependency<IAcceptOrgUserCommand>(),
|
||||
sutProvider.GetDependency<IProviderUserRepository>(),
|
||||
sutProvider.GetDependency<IStripeSyncService>(),
|
||||
new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>(),
|
||||
sutProvider.GetDependency<IFeatureService>(),
|
||||
sutProvider.GetDependency<IPremiumUserBillingService>(),
|
||||
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
|
||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(),
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>(),
|
||||
sutProvider.GetDependency<IDistributedCache>(),
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
);
|
||||
var substitutedUserPasswordStore = Substitute.For<IUserPasswordStore<User>>();
|
||||
|
||||
// IUserPasswordStore must be registered under the IUserStore parameter to be properly injected
|
||||
// because this is what the constructor expects
|
||||
sutProvider.SetDependency<IUserStore<User>>(substitutedUserPasswordStore);
|
||||
|
||||
// Also store it under its own type for retrieval and configuration
|
||||
sutProvider.SetDependency(substitutedUserPasswordStore);
|
||||
|
||||
return sutProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a hack: when autofixture initializes the sut in sutProvider, it overwrites the public
|
||||
/// PasswordHasher property with a new substitute, so it loses the configured sutProvider mock.
|
||||
/// This doesn't usually happen because our dependencies are not usually public.
|
||||
/// Call this AFTER SutProvider.Create().
|
||||
/// </summary>
|
||||
private static SutProvider<UserService> FixPasswordHasherBug(this SutProvider<UserService> sutProvider)
|
||||
{
|
||||
// Get the configured sutProvider mock and assign it back to the public property in the base class
|
||||
sutProvider.Sut.PasswordHasher = sutProvider.GetDependency<IPasswordHasher<User>>();
|
||||
return sutProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A helper that combines all SutProvider configuration usually required for UserService.
|
||||
/// Call this instead of SutProvider.Create, after any additional configuration your test needs.
|
||||
/// </summary>
|
||||
public static SutProvider<UserService> CreateWithUserServiceCustomizations(this SutProvider<UserService> sutProvider, User user)
|
||||
=> sutProvider
|
||||
.SetUserPasswordStore()
|
||||
.SetFakeTokenProvider(user)
|
||||
.Create()
|
||||
.FixPasswordHasherBug();
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user