1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 07:36:14 -05:00

Revert filescoped (#2227)

* Revert "Add git blame entry (#2226)"

This reverts commit 239286737d.

* Revert "Turn on file scoped namespaces (#2225)"

This reverts commit 34fb4cca2a.
This commit is contained in:
Justin Baur
2022-08-29 15:53:48 -04:00
committed by GitHub
parent 239286737d
commit bae03feffe
1208 changed files with 74317 additions and 73126 deletions

View File

@ -3,41 +3,42 @@ using Bit.IntegrationTestCommon.Factories;
using IdentityServer4.AccessTokenValidation;
using Microsoft.AspNetCore.TestHost;
namespace Bit.Api.IntegrationTest.Factories;
public class ApiApplicationFactory : WebApplicationFactoryBase<Startup>
namespace Bit.Api.IntegrationTest.Factories
{
private readonly IdentityApplicationFactory _identityApplicationFactory;
public ApiApplicationFactory()
public class ApiApplicationFactory : WebApplicationFactoryBase<Startup>
{
_identityApplicationFactory = new IdentityApplicationFactory();
}
private readonly IdentityApplicationFactory _identityApplicationFactory;
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(services =>
public ApiApplicationFactory()
{
services.PostConfigure<IdentityServerAuthenticationOptions>(IdentityServerAuthenticationDefaults.AuthenticationScheme, options =>
_identityApplicationFactory = new IdentityApplicationFactory();
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(services =>
{
options.JwtBackChannelHandler = _identityApplicationFactory.Server.CreateHandler();
services.PostConfigure<IdentityServerAuthenticationOptions>(IdentityServerAuthenticationDefaults.AuthenticationScheme, options =>
{
options.JwtBackChannelHandler = _identityApplicationFactory.Server.CreateHandler();
});
});
});
}
}
/// <summary>
/// Helper for registering and logging in to a new account
/// </summary>
public async Task<(string Token, string RefreshToken)> LoginWithNewAccount(string email = "integration-test@bitwarden.com", string masterPasswordHash = "master_password_hash")
{
await _identityApplicationFactory.RegisterAsync(new RegisterRequestModel
/// <summary>
/// Helper for registering and logging in to a new account
/// </summary>
public async Task<(string Token, string RefreshToken)> LoginWithNewAccount(string email = "integration-test@bitwarden.com", string masterPasswordHash = "master_password_hash")
{
Email = email,
MasterPasswordHash = masterPasswordHash,
});
await _identityApplicationFactory.RegisterAsync(new RegisterRequestModel
{
Email = email,
MasterPasswordHash = masterPasswordHash,
});
return await _identityApplicationFactory.TokenFromPasswordAsync(email, masterPasswordHash);
return await _identityApplicationFactory.TokenFromPasswordAsync(email, masterPasswordHash);
}
}
}

View File

@ -13,412 +13,413 @@ using Microsoft.AspNetCore.Identity;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Controllers;
public class AccountsControllerTests : IDisposable
namespace Bit.Api.Test.Controllers
{
private readonly AccountsController _sut;
private readonly GlobalSettings _globalSettings;
private readonly ICipherRepository _cipherRepository;
private readonly IFolderRepository _folderRepository;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IPaymentService _paymentService;
private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
private readonly ISendRepository _sendRepository;
private readonly ISendService _sendService;
private readonly IProviderUserRepository _providerUserRepository;
public AccountsControllerTests()
public class AccountsControllerTests : IDisposable
{
_userService = Substitute.For<IUserService>();
_userRepository = Substitute.For<IUserRepository>();
_cipherRepository = Substitute.For<ICipherRepository>();
_folderRepository = Substitute.For<IFolderRepository>();
_organizationService = Substitute.For<IOrganizationService>();
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_providerUserRepository = Substitute.For<IProviderUserRepository>();
_paymentService = Substitute.For<IPaymentService>();
_globalSettings = new GlobalSettings();
_sendRepository = Substitute.For<ISendRepository>();
_sendService = Substitute.For<ISendService>();
_sut = new AccountsController(
_globalSettings,
_cipherRepository,
_folderRepository,
_organizationService,
_organizationUserRepository,
_providerUserRepository,
_paymentService,
_userRepository,
_userService,
_sendRepository,
_sendService
);
}
public void Dispose()
{
_sut?.Dispose();
}
private readonly AccountsController _sut;
private readonly GlobalSettings _globalSettings;
private readonly ICipherRepository _cipherRepository;
private readonly IFolderRepository _folderRepository;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IPaymentService _paymentService;
private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
private readonly ISendRepository _sendRepository;
private readonly ISendService _sendService;
private readonly IProviderUserRepository _providerUserRepository;
[Fact]
public async Task PostPrelogin_WhenUserExists_ShouldReturnUserKdfInfo()
{
var userKdfInfo = new UserKdfInformation
public AccountsControllerTests()
{
Kdf = KdfType.PBKDF2_SHA256,
KdfIterations = 5000
};
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult(userKdfInfo));
_userService = Substitute.For<IUserService>();
_userRepository = Substitute.For<IUserRepository>();
_cipherRepository = Substitute.For<ICipherRepository>();
_folderRepository = Substitute.For<IFolderRepository>();
_organizationService = Substitute.For<IOrganizationService>();
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_providerUserRepository = Substitute.For<IProviderUserRepository>();
_paymentService = Substitute.For<IPaymentService>();
_globalSettings = new GlobalSettings();
_sendRepository = Substitute.For<ISendRepository>();
_sendService = Substitute.For<ISendService>();
_sut = new AccountsController(
_globalSettings,
_cipherRepository,
_folderRepository,
_organizationService,
_organizationUserRepository,
_providerUserRepository,
_paymentService,
_userRepository,
_userService,
_sendRepository,
_sendService
);
}
var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" });
Assert.Equal(userKdfInfo.Kdf, response.Kdf);
Assert.Equal(userKdfInfo.KdfIterations, response.KdfIterations);
}
[Fact]
public async Task PostPrelogin_WhenUserDoesNotExist_ShouldDefaultToSha256And100000Iterations()
{
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult((UserKdfInformation)null));
var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" });
Assert.Equal(KdfType.PBKDF2_SHA256, response.Kdf);
Assert.Equal(100000, response.KdfIterations);
}
[Fact]
public async Task PostRegister_ShouldRegisterUser()
{
var passwordHash = "abcdef";
var token = "123456";
var userGuid = new Guid();
_userService.RegisterUserAsync(Arg.Any<User>(), passwordHash, token, userGuid)
.Returns(Task.FromResult(IdentityResult.Success));
var request = new RegisterRequestModel
public void Dispose()
{
Name = "Example User",
Email = "user@example.com",
MasterPasswordHash = passwordHash,
MasterPasswordHint = "example",
Token = token,
OrganizationUserId = userGuid
};
_sut?.Dispose();
}
await _sut.PostRegister(request);
await _userService.Received(1).RegisterUserAsync(Arg.Any<User>(), passwordHash, token, userGuid);
}
[Fact]
public async Task PostRegister_WhenUserServiceFails_ShouldThrowBadRequestException()
{
var passwordHash = "abcdef";
var token = "123456";
var userGuid = new Guid();
_userService.RegisterUserAsync(Arg.Any<User>(), passwordHash, token, userGuid)
.Returns(Task.FromResult(IdentityResult.Failed()));
var request = new RegisterRequestModel
[Fact]
public async Task PostPrelogin_WhenUserExists_ShouldReturnUserKdfInfo()
{
Name = "Example User",
Email = "user@example.com",
MasterPasswordHash = passwordHash,
MasterPasswordHint = "example",
Token = token,
OrganizationUserId = userGuid
};
var userKdfInfo = new UserKdfInformation
{
Kdf = KdfType.PBKDF2_SHA256,
KdfIterations = 5000
};
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult(userKdfInfo));
await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostRegister(request));
}
var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" });
[Fact]
public async Task PostPasswordHint_ShouldNotifyUserService()
{
var email = "user@example.com";
Assert.Equal(userKdfInfo.Kdf, response.Kdf);
Assert.Equal(userKdfInfo.KdfIterations, response.KdfIterations);
}
await _sut.PostPasswordHint(new PasswordHintRequestModel { Email = email });
await _userService.Received(1).SendMasterPasswordHintAsync(email);
}
[Fact]
public async Task PostEmailToken_ShouldInitiateEmailChange()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user);
var newEmail = "example@user.com";
await _sut.PostEmailToken(new EmailTokenRequestModel { NewEmail = newEmail });
await _userService.Received(1).InitiateEmailChangeAsync(user, newEmail);
}
[Fact]
public async Task PostEmailToken_WhenNotAuthorized_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostEmailToken(new EmailTokenRequestModel())
);
}
[Fact]
public async Task PostEmailToken_WhenInvalidPasssword_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToRejectPasswordFor(user);
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostEmailToken(new EmailTokenRequestModel())
);
}
[Fact]
public async Task PostEmail_ShouldChangeUserEmail()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangeEmailAsync(user, default, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Success));
await _sut.PostEmail(new EmailRequestModel());
await _userService.Received(1).ChangeEmailAsync(user, default, default, default, default, default);
}
[Fact]
public async Task PostEmail_WhenNotAuthorized_ShouldThrownUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostEmail(new EmailRequestModel())
);
}
[Fact]
public async Task PostEmail_WhenEmailCannotBeChanged_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangeEmailAsync(user, default, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Failed()));
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostEmail(new EmailRequestModel())
);
}
[Fact]
public async Task PostVerifyEmail_ShouldSendEmailVerification()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
await _sut.PostVerifyEmail();
await _userService.Received(1).SendEmailVerificationAsync(user);
}
[Fact]
public async Task PostVerifyEmail_WhenNotAuthorized_ShouldThrownUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostVerifyEmail()
);
}
[Fact]
public async Task PostVerifyEmailToken_ShouldConfirmEmail()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidIdFor(user);
_userService.ConfirmEmailAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(IdentityResult.Success));
await _sut.PostVerifyEmailToken(new VerifyEmailRequestModel { UserId = "12345678-1234-1234-1234-123456789012" });
await _userService.Received(1).ConfirmEmailAsync(user, Arg.Any<string>());
}
[Fact]
public async Task PostVerifyEmailToken_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnNullUserId();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostVerifyEmailToken(new VerifyEmailRequestModel { UserId = "12345678-1234-1234-1234-123456789012" })
);
}
[Fact]
public async Task PostVerifyEmailToken_WhenEmailConfirmationFails_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidIdFor(user);
_userService.ConfirmEmailAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(IdentityResult.Failed()));
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostVerifyEmailToken(new VerifyEmailRequestModel { UserId = "12345678-1234-1234-1234-123456789012" })
);
}
[Fact]
public async Task PostPassword_ShouldChangePassword()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangePasswordAsync(user, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Success));
await _sut.PostPassword(new PasswordRequestModel());
await _userService.Received(1).ChangePasswordAsync(user, default, default, default, default);
}
[Fact]
public async Task PostPassword_WhenNotAuthorized_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostPassword(new PasswordRequestModel())
);
}
[Fact]
public async Task PostPassword_WhenPasswordChangeFails_ShouldBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangePasswordAsync(user, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Failed()));
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostPassword(new PasswordRequestModel())
);
}
[Fact]
public async Task GetApiKey_ShouldReturnApiKeyResponse()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user);
await _sut.ApiKey(new SecretVerificationRequestModel());
}
[Fact]
public async Task GetApiKey_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
[Fact]
public async Task GetApiKey_WhenPasswordCheckFails_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToRejectPasswordFor(user);
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
[Fact]
public async Task PostRotateApiKey_ShouldRotateApiKey()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user);
await _sut.RotateApiKey(new SecretVerificationRequestModel());
}
[Fact]
public async Task PostRotateApiKey_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
[Fact]
public async Task PostRotateApiKey_WhenPasswordCheckFails_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToRejectPasswordFor(user);
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
// Below are helper functions that currently belong to this
// test class, but ultimately may need to be split out into
// something greater in order to share common test steps with
// other test suites. They are included here for the time being
// until that day comes.
private User GenerateExampleUser()
{
return new User
[Fact]
public async Task PostPrelogin_WhenUserDoesNotExist_ShouldDefaultToSha256And100000Iterations()
{
Email = "user@example.com"
};
}
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult((UserKdfInformation)null));
private void ConfigureUserServiceToReturnNullPrincipal()
{
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(Task.FromResult((User)null));
}
var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" });
private void ConfigureUserServiceToReturnValidPrincipalFor(User user)
{
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(Task.FromResult(user));
}
Assert.Equal(KdfType.PBKDF2_SHA256, response.Kdf);
Assert.Equal(100000, response.KdfIterations);
}
private void ConfigureUserServiceToRejectPasswordFor(User user)
{
_userService.CheckPasswordAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(false));
}
[Fact]
public async Task PostRegister_ShouldRegisterUser()
{
var passwordHash = "abcdef";
var token = "123456";
var userGuid = new Guid();
_userService.RegisterUserAsync(Arg.Any<User>(), passwordHash, token, userGuid)
.Returns(Task.FromResult(IdentityResult.Success));
var request = new RegisterRequestModel
{
Name = "Example User",
Email = "user@example.com",
MasterPasswordHash = passwordHash,
MasterPasswordHint = "example",
Token = token,
OrganizationUserId = userGuid
};
private void ConfigureUserServiceToAcceptPasswordFor(User user)
{
_userService.CheckPasswordAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(true));
_userService.VerifySecretAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(true));
}
await _sut.PostRegister(request);
private void ConfigureUserServiceToReturnValidIdFor(User user)
{
_userService.GetUserByIdAsync(Arg.Any<Guid>())
.Returns(Task.FromResult(user));
}
await _userService.Received(1).RegisterUserAsync(Arg.Any<User>(), passwordHash, token, userGuid);
}
private void ConfigureUserServiceToReturnNullUserId()
{
_userService.GetUserByIdAsync(Arg.Any<Guid>())
.Returns(Task.FromResult((User)null));
[Fact]
public async Task PostRegister_WhenUserServiceFails_ShouldThrowBadRequestException()
{
var passwordHash = "abcdef";
var token = "123456";
var userGuid = new Guid();
_userService.RegisterUserAsync(Arg.Any<User>(), passwordHash, token, userGuid)
.Returns(Task.FromResult(IdentityResult.Failed()));
var request = new RegisterRequestModel
{
Name = "Example User",
Email = "user@example.com",
MasterPasswordHash = passwordHash,
MasterPasswordHint = "example",
Token = token,
OrganizationUserId = userGuid
};
await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostRegister(request));
}
[Fact]
public async Task PostPasswordHint_ShouldNotifyUserService()
{
var email = "user@example.com";
await _sut.PostPasswordHint(new PasswordHintRequestModel { Email = email });
await _userService.Received(1).SendMasterPasswordHintAsync(email);
}
[Fact]
public async Task PostEmailToken_ShouldInitiateEmailChange()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user);
var newEmail = "example@user.com";
await _sut.PostEmailToken(new EmailTokenRequestModel { NewEmail = newEmail });
await _userService.Received(1).InitiateEmailChangeAsync(user, newEmail);
}
[Fact]
public async Task PostEmailToken_WhenNotAuthorized_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostEmailToken(new EmailTokenRequestModel())
);
}
[Fact]
public async Task PostEmailToken_WhenInvalidPasssword_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToRejectPasswordFor(user);
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostEmailToken(new EmailTokenRequestModel())
);
}
[Fact]
public async Task PostEmail_ShouldChangeUserEmail()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangeEmailAsync(user, default, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Success));
await _sut.PostEmail(new EmailRequestModel());
await _userService.Received(1).ChangeEmailAsync(user, default, default, default, default, default);
}
[Fact]
public async Task PostEmail_WhenNotAuthorized_ShouldThrownUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostEmail(new EmailRequestModel())
);
}
[Fact]
public async Task PostEmail_WhenEmailCannotBeChanged_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangeEmailAsync(user, default, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Failed()));
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostEmail(new EmailRequestModel())
);
}
[Fact]
public async Task PostVerifyEmail_ShouldSendEmailVerification()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
await _sut.PostVerifyEmail();
await _userService.Received(1).SendEmailVerificationAsync(user);
}
[Fact]
public async Task PostVerifyEmail_WhenNotAuthorized_ShouldThrownUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostVerifyEmail()
);
}
[Fact]
public async Task PostVerifyEmailToken_ShouldConfirmEmail()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidIdFor(user);
_userService.ConfirmEmailAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(IdentityResult.Success));
await _sut.PostVerifyEmailToken(new VerifyEmailRequestModel { UserId = "12345678-1234-1234-1234-123456789012" });
await _userService.Received(1).ConfirmEmailAsync(user, Arg.Any<string>());
}
[Fact]
public async Task PostVerifyEmailToken_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnNullUserId();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostVerifyEmailToken(new VerifyEmailRequestModel { UserId = "12345678-1234-1234-1234-123456789012" })
);
}
[Fact]
public async Task PostVerifyEmailToken_WhenEmailConfirmationFails_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidIdFor(user);
_userService.ConfirmEmailAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(IdentityResult.Failed()));
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostVerifyEmailToken(new VerifyEmailRequestModel { UserId = "12345678-1234-1234-1234-123456789012" })
);
}
[Fact]
public async Task PostPassword_ShouldChangePassword()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangePasswordAsync(user, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Success));
await _sut.PostPassword(new PasswordRequestModel());
await _userService.Received(1).ChangePasswordAsync(user, default, default, default, default);
}
[Fact]
public async Task PostPassword_WhenNotAuthorized_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostPassword(new PasswordRequestModel())
);
}
[Fact]
public async Task PostPassword_WhenPasswordChangeFails_ShouldBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangePasswordAsync(user, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Failed()));
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostPassword(new PasswordRequestModel())
);
}
[Fact]
public async Task GetApiKey_ShouldReturnApiKeyResponse()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user);
await _sut.ApiKey(new SecretVerificationRequestModel());
}
[Fact]
public async Task GetApiKey_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
[Fact]
public async Task GetApiKey_WhenPasswordCheckFails_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToRejectPasswordFor(user);
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
[Fact]
public async Task PostRotateApiKey_ShouldRotateApiKey()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user);
await _sut.RotateApiKey(new SecretVerificationRequestModel());
}
[Fact]
public async Task PostRotateApiKey_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException()
{
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
[Fact]
public async Task PostRotateApiKey_WhenPasswordCheckFails_ShouldThrowBadRequestException()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToRejectPasswordFor(user);
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.ApiKey(new SecretVerificationRequestModel())
);
}
// Below are helper functions that currently belong to this
// test class, but ultimately may need to be split out into
// something greater in order to share common test steps with
// other test suites. They are included here for the time being
// until that day comes.
private User GenerateExampleUser()
{
return new User
{
Email = "user@example.com"
};
}
private void ConfigureUserServiceToReturnNullPrincipal()
{
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(Task.FromResult((User)null));
}
private void ConfigureUserServiceToReturnValidPrincipalFor(User user)
{
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(Task.FromResult(user));
}
private void ConfigureUserServiceToRejectPasswordFor(User user)
{
_userService.CheckPasswordAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(false));
}
private void ConfigureUserServiceToAcceptPasswordFor(User user)
{
_userService.CheckPasswordAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(true));
_userService.VerifySecretAsync(user, Arg.Any<string>())
.Returns(Task.FromResult(true));
}
private void ConfigureUserServiceToReturnValidIdFor(User user)
{
_userService.GetUserByIdAsync(Arg.Any<Guid>())
.Returns(Task.FromResult(user));
}
private void ConfigureUserServiceToReturnNullUserId()
{
_userService.GetUserByIdAsync(Arg.Any<Guid>())
.Returns(Task.FromResult((User)null));
}
}
}

View File

@ -11,78 +11,79 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Controllers;
[ControllerCustomize(typeof(CollectionsController))]
[SutProviderCustomize]
public class CollectionsControllerTests
namespace Bit.Api.Test.Controllers
{
[Theory, BitAutoData]
public async Task Post_Success(Guid orgId, SutProvider<CollectionsController> sutProvider)
[ControllerCustomize(typeof(CollectionsController))]
[SutProviderCustomize]
public class CollectionsControllerTests
{
sutProvider.GetDependency<ICurrentContext>()
.CreateNewCollections(orgId)
.Returns(true);
sutProvider.GetDependency<ICurrentContext>()
.EditAnyCollection(orgId)
.Returns(false);
var collectionRequest = new CollectionRequestModel
[Theory, BitAutoData]
public async Task Post_Success(Guid orgId, SutProvider<CollectionsController> sutProvider)
{
Name = "encrypted_string",
ExternalId = "my_external_id"
};
sutProvider.GetDependency<ICurrentContext>()
.CreateNewCollections(orgId)
.Returns(true);
_ = await sutProvider.Sut.Post(orgId, collectionRequest);
sutProvider.GetDependency<ICurrentContext>()
.EditAnyCollection(orgId)
.Returns(false);
await sutProvider.GetDependency<ICollectionService>()
.Received(1)
.SaveAsync(Arg.Any<Collection>(), Arg.Any<IEnumerable<SelectionReadOnly>>(), null);
}
[Theory, BitAutoData]
public async Task Put_Success(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>()
.ViewAssignedCollections(orgId)
.Returns(true);
sutProvider.GetDependency<ICurrentContext>()
.EditAssignedCollections(orgId)
.Returns(true);
sutProvider.GetDependency<ICurrentContext>()
.UserId
.Returns(userId);
sutProvider.GetDependency<ICollectionRepository>()
.GetByIdAsync(collectionId, userId)
.Returns(new CollectionDetails
var collectionRequest = new CollectionRequestModel
{
OrganizationId = orgId,
});
Name = "encrypted_string",
ExternalId = "my_external_id"
};
_ = await sutProvider.Sut.Put(orgId, collectionId, collectionRequest);
}
_ = await sutProvider.Sut.Post(orgId, collectionRequest);
[Theory, BitAutoData]
public async Task Put_CanNotEditAssignedCollection_ThrowsNotFound(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>()
.EditAssignedCollections(orgId)
.Returns(true);
await sutProvider.GetDependency<ICollectionService>()
.Received(1)
.SaveAsync(Arg.Any<Collection>(), Arg.Any<IEnumerable<SelectionReadOnly>>(), null);
}
sutProvider.GetDependency<ICurrentContext>()
.UserId
.Returns(userId);
[Theory, BitAutoData]
public async Task Put_Success(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>()
.ViewAssignedCollections(orgId)
.Returns(true);
sutProvider.GetDependency<ICollectionRepository>()
.GetByIdAsync(collectionId, userId)
.Returns(Task.FromResult<CollectionDetails>(null));
sutProvider.GetDependency<ICurrentContext>()
.EditAssignedCollections(orgId)
.Returns(true);
_ = await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.Put(orgId, collectionId, collectionRequest));
sutProvider.GetDependency<ICurrentContext>()
.UserId
.Returns(userId);
sutProvider.GetDependency<ICollectionRepository>()
.GetByIdAsync(collectionId, userId)
.Returns(new CollectionDetails
{
OrganizationId = orgId,
});
_ = await sutProvider.Sut.Put(orgId, collectionId, collectionRequest);
}
[Theory, BitAutoData]
public async Task Put_CanNotEditAssignedCollection_ThrowsNotFound(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest,
SutProvider<CollectionsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>()
.EditAssignedCollections(orgId)
.Returns(true);
sutProvider.GetDependency<ICurrentContext>()
.UserId
.Returns(userId);
sutProvider.GetDependency<ICollectionRepository>()
.GetByIdAsync(collectionId, userId)
.Returns(Task.FromResult<CollectionDetails>(null));
_ = await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.Put(orgId, collectionId, collectionRequest));
}
}
}

View File

@ -18,301 +18,302 @@ using Bit.Test.Common.Helpers;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Controllers;
[ControllerCustomize(typeof(OrganizationConnectionsController))]
[SutProviderCustomize]
[JsonDocumentCustomize]
public class OrganizationConnectionsControllerTests
namespace Bit.Api.Test.Controllers
{
public static IEnumerable<object[]> ConnectionTypes =>
Enum.GetValues<OrganizationConnectionType>().Select(p => new object[] { p });
[Theory]
[BitAutoData(true, true)]
[BitAutoData(false, true)]
[BitAutoData(true, false)]
[BitAutoData(false, false)]
public void ConnectionEnabled_RequiresBothSelfHostAndCommunications(bool selfHosted, bool enableCloudCommunication, SutProvider<OrganizationConnectionsController> sutProvider)
[ControllerCustomize(typeof(OrganizationConnectionsController))]
[SutProviderCustomize]
[JsonDocumentCustomize]
public class OrganizationConnectionsControllerTests
{
var globalSettingsMock = sutProvider.GetDependency<IGlobalSettings>();
globalSettingsMock.SelfHosted.Returns(selfHosted);
globalSettingsMock.EnableCloudCommunication.Returns(enableCloudCommunication);
public static IEnumerable<object[]> ConnectionTypes =>
Enum.GetValues<OrganizationConnectionType>().Select(p => new object[] { p });
Action<bool> assert = selfHosted && enableCloudCommunication ? Assert.True : Assert.False;
var result = sutProvider.Sut.ConnectionsEnabled();
assert(result);
}
[Theory]
[BitAutoData]
public async Task CreateConnection_CloudBillingSync_RequiresOwnerPermissions(SutProvider<OrganizationConnectionsController> sutProvider)
{
var model = new OrganizationConnectionRequestModel
[Theory]
[BitAutoData(true, true)]
[BitAutoData(false, true)]
[BitAutoData(true, false)]
[BitAutoData(false, false)]
public void ConnectionEnabled_RequiresBothSelfHostAndCommunications(bool selfHosted, bool enableCloudCommunication, SutProvider<OrganizationConnectionsController> sutProvider)
{
Type = OrganizationConnectionType.CloudBillingSync,
};
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateConnection(model));
var globalSettingsMock = sutProvider.GetDependency<IGlobalSettings>();
globalSettingsMock.SelfHosted.Returns(selfHosted);
globalSettingsMock.EnableCloudCommunication.Returns(enableCloudCommunication);
Assert.Contains($"You do not have permission to create a connection of type", exception.Message);
}
Action<bool> assert = selfHosted && enableCloudCommunication ? Assert.True : Assert.False;
[Theory]
[BitMemberAutoData(nameof(ConnectionTypes))]
public async Task CreateConnection_OnlyOneConnectionOfEachType(OrganizationConnectionType type,
OrganizationConnectionRequestModel model, BillingSyncConfig config, Guid existingEntityId,
SutProvider<OrganizationConnectionsController> sutProvider)
{
model.Type = type;
model.Config = JsonDocumentFromObject(config);
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
var existing = typedModel.ToData(existingEntityId).ToEntity();
var result = sutProvider.Sut.ConnectionsEnabled();
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.OrganizationId).Returns(true);
assert(result);
}
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByOrganizationIdTypeAsync(model.OrganizationId, type).Returns(new[] { existing });
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateConnection(model));
Assert.Contains($"The requested organization already has a connection of type {model.Type}. Only one of each connection type may exist per organization.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task CreateConnection_BillingSyncType_InvalidLicense_Throws(OrganizationConnectionRequestModel model,
BillingSyncConfig config, Guid cloudOrgId, OrganizationLicense organizationLicense,
SutProvider<OrganizationConnectionsController> sutProvider)
{
model.Type = OrganizationConnectionType.CloudBillingSync;
organizationLicense.Id = cloudOrgId;
model.Config = JsonDocumentFromObject(config);
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
typedModel.ParsedConfig.CloudOrganizationId = cloudOrgId;
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(model.OrganizationId)
.Returns(true);
sutProvider.GetDependency<ILicensingService>()
.ReadOrganizationLicenseAsync(model.OrganizationId)
.Returns(organizationLicense);
sutProvider.GetDependency<ILicensingService>()
.VerifyLicense(organizationLicense)
.Returns(false);
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateConnection(model));
}
[Theory]
[BitAutoData]
public async Task CreateConnection_Success(OrganizationConnectionRequestModel model, BillingSyncConfig config,
Guid cloudOrgId, OrganizationLicense organizationLicense, SutProvider<OrganizationConnectionsController> sutProvider)
{
organizationLicense.Id = cloudOrgId;
model.Config = JsonDocumentFromObject(config);
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
typedModel.ParsedConfig.CloudOrganizationId = cloudOrgId;
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
sutProvider.GetDependency<ICreateOrganizationConnectionCommand>().CreateAsync<BillingSyncConfig>(default)
.ReturnsForAnyArgs(typedModel.ToData(Guid.NewGuid()).ToEntity());
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.OrganizationId).Returns(true);
sutProvider.GetDependency<ILicensingService>()
.ReadOrganizationLicenseAsync(Arg.Any<Guid>())
.Returns(organizationLicense);
sutProvider.GetDependency<ILicensingService>()
.VerifyLicense(organizationLicense)
.Returns(true);
await sutProvider.Sut.CreateConnection(model);
await sutProvider.GetDependency<ICreateOrganizationConnectionCommand>().Received(1)
.CreateAsync(Arg.Is(AssertHelper.AssertPropertyEqual(typedModel.ToData())));
}
[Theory]
[BitAutoData]
public async Task UpdateConnection_RequiresOwnerPermissions(SutProvider<OrganizationConnectionsController> sutProvider)
{
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByIdAsync(Arg.Any<Guid>())
.Returns(new OrganizationConnection());
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateConnection(default, null));
Assert.Contains("You do not have permission to update this connection.", exception.Message);
}
[Theory]
[BitAutoData(OrganizationConnectionType.CloudBillingSync)]
public async Task UpdateConnection_BillingSync_OnlyOneConnectionOfEachType(OrganizationConnectionType type,
OrganizationConnection existing1, OrganizationConnection existing2, BillingSyncConfig config,
SutProvider<OrganizationConnectionsController> sutProvider)
{
existing1.Type = existing2.Type = type;
existing1.Config = JsonSerializer.Serialize(config);
var typedModel = RequestModelFromEntity<BillingSyncConfig>(existing1);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(typedModel.OrganizationId).Returns(true);
var orgConnectionRepository = sutProvider.GetDependency<IOrganizationConnectionRepository>();
orgConnectionRepository.GetByIdAsync(existing1.Id).Returns(existing1);
orgConnectionRepository.GetByIdAsync(existing2.Id).Returns(existing2);
orgConnectionRepository.GetByOrganizationIdTypeAsync(typedModel.OrganizationId, type).Returns(new[] { existing1, existing2 });
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateConnection(existing1.Id, typedModel));
Assert.Contains($"The requested organization already has a connection of type {typedModel.Type}. Only one of each connection type may exist per organization.", exception.Message);
}
[Theory]
[BitAutoData(OrganizationConnectionType.Scim)]
public async Task UpdateConnection_Scim_OnlyOneConnectionOfEachType(OrganizationConnectionType type,
OrganizationConnection existing1, OrganizationConnection existing2, ScimConfig config,
SutProvider<OrganizationConnectionsController> sutProvider)
{
existing1.Type = existing2.Type = type;
existing1.Config = JsonSerializer.Serialize(config);
var typedModel = RequestModelFromEntity<ScimConfig>(existing1);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(typedModel.OrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByIdAsync(existing1.Id)
.Returns(existing1);
sutProvider.GetDependency<ICurrentContext>().ManageScim(typedModel.OrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByOrganizationIdTypeAsync(typedModel.OrganizationId, type)
.Returns(new[] { existing1, existing2 });
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateConnection(existing1.Id, typedModel));
Assert.Contains($"The requested organization already has a connection of type {typedModel.Type}. Only one of each connection type may exist per organization.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task UpdateConnection_Success(OrganizationConnection existing, BillingSyncConfig config,
OrganizationConnection updated,
SutProvider<OrganizationConnectionsController> sutProvider)
{
existing.SetConfig(new BillingSyncConfig
[Theory]
[BitAutoData]
public async Task CreateConnection_CloudBillingSync_RequiresOwnerPermissions(SutProvider<OrganizationConnectionsController> sutProvider)
{
CloudOrganizationId = config.CloudOrganizationId,
});
updated.Config = JsonSerializer.Serialize(config);
updated.Id = existing.Id;
updated.Type = OrganizationConnectionType.CloudBillingSync;
var model = RequestModelFromEntity<BillingSyncConfig>(updated);
var model = new OrganizationConnectionRequestModel
{
Type = OrganizationConnectionType.CloudBillingSync,
};
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateConnection(model));
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.OrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByOrganizationIdTypeAsync(model.OrganizationId, model.Type)
.Returns(new[] { existing });
sutProvider.GetDependency<IUpdateOrganizationConnectionCommand>()
.UpdateAsync<BillingSyncConfig>(default)
.ReturnsForAnyArgs(updated);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByIdAsync(existing.Id)
.Returns(existing);
Assert.Contains($"You do not have permission to create a connection of type", exception.Message);
}
var expected = new OrganizationConnectionResponseModel(updated, typeof(BillingSyncConfig));
var result = await sutProvider.Sut.UpdateConnection(existing.Id, model);
AssertHelper.AssertPropertyEqual(expected, result);
await sutProvider.GetDependency<IUpdateOrganizationConnectionCommand>().Received(1)
.UpdateAsync(Arg.Is(AssertHelper.AssertPropertyEqual(model.ToData(updated.Id))));
}
[Theory]
[BitAutoData]
public async Task UpdateConnection_DoesNotExist_ThrowsNotFound(SutProvider<OrganizationConnectionsController> sutProvider)
{
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateConnection(Guid.NewGuid(), null));
}
[Theory]
[BitAutoData]
public async Task GetConnection_RequiresOwnerPermissions(Guid connectionId, SutProvider<OrganizationConnectionsController> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.GetConnection(connectionId, OrganizationConnectionType.CloudBillingSync));
Assert.Contains("You do not have permission to retrieve a connection of type", exception.Message);
}
[Theory]
[BitAutoData]
public async Task GetConnection_Success(OrganizationConnection connection, BillingSyncConfig config,
SutProvider<OrganizationConnectionsController> sutProvider)
{
connection.Config = JsonSerializer.Serialize(config);
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByOrganizationIdTypeAsync(connection.OrganizationId, connection.Type)
.Returns(new[] { connection });
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(connection.OrganizationId).Returns(true);
var expected = new OrganizationConnectionResponseModel(connection, typeof(BillingSyncConfig));
var actual = await sutProvider.Sut.GetConnection(connection.OrganizationId, connection.Type);
AssertHelper.AssertPropertyEqual(expected, actual);
}
[Theory]
[BitAutoData]
public async Task DeleteConnection_NotFound(Guid connectionId,
SutProvider<OrganizationConnectionsController> sutProvider)
{
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteConnection(connectionId));
}
[Theory]
[BitAutoData]
public async Task DeleteConnection_RequiresOwnerPermissions(OrganizationConnection connection,
SutProvider<OrganizationConnectionsController> sutProvider)
{
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByIdAsync(connection.Id).Returns(connection);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.DeleteConnection(connection.Id));
Assert.Contains("You do not have permission to remove this connection of type", exception.Message);
}
[Theory]
[BitAutoData]
public async Task DeleteConnection_Success(OrganizationConnection connection,
SutProvider<OrganizationConnectionsController> sutProvider)
{
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByIdAsync(connection.Id).Returns(connection);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(connection.OrganizationId).Returns(true);
await sutProvider.Sut.DeleteConnection(connection.Id);
await sutProvider.GetDependency<IDeleteOrganizationConnectionCommand>().DeleteAsync(connection);
}
private static OrganizationConnectionRequestModel<T> RequestModelFromEntity<T>(OrganizationConnection entity)
where T : new()
{
return new(new OrganizationConnectionRequestModel()
[Theory]
[BitMemberAutoData(nameof(ConnectionTypes))]
public async Task CreateConnection_OnlyOneConnectionOfEachType(OrganizationConnectionType type,
OrganizationConnectionRequestModel model, BillingSyncConfig config, Guid existingEntityId,
SutProvider<OrganizationConnectionsController> sutProvider)
{
Type = entity.Type,
OrganizationId = entity.OrganizationId,
Enabled = entity.Enabled,
Config = JsonDocument.Parse(entity.Config),
});
}
model.Type = type;
model.Config = JsonDocumentFromObject(config);
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
var existing = typedModel.ToData(existingEntityId).ToEntity();
private static JsonDocument JsonDocumentFromObject<T>(T obj) => JsonDocument.Parse(JsonSerializer.Serialize(obj));
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.OrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByOrganizationIdTypeAsync(model.OrganizationId, type).Returns(new[] { existing });
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateConnection(model));
Assert.Contains($"The requested organization already has a connection of type {model.Type}. Only one of each connection type may exist per organization.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task CreateConnection_BillingSyncType_InvalidLicense_Throws(OrganizationConnectionRequestModel model,
BillingSyncConfig config, Guid cloudOrgId, OrganizationLicense organizationLicense,
SutProvider<OrganizationConnectionsController> sutProvider)
{
model.Type = OrganizationConnectionType.CloudBillingSync;
organizationLicense.Id = cloudOrgId;
model.Config = JsonDocumentFromObject(config);
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
typedModel.ParsedConfig.CloudOrganizationId = cloudOrgId;
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(model.OrganizationId)
.Returns(true);
sutProvider.GetDependency<ILicensingService>()
.ReadOrganizationLicenseAsync(model.OrganizationId)
.Returns(organizationLicense);
sutProvider.GetDependency<ILicensingService>()
.VerifyLicense(organizationLicense)
.Returns(false);
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateConnection(model));
}
[Theory]
[BitAutoData]
public async Task CreateConnection_Success(OrganizationConnectionRequestModel model, BillingSyncConfig config,
Guid cloudOrgId, OrganizationLicense organizationLicense, SutProvider<OrganizationConnectionsController> sutProvider)
{
organizationLicense.Id = cloudOrgId;
model.Config = JsonDocumentFromObject(config);
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
typedModel.ParsedConfig.CloudOrganizationId = cloudOrgId;
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
sutProvider.GetDependency<ICreateOrganizationConnectionCommand>().CreateAsync<BillingSyncConfig>(default)
.ReturnsForAnyArgs(typedModel.ToData(Guid.NewGuid()).ToEntity());
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.OrganizationId).Returns(true);
sutProvider.GetDependency<ILicensingService>()
.ReadOrganizationLicenseAsync(Arg.Any<Guid>())
.Returns(organizationLicense);
sutProvider.GetDependency<ILicensingService>()
.VerifyLicense(organizationLicense)
.Returns(true);
await sutProvider.Sut.CreateConnection(model);
await sutProvider.GetDependency<ICreateOrganizationConnectionCommand>().Received(1)
.CreateAsync(Arg.Is(AssertHelper.AssertPropertyEqual(typedModel.ToData())));
}
[Theory]
[BitAutoData]
public async Task UpdateConnection_RequiresOwnerPermissions(SutProvider<OrganizationConnectionsController> sutProvider)
{
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByIdAsync(Arg.Any<Guid>())
.Returns(new OrganizationConnection());
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateConnection(default, null));
Assert.Contains("You do not have permission to update this connection.", exception.Message);
}
[Theory]
[BitAutoData(OrganizationConnectionType.CloudBillingSync)]
public async Task UpdateConnection_BillingSync_OnlyOneConnectionOfEachType(OrganizationConnectionType type,
OrganizationConnection existing1, OrganizationConnection existing2, BillingSyncConfig config,
SutProvider<OrganizationConnectionsController> sutProvider)
{
existing1.Type = existing2.Type = type;
existing1.Config = JsonSerializer.Serialize(config);
var typedModel = RequestModelFromEntity<BillingSyncConfig>(existing1);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(typedModel.OrganizationId).Returns(true);
var orgConnectionRepository = sutProvider.GetDependency<IOrganizationConnectionRepository>();
orgConnectionRepository.GetByIdAsync(existing1.Id).Returns(existing1);
orgConnectionRepository.GetByIdAsync(existing2.Id).Returns(existing2);
orgConnectionRepository.GetByOrganizationIdTypeAsync(typedModel.OrganizationId, type).Returns(new[] { existing1, existing2 });
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateConnection(existing1.Id, typedModel));
Assert.Contains($"The requested organization already has a connection of type {typedModel.Type}. Only one of each connection type may exist per organization.", exception.Message);
}
[Theory]
[BitAutoData(OrganizationConnectionType.Scim)]
public async Task UpdateConnection_Scim_OnlyOneConnectionOfEachType(OrganizationConnectionType type,
OrganizationConnection existing1, OrganizationConnection existing2, ScimConfig config,
SutProvider<OrganizationConnectionsController> sutProvider)
{
existing1.Type = existing2.Type = type;
existing1.Config = JsonSerializer.Serialize(config);
var typedModel = RequestModelFromEntity<ScimConfig>(existing1);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(typedModel.OrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByIdAsync(existing1.Id)
.Returns(existing1);
sutProvider.GetDependency<ICurrentContext>().ManageScim(typedModel.OrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByOrganizationIdTypeAsync(typedModel.OrganizationId, type)
.Returns(new[] { existing1, existing2 });
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateConnection(existing1.Id, typedModel));
Assert.Contains($"The requested organization already has a connection of type {typedModel.Type}. Only one of each connection type may exist per organization.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task UpdateConnection_Success(OrganizationConnection existing, BillingSyncConfig config,
OrganizationConnection updated,
SutProvider<OrganizationConnectionsController> sutProvider)
{
existing.SetConfig(new BillingSyncConfig
{
CloudOrganizationId = config.CloudOrganizationId,
});
updated.Config = JsonSerializer.Serialize(config);
updated.Id = existing.Id;
updated.Type = OrganizationConnectionType.CloudBillingSync;
var model = RequestModelFromEntity<BillingSyncConfig>(updated);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.OrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByOrganizationIdTypeAsync(model.OrganizationId, model.Type)
.Returns(new[] { existing });
sutProvider.GetDependency<IUpdateOrganizationConnectionCommand>()
.UpdateAsync<BillingSyncConfig>(default)
.ReturnsForAnyArgs(updated);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByIdAsync(existing.Id)
.Returns(existing);
var expected = new OrganizationConnectionResponseModel(updated, typeof(BillingSyncConfig));
var result = await sutProvider.Sut.UpdateConnection(existing.Id, model);
AssertHelper.AssertPropertyEqual(expected, result);
await sutProvider.GetDependency<IUpdateOrganizationConnectionCommand>().Received(1)
.UpdateAsync(Arg.Is(AssertHelper.AssertPropertyEqual(model.ToData(updated.Id))));
}
[Theory]
[BitAutoData]
public async Task UpdateConnection_DoesNotExist_ThrowsNotFound(SutProvider<OrganizationConnectionsController> sutProvider)
{
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateConnection(Guid.NewGuid(), null));
}
[Theory]
[BitAutoData]
public async Task GetConnection_RequiresOwnerPermissions(Guid connectionId, SutProvider<OrganizationConnectionsController> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.GetConnection(connectionId, OrganizationConnectionType.CloudBillingSync));
Assert.Contains("You do not have permission to retrieve a connection of type", exception.Message);
}
[Theory]
[BitAutoData]
public async Task GetConnection_Success(OrganizationConnection connection, BillingSyncConfig config,
SutProvider<OrganizationConnectionsController> sutProvider)
{
connection.Config = JsonSerializer.Serialize(config);
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
sutProvider.GetDependency<IOrganizationConnectionRepository>()
.GetByOrganizationIdTypeAsync(connection.OrganizationId, connection.Type)
.Returns(new[] { connection });
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(connection.OrganizationId).Returns(true);
var expected = new OrganizationConnectionResponseModel(connection, typeof(BillingSyncConfig));
var actual = await sutProvider.Sut.GetConnection(connection.OrganizationId, connection.Type);
AssertHelper.AssertPropertyEqual(expected, actual);
}
[Theory]
[BitAutoData]
public async Task DeleteConnection_NotFound(Guid connectionId,
SutProvider<OrganizationConnectionsController> sutProvider)
{
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteConnection(connectionId));
}
[Theory]
[BitAutoData]
public async Task DeleteConnection_RequiresOwnerPermissions(OrganizationConnection connection,
SutProvider<OrganizationConnectionsController> sutProvider)
{
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByIdAsync(connection.Id).Returns(connection);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.DeleteConnection(connection.Id));
Assert.Contains("You do not have permission to remove this connection of type", exception.Message);
}
[Theory]
[BitAutoData]
public async Task DeleteConnection_Success(OrganizationConnection connection,
SutProvider<OrganizationConnectionsController> sutProvider)
{
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByIdAsync(connection.Id).Returns(connection);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(connection.OrganizationId).Returns(true);
await sutProvider.Sut.DeleteConnection(connection.Id);
await sutProvider.GetDependency<IDeleteOrganizationConnectionCommand>().DeleteAsync(connection);
}
private static OrganizationConnectionRequestModel<T> RequestModelFromEntity<T>(OrganizationConnection entity)
where T : new()
{
return new(new OrganizationConnectionRequestModel()
{
Type = entity.Type,
OrganizationId = entity.OrganizationId,
Enabled = entity.Enabled,
Config = JsonDocument.Parse(entity.Config),
});
}
private static JsonDocument JsonDocumentFromObject<T>(T obj) => JsonDocument.Parse(JsonSerializer.Serialize(obj));
}
}

View File

@ -13,135 +13,136 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Controllers;
[ControllerCustomize(typeof(OrganizationSponsorshipsController))]
[SutProviderCustomize]
public class OrganizationSponsorshipsControllerTests
namespace Bit.Api.Test.Controllers
{
public static IEnumerable<object[]> EnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonEnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonFamiliesPlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
Enum.GetValues<OrganizationUserStatusType>()
.Where(s => s != OrganizationUserStatusType.Confirmed)
.Select(s => new object[] { s });
[Theory]
[BitAutoData]
public async Task RedeemSponsorship_BadToken_ThrowsBadRequest(string sponsorshipToken, User user,
OrganizationSponsorshipRedeemRequestModel model, SutProvider<OrganizationSponsorshipsController> sutProvider)
[ControllerCustomize(typeof(OrganizationSponsorshipsController))]
[SutProviderCustomize]
public class OrganizationSponsorshipsControllerTests
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>().ValidateRedemptionTokenAsync(sponsorshipToken,
user.Email).Returns((false, null));
public static IEnumerable<object[]> EnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonEnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonFamiliesPlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model));
public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
Enum.GetValues<OrganizationUserStatusType>()
.Where(s => s != OrganizationUserStatusType.Confirmed)
.Select(s => new object[] { s });
Assert.Contains("Failed to parse sponsorship token.", exception.Message);
await sutProvider.GetDependency<ISetUpSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.SetUpSponsorshipAsync(default, default);
}
[Theory]
[BitAutoData]
public async Task RedeemSponsorship_NotSponsoredOrgOwner_ThrowsBadRequest(string sponsorshipToken, User user,
OrganizationSponsorship sponsorship, OrganizationSponsorshipRedeemRequestModel model,
SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>().ValidateRedemptionTokenAsync(sponsorshipToken,
user.Email).Returns((true, sponsorship));
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.SponsoredOrganizationId).Returns(false);
[Theory]
[BitAutoData]
public async Task RedeemSponsorship_BadToken_ThrowsBadRequest(string sponsorshipToken, User user,
OrganizationSponsorshipRedeemRequestModel model, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>().ValidateRedemptionTokenAsync(sponsorshipToken,
user.Email).Returns((false, null));
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model));
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model));
Assert.Contains("Can only redeem sponsorship for an organization you own.", exception.Message);
await sutProvider.GetDependency<ISetUpSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.SetUpSponsorshipAsync(default, default);
}
Assert.Contains("Failed to parse sponsorship token.", exception.Message);
await sutProvider.GetDependency<ISetUpSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.SetUpSponsorshipAsync(default, default);
}
[Theory]
[BitAutoData]
public async Task RedeemSponsorship_NotSponsoredOrgOwner_Success(string sponsorshipToken, User user,
OrganizationSponsorship sponsorship, Organization sponsoringOrganization,
OrganizationSponsorshipRedeemRequestModel model, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>().ValidateRedemptionTokenAsync(sponsorshipToken,
user.Email).Returns((true, sponsorship));
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.SponsoredOrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(model.SponsoredOrganizationId).Returns(sponsoringOrganization);
[Theory]
[BitAutoData]
public async Task RedeemSponsorship_NotSponsoredOrgOwner_ThrowsBadRequest(string sponsorshipToken, User user,
OrganizationSponsorship sponsorship, OrganizationSponsorshipRedeemRequestModel model,
SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>().ValidateRedemptionTokenAsync(sponsorshipToken,
user.Email).Returns((true, sponsorship));
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.SponsoredOrganizationId).Returns(false);
await sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model));
await sutProvider.GetDependency<ISetUpSponsorshipCommand>().Received(1)
.SetUpSponsorshipAsync(sponsorship, sponsoringOrganization);
}
Assert.Contains("Can only redeem sponsorship for an organization you own.", exception.Message);
await sutProvider.GetDependency<ISetUpSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.SetUpSponsorshipAsync(default, default);
}
[Theory]
[BitAutoData]
public async Task PreValidateSponsorshipToken_ValidatesToken_Success(string sponsorshipToken, User user,
OrganizationSponsorship sponsorship, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>()
.ValidateRedemptionTokenAsync(sponsorshipToken, user.Email).Returns((true, sponsorship));
[Theory]
[BitAutoData]
public async Task RedeemSponsorship_NotSponsoredOrgOwner_Success(string sponsorshipToken, User user,
OrganizationSponsorship sponsorship, Organization sponsoringOrganization,
OrganizationSponsorshipRedeemRequestModel model, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>().ValidateRedemptionTokenAsync(sponsorshipToken,
user.Email).Returns((true, sponsorship));
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.SponsoredOrganizationId).Returns(true);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(model.SponsoredOrganizationId).Returns(sponsoringOrganization);
await sutProvider.Sut.PreValidateSponsorshipToken(sponsorshipToken);
await sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model);
await sutProvider.GetDependency<IValidateRedemptionTokenCommand>().Received(1)
.ValidateRedemptionTokenAsync(sponsorshipToken, user.Email);
}
await sutProvider.GetDependency<ISetUpSponsorshipCommand>().Received(1)
.SetUpSponsorshipAsync(sponsorship, sponsoringOrganization);
}
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_WrongSponsoringUser_ThrowsBadRequest(OrganizationUser sponsoringOrgUser,
Guid currentUserId, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(currentUserId);
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(sponsoringOrgUser.Id)
.Returns(sponsoringOrgUser);
[Theory]
[BitAutoData]
public async Task PreValidateSponsorshipToken_ValidatesToken_Success(string sponsorshipToken, User user,
OrganizationSponsorship sponsorship, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IValidateRedemptionTokenCommand>()
.ValidateRedemptionTokenAsync(sponsorshipToken, user.Email).Returns((true, sponsorship));
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RevokeSponsorship(sponsoringOrgUser.Id));
await sutProvider.Sut.PreValidateSponsorshipToken(sponsorshipToken);
Assert.Contains("Can only revoke a sponsorship you granted.", exception.Message);
await sutProvider.GetDependency<IRemoveSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.RemoveSponsorshipAsync(default);
}
await sutProvider.GetDependency<IValidateRedemptionTokenCommand>().Received(1)
.ValidateRedemptionTokenAsync(sponsorshipToken, user.Email);
}
[Theory]
[BitAutoData]
public async Task RemoveSponsorship_WrongOrgUserType_ThrowsBadRequest(Organization sponsoredOrg,
SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(Arg.Any<Guid>()).Returns(false);
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_WrongSponsoringUser_ThrowsBadRequest(OrganizationUser sponsoringOrgUser,
Guid currentUserId, SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(currentUserId);
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(sponsoringOrgUser.Id)
.Returns(sponsoringOrgUser);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RemoveSponsorship(sponsoredOrg.Id));
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RevokeSponsorship(sponsoringOrgUser.Id));
Assert.Contains("Only the owner of an organization can remove sponsorship.", exception.Message);
await sutProvider.GetDependency<IRemoveSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.RemoveSponsorshipAsync(default);
Assert.Contains("Can only revoke a sponsorship you granted.", exception.Message);
await sutProvider.GetDependency<IRemoveSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.RemoveSponsorshipAsync(default);
}
[Theory]
[BitAutoData]
public async Task RemoveSponsorship_WrongOrgUserType_ThrowsBadRequest(Organization sponsoredOrg,
SutProvider<OrganizationSponsorshipsController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(Arg.Any<Guid>()).Returns(false);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RemoveSponsorship(sponsoredOrg.Id));
Assert.Contains("Only the owner of an organization can remove sponsorship.", exception.Message);
await sutProvider.GetDependency<IRemoveSponsorshipCommand>()
.DidNotReceiveWithAnyArgs()
.RemoveSponsorshipAsync(default);
}
}
}

View File

@ -10,56 +10,57 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Controllers;
[ControllerCustomize(typeof(OrganizationUsersController))]
[SutProviderCustomize]
public class OrganizationUsersControllerTests
namespace Bit.Api.Test.Controllers
{
[Theory]
[BitAutoData]
public async Task Accept_RequiresKnownUser(Guid orgId, Guid orgUserId, OrganizationUserAcceptRequestModel model,
SutProvider<OrganizationUsersController> sutProvider)
[ControllerCustomize(typeof(OrganizationUsersController))]
[SutProviderCustomize]
public class OrganizationUsersControllerTests
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs((User)null);
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => sutProvider.Sut.Accept(orgId, orgUserId, model));
}
[Theory]
[BitAutoData]
public async Task Accept_NoMasterPasswordReset(Guid orgId, Guid orgUserId,
OrganizationUserAcceptRequestModel model, User user, SutProvider<OrganizationUsersController> sutProvider)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
await sutProvider.Sut.Accept(orgId, orgUserId, model);
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.AcceptUserAsync(orgUserId, user, model.Token, sutProvider.GetDependency<IUserService>());
await sutProvider.GetDependency<IOrganizationService>().DidNotReceiveWithAnyArgs()
.UpdateUserResetPasswordEnrollmentAsync(default, default, default, default);
}
[Theory]
[BitAutoData]
public async Task Accept_RequireMasterPasswordReset(Guid orgId, Guid orgUserId,
OrganizationUserAcceptRequestModel model, User user, SutProvider<OrganizationUsersController> sutProvider)
{
var policy = new Policy
[Theory]
[BitAutoData]
public async Task Accept_RequiresKnownUser(Guid orgId, Guid orgUserId, OrganizationUserAcceptRequestModel model,
SutProvider<OrganizationUsersController> sutProvider)
{
Enabled = true,
Data = CoreHelpers.ClassToJsonData(new ResetPasswordDataModel { AutoEnrollEnabled = true, }),
};
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
sutProvider.GetDependency<IPolicyRepository>().GetByOrganizationIdTypeAsync(orgId,
Core.Enums.PolicyType.ResetPassword).Returns(policy);
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs((User)null);
await sutProvider.Sut.Accept(orgId, orgUserId, model);
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => sutProvider.Sut.Accept(orgId, orgUserId, model));
}
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.AcceptUserAsync(orgUserId, user, model.Token, sutProvider.GetDependency<IUserService>());
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id);
[Theory]
[BitAutoData]
public async Task Accept_NoMasterPasswordReset(Guid orgId, Guid orgUserId,
OrganizationUserAcceptRequestModel model, User user, SutProvider<OrganizationUsersController> sutProvider)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
await sutProvider.Sut.Accept(orgId, orgUserId, model);
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.AcceptUserAsync(orgUserId, user, model.Token, sutProvider.GetDependency<IUserService>());
await sutProvider.GetDependency<IOrganizationService>().DidNotReceiveWithAnyArgs()
.UpdateUserResetPasswordEnrollmentAsync(default, default, default, default);
}
[Theory]
[BitAutoData]
public async Task Accept_RequireMasterPasswordReset(Guid orgId, Guid orgUserId,
OrganizationUserAcceptRequestModel model, User user, SutProvider<OrganizationUsersController> sutProvider)
{
var policy = new Policy
{
Enabled = true,
Data = CoreHelpers.ClassToJsonData(new ResetPasswordDataModel { AutoEnrollEnabled = true, }),
};
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
sutProvider.GetDependency<IPolicyRepository>().GetByOrganizationIdTypeAsync(orgId,
Core.Enums.PolicyType.ResetPassword).Returns(policy);
await sutProvider.Sut.Accept(orgId, orgUserId, model);
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.AcceptUserAsync(orgUserId, user, model.Token, sutProvider.GetDependency<IUserService>());
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id);
}
}
}

View File

@ -12,108 +12,109 @@ using Bit.Core.Settings;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Controllers;
public class OrganizationsControllerTests : IDisposable
namespace Bit.Api.Test.Controllers
{
private readonly GlobalSettings _globalSettings;
private readonly ICurrentContext _currentContext;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IPaymentService _paymentService;
private readonly IPolicyRepository _policyRepository;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly ISsoConfigService _ssoConfigService;
private readonly IUserService _userService;
private readonly IGetOrganizationApiKeyCommand _getOrganizationApiKeyCommand;
private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand;
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
private readonly OrganizationsController _sut;
public OrganizationsControllerTests()
public class OrganizationsControllerTests : IDisposable
{
_currentContext = Substitute.For<ICurrentContext>();
_globalSettings = Substitute.For<GlobalSettings>();
_organizationRepository = Substitute.For<IOrganizationRepository>();
_organizationService = Substitute.For<IOrganizationService>();
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_paymentService = Substitute.For<IPaymentService>();
_policyRepository = Substitute.For<IPolicyRepository>();
_ssoConfigRepository = Substitute.For<ISsoConfigRepository>();
_ssoConfigService = Substitute.For<ISsoConfigService>();
_getOrganizationApiKeyCommand = Substitute.For<IGetOrganizationApiKeyCommand>();
_rotateOrganizationApiKeyCommand = Substitute.For<IRotateOrganizationApiKeyCommand>();
_organizationApiKeyRepository = Substitute.For<IOrganizationApiKeyRepository>();
_userService = Substitute.For<IUserService>();
private readonly GlobalSettings _globalSettings;
private readonly ICurrentContext _currentContext;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IPaymentService _paymentService;
private readonly IPolicyRepository _policyRepository;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly ISsoConfigService _ssoConfigService;
private readonly IUserService _userService;
private readonly IGetOrganizationApiKeyCommand _getOrganizationApiKeyCommand;
private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand;
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
_sut = new OrganizationsController(_organizationRepository, _organizationUserRepository,
_policyRepository, _organizationService, _userService, _paymentService, _currentContext,
_ssoConfigRepository, _ssoConfigService, _getOrganizationApiKeyCommand, _rotateOrganizationApiKeyCommand,
_organizationApiKeyRepository, _globalSettings);
}
private readonly OrganizationsController _sut;
public void Dispose()
{
_sut?.Dispose();
}
[Theory, AutoData]
public async Task OrganizationsController_UserCannotLeaveOrganizationThatProvidesKeyConnector(
Guid orgId, User user)
{
var ssoConfig = new SsoConfig
public OrganizationsControllerTests()
{
Id = default,
Data = new SsoConfigurationData
{
KeyConnectorEnabled = true,
}.Serialize(),
Enabled = true,
OrganizationId = orgId,
};
_currentContext = Substitute.For<ICurrentContext>();
_globalSettings = Substitute.For<GlobalSettings>();
_organizationRepository = Substitute.For<IOrganizationRepository>();
_organizationService = Substitute.For<IOrganizationService>();
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_paymentService = Substitute.For<IPaymentService>();
_policyRepository = Substitute.For<IPolicyRepository>();
_ssoConfigRepository = Substitute.For<ISsoConfigRepository>();
_ssoConfigService = Substitute.For<ISsoConfigService>();
_getOrganizationApiKeyCommand = Substitute.For<IGetOrganizationApiKeyCommand>();
_rotateOrganizationApiKeyCommand = Substitute.For<IRotateOrganizationApiKeyCommand>();
_organizationApiKeyRepository = Substitute.For<IOrganizationApiKeyRepository>();
_userService = Substitute.For<IUserService>();
user.UsesKeyConnector = true;
_sut = new OrganizationsController(_organizationRepository, _organizationUserRepository,
_policyRepository, _organizationService, _userService, _paymentService, _currentContext,
_ssoConfigRepository, _ssoConfigService, _getOrganizationApiKeyCommand, _rotateOrganizationApiKeyCommand,
_organizationApiKeyRepository, _globalSettings);
}
_currentContext.OrganizationUser(orgId).Returns(true);
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => _sut.Leave(orgId.ToString()));
Assert.Contains("Your organization's Single Sign-On settings prevent you from leaving.",
exception.Message);
await _organizationService.DidNotReceiveWithAnyArgs().DeleteUserAsync(default, default);
}
[Theory]
[InlineAutoData(true, false)]
[InlineAutoData(false, true)]
[InlineAutoData(false, false)]
public async Task OrganizationsController_UserCanLeaveOrganizationThatDoesntProvideKeyConnector(
bool keyConnectorEnabled, bool userUsesKeyConnector, Guid orgId, User user)
{
var ssoConfig = new SsoConfig
public void Dispose()
{
Id = default,
Data = new SsoConfigurationData
_sut?.Dispose();
}
[Theory, AutoData]
public async Task OrganizationsController_UserCannotLeaveOrganizationThatProvidesKeyConnector(
Guid orgId, User user)
{
var ssoConfig = new SsoConfig
{
KeyConnectorEnabled = keyConnectorEnabled,
}.Serialize(),
Enabled = true,
OrganizationId = orgId,
};
Id = default,
Data = new SsoConfigurationData
{
KeyConnectorEnabled = true,
}.Serialize(),
Enabled = true,
OrganizationId = orgId,
};
user.UsesKeyConnector = userUsesKeyConnector;
user.UsesKeyConnector = true;
_currentContext.OrganizationUser(orgId).Returns(true);
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
_currentContext.OrganizationUser(orgId).Returns(true);
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
await _organizationService.DeleteUserAsync(orgId, user.Id);
await _organizationService.Received(1).DeleteUserAsync(orgId, user.Id);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => _sut.Leave(orgId.ToString()));
Assert.Contains("Your organization's Single Sign-On settings prevent you from leaving.",
exception.Message);
await _organizationService.DidNotReceiveWithAnyArgs().DeleteUserAsync(default, default);
}
[Theory]
[InlineAutoData(true, false)]
[InlineAutoData(false, true)]
[InlineAutoData(false, false)]
public async Task OrganizationsController_UserCanLeaveOrganizationThatDoesntProvideKeyConnector(
bool keyConnectorEnabled, bool userUsesKeyConnector, Guid orgId, User user)
{
var ssoConfig = new SsoConfig
{
Id = default,
Data = new SsoConfigurationData
{
KeyConnectorEnabled = keyConnectorEnabled,
}.Serialize(),
Enabled = true,
OrganizationId = orgId,
};
user.UsesKeyConnector = userUsesKeyConnector;
_currentContext.OrganizationUser(orgId).Returns(true);
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
await _organizationService.DeleteUserAsync(orgId, user.Id);
await _organizationService.Received(1).DeleteUserAsync(orgId, user.Id);
}
}
}

View File

@ -15,65 +15,66 @@ using Microsoft.Extensions.Logging;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Controllers;
public class SendsControllerTests : IDisposable
namespace Bit.Api.Test.Controllers
{
private readonly SendsController _sut;
private readonly GlobalSettings _globalSettings;
private readonly IUserService _userService;
private readonly ISendRepository _sendRepository;
private readonly ISendService _sendService;
private readonly ISendFileStorageService _sendFileStorageService;
private readonly ILogger<SendsController> _logger;
private readonly ICurrentContext _currentContext;
public SendsControllerTests()
public class SendsControllerTests : IDisposable
{
_userService = Substitute.For<IUserService>();
_sendRepository = Substitute.For<ISendRepository>();
_sendService = Substitute.For<ISendService>();
_sendFileStorageService = Substitute.For<ISendFileStorageService>();
_globalSettings = new GlobalSettings();
_logger = Substitute.For<ILogger<SendsController>>();
_currentContext = Substitute.For<ICurrentContext>();
_sut = new SendsController(
_sendRepository,
_userService,
_sendService,
_sendFileStorageService,
_logger,
_globalSettings,
_currentContext
);
}
private readonly SendsController _sut;
private readonly GlobalSettings _globalSettings;
private readonly IUserService _userService;
private readonly ISendRepository _sendRepository;
private readonly ISendService _sendService;
private readonly ISendFileStorageService _sendFileStorageService;
private readonly ILogger<SendsController> _logger;
private readonly ICurrentContext _currentContext;
public void Dispose()
{
_sut?.Dispose();
}
public SendsControllerTests()
{
_userService = Substitute.For<IUserService>();
_sendRepository = Substitute.For<ISendRepository>();
_sendService = Substitute.For<ISendService>();
_sendFileStorageService = Substitute.For<ISendFileStorageService>();
_globalSettings = new GlobalSettings();
_logger = Substitute.For<ILogger<SendsController>>();
_currentContext = Substitute.For<ICurrentContext>();
[Theory, AutoData]
public async Task SendsController_WhenSendHidesEmail_CreatorIdentifierShouldBeNull(
Guid id, Send send, User user)
{
var accessId = CoreHelpers.Base64UrlEncode(id.ToByteArray());
_sut = new SendsController(
_sendRepository,
_userService,
_sendService,
_sendFileStorageService,
_logger,
_globalSettings,
_currentContext
);
}
send.Id = default;
send.Type = SendType.Text;
send.Data = JsonSerializer.Serialize(new Dictionary<string, string>());
send.HideEmail = true;
public void Dispose()
{
_sut?.Dispose();
}
_sendService.AccessAsync(id, null).Returns((send, false, false));
_userService.GetUserByIdAsync(Arg.Any<Guid>()).Returns(user);
[Theory, AutoData]
public async Task SendsController_WhenSendHidesEmail_CreatorIdentifierShouldBeNull(
Guid id, Send send, User user)
{
var accessId = CoreHelpers.Base64UrlEncode(id.ToByteArray());
var request = new SendAccessRequestModel();
var actionResult = await _sut.Access(accessId, request);
var response = (actionResult as ObjectResult)?.Value as SendAccessResponseModel;
send.Id = default;
send.Type = SendType.Text;
send.Data = JsonSerializer.Serialize(new Dictionary<string, string>());
send.HideEmail = true;
Assert.NotNull(response);
Assert.Null(response.CreatorIdentifier);
_sendService.AccessAsync(id, null).Returns((send, false, false));
_userService.GetUserByIdAsync(Arg.Any<Guid>()).Returns(user);
var request = new SendAccessRequestModel();
var actionResult = await _sut.Access(accessId, request);
var response = (actionResult as ObjectResult)?.Value as SendAccessResponseModel;
Assert.NotNull(response);
Assert.Null(response.CreatorIdentifier);
}
}
}

View File

@ -3,62 +3,63 @@ using Bit.Core.Settings;
using Microsoft.AspNetCore.Http;
using Xunit;
namespace Bit.Api.Test.Models.Request.Accounts;
public class PremiumRequestModelTests
namespace Bit.Api.Test.Models.Request.Accounts
{
public static IEnumerable<object[]> GetValidateData()
public class PremiumRequestModelTests
{
// 1. selfHosted
// 2. formFile
// 3. country
// 4. expected
public static IEnumerable<object[]> GetValidateData()
{
// 1. selfHosted
// 2. formFile
// 3. country
// 4. expected
yield return new object[] { true, null, null, false };
yield return new object[] { true, null, "US", false };
yield return new object[] { true, new NotImplementedFormFile(), null, false };
yield return new object[] { true, new NotImplementedFormFile(), "US", false };
yield return new object[] { true, null, null, false };
yield return new object[] { true, null, "US", false };
yield return new object[] { true, new NotImplementedFormFile(), null, false };
yield return new object[] { true, new NotImplementedFormFile(), "US", false };
yield return new object[] { false, null, null, false };
yield return new object[] { false, null, "US", true }; // Only true, cloud with null license AND a Country
yield return new object[] { false, new NotImplementedFormFile(), null, false };
yield return new object[] { false, new NotImplementedFormFile(), "US", false };
yield return new object[] { false, null, null, false };
yield return new object[] { false, null, "US", true }; // Only true, cloud with null license AND a Country
yield return new object[] { false, new NotImplementedFormFile(), null, false };
yield return new object[] { false, new NotImplementedFormFile(), "US", false };
}
[Theory]
[MemberData(nameof(GetValidateData))]
public void Validate_Success(bool selfHosted, IFormFile formFile, string country, bool expected)
{
var gs = new GlobalSettings
{
SelfHosted = selfHosted
};
var sut = new PremiumRequestModel
{
License = formFile,
Country = country,
};
Assert.Equal(expected, sut.Validate(gs));
}
}
[Theory]
[MemberData(nameof(GetValidateData))]
public void Validate_Success(bool selfHosted, IFormFile formFile, string country, bool expected)
public class NotImplementedFormFile : IFormFile
{
var gs = new GlobalSettings
{
SelfHosted = selfHosted
};
public string ContentType => throw new NotImplementedException();
var sut = new PremiumRequestModel
{
License = formFile,
Country = country,
};
public string ContentDisposition => throw new NotImplementedException();
Assert.Equal(expected, sut.Validate(gs));
public IHeaderDictionary Headers => throw new NotImplementedException();
public long Length => throw new NotImplementedException();
public string Name => throw new NotImplementedException();
public string FileName => throw new NotImplementedException();
public void CopyTo(Stream target) => throw new NotImplementedException();
public Task CopyToAsync(Stream target, CancellationToken cancellationToken = default) => throw new NotImplementedException();
public Stream OpenReadStream() => throw new NotImplementedException();
}
}
public class NotImplementedFormFile : IFormFile
{
public string ContentType => throw new NotImplementedException();
public string ContentDisposition => throw new NotImplementedException();
public IHeaderDictionary Headers => throw new NotImplementedException();
public long Length => throw new NotImplementedException();
public string Name => throw new NotImplementedException();
public string FileName => throw new NotImplementedException();
public void CopyTo(Stream target) => throw new NotImplementedException();
public Task CopyToAsync(Stream target, CancellationToken cancellationToken = default) => throw new NotImplementedException();
public Stream OpenReadStream() => throw new NotImplementedException();
}

View File

@ -7,53 +7,54 @@ using Bit.Test.Common.Helpers;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Models.Request;
public class SendRequestModelTests
namespace Bit.Api.Test.Models.Request
{
[Fact]
public void ToSend_Text_Success()
public class SendRequestModelTests
{
var deletionDate = DateTime.UtcNow.AddDays(5);
var sendRequest = new SendRequestModel
[Fact]
public void ToSend_Text_Success()
{
DeletionDate = deletionDate,
Disabled = false,
ExpirationDate = null,
HideEmail = false,
Key = "encrypted_key",
MaxAccessCount = null,
Name = "encrypted_name",
Notes = null,
Password = "Password",
Text = new SendTextModel()
var deletionDate = DateTime.UtcNow.AddDays(5);
var sendRequest = new SendRequestModel
{
Hidden = false,
Text = "encrypted_text"
},
Type = SendType.Text,
};
DeletionDate = deletionDate,
Disabled = false,
ExpirationDate = null,
HideEmail = false,
Key = "encrypted_key",
MaxAccessCount = null,
Name = "encrypted_name",
Notes = null,
Password = "Password",
Text = new SendTextModel()
{
Hidden = false,
Text = "encrypted_text"
},
Type = SendType.Text,
};
var sendService = Substitute.For<ISendService>();
sendService.HashPassword(Arg.Any<string>())
.Returns((info) => $"hashed_{(string)info[0]}");
var sendService = Substitute.For<ISendService>();
sendService.HashPassword(Arg.Any<string>())
.Returns((info) => $"hashed_{(string)info[0]}");
var send = sendRequest.ToSend(Guid.NewGuid(), sendService);
var send = sendRequest.ToSend(Guid.NewGuid(), sendService);
Assert.Equal(deletionDate, send.DeletionDate);
Assert.False(send.Disabled);
Assert.Null(send.ExpirationDate);
Assert.False(send.HideEmail);
Assert.Equal("encrypted_key", send.Key);
Assert.Equal("hashed_Password", send.Password);
Assert.Equal(deletionDate, send.DeletionDate);
Assert.False(send.Disabled);
Assert.Null(send.ExpirationDate);
Assert.False(send.HideEmail);
Assert.Equal("encrypted_key", send.Key);
Assert.Equal("hashed_Password", send.Password);
using var jsonDocument = JsonDocument.Parse(send.Data);
var root = jsonDocument.RootElement;
var text = AssertHelper.AssertJsonProperty(root, "Text", JsonValueKind.String).GetString();
Assert.Equal("encrypted_text", text);
AssertHelper.AssertJsonProperty(root, "Hidden", JsonValueKind.False);
Assert.False(root.TryGetProperty("Notes", out var _));
var name = AssertHelper.AssertJsonProperty(root, "Name", JsonValueKind.String).GetString();
Assert.Equal("encrypted_name", name);
using var jsonDocument = JsonDocument.Parse(send.Data);
var root = jsonDocument.RootElement;
var text = AssertHelper.AssertJsonProperty(root, "Text", JsonValueKind.String).GetString();
Assert.Equal("encrypted_text", text);
AssertHelper.AssertJsonProperty(root, "Hidden", JsonValueKind.False);
Assert.False(root.TryGetProperty("Notes", out var _));
var name = AssertHelper.AssertJsonProperty(root, "Name", JsonValueKind.String).GetString();
Assert.Equal("encrypted_name", name);
}
}
}

View File

@ -5,22 +5,23 @@ using Microsoft.AspNetCore.Http;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Utilities;
public class ApiHelpersTests
namespace Bit.Api.Test.Utilities
{
[Fact]
public async Task ReadJsonFileFromBody_Success()
public class ApiHelpersTests
{
var context = Substitute.For<HttpContext>();
context.Request.ContentLength.Returns(200);
var bytes = Encoding.UTF8.GetBytes(testFile);
var formFile = new FormFile(new MemoryStream(bytes), 0, bytes.Length, "bitwarden_organization_license", "bitwarden_organization_license.json");
[Fact]
public async Task ReadJsonFileFromBody_Success()
{
var context = Substitute.For<HttpContext>();
context.Request.ContentLength.Returns(200);
var bytes = Encoding.UTF8.GetBytes(testFile);
var formFile = new FormFile(new MemoryStream(bytes), 0, bytes.Length, "bitwarden_organization_license", "bitwarden_organization_license.json");
var license = await ApiHelpers.ReadJsonFileFromBody<OrganizationLicense>(context, formFile);
Assert.Equal(8, license.Version);
var license = await ApiHelpers.ReadJsonFileFromBody<OrganizationLicense>(context, formFile);
Assert.Equal(8, license.Version);
}
const string testFile = "{\"licenseKey\": \"licenseKey\", \"installationId\": \"6285f891-b2ec-4047-84c5-2eb7f7747e74\", \"id\": \"1065216d-5854-4326-838d-635487f30b43\",\"name\": \"Test Org\",\"billingEmail\": \"test@email.com\",\"businessName\": null,\"enabled\": true, \"plan\": \"Enterprise (Annually)\",\"planType\": 11,\"seats\": 6,\"maxCollections\": null,\"usePolicies\": true,\"useSso\": true,\"useKeyConnector\": false,\"useGroups\": true,\"useEvents\": true,\"useDirectory\": true,\"useTotp\": true,\"use2fa\": true,\"useApi\": true,\"useResetPassword\": true,\"maxStorageGb\": 1,\"selfHost\": true,\"usersGetPremium\": true,\"version\": 8,\"issued\": \"2022-01-25T21:58:38.9454581Z\",\"refresh\": \"2022-01-28T14:26:31Z\",\"expires\": \"2022-01-28T14:26:31Z\",\"trial\": true,\"hash\": \"testvalue\",\"signature\": \"signature\"}";
}
const string testFile = "{\"licenseKey\": \"licenseKey\", \"installationId\": \"6285f891-b2ec-4047-84c5-2eb7f7747e74\", \"id\": \"1065216d-5854-4326-838d-635487f30b43\",\"name\": \"Test Org\",\"billingEmail\": \"test@email.com\",\"businessName\": null,\"enabled\": true, \"plan\": \"Enterprise (Annually)\",\"planType\": 11,\"seats\": 6,\"maxCollections\": null,\"usePolicies\": true,\"useSso\": true,\"useKeyConnector\": false,\"useGroups\": true,\"useEvents\": true,\"useDirectory\": true,\"useTotp\": true,\"use2fa\": true,\"useApi\": true,\"useResetPassword\": true,\"maxStorageGb\": 1,\"selfHost\": true,\"usersGetPremium\": true,\"version\": 8,\"issued\": \"2022-01-25T21:58:38.9454581Z\",\"refresh\": \"2022-01-28T14:26:31Z\",\"expires\": \"2022-01-28T14:26:31Z\",\"trial\": true,\"hash\": \"testvalue\",\"signature\": \"signature\"}";
}

View File

@ -10,70 +10,71 @@ using Microsoft.Extensions.Options;
using NSubstitute;
using Xunit;
namespace Bit.Billing.Test.Controllers;
[ControllerCustomize(typeof(FreshdeskController))]
[SutProviderCustomize]
public class FreshdeskControllerTests
namespace Bit.Billing.Test.Controllers
{
private const string ApiKey = "TESTFRESHDESKAPIKEY";
private const string WebhookKey = "TESTKEY";
[Theory]
[BitAutoData((string)null, null)]
[BitAutoData((string)null)]
[BitAutoData(WebhookKey, null)]
public async Task PostWebhook_NullRequiredParameters_BadRequest(string freshdeskWebhookKey, FreshdeskWebhookModel model,
BillingSettings billingSettings, SutProvider<FreshdeskController> sutProvider)
[ControllerCustomize(typeof(FreshdeskController))]
[SutProviderCustomize]
public class FreshdeskControllerTests
{
sutProvider.GetDependency<IOptions<BillingSettings>>().Value.FreshdeskWebhookKey.Returns(billingSettings.FreshdeskWebhookKey);
private const string ApiKey = "TESTFRESHDESKAPIKEY";
private const string WebhookKey = "TESTKEY";
var response = await sutProvider.Sut.PostWebhook(freshdeskWebhookKey, model);
var statusCodeResult = Assert.IsAssignableFrom<StatusCodeResult>(response);
Assert.Equal(StatusCodes.Status400BadRequest, statusCodeResult.StatusCode);
}
[Theory]
[BitAutoData]
public async Task PostWebhook_Success(User user, FreshdeskWebhookModel model,
List<Organization> organizations, SutProvider<FreshdeskController> sutProvider)
{
model.TicketContactEmail = user.Email;
sutProvider.GetDependency<IUserRepository>().GetByEmailAsync(user.Email).Returns(user);
sutProvider.GetDependency<IOrganizationRepository>().GetManyByUserIdAsync(user.Id).Returns(organizations);
var mockHttpMessageHandler = Substitute.ForPartsOf<MockHttpMessageHandler>();
var mockResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
mockHttpMessageHandler.Send(Arg.Any<HttpRequestMessage>(), Arg.Any<CancellationToken>())
.Returns(mockResponse);
var httpClient = new HttpClient(mockHttpMessageHandler);
sutProvider.GetDependency<IHttpClientFactory>().CreateClient("FreshdeskApi").Returns(httpClient);
sutProvider.GetDependency<IOptions<BillingSettings>>().Value.FreshdeskWebhookKey.Returns(WebhookKey);
sutProvider.GetDependency<IOptions<BillingSettings>>().Value.FreshdeskApiKey.Returns(ApiKey);
var response = await sutProvider.Sut.PostWebhook(WebhookKey, model);
var statusCodeResult = Assert.IsAssignableFrom<StatusCodeResult>(response);
Assert.Equal(StatusCodes.Status200OK, statusCodeResult.StatusCode);
_ = mockHttpMessageHandler.Received(1).Send(Arg.Is<HttpRequestMessage>(m => m.Method == HttpMethod.Put && m.RequestUri.ToString().EndsWith(model.TicketId)), Arg.Any<CancellationToken>());
_ = mockHttpMessageHandler.Received(1).Send(Arg.Is<HttpRequestMessage>(m => m.Method == HttpMethod.Post && m.RequestUri.ToString().EndsWith($"{model.TicketId}/notes")), Arg.Any<CancellationToken>());
}
public class MockHttpMessageHandler : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
[Theory]
[BitAutoData((string)null, null)]
[BitAutoData((string)null)]
[BitAutoData(WebhookKey, null)]
public async Task PostWebhook_NullRequiredParameters_BadRequest(string freshdeskWebhookKey, FreshdeskWebhookModel model,
BillingSettings billingSettings, SutProvider<FreshdeskController> sutProvider)
{
return Send(request, cancellationToken);
sutProvider.GetDependency<IOptions<BillingSettings>>().Value.FreshdeskWebhookKey.Returns(billingSettings.FreshdeskWebhookKey);
var response = await sutProvider.Sut.PostWebhook(freshdeskWebhookKey, model);
var statusCodeResult = Assert.IsAssignableFrom<StatusCodeResult>(response);
Assert.Equal(StatusCodes.Status400BadRequest, statusCodeResult.StatusCode);
}
public virtual Task<HttpResponseMessage> Send(HttpRequestMessage request, CancellationToken cancellationToken)
[Theory]
[BitAutoData]
public async Task PostWebhook_Success(User user, FreshdeskWebhookModel model,
List<Organization> organizations, SutProvider<FreshdeskController> sutProvider)
{
throw new NotImplementedException();
model.TicketContactEmail = user.Email;
sutProvider.GetDependency<IUserRepository>().GetByEmailAsync(user.Email).Returns(user);
sutProvider.GetDependency<IOrganizationRepository>().GetManyByUserIdAsync(user.Id).Returns(organizations);
var mockHttpMessageHandler = Substitute.ForPartsOf<MockHttpMessageHandler>();
var mockResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
mockHttpMessageHandler.Send(Arg.Any<HttpRequestMessage>(), Arg.Any<CancellationToken>())
.Returns(mockResponse);
var httpClient = new HttpClient(mockHttpMessageHandler);
sutProvider.GetDependency<IHttpClientFactory>().CreateClient("FreshdeskApi").Returns(httpClient);
sutProvider.GetDependency<IOptions<BillingSettings>>().Value.FreshdeskWebhookKey.Returns(WebhookKey);
sutProvider.GetDependency<IOptions<BillingSettings>>().Value.FreshdeskApiKey.Returns(ApiKey);
var response = await sutProvider.Sut.PostWebhook(WebhookKey, model);
var statusCodeResult = Assert.IsAssignableFrom<StatusCodeResult>(response);
Assert.Equal(StatusCodes.Status200OK, statusCodeResult.StatusCode);
_ = mockHttpMessageHandler.Received(1).Send(Arg.Is<HttpRequestMessage>(m => m.Method == HttpMethod.Put && m.RequestUri.ToString().EndsWith(model.TicketId)), Arg.Any<CancellationToken>());
_ = mockHttpMessageHandler.Received(1).Send(Arg.Is<HttpRequestMessage>(m => m.Method == HttpMethod.Post && m.RequestUri.ToString().EndsWith($"{model.TicketId}/notes")), Arg.Any<CancellationToken>());
}
public class MockHttpMessageHandler : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Send(request, cancellationToken);
}
public virtual Task<HttpResponseMessage> Send(HttpRequestMessage request, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
}
}

View File

@ -10,72 +10,73 @@ using Microsoft.Extensions.Options;
using NSubstitute;
using Xunit;
namespace Bit.Billing.Test.Controllers;
public class FreshsalesControllerTests
namespace Bit.Billing.Test.Controllers
{
private const string ApiKey = "TEST_FRESHSALES_APIKEY";
private const string TestLead = "TEST_FRESHSALES_TESTLEAD";
private static (FreshsalesController, IUserRepository, IOrganizationRepository) CreateSut(
string freshsalesApiKey)
public class FreshsalesControllerTests
{
var userRepository = Substitute.For<IUserRepository>();
var organizationRepository = Substitute.For<IOrganizationRepository>();
private const string ApiKey = "TEST_FRESHSALES_APIKEY";
private const string TestLead = "TEST_FRESHSALES_TESTLEAD";
var billingSettings = Options.Create(new BillingSettings
private static (FreshsalesController, IUserRepository, IOrganizationRepository) CreateSut(
string freshsalesApiKey)
{
FreshsalesApiKey = freshsalesApiKey,
});
var globalSettings = new GlobalSettings();
globalSettings.BaseServiceUri.Admin = "https://test.com";
var userRepository = Substitute.For<IUserRepository>();
var organizationRepository = Substitute.For<IOrganizationRepository>();
var sut = new FreshsalesController(
userRepository,
organizationRepository,
billingSettings,
Substitute.For<ILogger<FreshsalesController>>(),
globalSettings
);
return (sut, userRepository, organizationRepository);
}
[RequiredEnvironmentTheory(ApiKey, TestLead), EnvironmentData(ApiKey, TestLead)]
public async Task PostWebhook_Success(string freshsalesApiKey, long leadId)
{
// This test is only for development to use:
// `export TEST_FRESHSALES_APIKEY=[apikey]`
// `export TEST_FRESHSALES_TESTLEAD=[lead id]`
// `dotnet test --filter "FullyQualifiedName~FreshsalesControllerTests.PostWebhook_Success"`
var (sut, userRepository, organizationRepository) = CreateSut(freshsalesApiKey);
var user = new User
{
Id = Guid.NewGuid(),
Email = "test@email.com",
Premium = true,
};
userRepository.GetByEmailAsync(user.Email)
.Returns(user);
organizationRepository.GetManyByUserIdAsync(user.Id)
.Returns(new List<Organization>
var billingSettings = Options.Create(new BillingSettings
{
new Organization
{
Id = Guid.NewGuid(),
Name = "Test Org",
}
FreshsalesApiKey = freshsalesApiKey,
});
var globalSettings = new GlobalSettings();
globalSettings.BaseServiceUri.Admin = "https://test.com";
var response = await sut.PostWebhook(freshsalesApiKey, new CustomWebhookRequestModel
var sut = new FreshsalesController(
userRepository,
organizationRepository,
billingSettings,
Substitute.For<ILogger<FreshsalesController>>(),
globalSettings
);
return (sut, userRepository, organizationRepository);
}
[RequiredEnvironmentTheory(ApiKey, TestLead), EnvironmentData(ApiKey, TestLead)]
public async Task PostWebhook_Success(string freshsalesApiKey, long leadId)
{
LeadId = leadId,
}, new CancellationToken(false));
// This test is only for development to use:
// `export TEST_FRESHSALES_APIKEY=[apikey]`
// `export TEST_FRESHSALES_TESTLEAD=[lead id]`
// `dotnet test --filter "FullyQualifiedName~FreshsalesControllerTests.PostWebhook_Success"`
var (sut, userRepository, organizationRepository) = CreateSut(freshsalesApiKey);
var statusCodeResult = Assert.IsAssignableFrom<StatusCodeResult>(response);
Assert.Equal(StatusCodes.Status204NoContent, statusCodeResult.StatusCode);
var user = new User
{
Id = Guid.NewGuid(),
Email = "test@email.com",
Premium = true,
};
userRepository.GetByEmailAsync(user.Email)
.Returns(user);
organizationRepository.GetManyByUserIdAsync(user.Id)
.Returns(new List<Organization>
{
new Organization
{
Id = Guid.NewGuid(),
Name = "Test Org",
}
});
var response = await sut.PostWebhook(freshsalesApiKey, new CustomWebhookRequestModel
{
LeadId = leadId,
}, new CancellationToken(false));
var statusCodeResult = Assert.IsAssignableFrom<StatusCodeResult>(response);
Assert.Equal(StatusCodes.Status204NoContent, statusCodeResult.StatusCode);
}
}
}

View File

@ -3,25 +3,26 @@ using AutoFixture;
using Bit.Test.Common.Helpers;
using Xunit.Sdk;
namespace Bit.Test.Common.AutoFixture.Attributes;
[DataDiscoverer("AutoFixture.Xunit2.NoPreDiscoveryDataDiscoverer", "AutoFixture.Xunit2")]
public class BitAutoDataAttribute : DataAttribute
namespace Bit.Test.Common.AutoFixture.Attributes
{
private readonly Func<IFixture> _createFixture;
private readonly object[] _fixedTestParameters;
public BitAutoDataAttribute(params object[] fixedTestParameters) :
this(() => new Fixture(), fixedTestParameters)
{ }
public BitAutoDataAttribute(Func<IFixture> createFixture, params object[] fixedTestParameters) :
base()
[DataDiscoverer("AutoFixture.Xunit2.NoPreDiscoveryDataDiscoverer", "AutoFixture.Xunit2")]
public class BitAutoDataAttribute : DataAttribute
{
_createFixture = createFixture;
_fixedTestParameters = fixedTestParameters;
}
private readonly Func<IFixture> _createFixture;
private readonly object[] _fixedTestParameters;
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
=> BitAutoDataAttributeHelpers.GetData(testMethod, _createFixture(), _fixedTestParameters);
public BitAutoDataAttribute(params object[] fixedTestParameters) :
this(() => new Fixture(), fixedTestParameters)
{ }
public BitAutoDataAttribute(Func<IFixture> createFixture, params object[] fixedTestParameters) :
base()
{
_createFixture = createFixture;
_fixedTestParameters = fixedTestParameters;
}
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
=> BitAutoDataAttributeHelpers.GetData(testMethod, _createFixture(), _fixedTestParameters);
}
}

View File

@ -1,20 +1,21 @@
using AutoFixture;
namespace Bit.Test.Common.AutoFixture.Attributes;
/// <summary>
/// <para>
/// Base class for customizing parameters in methods decorated with the
/// Bit.Test.Common.AutoFixture.Attributes.MemberAutoDataAttribute.
/// </para>
/// ⚠ Warning ⚠ Will not insert customizations into AutoFixture's AutoDataAttribute build chain
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Parameter, AllowMultiple = true)]
public abstract class BitCustomizeAttribute : Attribute
namespace Bit.Test.Common.AutoFixture.Attributes
{
/// <summary>
/// /// Gets a customization for the method's parameters.
/// <para>
/// Base class for customizing parameters in methods decorated with the
/// Bit.Test.Common.AutoFixture.Attributes.MemberAutoDataAttribute.
/// </para>
/// ⚠ Warning ⚠ Will not insert customizations into AutoFixture's AutoDataAttribute build chain
/// </summary>
/// <returns>A customization for the method's paramters.</returns>
public abstract ICustomization GetCustomization();
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Parameter, AllowMultiple = true)]
public abstract class BitCustomizeAttribute : Attribute
{
/// <summary>
/// /// Gets a customization for the method's parameters.
/// </summary>
/// <returns>A customization for the method's paramters.</returns>
public abstract ICustomization GetCustomization();
}
}

View File

@ -3,22 +3,23 @@ using AutoFixture;
using Bit.Test.Common.Helpers;
using Xunit;
namespace Bit.Test.Common.AutoFixture.Attributes;
public class BitMemberAutoDataAttribute : MemberDataAttributeBase
namespace Bit.Test.Common.AutoFixture.Attributes
{
private readonly Func<IFixture> _createFixture;
public BitMemberAutoDataAttribute(string memberName, params object[] parameters) :
this(() => new Fixture(), memberName, parameters)
{ }
public BitMemberAutoDataAttribute(Func<IFixture> createFixture, string memberName, params object[] parameters) :
base(memberName, parameters)
public class BitMemberAutoDataAttribute : MemberDataAttributeBase
{
_createFixture = createFixture;
}
private readonly Func<IFixture> _createFixture;
protected override object[] ConvertDataItem(MethodInfo testMethod, object item) =>
BitAutoDataAttributeHelpers.GetData(testMethod, _createFixture(), item as object[]).First();
public BitMemberAutoDataAttribute(string memberName, params object[] parameters) :
this(() => new Fixture(), memberName, parameters)
{ }
public BitMemberAutoDataAttribute(Func<IFixture> createFixture, string memberName, params object[] parameters) :
base(memberName, parameters)
{
_createFixture = createFixture;
}
protected override object[] ConvertDataItem(MethodInfo testMethod, object item) =>
BitAutoDataAttributeHelpers.GetData(testMethod, _createFixture(), item as object[]).First();
}
}

View File

@ -1,22 +1,23 @@
using AutoFixture;
namespace Bit.Test.Common.AutoFixture.Attributes;
/// <summary>
/// Disables setting of Auto Properties on the Controller to avoid ASP.net initialization errors from a mock environment. Still sets constructor dependencies.
/// </summary>
public class ControllerCustomizeAttribute : BitCustomizeAttribute
namespace Bit.Test.Common.AutoFixture.Attributes
{
private readonly Type _controllerType;
/// <summary>
/// Initialize an instance of the ControllerCustomizeAttribute class
/// Disables setting of Auto Properties on the Controller to avoid ASP.net initialization errors from a mock environment. Still sets constructor dependencies.
/// </summary>
/// <param name="controllerType">The Type of the controller to allow autofixture to create</param>
public ControllerCustomizeAttribute(Type controllerType)
public class ControllerCustomizeAttribute : BitCustomizeAttribute
{
_controllerType = controllerType;
}
private readonly Type _controllerType;
public override ICustomization GetCustomization() => new ControllerCustomization(_controllerType);
/// <summary>
/// Initialize an instance of the ControllerCustomizeAttribute class
/// </summary>
/// <param name="controllerType">The Type of the controller to allow autofixture to create</param>
public ControllerCustomizeAttribute(Type controllerType)
{
_controllerType = controllerType;
}
public override ICustomization GetCustomization() => new ControllerCustomization(_controllerType);
}
}

View File

@ -1,22 +1,23 @@
using AutoFixture;
using AutoFixture.Xunit2;
namespace Bit.Test.Common.AutoFixture.Attributes;
public class CustomAutoDataAttribute : AutoDataAttribute
namespace Bit.Test.Common.AutoFixture.Attributes
{
public CustomAutoDataAttribute(params Type[] iCustomizationTypes) : this(iCustomizationTypes
.Select(t => (ICustomization)Activator.CreateInstance(t)).ToArray())
{ }
public CustomAutoDataAttribute(params ICustomization[] customizations) : base(() =>
public class CustomAutoDataAttribute : AutoDataAttribute
{
var fixture = new Fixture();
foreach (var customization in customizations)
public CustomAutoDataAttribute(params Type[] iCustomizationTypes) : this(iCustomizationTypes
.Select(t => (ICustomization)Activator.CreateInstance(t)).ToArray())
{ }
public CustomAutoDataAttribute(params ICustomization[] customizations) : base(() =>
{
fixture.Customize(customization);
}
return fixture;
})
{ }
var fixture = new Fixture();
foreach (var customization in customizations)
{
fixture.Customize(customization);
}
return fixture;
})
{ }
}
}

View File

@ -1,42 +1,43 @@
using System.Reflection;
using Xunit.Sdk;
namespace Bit.Test.Common.AutoFixture.Attributes;
/// <summary>
/// Used for collecting data from environment useful for when we want to test an integration with another service and
/// it might require an api key or other piece of sensitive data that we don't want slipping into the wrong hands.
/// </summary>
/// <remarks>
/// It probably should be refactored to support fixtures and other customization so it can more easily be used in conjunction
/// with more parameters. Currently it attempt to match environment variable names to values of the parameter type in that positions.
/// It will start from the first parameter and go for each supplied name.
/// </remarks>
public class EnvironmentDataAttribute : DataAttribute
namespace Bit.Test.Common.AutoFixture.Attributes
{
private readonly string[] _environmentVariableNames;
public EnvironmentDataAttribute(params string[] environmentVariableNames)
/// <summary>
/// Used for collecting data from environment useful for when we want to test an integration with another service and
/// it might require an api key or other piece of sensitive data that we don't want slipping into the wrong hands.
/// </summary>
/// <remarks>
/// It probably should be refactored to support fixtures and other customization so it can more easily be used in conjunction
/// with more parameters. Currently it attempt to match environment variable names to values of the parameter type in that positions.
/// It will start from the first parameter and go for each supplied name.
/// </remarks>
public class EnvironmentDataAttribute : DataAttribute
{
_environmentVariableNames = environmentVariableNames;
}
private readonly string[] _environmentVariableNames;
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
{
var methodParameters = testMethod.GetParameters();
if (methodParameters.Length < _environmentVariableNames.Length)
public EnvironmentDataAttribute(params string[] environmentVariableNames)
{
throw new ArgumentException($"The target test method only has {methodParameters.Length} arguments but you supplied {_environmentVariableNames.Length}");
_environmentVariableNames = environmentVariableNames;
}
var values = new object[_environmentVariableNames.Length];
for (var i = 0; i < _environmentVariableNames.Length; i++)
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
{
values[i] = Convert.ChangeType(Environment.GetEnvironmentVariable(_environmentVariableNames[i]), methodParameters[i].ParameterType);
}
var methodParameters = testMethod.GetParameters();
return new[] { values };
if (methodParameters.Length < _environmentVariableNames.Length)
{
throw new ArgumentException($"The target test method only has {methodParameters.Length} arguments but you supplied {_environmentVariableNames.Length}");
}
var values = new object[_environmentVariableNames.Length];
for (var i = 0; i < _environmentVariableNames.Length; i++)
{
values[i] = Convert.ChangeType(Environment.GetEnvironmentVariable(_environmentVariableNames[i]), methodParameters[i].ParameterType);
}
return new[] { values };
}
}
}

View File

@ -3,19 +3,20 @@ using AutoFixture.Xunit2;
using Xunit;
using Xunit.Sdk;
namespace Bit.Test.Common.AutoFixture.Attributes;
public class InlineCustomAutoDataAttribute : CompositeDataAttribute
namespace Bit.Test.Common.AutoFixture.Attributes
{
public InlineCustomAutoDataAttribute(Type[] iCustomizationTypes, params object[] values) : base(new DataAttribute[] {
new InlineDataAttribute(values),
new CustomAutoDataAttribute(iCustomizationTypes)
})
{ }
public class InlineCustomAutoDataAttribute : CompositeDataAttribute
{
public InlineCustomAutoDataAttribute(Type[] iCustomizationTypes, params object[] values) : base(new DataAttribute[] {
new InlineDataAttribute(values),
new CustomAutoDataAttribute(iCustomizationTypes)
})
{ }
public InlineCustomAutoDataAttribute(ICustomization[] customizations, params object[] values) : base(new DataAttribute[] {
new InlineDataAttribute(values),
new CustomAutoDataAttribute(customizations)
})
{ }
public InlineCustomAutoDataAttribute(ICustomization[] customizations, params object[] values) : base(new DataAttribute[] {
new InlineDataAttribute(values),
new CustomAutoDataAttribute(customizations)
})
{ }
}
}

View File

@ -1,17 +1,18 @@
using AutoFixture;
namespace Bit.Test.Common.AutoFixture.Attributes;
public class InlineSutAutoDataAttribute : InlineCustomAutoDataAttribute
namespace Bit.Test.Common.AutoFixture.Attributes
{
public InlineSutAutoDataAttribute(params object[] values) : base(
new Type[] { typeof(SutProviderCustomization) }, values)
{ }
public InlineSutAutoDataAttribute(Type[] iCustomizationTypes, params object[] values) : base(
iCustomizationTypes.Append(typeof(SutProviderCustomization)).ToArray(), values)
{ }
public class InlineSutAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineSutAutoDataAttribute(params object[] values) : base(
new Type[] { typeof(SutProviderCustomization) }, values)
{ }
public InlineSutAutoDataAttribute(Type[] iCustomizationTypes, params object[] values) : base(
iCustomizationTypes.Append(typeof(SutProviderCustomization)).ToArray(), values)
{ }
public InlineSutAutoDataAttribute(ICustomization[] customizations, params object[] values) : base(
customizations.Append(new SutProviderCustomization()).ToArray(), values)
{ }
public InlineSutAutoDataAttribute(ICustomization[] customizations, params object[] values) : base(
customizations.Append(new SutProviderCustomization()).ToArray(), values)
{ }
}
}

View File

@ -1,10 +1,11 @@
using AutoFixture;
using Bit.Test.Common.AutoFixture.JsonDocumentFixtures;
namespace Bit.Test.Common.AutoFixture.Attributes;
public class JsonDocumentCustomizeAttribute : BitCustomizeAttribute
namespace Bit.Test.Common.AutoFixture.Attributes
{
public string Json { get; set; }
public override ICustomization GetCustomization() => new JsonDocumentCustomization() { Json = Json };
public class JsonDocumentCustomizeAttribute : BitCustomizeAttribute
{
public string Json { get; set; }
public override ICustomization GetCustomization() => new JsonDocumentCustomization() { Json = Json };
}
}

View File

@ -1,37 +1,38 @@
using Xunit;
namespace Bit.Test.Common.AutoFixture.Attributes;
/// <summary>
/// Used for requiring certain environment variables exist at the time. Mostly used for more edge unit tests that shouldn't
/// be run during CI builds or should only be ran in CI builds when pieces of information are available.
/// </summary>
public class RequiredEnvironmentTheoryAttribute : TheoryAttribute
namespace Bit.Test.Common.AutoFixture.Attributes
{
private readonly string[] _environmentVariableNames;
public RequiredEnvironmentTheoryAttribute(params string[] environmentVariableNames)
/// <summary>
/// Used for requiring certain environment variables exist at the time. Mostly used for more edge unit tests that shouldn't
/// be run during CI builds or should only be ran in CI builds when pieces of information are available.
/// </summary>
public class RequiredEnvironmentTheoryAttribute : TheoryAttribute
{
_environmentVariableNames = environmentVariableNames;
private readonly string[] _environmentVariableNames;
if (!HasRequiredEnvironmentVariables())
public RequiredEnvironmentTheoryAttribute(params string[] environmentVariableNames)
{
Skip = $"Missing one or more required environment variables. ({string.Join(", ", _environmentVariableNames)})";
}
}
_environmentVariableNames = environmentVariableNames;
private bool HasRequiredEnvironmentVariables()
{
foreach (var env in _environmentVariableNames)
{
var value = Environment.GetEnvironmentVariable(env);
if (value == null)
if (!HasRequiredEnvironmentVariables())
{
return false;
Skip = $"Missing one or more required environment variables. ({string.Join(", ", _environmentVariableNames)})";
}
}
return true;
private bool HasRequiredEnvironmentVariables()
{
foreach (var env in _environmentVariableNames)
{
var value = Environment.GetEnvironmentVariable(env);
if (value == null)
{
return false;
}
}
return true;
}
}
}

View File

@ -1,15 +1,16 @@
using AutoFixture;
namespace Bit.Test.Common.AutoFixture.Attributes;
public class SutProviderCustomizeAttribute : BitCustomizeAttribute
namespace Bit.Test.Common.AutoFixture.Attributes
{
public override ICustomization GetCustomization() => new SutProviderCustomization();
}
public class SutProviderCustomizeAttribute : BitCustomizeAttribute
{
public override ICustomization GetCustomization() => new SutProviderCustomization();
}
public class SutAutoDataAttribute : CustomAutoDataAttribute
{
public SutAutoDataAttribute(params Type[] iCustomizationTypes) : base(
iCustomizationTypes.Append(typeof(SutProviderCustomization)).ToArray())
{ }
public class SutAutoDataAttribute : CustomAutoDataAttribute
{
public SutAutoDataAttribute(params Type[] iCustomizationTypes) : base(
iCustomizationTypes.Append(typeof(SutProviderCustomization)).ToArray())
{ }
}
}

View File

@ -1,38 +1,39 @@
using AutoFixture;
using AutoFixture.Kernel;
namespace Bit.Test.Common.AutoFixture;
public class BuilderWithoutAutoProperties : ISpecimenBuilder
namespace Bit.Test.Common.AutoFixture
{
private readonly Type _type;
public BuilderWithoutAutoProperties(Type type)
public class BuilderWithoutAutoProperties : ISpecimenBuilder
{
_type = type;
}
public object Create(object request, ISpecimenContext context)
{
if (context == null)
private readonly Type _type;
public BuilderWithoutAutoProperties(Type type)
{
throw new ArgumentNullException(nameof(context));
_type = type;
}
var type = request as Type;
if (type == null || type != _type)
public object Create(object request, ISpecimenContext context)
{
return new NoSpecimen();
}
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var fixture = new Fixture();
// This is the equivalent of _fixture.Build<_type>().OmitAutoProperties().Create(request, context), but no overload for
// Build(Type type) exists.
dynamic reflectedComposer = typeof(Fixture).GetMethod("Build").MakeGenericMethod(_type).Invoke(fixture, null);
return reflectedComposer.OmitAutoProperties().Create(request, context);
var type = request as Type;
if (type == null || type != _type)
{
return new NoSpecimen();
}
var fixture = new Fixture();
// This is the equivalent of _fixture.Build<_type>().OmitAutoProperties().Create(request, context), but no overload for
// Build(Type type) exists.
dynamic reflectedComposer = typeof(Fixture).GetMethod("Build").MakeGenericMethod(_type).Invoke(fixture, null);
return reflectedComposer.OmitAutoProperties().Create(request, context);
}
}
public class BuilderWithoutAutoProperties<T> : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context) =>
new BuilderWithoutAutoProperties(typeof(T)).Create(request, context);
}
}
public class BuilderWithoutAutoProperties<T> : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context) =>
new BuilderWithoutAutoProperties(typeof(T)).Create(request, context);
}

View File

@ -2,31 +2,32 @@
using Microsoft.AspNetCore.Mvc;
using Org.BouncyCastle.Security;
namespace Bit.Test.Common.AutoFixture;
/// <summary>
/// Disables setting of Auto Properties on the Controller to avoid ASP.net initialization errors. Still sets constructor dependencies.
/// </summary>
/// <param name="fixture"></param>
public class ControllerCustomization : ICustomization
namespace Bit.Test.Common.AutoFixture
{
private readonly Type _controllerType;
public ControllerCustomization(Type controllerType)
/// <summary>
/// Disables setting of Auto Properties on the Controller to avoid ASP.net initialization errors. Still sets constructor dependencies.
/// </summary>
/// <param name="fixture"></param>
public class ControllerCustomization : ICustomization
{
if (!controllerType.IsAssignableTo(typeof(Controller)))
private readonly Type _controllerType;
public ControllerCustomization(Type controllerType)
{
throw new InvalidParameterException($"{nameof(controllerType)} must derive from {typeof(Controller).Name}");
if (!controllerType.IsAssignableTo(typeof(Controller)))
{
throw new InvalidParameterException($"{nameof(controllerType)} must derive from {typeof(Controller).Name}");
}
_controllerType = controllerType;
}
_controllerType = controllerType;
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new BuilderWithoutAutoProperties(_controllerType));
}
}
public void Customize(IFixture fixture)
public class ControllerCustomization<T> : ICustomization where T : Controller
{
fixture.Customizations.Add(new BuilderWithoutAutoProperties(_controllerType));
public void Customize(IFixture fixture) => new ControllerCustomization(typeof(T)).Customize(fixture);
}
}
public class ControllerCustomization<T> : ICustomization where T : Controller
{
public void Customize(IFixture fixture) => new ControllerCustomization(typeof(T)).Customize(fixture);
}

View File

@ -1,13 +1,14 @@
using AutoFixture;
using AutoFixture.AutoNSubstitute;
namespace Bit.Test.Common.AutoFixture;
public static class FixtureExtensions
namespace Bit.Test.Common.AutoFixture
{
public static IFixture WithAutoNSubstitutions(this IFixture fixture)
=> fixture.Customize(new AutoNSubstituteCustomization());
public static class FixtureExtensions
{
public static IFixture WithAutoNSubstitutions(this IFixture fixture)
=> fixture.Customize(new AutoNSubstituteCustomization());
public static IFixture WithAutoNSubstitutionsAutoPopulatedProperties(this IFixture fixture)
=> fixture.Customize(new AutoNSubstituteCustomization { ConfigureMembers = true });
public static IFixture WithAutoNSubstitutionsAutoPopulatedProperties(this IFixture fixture)
=> fixture.Customize(new AutoNSubstituteCustomization { ConfigureMembers = true });
}
}

View File

@ -1,15 +1,16 @@
using AutoFixture;
namespace Bit.Test.Common.AutoFixture;
public class GlobalSettings : ICustomization
namespace Bit.Test.Common.AutoFixture
{
public void Customize(IFixture fixture)
public class GlobalSettings : ICustomization
{
fixture.Customize<Bit.Core.Settings.GlobalSettings>(composer => composer
.Without(s => s.BaseServiceUri)
.Without(s => s.Attachment)
.Without(s => s.Send)
.Without(s => s.DataProtection));
public void Customize(IFixture fixture)
{
fixture.Customize<Bit.Core.Settings.GlobalSettings>(composer => composer
.Without(s => s.BaseServiceUri)
.Without(s => s.Attachment)
.Without(s => s.Send)
.Without(s => s.DataProtection));
}
}
}

View File

@ -1,7 +1,8 @@
namespace Bit.Test.Common.AutoFixture;
public interface ISutProvider
namespace Bit.Test.Common.AutoFixture
{
Type SutType { get; }
ISutProvider Create();
public interface ISutProvider
{
Type SutType { get; }
ISutProvider Create();
}
}

View File

@ -2,30 +2,31 @@
using AutoFixture;
using AutoFixture.Kernel;
namespace Bit.Test.Common.AutoFixture.JsonDocumentFixtures;
public class JsonDocumentCustomization : ICustomization, ISpecimenBuilder
namespace Bit.Test.Common.AutoFixture.JsonDocumentFixtures
{
public string Json { get; set; }
public void Customize(IFixture fixture)
public class JsonDocumentCustomization : ICustomization, ISpecimenBuilder
{
fixture.Customizations.Add(this);
}
public object Create(object request, ISpecimenContext context)
{
if (context == null)
public string Json { get; set; }
public void Customize(IFixture fixture)
{
throw new ArgumentNullException(nameof(context));
}
var type = request as Type;
if (type == null || (type != typeof(JsonDocument)))
{
return new NoSpecimen();
fixture.Customizations.Add(this);
}
return JsonDocument.Parse(Json ?? "{}");
public object Create(object request, ISpecimenContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var type = request as Type;
if (type == null || (type != typeof(JsonDocument)))
{
return new NoSpecimen();
}
return JsonDocument.Parse(Json ?? "{}");
}
}
}

View File

@ -2,132 +2,133 @@
using AutoFixture;
using AutoFixture.Kernel;
namespace Bit.Test.Common.AutoFixture;
public class SutProvider<TSut> : ISutProvider
namespace Bit.Test.Common.AutoFixture
{
private Dictionary<Type, Dictionary<string, object>> _dependencies;
private readonly IFixture _fixture;
private readonly ConstructorParameterRelay<TSut> _constructorParameterRelay;
public TSut Sut { get; private set; }
public Type SutType => typeof(TSut);
public SutProvider() : this(new Fixture()) { }
public SutProvider(IFixture fixture)
public class SutProvider<TSut> : ISutProvider
{
_dependencies = new Dictionary<Type, Dictionary<string, object>>();
_fixture = (fixture ?? new Fixture()).WithAutoNSubstitutions().Customize(new GlobalSettings());
_constructorParameterRelay = new ConstructorParameterRelay<TSut>(this, _fixture);
_fixture.Customizations.Add(_constructorParameterRelay);
}
private Dictionary<Type, Dictionary<string, object>> _dependencies;
private readonly IFixture _fixture;
private readonly ConstructorParameterRelay<TSut> _constructorParameterRelay;
public SutProvider<TSut> SetDependency<T>(T dependency, string parameterName = "")
=> SetDependency(typeof(T), dependency, parameterName);
public SutProvider<TSut> SetDependency(Type dependencyType, object dependency, string parameterName = "")
{
if (_dependencies.ContainsKey(dependencyType))
public TSut Sut { get; private set; }
public Type SutType => typeof(TSut);
public SutProvider() : this(new Fixture()) { }
public SutProvider(IFixture fixture)
{
_dependencies[dependencyType][parameterName] = dependency;
}
else
{
_dependencies[dependencyType] = new Dictionary<string, object> { { parameterName, dependency } };
_dependencies = new Dictionary<Type, Dictionary<string, object>>();
_fixture = (fixture ?? new Fixture()).WithAutoNSubstitutions().Customize(new GlobalSettings());
_constructorParameterRelay = new ConstructorParameterRelay<TSut>(this, _fixture);
_fixture.Customizations.Add(_constructorParameterRelay);
}
return this;
}
public T GetDependency<T>(string parameterName = "") => (T)GetDependency(typeof(T), parameterName);
public object GetDependency(Type dependencyType, string parameterName = "")
{
if (DependencyIsSet(dependencyType, parameterName))
public SutProvider<TSut> SetDependency<T>(T dependency, string parameterName = "")
=> SetDependency(typeof(T), dependency, parameterName);
public SutProvider<TSut> SetDependency(Type dependencyType, object dependency, string parameterName = "")
{
return _dependencies[dependencyType][parameterName];
}
else if (_dependencies.ContainsKey(dependencyType))
{
var knownDependencies = _dependencies[dependencyType];
if (knownDependencies.Values.Count == 1)
if (_dependencies.ContainsKey(dependencyType))
{
return _dependencies[dependencyType].Values.Single();
_dependencies[dependencyType][parameterName] = dependency;
}
else
{
throw new ArgumentException(string.Concat($"Dependency of type {dependencyType.Name} and name ",
$"{parameterName} does not exist. Available dependency names are: ",
string.Join(", ", knownDependencies.Keys)));
_dependencies[dependencyType] = new Dictionary<string, object> { { parameterName, dependency } };
}
}
else
{
throw new ArgumentException($"Dependency of type {dependencyType.Name} and name {parameterName} has not been set.");
}
}
public void Reset()
{
_dependencies = new Dictionary<Type, Dictionary<string, object>>();
Sut = default;
}
ISutProvider ISutProvider.Create() => Create();
public SutProvider<TSut> Create()
{
Sut = _fixture.Create<TSut>();
return this;
}
private bool DependencyIsSet(Type dependencyType, string parameterName = "")
=> _dependencies.ContainsKey(dependencyType) && _dependencies[dependencyType].ContainsKey(parameterName);
private object GetDefault(Type type) => type.IsValueType ? Activator.CreateInstance(type) : null;
private class ConstructorParameterRelay<T> : ISpecimenBuilder
{
private readonly SutProvider<T> _sutProvider;
private readonly IFixture _fixture;
public ConstructorParameterRelay(SutProvider<T> sutProvider, IFixture fixture)
{
_sutProvider = sutProvider;
_fixture = fixture;
return this;
}
public object Create(object request, ISpecimenContext context)
public T GetDependency<T>(string parameterName = "") => (T)GetDependency(typeof(T), parameterName);
public object GetDependency(Type dependencyType, string parameterName = "")
{
if (context == null)
if (DependencyIsSet(dependencyType, parameterName))
{
throw new ArgumentNullException(nameof(context));
return _dependencies[dependencyType][parameterName];
}
if (!(request is ParameterInfo parameterInfo))
else if (_dependencies.ContainsKey(dependencyType))
{
return new NoSpecimen();
var knownDependencies = _dependencies[dependencyType];
if (knownDependencies.Values.Count == 1)
{
return _dependencies[dependencyType].Values.Single();
}
else
{
throw new ArgumentException(string.Concat($"Dependency of type {dependencyType.Name} and name ",
$"{parameterName} does not exist. Available dependency names are: ",
string.Join(", ", knownDependencies.Keys)));
}
}
if (parameterInfo.Member.DeclaringType != typeof(T) ||
parameterInfo.Member.MemberType != MemberTypes.Constructor)
else
{
return new NoSpecimen();
throw new ArgumentException($"Dependency of type {dependencyType.Name} and name {parameterName} has not been set.");
}
}
public void Reset()
{
_dependencies = new Dictionary<Type, Dictionary<string, object>>();
Sut = default;
}
ISutProvider ISutProvider.Create() => Create();
public SutProvider<TSut> Create()
{
Sut = _fixture.Create<TSut>();
return this;
}
private bool DependencyIsSet(Type dependencyType, string parameterName = "")
=> _dependencies.ContainsKey(dependencyType) && _dependencies[dependencyType].ContainsKey(parameterName);
private object GetDefault(Type type) => type.IsValueType ? Activator.CreateInstance(type) : null;
private class ConstructorParameterRelay<T> : ISpecimenBuilder
{
private readonly SutProvider<T> _sutProvider;
private readonly IFixture _fixture;
public ConstructorParameterRelay(SutProvider<T> sutProvider, IFixture fixture)
{
_sutProvider = sutProvider;
_fixture = fixture;
}
if (_sutProvider.DependencyIsSet(parameterInfo.ParameterType, parameterInfo.Name))
public object Create(object request, ISpecimenContext context)
{
return _sutProvider.GetDependency(parameterInfo.ParameterType, parameterInfo.Name);
}
// Return default type if set
else if (_sutProvider.DependencyIsSet(parameterInfo.ParameterType, ""))
{
return _sutProvider.GetDependency(parameterInfo.ParameterType, "");
}
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!(request is ParameterInfo parameterInfo))
{
return new NoSpecimen();
}
if (parameterInfo.Member.DeclaringType != typeof(T) ||
parameterInfo.Member.MemberType != MemberTypes.Constructor)
{
return new NoSpecimen();
}
if (_sutProvider.DependencyIsSet(parameterInfo.ParameterType, parameterInfo.Name))
{
return _sutProvider.GetDependency(parameterInfo.ParameterType, parameterInfo.Name);
}
// Return default type if set
else if (_sutProvider.DependencyIsSet(parameterInfo.ParameterType, ""))
{
return _sutProvider.GetDependency(parameterInfo.ParameterType, "");
}
// This is the equivalent of _fixture.Create<parameterInfo.ParameterType>, but no overload for
// Create(Type type) exists.
var dependency = new SpecimenContext(_fixture).Resolve(new SeededRequest(parameterInfo.ParameterType,
_sutProvider.GetDefault(parameterInfo.ParameterType)));
_sutProvider.SetDependency(parameterInfo.ParameterType, dependency, parameterInfo.Name);
return dependency;
// This is the equivalent of _fixture.Create<parameterInfo.ParameterType>, but no overload for
// Create(Type type) exists.
var dependency = new SpecimenContext(_fixture).Resolve(new SeededRequest(parameterInfo.ParameterType,
_sutProvider.GetDefault(parameterInfo.ParameterType)));
_sutProvider.SetDependency(parameterInfo.ParameterType, dependency, parameterInfo.Name);
return dependency;
}
}
}
}

View File

@ -1,33 +1,34 @@
using AutoFixture;
using AutoFixture.Kernel;
namespace Bit.Test.Common.AutoFixture.Attributes;
public class SutProviderCustomization : ICustomization, ISpecimenBuilder
namespace Bit.Test.Common.AutoFixture.Attributes
{
private IFixture _fixture = null;
public object Create(object request, ISpecimenContext context)
public class SutProviderCustomization : ICustomization, ISpecimenBuilder
{
if (context == null)
private IFixture _fixture = null;
public object Create(object request, ISpecimenContext context)
{
throw new ArgumentNullException(nameof(context));
}
if (!(request is Type typeRequest))
{
return new NoSpecimen();
}
if (!typeof(ISutProvider).IsAssignableFrom(typeRequest))
{
return new NoSpecimen();
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!(request is Type typeRequest))
{
return new NoSpecimen();
}
if (!typeof(ISutProvider).IsAssignableFrom(typeRequest))
{
return new NoSpecimen();
}
return ((ISutProvider)Activator.CreateInstance(typeRequest, _fixture)).Create();
}
return ((ISutProvider)Activator.CreateInstance(typeRequest, _fixture)).Create();
}
public void Customize(IFixture fixture)
{
_fixture = fixture;
fixture.Customizations.Add(this);
public void Customize(IFixture fixture)
{
_fixture = fixture;
fixture.Customizations.Add(this);
}
}
}

View File

@ -7,222 +7,223 @@ using Microsoft.AspNetCore.Http;
using Xunit;
using Xunit.Sdk;
namespace Bit.Test.Common.Helpers;
public static class AssertHelper
namespace Bit.Test.Common.Helpers
{
public static void AssertPropertyEqual(object expected, object actual, params string[] excludedPropertyStrings)
public static class AssertHelper
{
var relevantExcludedProperties = excludedPropertyStrings.Where(name => !name.Contains('.')).ToList();
if (expected == null)
public static void AssertPropertyEqual(object expected, object actual, params string[] excludedPropertyStrings)
{
Assert.Null(actual);
return;
var relevantExcludedProperties = excludedPropertyStrings.Where(name => !name.Contains('.')).ToList();
if (expected == null)
{
Assert.Null(actual);
return;
}
if (actual == null)
{
throw new Exception("Actual object is null but expected is not");
}
foreach (var expectedPropInfo in expected.GetType().GetProperties().Where(pi => !relevantExcludedProperties.Contains(pi.Name)))
{
var actualPropInfo = actual.GetType().GetProperty(expectedPropInfo.Name);
if (actualPropInfo == null)
{
throw new Exception(string.Concat($"Expected actual object to contain a property named {expectedPropInfo.Name}, but it does not\n",
$"Expected:\n{JsonSerializer.Serialize(expected, JsonHelpers.Indented)}\n",
$"Actual:\n{JsonSerializer.Serialize(actual, JsonHelpers.Indented)}"));
}
if (expectedPropInfo.PropertyType == typeof(string) || expectedPropInfo.PropertyType.IsValueType)
{
Assert.Equal(expectedPropInfo.GetValue(expected), actualPropInfo.GetValue(actual));
}
else if (expectedPropInfo.PropertyType == typeof(JsonDocument) && actualPropInfo.PropertyType == typeof(JsonDocument))
{
static string JsonDocString(PropertyInfo info, object obj) => JsonSerializer.Serialize(info.GetValue(obj));
Assert.Equal(JsonDocString(expectedPropInfo, expected), JsonDocString(actualPropInfo, actual));
}
else
{
var prefix = $"{expectedPropInfo.PropertyType.Name}.";
var nextExcludedProperties = excludedPropertyStrings.Where(name => name.StartsWith(prefix))
.Select(name => name[prefix.Length..]).ToArray();
AssertPropertyEqual(expectedPropInfo.GetValue(expected), actualPropInfo.GetValue(actual), nextExcludedProperties);
}
}
}
if (actual == null)
private static Predicate<T> AssertPropertyEqualPredicate<T>(T expected, params string[] excludedPropertyStrings) => (actual) =>
{
throw new Exception("Actual object is null but expected is not");
AssertPropertyEqual(expected, actual, excludedPropertyStrings);
return true;
};
public static Expression<Predicate<T>> AssertPropertyEqual<T>(T expected, params string[] excludedPropertyStrings) =>
(T actual) => AssertPropertyEqualPredicate(expected, excludedPropertyStrings)(actual);
private static Predicate<IEnumerable<T>> AssertPropertyEqualPredicate<T>(IEnumerable<T> expected, params string[] excludedPropertyStrings) => (actual) =>
{
// IEnumerable.Zip doesn't account for different lengths, we need to check this ourselves
if (actual.Count() != expected.Count())
{
throw new Exception(string.Concat($"Actual IEnumerable does not have the expected length.\n",
$"Expected: {expected.Count()}\n",
$"Actual: {actual.Count()}"));
}
var elements = expected.Zip(actual);
foreach (var (expectedEl, actualEl) in elements)
{
AssertPropertyEqual(expectedEl, actualEl, excludedPropertyStrings);
}
return true;
};
public static Expression<Predicate<IEnumerable<T>>> AssertPropertyEqual<T>(IEnumerable<T> expected, params string[] excludedPropertyStrings) =>
(actual) => AssertPropertyEqualPredicate(expected, excludedPropertyStrings)(actual);
private static Predicate<T> AssertEqualExpectedPredicate<T>(T expected) => (actual) =>
{
Assert.Equal(expected, actual);
return true;
};
public static Expression<Predicate<T>> AssertEqualExpected<T>(T expected) =>
(T actual) => AssertEqualExpectedPredicate(expected)(actual);
public static JsonElement AssertJsonProperty(JsonElement element, string propertyName, JsonValueKind jsonValueKind)
{
if (!element.TryGetProperty(propertyName, out var subElement))
{
throw new XunitException($"Could not find property by name '{propertyName}'");
}
Assert.Equal(jsonValueKind, subElement.ValueKind);
return subElement;
}
foreach (var expectedPropInfo in expected.GetType().GetProperties().Where(pi => !relevantExcludedProperties.Contains(pi.Name)))
public static void AssertEqualJson(JsonElement a, JsonElement b)
{
var actualPropInfo = actual.GetType().GetProperty(expectedPropInfo.Name);
if (actualPropInfo == null)
switch (a.ValueKind)
{
throw new Exception(string.Concat($"Expected actual object to contain a property named {expectedPropInfo.Name}, but it does not\n",
$"Expected:\n{JsonSerializer.Serialize(expected, JsonHelpers.Indented)}\n",
$"Actual:\n{JsonSerializer.Serialize(actual, JsonHelpers.Indented)}"));
}
if (expectedPropInfo.PropertyType == typeof(string) || expectedPropInfo.PropertyType.IsValueType)
{
Assert.Equal(expectedPropInfo.GetValue(expected), actualPropInfo.GetValue(actual));
}
else if (expectedPropInfo.PropertyType == typeof(JsonDocument) && actualPropInfo.PropertyType == typeof(JsonDocument))
{
static string JsonDocString(PropertyInfo info, object obj) => JsonSerializer.Serialize(info.GetValue(obj));
Assert.Equal(JsonDocString(expectedPropInfo, expected), JsonDocString(actualPropInfo, actual));
}
else
{
var prefix = $"{expectedPropInfo.PropertyType.Name}.";
var nextExcludedProperties = excludedPropertyStrings.Where(name => name.StartsWith(prefix))
.Select(name => name[prefix.Length..]).ToArray();
AssertPropertyEqual(expectedPropInfo.GetValue(expected), actualPropInfo.GetValue(actual), nextExcludedProperties);
case JsonValueKind.Array:
Assert.Equal(JsonValueKind.Array, b.ValueKind);
AssertEqualJsonArray(a, b);
break;
case JsonValueKind.Object:
Assert.Equal(JsonValueKind.Object, b.ValueKind);
AssertEqualJsonObject(a, b);
break;
case JsonValueKind.False:
Assert.Equal(JsonValueKind.False, b.ValueKind);
break;
case JsonValueKind.True:
Assert.Equal(JsonValueKind.True, b.ValueKind);
break;
case JsonValueKind.Number:
Assert.Equal(JsonValueKind.Number, b.ValueKind);
Assert.Equal(a.GetDouble(), b.GetDouble());
break;
case JsonValueKind.String:
Assert.Equal(JsonValueKind.String, b.ValueKind);
Assert.Equal(a.GetString(), b.GetString());
break;
case JsonValueKind.Null:
Assert.Equal(JsonValueKind.Null, b.ValueKind);
break;
default:
throw new XunitException($"Bad JsonValueKind '{a.ValueKind}'");
}
}
}
private static Predicate<T> AssertPropertyEqualPredicate<T>(T expected, params string[] excludedPropertyStrings) => (actual) =>
{
AssertPropertyEqual(expected, actual, excludedPropertyStrings);
return true;
};
public static Expression<Predicate<T>> AssertPropertyEqual<T>(T expected, params string[] excludedPropertyStrings) =>
(T actual) => AssertPropertyEqualPredicate(expected, excludedPropertyStrings)(actual);
private static Predicate<IEnumerable<T>> AssertPropertyEqualPredicate<T>(IEnumerable<T> expected, params string[] excludedPropertyStrings) => (actual) =>
{
// IEnumerable.Zip doesn't account for different lengths, we need to check this ourselves
if (actual.Count() != expected.Count())
private static void AssertEqualJsonObject(JsonElement a, JsonElement b)
{
throw new Exception(string.Concat($"Actual IEnumerable does not have the expected length.\n",
$"Expected: {expected.Count()}\n",
$"Actual: {actual.Count()}"));
Debug.Assert(a.ValueKind == JsonValueKind.Object && b.ValueKind == JsonValueKind.Object);
var aObjectEnumerator = a.EnumerateObject();
var bObjectEnumerator = b.EnumerateObject();
while (true)
{
var aCanMove = aObjectEnumerator.MoveNext();
var bCanMove = bObjectEnumerator.MoveNext();
if (aCanMove)
{
Assert.True(bCanMove, $"a was able to enumerate over object '{a}' but b was NOT able to '{b}'");
}
else
{
Assert.False(bCanMove, $"a was NOT able to enumerate over object '{a}' but b was able to '{b}'");
}
if (aCanMove == false && bCanMove == false)
{
// They both can't continue to enumerate at the same time, that is valid
break;
}
var aProp = aObjectEnumerator.Current;
var bProp = bObjectEnumerator.Current;
Assert.Equal(aProp.Name, bProp.Name);
// Recursion!
AssertEqualJson(aProp.Value, bProp.Value);
}
}
var elements = expected.Zip(actual);
foreach (var (expectedEl, actualEl) in elements)
private static void AssertEqualJsonArray(JsonElement a, JsonElement b)
{
AssertPropertyEqual(expectedEl, actualEl, excludedPropertyStrings);
Debug.Assert(a.ValueKind == JsonValueKind.Array && b.ValueKind == JsonValueKind.Array);
var aArrayEnumerator = a.EnumerateArray();
var bArrayEnumerator = b.EnumerateArray();
while (true)
{
var aCanMove = aArrayEnumerator.MoveNext();
var bCanMove = bArrayEnumerator.MoveNext();
if (aCanMove)
{
Assert.True(bCanMove, $"a was able to enumerate over array '{a}' but b was NOT able to '{b}'");
}
else
{
Assert.False(bCanMove, $"a was NOT able to enumerate over array '{a}' but b was able to '{b}'");
}
if (aCanMove == false && bCanMove == false)
{
// They both can't continue to enumerate at the same time, that is valid
break;
}
var aElement = aArrayEnumerator.Current;
var bElement = bArrayEnumerator.Current;
// Recursion!
AssertEqualJson(aElement, bElement);
}
}
return true;
};
public static Expression<Predicate<IEnumerable<T>>> AssertPropertyEqual<T>(IEnumerable<T> expected, params string[] excludedPropertyStrings) =>
(actual) => AssertPropertyEqualPredicate(expected, excludedPropertyStrings)(actual);
private static Predicate<T> AssertEqualExpectedPredicate<T>(T expected) => (actual) =>
{
Assert.Equal(expected, actual);
return true;
};
public static Expression<Predicate<T>> AssertEqualExpected<T>(T expected) =>
(T actual) => AssertEqualExpectedPredicate(expected)(actual);
public static JsonElement AssertJsonProperty(JsonElement element, string propertyName, JsonValueKind jsonValueKind)
{
if (!element.TryGetProperty(propertyName, out var subElement))
public async static Task<T> AssertResponseTypeIs<T>(HttpContext context)
{
throw new XunitException($"Could not find property by name '{propertyName}'");
return await JsonSerializer.DeserializeAsync<T>(context.Response.Body);
}
Assert.Equal(jsonValueKind, subElement.ValueKind);
return subElement;
}
public static TimeSpan AssertRecent(DateTime dateTime, int skewSeconds = 2)
=> AssertRecent(dateTime, TimeSpan.FromSeconds(skewSeconds));
public static void AssertEqualJson(JsonElement a, JsonElement b)
{
switch (a.ValueKind)
public static TimeSpan AssertRecent(DateTime dateTime, TimeSpan skew)
{
case JsonValueKind.Array:
Assert.Equal(JsonValueKind.Array, b.ValueKind);
AssertEqualJsonArray(a, b);
break;
case JsonValueKind.Object:
Assert.Equal(JsonValueKind.Object, b.ValueKind);
AssertEqualJsonObject(a, b);
break;
case JsonValueKind.False:
Assert.Equal(JsonValueKind.False, b.ValueKind);
break;
case JsonValueKind.True:
Assert.Equal(JsonValueKind.True, b.ValueKind);
break;
case JsonValueKind.Number:
Assert.Equal(JsonValueKind.Number, b.ValueKind);
Assert.Equal(a.GetDouble(), b.GetDouble());
break;
case JsonValueKind.String:
Assert.Equal(JsonValueKind.String, b.ValueKind);
Assert.Equal(a.GetString(), b.GetString());
break;
case JsonValueKind.Null:
Assert.Equal(JsonValueKind.Null, b.ValueKind);
break;
default:
throw new XunitException($"Bad JsonValueKind '{a.ValueKind}'");
var difference = DateTime.UtcNow - dateTime;
Assert.True(difference < skew);
return difference;
}
}
private static void AssertEqualJsonObject(JsonElement a, JsonElement b)
{
Debug.Assert(a.ValueKind == JsonValueKind.Object && b.ValueKind == JsonValueKind.Object);
var aObjectEnumerator = a.EnumerateObject();
var bObjectEnumerator = b.EnumerateObject();
while (true)
{
var aCanMove = aObjectEnumerator.MoveNext();
var bCanMove = bObjectEnumerator.MoveNext();
if (aCanMove)
{
Assert.True(bCanMove, $"a was able to enumerate over object '{a}' but b was NOT able to '{b}'");
}
else
{
Assert.False(bCanMove, $"a was NOT able to enumerate over object '{a}' but b was able to '{b}'");
}
if (aCanMove == false && bCanMove == false)
{
// They both can't continue to enumerate at the same time, that is valid
break;
}
var aProp = aObjectEnumerator.Current;
var bProp = bObjectEnumerator.Current;
Assert.Equal(aProp.Name, bProp.Name);
// Recursion!
AssertEqualJson(aProp.Value, bProp.Value);
}
}
private static void AssertEqualJsonArray(JsonElement a, JsonElement b)
{
Debug.Assert(a.ValueKind == JsonValueKind.Array && b.ValueKind == JsonValueKind.Array);
var aArrayEnumerator = a.EnumerateArray();
var bArrayEnumerator = b.EnumerateArray();
while (true)
{
var aCanMove = aArrayEnumerator.MoveNext();
var bCanMove = bArrayEnumerator.MoveNext();
if (aCanMove)
{
Assert.True(bCanMove, $"a was able to enumerate over array '{a}' but b was NOT able to '{b}'");
}
else
{
Assert.False(bCanMove, $"a was NOT able to enumerate over array '{a}' but b was able to '{b}'");
}
if (aCanMove == false && bCanMove == false)
{
// They both can't continue to enumerate at the same time, that is valid
break;
}
var aElement = aArrayEnumerator.Current;
var bElement = bArrayEnumerator.Current;
// Recursion!
AssertEqualJson(aElement, bElement);
}
}
public async static Task<T> AssertResponseTypeIs<T>(HttpContext context)
{
return await JsonSerializer.DeserializeAsync<T>(context.Response.Body);
}
public static TimeSpan AssertRecent(DateTime dateTime, int skewSeconds = 2)
=> AssertRecent(dateTime, TimeSpan.FromSeconds(skewSeconds));
public static TimeSpan AssertRecent(DateTime dateTime, TimeSpan skew)
{
var difference = DateTime.UtcNow - dateTime;
Assert.True(difference < skew);
return difference;
}
}

View File

@ -4,48 +4,49 @@ using AutoFixture.Kernel;
using AutoFixture.Xunit2;
using Bit.Test.Common.AutoFixture.Attributes;
namespace Bit.Test.Common.Helpers;
public static class BitAutoDataAttributeHelpers
namespace Bit.Test.Common.Helpers
{
public static IEnumerable<object[]> GetData(MethodInfo testMethod, IFixture fixture, object[] fixedTestParameters)
public static class BitAutoDataAttributeHelpers
{
var methodParameters = testMethod.GetParameters();
var classCustomizations = testMethod.DeclaringType.GetCustomAttributes<BitCustomizeAttribute>().Select(attr => attr.GetCustomization());
var methodCustomizations = testMethod.GetCustomAttributes<BitCustomizeAttribute>().Select(attr => attr.GetCustomization());
fixedTestParameters = fixedTestParameters ?? Array.Empty<object>();
fixture = ApplyCustomizations(ApplyCustomizations(fixture, classCustomizations), methodCustomizations);
var missingParameters = methodParameters.Skip(fixedTestParameters.Length).Select(p => CustomizeAndCreate(p, fixture));
return new object[1][] { fixedTestParameters.Concat(missingParameters).ToArray() };
}
public static object CustomizeAndCreate(ParameterInfo p, IFixture fixture)
{
var customizations = p.GetCustomAttributes(typeof(CustomizeAttribute), false)
.OfType<CustomizeAttribute>()
.Select(attr => attr.GetCustomization(p));
var context = new SpecimenContext(ApplyCustomizations(fixture, customizations));
return context.Resolve(p);
}
public static IFixture ApplyCustomizations(IFixture fixture, IEnumerable<ICustomization> customizations)
{
var newFixture = new Fixture();
foreach (var customization in fixture.Customizations.Reverse().Select(b => b.ToCustomization()))
public static IEnumerable<object[]> GetData(MethodInfo testMethod, IFixture fixture, object[] fixedTestParameters)
{
newFixture.Customize(customization);
var methodParameters = testMethod.GetParameters();
var classCustomizations = testMethod.DeclaringType.GetCustomAttributes<BitCustomizeAttribute>().Select(attr => attr.GetCustomization());
var methodCustomizations = testMethod.GetCustomAttributes<BitCustomizeAttribute>().Select(attr => attr.GetCustomization());
fixedTestParameters = fixedTestParameters ?? Array.Empty<object>();
fixture = ApplyCustomizations(ApplyCustomizations(fixture, classCustomizations), methodCustomizations);
var missingParameters = methodParameters.Skip(fixedTestParameters.Length).Select(p => CustomizeAndCreate(p, fixture));
return new object[1][] { fixedTestParameters.Concat(missingParameters).ToArray() };
}
foreach (var customization in customizations)
public static object CustomizeAndCreate(ParameterInfo p, IFixture fixture)
{
newFixture.Customize(customization);
var customizations = p.GetCustomAttributes(typeof(CustomizeAttribute), false)
.OfType<CustomizeAttribute>()
.Select(attr => attr.GetCustomization(p));
var context = new SpecimenContext(ApplyCustomizations(fixture, customizations));
return context.Resolve(p);
}
return newFixture;
public static IFixture ApplyCustomizations(IFixture fixture, IEnumerable<ICustomization> customizations)
{
var newFixture = new Fixture();
foreach (var customization in fixture.Customizations.Reverse().Select(b => b.ToCustomization()))
{
newFixture.Customize(customization);
}
foreach (var customization in customizations)
{
newFixture.Customize(customization);
}
return newFixture;
}
}
}

View File

@ -1,44 +1,45 @@
namespace Bit.Test.Common.Helpers;
public static class TestCaseHelper
namespace Bit.Test.Common.Helpers
{
public static IEnumerable<IEnumerable<T>> GetCombinations<T>(params T[] items)
public static class TestCaseHelper
{
var count = Math.Pow(2, items.Length);
for (var i = 0; i < count; i++)
public static IEnumerable<IEnumerable<T>> GetCombinations<T>(params T[] items)
{
var str = Convert.ToString(i, 2).PadLeft(items.Length, '0');
List<T> combination = new();
for (var j = 0; j < str.Length; j++)
var count = Math.Pow(2, items.Length);
for (var i = 0; i < count; i++)
{
if (str[j] == '1')
var str = Convert.ToString(i, 2).PadLeft(items.Length, '0');
List<T> combination = new();
for (var j = 0; j < str.Length; j++)
{
combination.Add(items[j]);
if (str[j] == '1')
{
combination.Add(items[j]);
}
}
yield return combination;
}
yield return combination;
}
}
public static IEnumerable<IEnumerable<object>> GetCombinationsOfMultipleLists(params IEnumerable<object>[] optionLists)
{
if (!optionLists.Any())
{
yield break;
}
foreach (var item in optionLists.First())
public static IEnumerable<IEnumerable<object>> GetCombinationsOfMultipleLists(params IEnumerable<object>[] optionLists)
{
var itemArray = new[] { item };
if (optionLists.Length == 1)
if (!optionLists.Any())
{
yield return itemArray;
yield break;
}
foreach (var nextCombination in GetCombinationsOfMultipleLists(optionLists.Skip(1).ToArray()))
foreach (var item in optionLists.First())
{
yield return itemArray.Concat(nextCombination);
var itemArray = new[] { item };
if (optionLists.Length == 1)
{
yield return itemArray;
}
foreach (var nextCombination in GetCombinationsOfMultipleLists(optionLists.Skip(1).ToArray()))
{
yield return itemArray.Concat(nextCombination);
}
}
}
}

View File

@ -1,50 +1,51 @@
using Bit.Test.Common.Helpers;
using Xunit;
namespace Bit.Test.Common.Test;
public class TestCaseHelperTests
namespace Bit.Test.Common.Test
{
[Fact]
public void GetCombinations_EmptyList()
public class TestCaseHelperTests
{
Assert.Equal(new[] { Array.Empty<int>() }, TestCaseHelper.GetCombinations(Array.Empty<int>()).ToArray());
}
[Fact]
public void GetCombinations_EmptyList()
{
Assert.Equal(new[] { Array.Empty<int>() }, TestCaseHelper.GetCombinations(Array.Empty<int>()).ToArray());
}
[Fact]
public void GetCombinations_OneItemList()
{
Assert.Equal(new[] { Array.Empty<int>(), new[] { 1 } }, TestCaseHelper.GetCombinations(1));
}
[Fact]
public void GetCombinations_OneItemList()
{
Assert.Equal(new[] { Array.Empty<int>(), new[] { 1 } }, TestCaseHelper.GetCombinations(1));
}
[Fact]
public void GetCombinations_TwoItemList()
{
Assert.Equal(new[] { Array.Empty<int>(), new[] { 2 }, new[] { 1 }, new[] { 1, 2 } }, TestCaseHelper.GetCombinations(1, 2));
}
[Fact]
public void GetCombinations_TwoItemList()
{
Assert.Equal(new[] { Array.Empty<int>(), new[] { 2 }, new[] { 1 }, new[] { 1, 2 } }, TestCaseHelper.GetCombinations(1, 2));
}
[Fact]
public void GetCombinationsOfMultipleLists_OneOne()
{
Assert.Equal(new[] { new object[] { 1, "1" } }, TestCaseHelper.GetCombinationsOfMultipleLists(new object[] { 1 }, new object[] { "1" }));
}
[Fact]
public void GetCombinationsOfMultipleLists_OneOne()
{
Assert.Equal(new[] { new object[] { 1, "1" } }, TestCaseHelper.GetCombinationsOfMultipleLists(new object[] { 1 }, new object[] { "1" }));
}
[Fact]
public void GetCombinationsOfMultipleLists_OneTwo()
{
Assert.Equal(new[] { new object[] { 1, "1" }, new object[] { 1, "2" } }, TestCaseHelper.GetCombinationsOfMultipleLists(new object[] { 1 }, new object[] { "1", "2" }));
}
[Fact]
public void GetCombinationsOfMultipleLists_OneTwo()
{
Assert.Equal(new[] { new object[] { 1, "1" }, new object[] { 1, "2" } }, TestCaseHelper.GetCombinationsOfMultipleLists(new object[] { 1 }, new object[] { "1", "2" }));
}
[Fact]
public void GetCombinationsOfMultipleLists_TwoOne()
{
Assert.Equal(new[] { new object[] { 1, "1" }, new object[] { 2, "1" } }, TestCaseHelper.GetCombinationsOfMultipleLists(new object[] { 1, 2 }, new object[] { "1" }));
}
[Fact]
public void GetCombinationsOfMultipleLists_TwoOne()
{
Assert.Equal(new[] { new object[] { 1, "1" }, new object[] { 2, "1" } }, TestCaseHelper.GetCombinationsOfMultipleLists(new object[] { 1, 2 }, new object[] { "1" }));
}
[Fact]
public void GetCombinationsOfMultipleLists_TwoTwo()
{
Assert.Equal(new[] { new object[] { 1, "1" }, new object[] { 1, "2" }, new object[] { 2, "1" }, new object[] { 2, "2" } }, TestCaseHelper.GetCombinationsOfMultipleLists(new object[] { 1, 2 }, new object[] { "1", "2" }));
[Fact]
public void GetCombinationsOfMultipleLists_TwoTwo()
{
Assert.Equal(new[] { new object[] { 1, "1" }, new object[] { 1, "2" }, new object[] { 2, "1" }, new object[] { 2, "2" } }, TestCaseHelper.GetCombinationsOfMultipleLists(new object[] { 1, 2 }, new object[] { "1", "2" }));
}
}
}

View File

@ -1,13 +1,14 @@
namespace Bit.Core.Test.AutoFixture.Attributes;
public sealed class CiSkippedTheory : Xunit.TheoryAttribute
namespace Bit.Core.Test.AutoFixture.Attributes
{
private static bool IsGithubActions() => Environment.GetEnvironmentVariable("CI") != null;
public CiSkippedTheory()
public sealed class CiSkippedTheory : Xunit.TheoryAttribute
{
if (IsGithubActions())
private static bool IsGithubActions() => Environment.GetEnvironmentVariable("CI") != null;
public CiSkippedTheory()
{
Skip = "Ignore during CI builds";
if (IsGithubActions())
{
Skip = "Ignore during CI builds";
}
}
}
}

View File

@ -2,31 +2,32 @@
using AutoFixture.Dsl;
using Bit.Core.Models.Data;
namespace Bit.Core.Test.AutoFixture.CipherAttachmentMetaData;
public class MetaData : ICustomization
namespace Bit.Core.Test.AutoFixture.CipherAttachmentMetaData
{
protected virtual IPostprocessComposer<CipherAttachment.MetaData> ComposerAction(IFixture fixture,
ICustomizationComposer<CipherAttachment.MetaData> composer)
public class MetaData : ICustomization
{
return composer.With(d => d.Size, fixture.Create<long>());
protected virtual IPostprocessComposer<CipherAttachment.MetaData> ComposerAction(IFixture fixture,
ICustomizationComposer<CipherAttachment.MetaData> composer)
{
return composer.With(d => d.Size, fixture.Create<long>());
}
public void Customize(IFixture fixture)
{
fixture.Customize<CipherAttachment.MetaData>(composer => ComposerAction(fixture, composer));
}
}
public void Customize(IFixture fixture)
public class MetaDataWithoutContainer : MetaData
{
fixture.Customize<CipherAttachment.MetaData>(composer => ComposerAction(fixture, composer));
protected override IPostprocessComposer<CipherAttachment.MetaData> ComposerAction(IFixture fixture,
ICustomizationComposer<CipherAttachment.MetaData> composer) =>
base.ComposerAction(fixture, composer).With(d => d.ContainerName, (string)null);
}
public class MetaDataWithoutKey : MetaDataWithoutContainer
{
protected override IPostprocessComposer<CipherAttachment.MetaData> ComposerAction(IFixture fixture,
ICustomizationComposer<CipherAttachment.MetaData> composer) =>
base.ComposerAction(fixture, composer).Without(d => d.Key);
}
}
public class MetaDataWithoutContainer : MetaData
{
protected override IPostprocessComposer<CipherAttachment.MetaData> ComposerAction(IFixture fixture,
ICustomizationComposer<CipherAttachment.MetaData> composer) =>
base.ComposerAction(fixture, composer).With(d => d.ContainerName, (string)null);
}
public class MetaDataWithoutKey : MetaDataWithoutContainer
{
protected override IPostprocessComposer<CipherAttachment.MetaData> ComposerAction(IFixture fixture,
ICustomizationComposer<CipherAttachment.MetaData> composer) =>
base.ComposerAction(fixture, composer).Without(d => d.Key);
}

View File

@ -3,66 +3,67 @@ using Bit.Core.Entities;
using Bit.Test.Common.AutoFixture.Attributes;
using Core.Models.Data;
namespace Bit.Core.Test.AutoFixture.CipherFixtures;
internal class OrganizationCipher : ICustomization
namespace Bit.Core.Test.AutoFixture.CipherFixtures
{
public Guid? OrganizationId { get; set; }
public void Customize(IFixture fixture)
internal class OrganizationCipher : ICustomization
{
fixture.Customize<Cipher>(composer => composer
.With(c => c.OrganizationId, OrganizationId ?? Guid.NewGuid())
.Without(c => c.UserId));
fixture.Customize<CipherDetails>(composer => composer
.With(c => c.OrganizationId, Guid.NewGuid())
.Without(c => c.UserId));
public Guid? OrganizationId { get; set; }
public void Customize(IFixture fixture)
{
fixture.Customize<Cipher>(composer => composer
.With(c => c.OrganizationId, OrganizationId ?? Guid.NewGuid())
.Without(c => c.UserId));
fixture.Customize<CipherDetails>(composer => composer
.With(c => c.OrganizationId, Guid.NewGuid())
.Without(c => c.UserId));
}
}
internal class UserCipher : ICustomization
{
public Guid? UserId { get; set; }
public void Customize(IFixture fixture)
{
fixture.Customize<Cipher>(composer => composer
.With(c => c.UserId, UserId ?? Guid.NewGuid())
.Without(c => c.OrganizationId));
fixture.Customize<CipherDetails>(composer => composer
.With(c => c.UserId, Guid.NewGuid())
.Without(c => c.OrganizationId));
}
}
internal class UserCipherAutoDataAttribute : CustomAutoDataAttribute
{
public UserCipherAutoDataAttribute(string userId = null) : base(new SutProviderCustomization(),
new UserCipher { UserId = userId == null ? (Guid?)null : new Guid(userId) })
{ }
}
internal class InlineUserCipherAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineUserCipherAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
typeof(UserCipher) }, values)
{ }
}
internal class InlineKnownUserCipherAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineKnownUserCipherAutoDataAttribute(string userId, params object[] values) : base(new ICustomization[]
{ new SutProviderCustomization(), new UserCipher { UserId = new Guid(userId) } }, values)
{ }
}
internal class OrganizationCipherAutoDataAttribute : CustomAutoDataAttribute
{
public OrganizationCipherAutoDataAttribute(string organizationId = null) : base(new SutProviderCustomization(),
new OrganizationCipher { OrganizationId = organizationId == null ? (Guid?)null : new Guid(organizationId) })
{ }
}
internal class InlineOrganizationCipherAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineOrganizationCipherAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
typeof(OrganizationCipher) }, values)
{ }
}
}
internal class UserCipher : ICustomization
{
public Guid? UserId { get; set; }
public void Customize(IFixture fixture)
{
fixture.Customize<Cipher>(composer => composer
.With(c => c.UserId, UserId ?? Guid.NewGuid())
.Without(c => c.OrganizationId));
fixture.Customize<CipherDetails>(composer => composer
.With(c => c.UserId, Guid.NewGuid())
.Without(c => c.OrganizationId));
}
}
internal class UserCipherAutoDataAttribute : CustomAutoDataAttribute
{
public UserCipherAutoDataAttribute(string userId = null) : base(new SutProviderCustomization(),
new UserCipher { UserId = userId == null ? (Guid?)null : new Guid(userId) })
{ }
}
internal class InlineUserCipherAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineUserCipherAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
typeof(UserCipher) }, values)
{ }
}
internal class InlineKnownUserCipherAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineKnownUserCipherAutoDataAttribute(string userId, params object[] values) : base(new ICustomization[]
{ new SutProviderCustomization(), new UserCipher { UserId = new Guid(userId) } }, values)
{ }
}
internal class OrganizationCipherAutoDataAttribute : CustomAutoDataAttribute
{
public OrganizationCipherAutoDataAttribute(string organizationId = null) : base(new SutProviderCustomization(),
new OrganizationCipher { OrganizationId = organizationId == null ? (Guid?)null : new Guid(organizationId) })
{ }
}
internal class InlineOrganizationCipherAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineOrganizationCipherAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
typeof(OrganizationCipher) }, values)
{ }
}

View File

@ -1,10 +1,11 @@
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
using Bit.Test.Common.AutoFixture.Attributes;
namespace Bit.Core.Test.AutoFixture.CollectionFixtures;
internal class CollectionAutoDataAttribute : CustomAutoDataAttribute
namespace Bit.Core.Test.AutoFixture.CollectionFixtures
{
public CollectionAutoDataAttribute() : base(new SutProviderCustomization(), new OrganizationCustomization())
{ }
internal class CollectionAutoDataAttribute : CustomAutoDataAttribute
{
public CollectionAutoDataAttribute() : base(new SutProviderCustomization(), new OrganizationCustomization())
{ }
}
}

View File

@ -3,35 +3,36 @@ using AutoFixture.Kernel;
using Bit.Core.Context;
using Bit.Test.Common.AutoFixture;
namespace Bit.Core.Test.AutoFixture.CurrentContextFixtures;
internal class CurrentContext : ICustomization
namespace Bit.Core.Test.AutoFixture.CurrentContextFixtures
{
public void Customize(IFixture fixture)
internal class CurrentContext : ICustomization
{
fixture.Customizations.Add(new CurrentContextBuilder());
}
}
internal class CurrentContextBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!(request is Type typeRequest))
{
return new NoSpecimen();
}
if (typeof(ICurrentContext) != typeRequest)
{
return new NoSpecimen();
}
var obj = new Fixture().WithAutoNSubstitutions().Create<ICurrentContext>();
obj.Organizations = context.Create<List<CurrentContentOrganization>>();
return obj;
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new CurrentContextBuilder());
}
}
internal class CurrentContextBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!(request is Type typeRequest))
{
return new NoSpecimen();
}
if (typeof(ICurrentContext) != typeRequest)
{
return new NoSpecimen();
}
var obj = new Fixture().WithAutoNSubstitutions().Create<ICurrentContext>();
obj.Organizations = context.Create<List<CurrentContentOrganization>>();
return obj;
}
}
}

View File

@ -4,28 +4,29 @@ using AutoFixture.Kernel;
using AutoFixture.Xunit2;
using Bit.Core.Test.Helpers.Factories;
namespace Bit.Test.Common.AutoFixture;
public class GlobalSettingsBuilder : ISpecimenBuilder
namespace Bit.Test.Common.AutoFixture
{
public object Create(object request, ISpecimenContext context)
public class GlobalSettingsBuilder : ISpecimenBuilder
{
if (context == null)
public object Create(object request, ISpecimenContext context)
{
throw new ArgumentNullException(nameof(context));
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var pi = request as ParameterInfo;
var fixture = new Fixture();
if (pi == null || pi.ParameterType != typeof(Bit.Core.Settings.GlobalSettings))
return new NoSpecimen();
return GlobalSettingsFactory.GlobalSettings;
}
}
var pi = request as ParameterInfo;
var fixture = new Fixture();
if (pi == null || pi.ParameterType != typeof(Bit.Core.Settings.GlobalSettings))
return new NoSpecimen();
return GlobalSettingsFactory.GlobalSettings;
public class GlobalSettingsCustomizeAttribute : CustomizeAttribute
{
public override ICustomization GetCustomization(ParameterInfo parameter) => new GlobalSettings();
}
}
public class GlobalSettingsCustomizeAttribute : CustomizeAttribute
{
public override ICustomization GetCustomization(ParameterInfo parameter) => new GlobalSettings();
}

View File

@ -1,18 +1,19 @@
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
using Bit.Test.Common.AutoFixture.Attributes;
namespace Bit.Core.Test.AutoFixture.GroupFixtures;
internal class GroupOrganizationAutoDataAttribute : CustomAutoDataAttribute
namespace Bit.Core.Test.AutoFixture.GroupFixtures
{
public GroupOrganizationAutoDataAttribute() : base(
new SutProviderCustomization(), new OrganizationCustomization { UseGroups = true })
{ }
}
internal class GroupOrganizationAutoDataAttribute : CustomAutoDataAttribute
{
public GroupOrganizationAutoDataAttribute() : base(
new SutProviderCustomization(), new OrganizationCustomization { UseGroups = true })
{ }
}
internal class GroupOrganizationNotUseGroupsAutoDataAttribute : CustomAutoDataAttribute
{
public GroupOrganizationNotUseGroupsAutoDataAttribute() : base(
new SutProviderCustomization(), new OrganizationCustomization { UseGroups = false })
{ }
internal class GroupOrganizationNotUseGroupsAutoDataAttribute : CustomAutoDataAttribute
{
public GroupOrganizationNotUseGroupsAutoDataAttribute() : base(
new SutProviderCustomization(), new OrganizationCustomization { UseGroups = false })
{ }
}
}

View File

@ -10,176 +10,177 @@ using Bit.Core.Utilities;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
namespace Bit.Core.Test.AutoFixture.OrganizationFixtures;
public class OrganizationCustomization : ICustomization
namespace Bit.Core.Test.AutoFixture.OrganizationFixtures
{
public bool UseGroups { get; set; }
public void Customize(IFixture fixture)
public class OrganizationCustomization : ICustomization
{
var organizationId = Guid.NewGuid();
var maxConnections = (short)new Random().Next(10, short.MaxValue);
public bool UseGroups { get; set; }
fixture.Customize<Organization>(composer => composer
.With(o => o.Id, organizationId)
.With(o => o.MaxCollections, maxConnections)
.With(o => o.UseGroups, UseGroups));
fixture.Customize<Collection>(composer =>
composer
.With(c => c.OrganizationId, organizationId)
.Without(o => o.CreationDate)
.Without(o => o.RevisionDate));
fixture.Customize<Group>(composer => composer.With(g => g.OrganizationId, organizationId));
}
}
internal class OrganizationBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
if (context == null)
public void Customize(IFixture fixture)
{
throw new ArgumentNullException(nameof(context));
var organizationId = Guid.NewGuid();
var maxConnections = (short)new Random().Next(10, short.MaxValue);
fixture.Customize<Organization>(composer => composer
.With(o => o.Id, organizationId)
.With(o => o.MaxCollections, maxConnections)
.With(o => o.UseGroups, UseGroups));
fixture.Customize<Collection>(composer =>
composer
.With(c => c.OrganizationId, organizationId)
.Without(o => o.CreationDate)
.Without(o => o.RevisionDate));
fixture.Customize<Group>(composer => composer.With(g => g.OrganizationId, organizationId));
}
}
var type = request as Type;
if (type == null || type != typeof(Organization))
internal class OrganizationBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
return new NoSpecimen();
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var type = request as Type;
if (type == null || type != typeof(Organization))
{
return new NoSpecimen();
}
var fixture = new Fixture();
var providers = fixture.Create<Dictionary<TwoFactorProviderType, TwoFactorProvider>>();
var organization = new Fixture().WithAutoNSubstitutions().Create<Organization>();
organization.SetTwoFactorProviders(providers);
return organization;
}
var fixture = new Fixture();
var providers = fixture.Create<Dictionary<TwoFactorProviderType, TwoFactorProvider>>();
var organization = new Fixture().WithAutoNSubstitutions().Create<Organization>();
organization.SetTwoFactorProviders(providers);
return organization;
}
}
internal class PaidOrganization : ICustomization
{
public PlanType CheckedPlanType { get; set; }
public void Customize(IFixture fixture)
internal class PaidOrganization : ICustomization
{
var validUpgradePlans = StaticStore.Plans.Where(p => p.Type != PlanType.Free && !p.Disabled).Select(p => p.Type).ToList();
var lowestActivePaidPlan = validUpgradePlans.First();
CheckedPlanType = CheckedPlanType.Equals(PlanType.Free) ? lowestActivePaidPlan : CheckedPlanType;
validUpgradePlans.Remove(lowestActivePaidPlan);
fixture.Customize<Organization>(composer => composer
.With(o => o.PlanType, CheckedPlanType));
fixture.Customize<OrganizationUpgrade>(composer => composer
.With(ou => ou.Plan, validUpgradePlans.First()));
}
}
internal class FreeOrganization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<Organization>(composer => composer
.With(o => o.PlanType, PlanType.Free));
}
}
internal class FreeOrganizationUpgrade : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<Organization>(composer => composer
.With(o => o.PlanType, PlanType.Free));
var plansToIgnore = new List<PlanType> { PlanType.Free, PlanType.Custom };
var selectedPlan = StaticStore.Plans.Last(p => !plansToIgnore.Contains(p.Type) && !p.Disabled);
fixture.Customize<OrganizationUpgrade>(composer => composer
.With(ou => ou.Plan, selectedPlan.Type)
.With(ou => ou.PremiumAccessAddon, selectedPlan.HasPremiumAccessOption));
fixture.Customize<Organization>(composer => composer
.Without(o => o.GatewaySubscriptionId));
}
}
internal class OrganizationInvite : ICustomization
{
public OrganizationUserType InviteeUserType { get; set; }
public OrganizationUserType InvitorUserType { get; set; }
public string PermissionsBlob { get; set; }
public void Customize(IFixture fixture)
{
var organizationId = new Guid();
PermissionsBlob = PermissionsBlob ?? JsonSerializer.Serialize(new Permissions(), new JsonSerializerOptions
public PlanType CheckedPlanType { get; set; }
public void Customize(IFixture fixture)
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
fixture.Customize<Organization>(composer => composer
.With(o => o.Id, organizationId)
.With(o => o.Seats, (short)100));
fixture.Customize<OrganizationUser>(composer => composer
.With(ou => ou.OrganizationId, organizationId)
.With(ou => ou.Type, InvitorUserType)
.With(ou => ou.Permissions, PermissionsBlob));
fixture.Customize<OrganizationUserInvite>(composer => composer
.With(oi => oi.Type, InviteeUserType));
var validUpgradePlans = StaticStore.Plans.Where(p => p.Type != PlanType.Free && !p.Disabled).Select(p => p.Type).ToList();
var lowestActivePaidPlan = validUpgradePlans.First();
CheckedPlanType = CheckedPlanType.Equals(PlanType.Free) ? lowestActivePaidPlan : CheckedPlanType;
validUpgradePlans.Remove(lowestActivePaidPlan);
fixture.Customize<Organization>(composer => composer
.With(o => o.PlanType, CheckedPlanType));
fixture.Customize<OrganizationUpgrade>(composer => composer
.With(ou => ou.Plan, validUpgradePlans.First()));
}
}
internal class FreeOrganization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<Organization>(composer => composer
.With(o => o.PlanType, PlanType.Free));
}
}
internal class FreeOrganizationUpgrade : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<Organization>(composer => composer
.With(o => o.PlanType, PlanType.Free));
var plansToIgnore = new List<PlanType> { PlanType.Free, PlanType.Custom };
var selectedPlan = StaticStore.Plans.Last(p => !plansToIgnore.Contains(p.Type) && !p.Disabled);
fixture.Customize<OrganizationUpgrade>(composer => composer
.With(ou => ou.Plan, selectedPlan.Type)
.With(ou => ou.PremiumAccessAddon, selectedPlan.HasPremiumAccessOption));
fixture.Customize<Organization>(composer => composer
.Without(o => o.GatewaySubscriptionId));
}
}
internal class OrganizationInvite : ICustomization
{
public OrganizationUserType InviteeUserType { get; set; }
public OrganizationUserType InvitorUserType { get; set; }
public string PermissionsBlob { get; set; }
public void Customize(IFixture fixture)
{
var organizationId = new Guid();
PermissionsBlob = PermissionsBlob ?? JsonSerializer.Serialize(new Permissions(), new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
fixture.Customize<Organization>(composer => composer
.With(o => o.Id, organizationId)
.With(o => o.Seats, (short)100));
fixture.Customize<OrganizationUser>(composer => composer
.With(ou => ou.OrganizationId, organizationId)
.With(ou => ou.Type, InvitorUserType)
.With(ou => ou.Permissions, PermissionsBlob));
fixture.Customize<OrganizationUserInvite>(composer => composer
.With(oi => oi.Type, InviteeUserType));
}
}
internal class PaidOrganizationAutoDataAttribute : CustomAutoDataAttribute
{
public PaidOrganizationAutoDataAttribute(PlanType planType) : base(new SutProviderCustomization(),
new PaidOrganization { CheckedPlanType = planType })
{ }
public PaidOrganizationAutoDataAttribute(int planType = 0) : this((PlanType)planType) { }
}
internal class InlinePaidOrganizationAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlinePaidOrganizationAutoDataAttribute(PlanType planType, object[] values) : base(
new ICustomization[] { new SutProviderCustomization(), new PaidOrganization { CheckedPlanType = planType } }, values)
{ }
public InlinePaidOrganizationAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
typeof(PaidOrganization) }, values)
{ }
}
internal class InlineFreeOrganizationAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineFreeOrganizationAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
typeof(FreeOrganization) }, values)
{ }
}
internal class FreeOrganizationUpgradeAutoDataAttribute : CustomAutoDataAttribute
{
public FreeOrganizationUpgradeAutoDataAttribute() : base(new SutProviderCustomization(), new FreeOrganizationUpgrade())
{ }
}
internal class InlineFreeOrganizationUpgradeAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineFreeOrganizationUpgradeAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
typeof(FreeOrganizationUpgrade) }, values)
{ }
}
internal class OrganizationInviteAutoDataAttribute : CustomAutoDataAttribute
{
public OrganizationInviteAutoDataAttribute(int inviteeUserType = 0, int invitorUserType = 0, string permissionsBlob = null) : base(new SutProviderCustomization(),
new OrganizationInvite
{
InviteeUserType = (OrganizationUserType)inviteeUserType,
InvitorUserType = (OrganizationUserType)invitorUserType,
PermissionsBlob = permissionsBlob,
})
{ }
}
internal class InlineOrganizationInviteAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineOrganizationInviteAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
typeof(OrganizationInvite) }, values)
{ }
}
}
internal class PaidOrganizationAutoDataAttribute : CustomAutoDataAttribute
{
public PaidOrganizationAutoDataAttribute(PlanType planType) : base(new SutProviderCustomization(),
new PaidOrganization { CheckedPlanType = planType })
{ }
public PaidOrganizationAutoDataAttribute(int planType = 0) : this((PlanType)planType) { }
}
internal class InlinePaidOrganizationAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlinePaidOrganizationAutoDataAttribute(PlanType planType, object[] values) : base(
new ICustomization[] { new SutProviderCustomization(), new PaidOrganization { CheckedPlanType = planType } }, values)
{ }
public InlinePaidOrganizationAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
typeof(PaidOrganization) }, values)
{ }
}
internal class InlineFreeOrganizationAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineFreeOrganizationAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
typeof(FreeOrganization) }, values)
{ }
}
internal class FreeOrganizationUpgradeAutoDataAttribute : CustomAutoDataAttribute
{
public FreeOrganizationUpgradeAutoDataAttribute() : base(new SutProviderCustomization(), new FreeOrganizationUpgrade())
{ }
}
internal class InlineFreeOrganizationUpgradeAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineFreeOrganizationUpgradeAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
typeof(FreeOrganizationUpgrade) }, values)
{ }
}
internal class OrganizationInviteAutoDataAttribute : CustomAutoDataAttribute
{
public OrganizationInviteAutoDataAttribute(int inviteeUserType = 0, int invitorUserType = 0, string permissionsBlob = null) : base(new SutProviderCustomization(),
new OrganizationInvite
{
InviteeUserType = (OrganizationUserType)inviteeUserType,
InvitorUserType = (OrganizationUserType)invitorUserType,
PermissionsBlob = permissionsBlob,
})
{ }
}
internal class InlineOrganizationInviteAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineOrganizationInviteAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
typeof(OrganizationInvite) }, values)
{ }
}

View File

@ -2,17 +2,18 @@
using Bit.Core.Models.Business;
using Bit.Test.Common.AutoFixture.Attributes;
namespace Bit.Core.Test.AutoFixture;
public class OrganizationLicenseCustomizeAttribute : BitCustomizeAttribute
namespace Bit.Core.Test.AutoFixture
{
public override ICustomization GetCustomization() => new OrganizationLicenseCustomization();
}
public class OrganizationLicenseCustomization : ICustomization
{
public void Customize(IFixture fixture)
public class OrganizationLicenseCustomizeAttribute : BitCustomizeAttribute
{
fixture.Customize<OrganizationLicense>(composer => composer
.With(o => o.Signature, Guid.NewGuid().ToString().Replace('-', '+')));
public override ICustomization GetCustomization() => new OrganizationLicenseCustomization();
}
public class OrganizationLicenseCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<OrganizationLicense>(composer => composer
.With(o => o.Signature, Guid.NewGuid().ToString().Replace('-', '+')));
}
}
}

View File

@ -2,31 +2,32 @@
using Bit.Core.Entities;
using Bit.Test.Common.AutoFixture.Attributes;
namespace Bit.Core.Test.AutoFixture.OrganizationSponsorshipFixtures;
public class OrganizationSponsorshipCustomizeAttribute : BitCustomizeAttribute
namespace Bit.Core.Test.AutoFixture.OrganizationSponsorshipFixtures
{
public bool ToDelete = false;
public override ICustomization GetCustomization() => ToDelete ?
new ToDeleteOrganizationSponsorship() :
new ValidOrganizationSponsorship();
}
public class ValidOrganizationSponsorship : ICustomization
{
public void Customize(IFixture fixture)
public class OrganizationSponsorshipCustomizeAttribute : BitCustomizeAttribute
{
fixture.Customize<OrganizationSponsorship>(composer => composer
.With(s => s.ToDelete, false)
.With(s => s.LastSyncDate, DateTime.UtcNow.AddDays(new Random().Next(-90, 0))));
}
}
public class ToDeleteOrganizationSponsorship : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<OrganizationSponsorship>(composer => composer
.With(s => s.ToDelete, true));
public bool ToDelete = false;
public override ICustomization GetCustomization() => ToDelete ?
new ToDeleteOrganizationSponsorship() :
new ValidOrganizationSponsorship();
}
public class ValidOrganizationSponsorship : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<OrganizationSponsorship>(composer => composer
.With(s => s.ToDelete, false)
.With(s => s.LastSyncDate, DateTime.UtcNow.AddDays(new Random().Next(-90, 0))));
}
}
public class ToDeleteOrganizationSponsorship : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<OrganizationSponsorship>(composer => composer
.With(s => s.ToDelete, true));
}
}
}

View File

@ -4,42 +4,43 @@ using AutoFixture.Xunit2;
using Bit.Core.Entities;
using Bit.Core.Enums;
namespace Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
public class OrganizationUserCustomization : ICustomization
namespace Bit.Core.Test.AutoFixture.OrganizationUserFixtures
{
public OrganizationUserStatusType Status { get; set; }
public OrganizationUserType Type { get; set; }
public OrganizationUserCustomization(OrganizationUserStatusType status, OrganizationUserType type)
public class OrganizationUserCustomization : ICustomization
{
Status = status;
Type = type;
public OrganizationUserStatusType Status { get; set; }
public OrganizationUserType Type { get; set; }
public OrganizationUserCustomization(OrganizationUserStatusType status, OrganizationUserType type)
{
Status = status;
Type = type;
}
public void Customize(IFixture fixture)
{
fixture.Customize<OrganizationUser>(composer => composer
.With(o => o.Type, Type)
.With(o => o.Status, Status));
}
}
public void Customize(IFixture fixture)
public class OrganizationUserAttribute : CustomizeAttribute
{
fixture.Customize<OrganizationUser>(composer => composer
.With(o => o.Type, Type)
.With(o => o.Status, Status));
}
}
public class OrganizationUserAttribute : CustomizeAttribute
{
private readonly OrganizationUserStatusType _status;
private readonly OrganizationUserType _type;
public OrganizationUserAttribute(
OrganizationUserStatusType status = OrganizationUserStatusType.Confirmed,
OrganizationUserType type = OrganizationUserType.User)
{
_status = status;
_type = type;
}
public override ICustomization GetCustomization(ParameterInfo parameter)
{
return new OrganizationUserCustomization(_status, _type);
private readonly OrganizationUserStatusType _status;
private readonly OrganizationUserType _type;
public OrganizationUserAttribute(
OrganizationUserStatusType status = OrganizationUserStatusType.Confirmed,
OrganizationUserType type = OrganizationUserType.User)
{
_status = status;
_type = type;
}
public override ICustomization GetCustomization(ParameterInfo parameter)
{
return new OrganizationUserCustomization(_status, _type);
}
}
}

View File

@ -4,37 +4,38 @@ using AutoFixture.Xunit2;
using Bit.Core.Entities;
using Bit.Core.Enums;
namespace Bit.Core.Test.AutoFixture.PolicyFixtures;
internal class PolicyCustomization : ICustomization
namespace Bit.Core.Test.AutoFixture.PolicyFixtures
{
public PolicyType Type { get; set; }
public PolicyCustomization(PolicyType type)
internal class PolicyCustomization : ICustomization
{
Type = type;
public PolicyType Type { get; set; }
public PolicyCustomization(PolicyType type)
{
Type = type;
}
public void Customize(IFixture fixture)
{
fixture.Customize<Policy>(composer => composer
.With(o => o.OrganizationId, Guid.NewGuid())
.With(o => o.Type, Type)
.With(o => o.Enabled, true));
}
}
public void Customize(IFixture fixture)
public class PolicyAttribute : CustomizeAttribute
{
fixture.Customize<Policy>(composer => composer
.With(o => o.OrganizationId, Guid.NewGuid())
.With(o => o.Type, Type)
.With(o => o.Enabled, true));
}
}
public class PolicyAttribute : CustomizeAttribute
{
private readonly PolicyType _type;
public PolicyAttribute(PolicyType type)
{
_type = type;
}
public override ICustomization GetCustomization(ParameterInfo parameter)
{
return new PolicyCustomization(_type);
private readonly PolicyType _type;
public PolicyAttribute(PolicyType type)
{
_type = type;
}
public override ICustomization GetCustomization(ParameterInfo parameter)
{
return new PolicyCustomization(_type);
}
}
}

View File

@ -2,62 +2,63 @@
using Bit.Core.Entities;
using Bit.Test.Common.AutoFixture.Attributes;
namespace Bit.Core.Test.AutoFixture.SendFixtures;
internal class OrganizationSend : ICustomization
namespace Bit.Core.Test.AutoFixture.SendFixtures
{
public Guid? OrganizationId { get; set; }
public void Customize(IFixture fixture)
internal class OrganizationSend : ICustomization
{
fixture.Customize<Send>(composer => composer
.With(s => s.OrganizationId, OrganizationId ?? Guid.NewGuid())
.Without(s => s.UserId));
public Guid? OrganizationId { get; set; }
public void Customize(IFixture fixture)
{
fixture.Customize<Send>(composer => composer
.With(s => s.OrganizationId, OrganizationId ?? Guid.NewGuid())
.Without(s => s.UserId));
}
}
internal class UserSend : ICustomization
{
public Guid? UserId { get; set; }
public void Customize(IFixture fixture)
{
fixture.Customize<Send>(composer => composer
.With(s => s.UserId, UserId ?? Guid.NewGuid())
.Without(s => s.OrganizationId));
}
}
internal class UserSendAutoDataAttribute : CustomAutoDataAttribute
{
public UserSendAutoDataAttribute(string userId = null) : base(new SutProviderCustomization(),
new UserSend { UserId = userId == null ? (Guid?)null : new Guid(userId) })
{ }
}
internal class InlineUserSendAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineUserSendAutoDataAttribute(params object[] values) : base(new[] { typeof(CurrentContextFixtures.CurrentContext),
typeof(SutProviderCustomization), typeof(UserSend) }, values)
{ }
}
internal class InlineKnownUserSendAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineKnownUserSendAutoDataAttribute(string userId, params object[] values) : base(new ICustomization[]
{ new CurrentContextFixtures.CurrentContext(), new SutProviderCustomization(),
new UserSend { UserId = new Guid(userId) } }, values)
{ }
}
internal class OrganizationSendAutoDataAttribute : CustomAutoDataAttribute
{
public OrganizationSendAutoDataAttribute(string organizationId = null) : base(new CurrentContextFixtures.CurrentContext(),
new SutProviderCustomization(),
new OrganizationSend { OrganizationId = organizationId == null ? (Guid?)null : new Guid(organizationId) })
{ }
}
internal class InlineOrganizationSendAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineOrganizationSendAutoDataAttribute(params object[] values) : base(new[] { typeof(CurrentContextFixtures.CurrentContext),
typeof(SutProviderCustomization), typeof(OrganizationSend) }, values)
{ }
}
}
internal class UserSend : ICustomization
{
public Guid? UserId { get; set; }
public void Customize(IFixture fixture)
{
fixture.Customize<Send>(composer => composer
.With(s => s.UserId, UserId ?? Guid.NewGuid())
.Without(s => s.OrganizationId));
}
}
internal class UserSendAutoDataAttribute : CustomAutoDataAttribute
{
public UserSendAutoDataAttribute(string userId = null) : base(new SutProviderCustomization(),
new UserSend { UserId = userId == null ? (Guid?)null : new Guid(userId) })
{ }
}
internal class InlineUserSendAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineUserSendAutoDataAttribute(params object[] values) : base(new[] { typeof(CurrentContextFixtures.CurrentContext),
typeof(SutProviderCustomization), typeof(UserSend) }, values)
{ }
}
internal class InlineKnownUserSendAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineKnownUserSendAutoDataAttribute(string userId, params object[] values) : base(new ICustomization[]
{ new CurrentContextFixtures.CurrentContext(), new SutProviderCustomization(),
new UserSend { UserId = new Guid(userId) } }, values)
{ }
}
internal class OrganizationSendAutoDataAttribute : CustomAutoDataAttribute
{
public OrganizationSendAutoDataAttribute(string organizationId = null) : base(new CurrentContextFixtures.CurrentContext(),
new SutProviderCustomization(),
new OrganizationSend { OrganizationId = organizationId == null ? (Guid?)null : new Guid(organizationId) })
{ }
}
internal class InlineOrganizationSendAutoDataAttribute : InlineCustomAutoDataAttribute
{
public InlineOrganizationSendAutoDataAttribute(params object[] values) : base(new[] { typeof(CurrentContextFixtures.CurrentContext),
typeof(SutProviderCustomization), typeof(OrganizationSend) }, values)
{ }
}

View File

@ -6,48 +6,49 @@ using Bit.Core.Models;
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
using Bit.Test.Common.AutoFixture;
namespace Bit.Core.Test.AutoFixture.UserFixtures;
public class UserBuilder : ISpecimenBuilder
namespace Bit.Core.Test.AutoFixture.UserFixtures
{
public object Create(object request, ISpecimenContext context)
public class UserBuilder : ISpecimenBuilder
{
if (context == null)
public object Create(object request, ISpecimenContext context)
{
throw new ArgumentNullException(nameof(context));
}
var type = request as Type;
if (type == typeof(User))
{
var fixture = new Fixture();
var providers = fixture.Create<Dictionary<TwoFactorProviderType, TwoFactorProvider>>();
var user = fixture.WithAutoNSubstitutions().Create<User>();
user.SetTwoFactorProviders(providers);
return user;
}
else if (type == typeof(List<User>))
{
var fixture = new Fixture();
var users = fixture.WithAutoNSubstitutions().CreateMany<User>(2);
foreach (var user in users)
if (context == null)
{
var providers = fixture.Create<Dictionary<TwoFactorProviderType, TwoFactorProvider>>();
user.SetTwoFactorProviders(providers);
throw new ArgumentNullException(nameof(context));
}
return users;
var type = request as Type;
if (type == typeof(User))
{
var fixture = new Fixture();
var providers = fixture.Create<Dictionary<TwoFactorProviderType, TwoFactorProvider>>();
var user = fixture.WithAutoNSubstitutions().Create<User>();
user.SetTwoFactorProviders(providers);
return user;
}
else if (type == typeof(List<User>))
{
var fixture = new Fixture();
var users = fixture.WithAutoNSubstitutions().CreateMany<User>(2);
foreach (var user in users)
{
var providers = fixture.Create<Dictionary<TwoFactorProviderType, TwoFactorProvider>>();
user.SetTwoFactorProviders(providers);
}
return users;
}
return new NoSpecimen();
}
return new NoSpecimen();
}
}
public class UserFixture : ICustomization
{
public virtual void Customize(IFixture fixture)
public class UserFixture : ICustomization
{
fixture.Customizations.Add(new GlobalSettingsBuilder());
fixture.Customizations.Add(new UserBuilder());
fixture.Customizations.Add(new OrganizationBuilder());
public virtual void Customize(IFixture fixture)
{
fixture.Customizations.Add(new GlobalSettingsBuilder());
fixture.Customizations.Add(new UserBuilder());
fixture.Customizations.Add(new OrganizationBuilder());
}
}
}

View File

@ -5,95 +5,96 @@ using Bit.Core.Models;
using Bit.Test.Common.Helpers;
using Xunit;
namespace Bit.Core.Test.Entities;
public class OrganizationTests
namespace Bit.Core.Test.Entities
{
private static readonly Dictionary<TwoFactorProviderType, TwoFactorProvider> _testConfig = new Dictionary<TwoFactorProviderType, TwoFactorProvider>()
public class OrganizationTests
{
[TwoFactorProviderType.OrganizationDuo] = new TwoFactorProvider
private static readonly Dictionary<TwoFactorProviderType, TwoFactorProvider> _testConfig = new Dictionary<TwoFactorProviderType, TwoFactorProvider>()
{
Enabled = true,
MetaData = new Dictionary<string, object>
[TwoFactorProviderType.OrganizationDuo] = new TwoFactorProvider
{
["IKey"] = "IKey_value",
["SKey"] = "SKey_value",
["Host"] = "Host_value",
},
}
};
[Fact]
public void SetTwoFactorProviders_Success()
{
var organization = new Organization();
organization.SetTwoFactorProviders(_testConfig);
using var jsonDocument = JsonDocument.Parse(organization.TwoFactorProviders);
var root = jsonDocument.RootElement;
var duo = AssertHelper.AssertJsonProperty(root, "6", JsonValueKind.Object);
AssertHelper.AssertJsonProperty(duo, "Enabled", JsonValueKind.True);
var duoMetaData = AssertHelper.AssertJsonProperty(duo, "MetaData", JsonValueKind.Object);
var iKey = AssertHelper.AssertJsonProperty(duoMetaData, "IKey", JsonValueKind.String).GetString();
Assert.Equal("IKey_value", iKey);
var sKey = AssertHelper.AssertJsonProperty(duoMetaData, "SKey", JsonValueKind.String).GetString();
Assert.Equal("SKey_value", sKey);
var host = AssertHelper.AssertJsonProperty(duoMetaData, "Host", JsonValueKind.String).GetString();
Assert.Equal("Host_value", host);
}
[Fact]
public void GetTwoFactorProviders_Success()
{
// This is to get rid of the cached dictionary the SetTwoFactorProviders keeps so we can fully test the JSON reading
// It intent is to mimic a storing of the entity in the database and it being read later
var tempOrganization = new Organization();
tempOrganization.SetTwoFactorProviders(_testConfig);
var organization = new Organization
{
TwoFactorProviders = tempOrganization.TwoFactorProviders,
Enabled = true,
MetaData = new Dictionary<string, object>
{
["IKey"] = "IKey_value",
["SKey"] = "SKey_value",
["Host"] = "Host_value",
},
}
};
var twoFactorProviders = organization.GetTwoFactorProviders();
var duo = Assert.Contains(TwoFactorProviderType.OrganizationDuo, (IDictionary<TwoFactorProviderType, TwoFactorProvider>)twoFactorProviders);
Assert.True(duo.Enabled);
Assert.NotNull(duo.MetaData);
var iKey = Assert.Contains("IKey", (IDictionary<string, object>)duo.MetaData);
Assert.Equal("IKey_value", iKey);
var sKey = Assert.Contains("SKey", (IDictionary<string, object>)duo.MetaData);
Assert.Equal("SKey_value", sKey);
var host = Assert.Contains("Host", (IDictionary<string, object>)duo.MetaData);
Assert.Equal("Host_value", host);
}
[Fact]
public void SetTwoFactorProviders_Success()
{
var organization = new Organization();
organization.SetTwoFactorProviders(_testConfig);
[Fact]
public void GetTwoFactorProviders_SavedWithName_Success()
{
var organization = new Organization();
// This should save items with the string name of the enum and we will validate that we can read
// from that just incase some organizations have it saved that way.
organization.TwoFactorProviders = JsonSerializer.Serialize(_testConfig);
using var jsonDocument = JsonDocument.Parse(organization.TwoFactorProviders);
var root = jsonDocument.RootElement;
// Preliminary Asserts to make sure we are testing what we want to be testing
using var jsonDocument = JsonDocument.Parse(organization.TwoFactorProviders);
var root = jsonDocument.RootElement;
// This means it saved the enum as its string name
AssertHelper.AssertJsonProperty(root, "OrganizationDuo", JsonValueKind.Object);
var duo = AssertHelper.AssertJsonProperty(root, "6", JsonValueKind.Object);
AssertHelper.AssertJsonProperty(duo, "Enabled", JsonValueKind.True);
var duoMetaData = AssertHelper.AssertJsonProperty(duo, "MetaData", JsonValueKind.Object);
var iKey = AssertHelper.AssertJsonProperty(duoMetaData, "IKey", JsonValueKind.String).GetString();
Assert.Equal("IKey_value", iKey);
var sKey = AssertHelper.AssertJsonProperty(duoMetaData, "SKey", JsonValueKind.String).GetString();
Assert.Equal("SKey_value", sKey);
var host = AssertHelper.AssertJsonProperty(duoMetaData, "Host", JsonValueKind.String).GetString();
Assert.Equal("Host_value", host);
}
// Actual checks
var twoFactorProviders = organization.GetTwoFactorProviders();
[Fact]
public void GetTwoFactorProviders_Success()
{
// This is to get rid of the cached dictionary the SetTwoFactorProviders keeps so we can fully test the JSON reading
// It intent is to mimic a storing of the entity in the database and it being read later
var tempOrganization = new Organization();
tempOrganization.SetTwoFactorProviders(_testConfig);
var organization = new Organization
{
TwoFactorProviders = tempOrganization.TwoFactorProviders,
};
var duo = Assert.Contains(TwoFactorProviderType.OrganizationDuo, (IDictionary<TwoFactorProviderType, TwoFactorProvider>)twoFactorProviders);
Assert.True(duo.Enabled);
Assert.NotNull(duo.MetaData);
var iKey = Assert.Contains("IKey", (IDictionary<string, object>)duo.MetaData);
Assert.Equal("IKey_value", iKey);
var sKey = Assert.Contains("SKey", (IDictionary<string, object>)duo.MetaData);
Assert.Equal("SKey_value", sKey);
var host = Assert.Contains("Host", (IDictionary<string, object>)duo.MetaData);
Assert.Equal("Host_value", host);
var twoFactorProviders = organization.GetTwoFactorProviders();
var duo = Assert.Contains(TwoFactorProviderType.OrganizationDuo, (IDictionary<TwoFactorProviderType, TwoFactorProvider>)twoFactorProviders);
Assert.True(duo.Enabled);
Assert.NotNull(duo.MetaData);
var iKey = Assert.Contains("IKey", (IDictionary<string, object>)duo.MetaData);
Assert.Equal("IKey_value", iKey);
var sKey = Assert.Contains("SKey", (IDictionary<string, object>)duo.MetaData);
Assert.Equal("SKey_value", sKey);
var host = Assert.Contains("Host", (IDictionary<string, object>)duo.MetaData);
Assert.Equal("Host_value", host);
}
[Fact]
public void GetTwoFactorProviders_SavedWithName_Success()
{
var organization = new Organization();
// This should save items with the string name of the enum and we will validate that we can read
// from that just incase some organizations have it saved that way.
organization.TwoFactorProviders = JsonSerializer.Serialize(_testConfig);
// Preliminary Asserts to make sure we are testing what we want to be testing
using var jsonDocument = JsonDocument.Parse(organization.TwoFactorProviders);
var root = jsonDocument.RootElement;
// This means it saved the enum as its string name
AssertHelper.AssertJsonProperty(root, "OrganizationDuo", JsonValueKind.Object);
// Actual checks
var twoFactorProviders = organization.GetTwoFactorProviders();
var duo = Assert.Contains(TwoFactorProviderType.OrganizationDuo, (IDictionary<TwoFactorProviderType, TwoFactorProvider>)twoFactorProviders);
Assert.True(duo.Enabled);
Assert.NotNull(duo.MetaData);
var iKey = Assert.Contains("IKey", (IDictionary<string, object>)duo.MetaData);
Assert.Equal("IKey_value", iKey);
var sKey = Assert.Contains("SKey", (IDictionary<string, object>)duo.MetaData);
Assert.Equal("SKey_value", sKey);
var host = Assert.Contains("Host", (IDictionary<string, object>)duo.MetaData);
Assert.Equal("Host_value", host);
}
}
}

View File

@ -5,140 +5,141 @@ using Bit.Core.Models;
using Bit.Test.Common.Helpers;
using Xunit;
namespace Bit.Core.Test.Entities;
public class UserTests
namespace Bit.Core.Test.Entities
{
// KB MB GB
public const long Multiplier = 1024 * 1024 * 1024;
[Fact]
public void StorageBytesRemaining_HasMax_DoesNotHaveStorage_ReturnsMaxAsBytes()
public class UserTests
{
short maxStorageGb = 1;
// KB MB GB
public const long Multiplier = 1024 * 1024 * 1024;
var user = new User
[Fact]
public void StorageBytesRemaining_HasMax_DoesNotHaveStorage_ReturnsMaxAsBytes()
{
MaxStorageGb = maxStorageGb,
Storage = null,
};
short maxStorageGb = 1;
var bytesRemaining = user.StorageBytesRemaining();
Assert.Equal(bytesRemaining, maxStorageGb * Multiplier);
}
[Theory]
[InlineData(2, 1 * Multiplier, 1 * Multiplier)]
public void StorageBytesRemaining_HasMax_HasStorage_ReturnRemainingStorage(short maxStorageGb, long storageBytes, long expectedRemainingBytes)
{
var user = new User
{
MaxStorageGb = maxStorageGb,
Storage = storageBytes,
};
var bytesRemaining = user.StorageBytesRemaining();
Assert.Equal(expectedRemainingBytes, bytesRemaining);
}
private static readonly Dictionary<TwoFactorProviderType, TwoFactorProvider> _testTwoFactorConfig = new Dictionary<TwoFactorProviderType, TwoFactorProvider>
{
[TwoFactorProviderType.WebAuthn] = new TwoFactorProvider
{
Enabled = true,
MetaData = new Dictionary<string, object>
var user = new User
{
["Item"] = "thing",
},
},
[TwoFactorProviderType.Email] = new TwoFactorProvider
MaxStorageGb = maxStorageGb,
Storage = null,
};
var bytesRemaining = user.StorageBytesRemaining();
Assert.Equal(bytesRemaining, maxStorageGb * Multiplier);
}
[Theory]
[InlineData(2, 1 * Multiplier, 1 * Multiplier)]
public void StorageBytesRemaining_HasMax_HasStorage_ReturnRemainingStorage(short maxStorageGb, long storageBytes, long expectedRemainingBytes)
{
Enabled = false,
MetaData = new Dictionary<string, object>
var user = new User
{
["Email"] = "test@email.com",
},
},
};
MaxStorageGb = maxStorageGb,
Storage = storageBytes,
};
[Fact]
public void SetTwoFactorProviders_Success()
{
var user = new User();
user.SetTwoFactorProviders(_testTwoFactorConfig);
var bytesRemaining = user.StorageBytesRemaining();
using var jsonDocument = JsonDocument.Parse(user.TwoFactorProviders);
var root = jsonDocument.RootElement;
Assert.Equal(expectedRemainingBytes, bytesRemaining);
}
var webAuthn = AssertHelper.AssertJsonProperty(root, "7", JsonValueKind.Object);
AssertHelper.AssertJsonProperty(webAuthn, "Enabled", JsonValueKind.True);
var webMetaData = AssertHelper.AssertJsonProperty(webAuthn, "MetaData", JsonValueKind.Object);
AssertHelper.AssertJsonProperty(webMetaData, "Item", JsonValueKind.String);
var email = AssertHelper.AssertJsonProperty(root, "1", JsonValueKind.Object);
AssertHelper.AssertJsonProperty(email, "Enabled", JsonValueKind.False);
var emailMetaData = AssertHelper.AssertJsonProperty(email, "MetaData", JsonValueKind.Object);
AssertHelper.AssertJsonProperty(emailMetaData, "Email", JsonValueKind.String);
}
[Fact]
public void GetTwoFactorProviders_Success()
{
// This is to get rid of the cached dictionary the SetTwoFactorProviders keeps so we can fully test the JSON reading
// It intent is to mimic a storing of the entity in the database and it being read later
var tempUser = new User();
tempUser.SetTwoFactorProviders(_testTwoFactorConfig);
var user = new User
private static readonly Dictionary<TwoFactorProviderType, TwoFactorProvider> _testTwoFactorConfig = new Dictionary<TwoFactorProviderType, TwoFactorProvider>
{
TwoFactorProviders = tempUser.TwoFactorProviders,
[TwoFactorProviderType.WebAuthn] = new TwoFactorProvider
{
Enabled = true,
MetaData = new Dictionary<string, object>
{
["Item"] = "thing",
},
},
[TwoFactorProviderType.Email] = new TwoFactorProvider
{
Enabled = false,
MetaData = new Dictionary<string, object>
{
["Email"] = "test@email.com",
},
},
};
var twoFactorProviders = user.GetTwoFactorProviders();
[Fact]
public void SetTwoFactorProviders_Success()
{
var user = new User();
user.SetTwoFactorProviders(_testTwoFactorConfig);
var webAuthn = Assert.Contains(TwoFactorProviderType.WebAuthn, (IDictionary<TwoFactorProviderType, TwoFactorProvider>)twoFactorProviders);
Assert.True(webAuthn.Enabled);
Assert.NotNull(webAuthn.MetaData);
var webAuthnMetaDataItem = Assert.Contains("Item", (IDictionary<string, object>)webAuthn.MetaData);
Assert.Equal("thing", webAuthnMetaDataItem);
using var jsonDocument = JsonDocument.Parse(user.TwoFactorProviders);
var root = jsonDocument.RootElement;
var email = Assert.Contains(TwoFactorProviderType.Email, (IDictionary<TwoFactorProviderType, TwoFactorProvider>)twoFactorProviders);
Assert.False(email.Enabled);
Assert.NotNull(email.MetaData);
var emailMetaDataEmail = Assert.Contains("Email", (IDictionary<string, object>)email.MetaData);
Assert.Equal("test@email.com", emailMetaDataEmail);
}
var webAuthn = AssertHelper.AssertJsonProperty(root, "7", JsonValueKind.Object);
AssertHelper.AssertJsonProperty(webAuthn, "Enabled", JsonValueKind.True);
var webMetaData = AssertHelper.AssertJsonProperty(webAuthn, "MetaData", JsonValueKind.Object);
AssertHelper.AssertJsonProperty(webMetaData, "Item", JsonValueKind.String);
[Fact]
public void GetTwoFactorProviders_SavedWithName_Success()
{
var user = new User();
// This should save items with the string name of the enum and we will validate that we can read
// from that just incase some users have it saved that way.
user.TwoFactorProviders = JsonSerializer.Serialize(_testTwoFactorConfig);
var email = AssertHelper.AssertJsonProperty(root, "1", JsonValueKind.Object);
AssertHelper.AssertJsonProperty(email, "Enabled", JsonValueKind.False);
var emailMetaData = AssertHelper.AssertJsonProperty(email, "MetaData", JsonValueKind.Object);
AssertHelper.AssertJsonProperty(emailMetaData, "Email", JsonValueKind.String);
}
// Preliminary Asserts to make sure we are testing what we want to be testing
using var jsonDocument = JsonDocument.Parse(user.TwoFactorProviders);
var root = jsonDocument.RootElement;
// This means it saved the enum as its string name
AssertHelper.AssertJsonProperty(root, "WebAuthn", JsonValueKind.Object);
AssertHelper.AssertJsonProperty(root, "Email", JsonValueKind.Object);
[Fact]
public void GetTwoFactorProviders_Success()
{
// This is to get rid of the cached dictionary the SetTwoFactorProviders keeps so we can fully test the JSON reading
// It intent is to mimic a storing of the entity in the database and it being read later
var tempUser = new User();
tempUser.SetTwoFactorProviders(_testTwoFactorConfig);
var user = new User
{
TwoFactorProviders = tempUser.TwoFactorProviders,
};
// Actual checks
var twoFactorProviders = user.GetTwoFactorProviders();
var twoFactorProviders = user.GetTwoFactorProviders();
var webAuthn = Assert.Contains(TwoFactorProviderType.WebAuthn, (IDictionary<TwoFactorProviderType, TwoFactorProvider>)twoFactorProviders);
Assert.True(webAuthn.Enabled);
Assert.NotNull(webAuthn.MetaData);
var webAuthnMetaDataItem = Assert.Contains("Item", (IDictionary<string, object>)webAuthn.MetaData);
Assert.Equal("thing", webAuthnMetaDataItem);
var webAuthn = Assert.Contains(TwoFactorProviderType.WebAuthn, (IDictionary<TwoFactorProviderType, TwoFactorProvider>)twoFactorProviders);
Assert.True(webAuthn.Enabled);
Assert.NotNull(webAuthn.MetaData);
var webAuthnMetaDataItem = Assert.Contains("Item", (IDictionary<string, object>)webAuthn.MetaData);
Assert.Equal("thing", webAuthnMetaDataItem);
var email = Assert.Contains(TwoFactorProviderType.Email, (IDictionary<TwoFactorProviderType, TwoFactorProvider>)twoFactorProviders);
Assert.False(email.Enabled);
Assert.NotNull(email.MetaData);
var emailMetaDataEmail = Assert.Contains("Email", (IDictionary<string, object>)email.MetaData);
Assert.Equal("test@email.com", emailMetaDataEmail);
var email = Assert.Contains(TwoFactorProviderType.Email, (IDictionary<TwoFactorProviderType, TwoFactorProvider>)twoFactorProviders);
Assert.False(email.Enabled);
Assert.NotNull(email.MetaData);
var emailMetaDataEmail = Assert.Contains("Email", (IDictionary<string, object>)email.MetaData);
Assert.Equal("test@email.com", emailMetaDataEmail);
}
[Fact]
public void GetTwoFactorProviders_SavedWithName_Success()
{
var user = new User();
// This should save items with the string name of the enum and we will validate that we can read
// from that just incase some users have it saved that way.
user.TwoFactorProviders = JsonSerializer.Serialize(_testTwoFactorConfig);
// Preliminary Asserts to make sure we are testing what we want to be testing
using var jsonDocument = JsonDocument.Parse(user.TwoFactorProviders);
var root = jsonDocument.RootElement;
// This means it saved the enum as its string name
AssertHelper.AssertJsonProperty(root, "WebAuthn", JsonValueKind.Object);
AssertHelper.AssertJsonProperty(root, "Email", JsonValueKind.Object);
// Actual checks
var twoFactorProviders = user.GetTwoFactorProviders();
var webAuthn = Assert.Contains(TwoFactorProviderType.WebAuthn, (IDictionary<TwoFactorProviderType, TwoFactorProvider>)twoFactorProviders);
Assert.True(webAuthn.Enabled);
Assert.NotNull(webAuthn.MetaData);
var webAuthnMetaDataItem = Assert.Contains("Item", (IDictionary<string, object>)webAuthn.MetaData);
Assert.Equal("thing", webAuthnMetaDataItem);
var email = Assert.Contains(TwoFactorProviderType.Email, (IDictionary<TwoFactorProviderType, TwoFactorProvider>)twoFactorProviders);
Assert.False(email.Enabled);
Assert.NotNull(email.MetaData);
var emailMetaDataEmail = Assert.Contains("Email", (IDictionary<string, object>)email.MetaData);
Assert.Equal("test@email.com", emailMetaDataEmail);
}
}
}

View File

@ -1,15 +1,16 @@
using Bit.Core.Settings;
using Microsoft.Extensions.Configuration;
namespace Bit.Core.Test.Helpers.Factories;
public static class GlobalSettingsFactory
namespace Bit.Core.Test.Helpers.Factories
{
public static GlobalSettings GlobalSettings { get; } = new();
static GlobalSettingsFactory()
public static class GlobalSettingsFactory
{
var configBuilder = new ConfigurationBuilder().AddUserSecrets("bitwarden-Api");
var Configuration = configBuilder.Build();
ConfigurationBinder.Bind(Configuration.GetSection("GlobalSettings"), GlobalSettings);
public static GlobalSettings GlobalSettings { get; } = new();
static GlobalSettingsFactory()
{
var configBuilder = new ConfigurationBuilder().AddUserSecrets("bitwarden-Api");
var Configuration = configBuilder.Build();
ConfigurationBinder.Bind(Configuration.GetSection("GlobalSettings"), GlobalSettings);
}
}
}

View File

@ -5,34 +5,35 @@ using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Core.Test.Identity;
public class AuthenticationTokenProviderTests : BaseTokenProviderTests<AuthenticatorTokenProvider>
namespace Bit.Core.Test.Identity
{
public override TwoFactorProviderType TwoFactorProviderType => TwoFactorProviderType.Authenticator;
public static IEnumerable<object[]> CanGenerateTwoFactorTokenAsyncData
=> SetupCanGenerateData(
(
new Dictionary<string, object>
{
["Key"] = "stuff",
},
true
),
(
new Dictionary<string, object>
{
["Key"] = ""
},
false
)
);
[Theory, BitMemberAutoData(nameof(CanGenerateTwoFactorTokenAsyncData))]
public override async Task RunCanGenerateTwoFactorTokenAsync(Dictionary<string, object> metaData, bool expectedResponse,
User user, SutProvider<AuthenticatorTokenProvider> sutProvider)
public class AuthenticationTokenProviderTests : BaseTokenProviderTests<AuthenticatorTokenProvider>
{
await base.RunCanGenerateTwoFactorTokenAsync(metaData, expectedResponse, user, sutProvider);
public override TwoFactorProviderType TwoFactorProviderType => TwoFactorProviderType.Authenticator;
public static IEnumerable<object[]> CanGenerateTwoFactorTokenAsyncData
=> SetupCanGenerateData(
(
new Dictionary<string, object>
{
["Key"] = "stuff",
},
true
),
(
new Dictionary<string, object>
{
["Key"] = ""
},
false
)
);
[Theory, BitMemberAutoData(nameof(CanGenerateTwoFactorTokenAsyncData))]
public override async Task RunCanGenerateTwoFactorTokenAsync(Dictionary<string, object> metaData, bool expectedResponse,
User user, SutProvider<AuthenticatorTokenProvider> sutProvider)
{
await base.RunCanGenerateTwoFactorTokenAsync(metaData, expectedResponse, user, sutProvider);
}
}
}

View File

@ -11,82 +11,83 @@ using Microsoft.Extensions.Options;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Identity;
[SutProviderCustomize]
public abstract class BaseTokenProviderTests<T>
where T : IUserTwoFactorTokenProvider<User>
namespace Bit.Core.Test.Identity
{
public abstract TwoFactorProviderType TwoFactorProviderType { get; }
#region Helpers
protected static IEnumerable<object[]> SetupCanGenerateData(params (Dictionary<string, object> MetaData, bool ExpectedResponse)[] data)
[SutProviderCustomize]
public abstract class BaseTokenProviderTests<T>
where T : IUserTwoFactorTokenProvider<User>
{
return data.Select(d =>
new object[]
{
d.MetaData,
d.ExpectedResponse,
});
}
public abstract TwoFactorProviderType TwoFactorProviderType { get; }
protected virtual IUserService AdditionalSetup(SutProvider<T> sutProvider, User user)
{
var userService = Substitute.For<IUserService>();
sutProvider.GetDependency<IServiceProvider>()
.GetService(typeof(IUserService))
.Returns(userService);
SetupUserService(userService, user);
return userService;
}
protected virtual void SetupUserService(IUserService userService, User user)
{
userService
.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType, user)
.Returns(true);
}
protected static UserManager<User> SubstituteUserManager()
{
return new UserManager<User>(Substitute.For<IUserStore<User>>(),
Substitute.For<IOptions<IdentityOptions>>(),
Substitute.For<IPasswordHasher<User>>(),
Enumerable.Empty<IUserValidator<User>>(),
Enumerable.Empty<IPasswordValidator<User>>(),
Substitute.For<ILookupNormalizer>(),
Substitute.For<IdentityErrorDescriber>(),
Substitute.For<IServiceProvider>(),
Substitute.For<ILogger<UserManager<User>>>());
}
protected void MockDatabase(User user, Dictionary<string, object> metaData)
{
var providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>
#region Helpers
protected static IEnumerable<object[]> SetupCanGenerateData(params (Dictionary<string, object> MetaData, bool ExpectedResponse)[] data)
{
[TwoFactorProviderType] = new TwoFactorProvider
return data.Select(d =>
new object[]
{
d.MetaData,
d.ExpectedResponse,
});
}
protected virtual IUserService AdditionalSetup(SutProvider<T> sutProvider, User user)
{
var userService = Substitute.For<IUserService>();
sutProvider.GetDependency<IServiceProvider>()
.GetService(typeof(IUserService))
.Returns(userService);
SetupUserService(userService, user);
return userService;
}
protected virtual void SetupUserService(IUserService userService, User user)
{
userService
.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType, user)
.Returns(true);
}
protected static UserManager<User> SubstituteUserManager()
{
return new UserManager<User>(Substitute.For<IUserStore<User>>(),
Substitute.For<IOptions<IdentityOptions>>(),
Substitute.For<IPasswordHasher<User>>(),
Enumerable.Empty<IUserValidator<User>>(),
Enumerable.Empty<IPasswordValidator<User>>(),
Substitute.For<ILookupNormalizer>(),
Substitute.For<IdentityErrorDescriber>(),
Substitute.For<IServiceProvider>(),
Substitute.For<ILogger<UserManager<User>>>());
}
protected void MockDatabase(User user, Dictionary<string, object> metaData)
{
var providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>
{
Enabled = true,
MetaData = metaData,
},
};
[TwoFactorProviderType] = new TwoFactorProvider
{
Enabled = true,
MetaData = metaData,
},
};
user.TwoFactorProviders = JsonHelpers.LegacySerialize(providers);
}
#endregion
user.TwoFactorProviders = JsonHelpers.LegacySerialize(providers);
}
#endregion
public virtual async Task RunCanGenerateTwoFactorTokenAsync(Dictionary<string, object> metaData, bool expectedResponse,
User user, SutProvider<T> sutProvider)
{
var userManager = SubstituteUserManager();
MockDatabase(user, metaData);
public virtual async Task RunCanGenerateTwoFactorTokenAsync(Dictionary<string, object> metaData, bool expectedResponse,
User user, SutProvider<T> sutProvider)
{
var userManager = SubstituteUserManager();
MockDatabase(user, metaData);
AdditionalSetup(sutProvider, user);
AdditionalSetup(sutProvider, user);
var response = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(userManager, user);
Assert.Equal(expectedResponse, response);
var response = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(userManager, user);
Assert.Equal(expectedResponse, response);
}
}
}

View File

@ -5,41 +5,42 @@ using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Core.Test.Identity;
public class EmailTokenProviderTests : BaseTokenProviderTests<EmailTokenProvider>
namespace Bit.Core.Test.Identity
{
public override TwoFactorProviderType TwoFactorProviderType => TwoFactorProviderType.Email;
public static IEnumerable<object[]> CanGenerateTwoFactorTokenAsyncData
=> SetupCanGenerateData(
(
new Dictionary<string, object>
{
["Email"] = "test@email.com",
},
true
),
(
new Dictionary<string, object>
{
["NotEmail"] = "value",
},
false
),
(
new Dictionary<string, object>
{
["Email"] = "",
},
false
)
);
[Theory, BitMemberAutoData(nameof(CanGenerateTwoFactorTokenAsyncData))]
public override async Task RunCanGenerateTwoFactorTokenAsync(Dictionary<string, object> metaData, bool expectedResponse,
User user, SutProvider<EmailTokenProvider> sutProvider)
public class EmailTokenProviderTests : BaseTokenProviderTests<EmailTokenProvider>
{
await base.RunCanGenerateTwoFactorTokenAsync(metaData, expectedResponse, user, sutProvider);
public override TwoFactorProviderType TwoFactorProviderType => TwoFactorProviderType.Email;
public static IEnumerable<object[]> CanGenerateTwoFactorTokenAsyncData
=> SetupCanGenerateData(
(
new Dictionary<string, object>
{
["Email"] = "test@email.com",
},
true
),
(
new Dictionary<string, object>
{
["NotEmail"] = "value",
},
false
),
(
new Dictionary<string, object>
{
["Email"] = "",
},
false
)
);
[Theory, BitMemberAutoData(nameof(CanGenerateTwoFactorTokenAsyncData))]
public override async Task RunCanGenerateTwoFactorTokenAsync(Dictionary<string, object> metaData, bool expectedResponse,
User user, SutProvider<EmailTokenProvider> sutProvider)
{
await base.RunCanGenerateTwoFactorTokenAsync(metaData, expectedResponse, user, sutProvider);
}
}
}

View File

@ -4,90 +4,91 @@ using Microsoft.Extensions.Primitives;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.IdentityServer;
public class TokenRetrievalTests
namespace Bit.Core.Test.IdentityServer
{
private readonly Func<HttpRequest, string> _sut = TokenRetrieval.FromAuthorizationHeaderOrQueryString();
[Fact]
public void RetrieveToken_FromHeader_ReturnsToken()
public class TokenRetrievalTests
{
// Arrange
var headers = new HeaderDictionary
private readonly Func<HttpRequest, string> _sut = TokenRetrieval.FromAuthorizationHeaderOrQueryString();
[Fact]
public void RetrieveToken_FromHeader_ReturnsToken()
{
{ "Authorization", "Bearer test_value" },
{ "X-Test-Header", "random_value" }
};
// Arrange
var headers = new HeaderDictionary
{
{ "Authorization", "Bearer test_value" },
{ "X-Test-Header", "random_value" }
};
var request = Substitute.For<HttpRequest>();
var request = Substitute.For<HttpRequest>();
request.Headers.Returns(headers);
request.Headers.Returns(headers);
// Act
var token = _sut(request);
// Act
var token = _sut(request);
// Assert
Assert.Equal("test_value", token);
}
// Assert
Assert.Equal("test_value", token);
}
[Fact]
public void RetrieveToken_FromQueryString_ReturnsToken()
{
// Arrange
var queryString = new Dictionary<string, StringValues>
[Fact]
public void RetrieveToken_FromQueryString_ReturnsToken()
{
{ "access_token", "test_value" },
{ "test-query", "random_value" }
};
// Arrange
var queryString = new Dictionary<string, StringValues>
{
{ "access_token", "test_value" },
{ "test-query", "random_value" }
};
var request = Substitute.For<HttpRequest>();
request.Query.Returns(new QueryCollection(queryString));
var request = Substitute.For<HttpRequest>();
request.Query.Returns(new QueryCollection(queryString));
// Act
var token = _sut(request);
// Act
var token = _sut(request);
// Assert
Assert.Equal("test_value", token);
}
// Assert
Assert.Equal("test_value", token);
}
[Fact]
public void RetrieveToken_HasBoth_ReturnsHeaderToken()
{
// Arrange
var queryString = new Dictionary<string, StringValues>
[Fact]
public void RetrieveToken_HasBoth_ReturnsHeaderToken()
{
{ "access_token", "query_string_token" },
{ "test-query", "random_value" }
};
// Arrange
var queryString = new Dictionary<string, StringValues>
{
{ "access_token", "query_string_token" },
{ "test-query", "random_value" }
};
var headers = new HeaderDictionary
var headers = new HeaderDictionary
{
{ "Authorization", "Bearer header_token" },
{ "X-Test-Header", "random_value" }
};
var request = Substitute.For<HttpRequest>();
request.Headers.Returns(headers);
request.Query.Returns(new QueryCollection(queryString));
// Act
var token = _sut(request);
// Assert
Assert.Equal("header_token", token);
}
[Fact]
public void RetrieveToken_NoToken_ReturnsNull()
{
{ "Authorization", "Bearer header_token" },
{ "X-Test-Header", "random_value" }
};
// Arrange
var request = Substitute.For<HttpRequest>();
var request = Substitute.For<HttpRequest>();
request.Headers.Returns(headers);
request.Query.Returns(new QueryCollection(queryString));
// Act
var token = _sut(request);
// Act
var token = _sut(request);
// Assert
Assert.Equal("header_token", token);
}
[Fact]
public void RetrieveToken_NoToken_ReturnsNull()
{
// Arrange
var request = Substitute.For<HttpRequest>();
// Act
var token = _sut(request);
// Assert
Assert.Null(token);
// Assert
Assert.Null(token);
}
}
}

View File

@ -1,22 +1,23 @@
using Bit.Core.Models.Business;
using Xunit;
namespace Bit.Core.Test.Models.Business;
public class BillingInfoTests
namespace Bit.Core.Test.Models.Business
{
[Fact]
public void BillingInvoice_Amount_ShouldComeFrom_InvoiceTotal()
public class BillingInfoTests
{
var invoice = new Stripe.Invoice
[Fact]
public void BillingInvoice_Amount_ShouldComeFrom_InvoiceTotal()
{
AmountDue = 1000,
Total = 2000,
};
var invoice = new Stripe.Invoice
{
AmountDue = 1000,
Total = 2000,
};
var billingInvoice = new BillingInfo.BillingInvoice(invoice);
var billingInvoice = new BillingInfo.BillingInvoice(invoice);
// Should have been set from Total
Assert.Equal(20M, billingInvoice.Amount);
// Should have been set from Total
Assert.Equal(20M, billingInvoice.Amount);
}
}
}

View File

@ -1,114 +1,115 @@
using Bit.Core.Models.Business;
using Xunit;
namespace Bit.Core.Test.Models.Business;
public class TaxInfoTests
namespace Bit.Core.Test.Models.Business
{
// PH = Placeholder
[Theory]
[InlineData(null, null, null, null)]
[InlineData("", "", null, null)]
[InlineData("PH", "", null, null)]
[InlineData("", "PH", null, null)]
[InlineData("AE", "PH", null, "ae_trn")]
[InlineData("AU", "PH", null, "au_abn")]
[InlineData("BR", "PH", null, "br_cnpj")]
[InlineData("CA", "PH", "bec", "ca_qst")]
[InlineData("CA", "PH", null, "ca_bn")]
[InlineData("CL", "PH", null, "cl_tin")]
[InlineData("AT", "PH", null, "eu_vat")]
[InlineData("BE", "PH", null, "eu_vat")]
[InlineData("BG", "PH", null, "eu_vat")]
[InlineData("CY", "PH", null, "eu_vat")]
[InlineData("CZ", "PH", null, "eu_vat")]
[InlineData("DE", "PH", null, "eu_vat")]
[InlineData("DK", "PH", null, "eu_vat")]
[InlineData("EE", "PH", null, "eu_vat")]
[InlineData("ES", "PH", null, "eu_vat")]
[InlineData("FI", "PH", null, "eu_vat")]
[InlineData("FR", "PH", null, "eu_vat")]
[InlineData("GB", "PH", null, "eu_vat")]
[InlineData("GR", "PH", null, "eu_vat")]
[InlineData("HR", "PH", null, "eu_vat")]
[InlineData("HU", "PH", null, "eu_vat")]
[InlineData("IE", "PH", null, "eu_vat")]
[InlineData("IT", "PH", null, "eu_vat")]
[InlineData("LT", "PH", null, "eu_vat")]
[InlineData("LU", "PH", null, "eu_vat")]
[InlineData("LV", "PH", null, "eu_vat")]
[InlineData("MT", "PH", null, "eu_vat")]
[InlineData("NL", "PH", null, "eu_vat")]
[InlineData("PL", "PH", null, "eu_vat")]
[InlineData("PT", "PH", null, "eu_vat")]
[InlineData("RO", "PH", null, "eu_vat")]
[InlineData("SE", "PH", null, "eu_vat")]
[InlineData("SI", "PH", null, "eu_vat")]
[InlineData("SK", "PH", null, "eu_vat")]
[InlineData("HK", "PH", null, "hk_br")]
[InlineData("IN", "PH", null, "in_gst")]
[InlineData("JP", "PH", null, "jp_cn")]
[InlineData("KR", "PH", null, "kr_brn")]
[InlineData("LI", "PH", null, "li_uid")]
[InlineData("MX", "PH", null, "mx_rfc")]
[InlineData("MY", "PH", null, "my_sst")]
[InlineData("NO", "PH", null, "no_vat")]
[InlineData("NZ", "PH", null, "nz_gst")]
[InlineData("RU", "PH", null, "ru_inn")]
[InlineData("SA", "PH", null, "sa_vat")]
[InlineData("SG", "PH", null, "sg_gst")]
[InlineData("TH", "PH", null, "th_vat")]
[InlineData("TW", "PH", null, "tw_vat")]
[InlineData("US", "PH", null, "us_ein")]
[InlineData("ZA", "PH", null, "za_vat")]
[InlineData("ABCDEF", "PH", null, null)]
public void GetTaxIdType_Success(string billingAddressCountry,
string taxIdNumber,
string billingAddressState,
string expectedTaxIdType)
public class TaxInfoTests
{
var taxInfo = new TaxInfo
// PH = Placeholder
[Theory]
[InlineData(null, null, null, null)]
[InlineData("", "", null, null)]
[InlineData("PH", "", null, null)]
[InlineData("", "PH", null, null)]
[InlineData("AE", "PH", null, "ae_trn")]
[InlineData("AU", "PH", null, "au_abn")]
[InlineData("BR", "PH", null, "br_cnpj")]
[InlineData("CA", "PH", "bec", "ca_qst")]
[InlineData("CA", "PH", null, "ca_bn")]
[InlineData("CL", "PH", null, "cl_tin")]
[InlineData("AT", "PH", null, "eu_vat")]
[InlineData("BE", "PH", null, "eu_vat")]
[InlineData("BG", "PH", null, "eu_vat")]
[InlineData("CY", "PH", null, "eu_vat")]
[InlineData("CZ", "PH", null, "eu_vat")]
[InlineData("DE", "PH", null, "eu_vat")]
[InlineData("DK", "PH", null, "eu_vat")]
[InlineData("EE", "PH", null, "eu_vat")]
[InlineData("ES", "PH", null, "eu_vat")]
[InlineData("FI", "PH", null, "eu_vat")]
[InlineData("FR", "PH", null, "eu_vat")]
[InlineData("GB", "PH", null, "eu_vat")]
[InlineData("GR", "PH", null, "eu_vat")]
[InlineData("HR", "PH", null, "eu_vat")]
[InlineData("HU", "PH", null, "eu_vat")]
[InlineData("IE", "PH", null, "eu_vat")]
[InlineData("IT", "PH", null, "eu_vat")]
[InlineData("LT", "PH", null, "eu_vat")]
[InlineData("LU", "PH", null, "eu_vat")]
[InlineData("LV", "PH", null, "eu_vat")]
[InlineData("MT", "PH", null, "eu_vat")]
[InlineData("NL", "PH", null, "eu_vat")]
[InlineData("PL", "PH", null, "eu_vat")]
[InlineData("PT", "PH", null, "eu_vat")]
[InlineData("RO", "PH", null, "eu_vat")]
[InlineData("SE", "PH", null, "eu_vat")]
[InlineData("SI", "PH", null, "eu_vat")]
[InlineData("SK", "PH", null, "eu_vat")]
[InlineData("HK", "PH", null, "hk_br")]
[InlineData("IN", "PH", null, "in_gst")]
[InlineData("JP", "PH", null, "jp_cn")]
[InlineData("KR", "PH", null, "kr_brn")]
[InlineData("LI", "PH", null, "li_uid")]
[InlineData("MX", "PH", null, "mx_rfc")]
[InlineData("MY", "PH", null, "my_sst")]
[InlineData("NO", "PH", null, "no_vat")]
[InlineData("NZ", "PH", null, "nz_gst")]
[InlineData("RU", "PH", null, "ru_inn")]
[InlineData("SA", "PH", null, "sa_vat")]
[InlineData("SG", "PH", null, "sg_gst")]
[InlineData("TH", "PH", null, "th_vat")]
[InlineData("TW", "PH", null, "tw_vat")]
[InlineData("US", "PH", null, "us_ein")]
[InlineData("ZA", "PH", null, "za_vat")]
[InlineData("ABCDEF", "PH", null, null)]
public void GetTaxIdType_Success(string billingAddressCountry,
string taxIdNumber,
string billingAddressState,
string expectedTaxIdType)
{
BillingAddressCountry = billingAddressCountry,
TaxIdNumber = taxIdNumber,
BillingAddressState = billingAddressState,
};
var taxInfo = new TaxInfo
{
BillingAddressCountry = billingAddressCountry,
TaxIdNumber = taxIdNumber,
BillingAddressState = billingAddressState,
};
Assert.Equal(expectedTaxIdType, taxInfo.TaxIdType);
}
Assert.Equal(expectedTaxIdType, taxInfo.TaxIdType);
}
[Fact]
public void GetTaxIdType_CreateOnce_ReturnCacheSecondTime()
{
var taxInfo = new TaxInfo
[Fact]
public void GetTaxIdType_CreateOnce_ReturnCacheSecondTime()
{
BillingAddressCountry = "US",
TaxIdNumber = "PH",
BillingAddressState = null,
};
var taxInfo = new TaxInfo
{
BillingAddressCountry = "US",
TaxIdNumber = "PH",
BillingAddressState = null,
};
Assert.Equal("us_ein", taxInfo.TaxIdType);
Assert.Equal("us_ein", taxInfo.TaxIdType);
// Per the current spec even if the values change to something other than null it
// will return the cached version of TaxIdType.
taxInfo.BillingAddressCountry = "ZA";
// Per the current spec even if the values change to something other than null it
// will return the cached version of TaxIdType.
taxInfo.BillingAddressCountry = "ZA";
Assert.Equal("us_ein", taxInfo.TaxIdType);
}
Assert.Equal("us_ein", taxInfo.TaxIdType);
}
[Theory]
[InlineData(null, null, false)]
[InlineData("123", "US", true)]
[InlineData("123", "ZQ12", false)]
[InlineData(" ", "US", false)]
public void HasTaxId_ReturnsExpected(string taxIdNumber, string billingAddressCountry, bool expected)
{
var taxInfo = new TaxInfo
[Theory]
[InlineData(null, null, false)]
[InlineData("123", "US", true)]
[InlineData("123", "ZQ12", false)]
[InlineData(" ", "US", false)]
public void HasTaxId_ReturnsExpected(string taxIdNumber, string billingAddressCountry, bool expected)
{
TaxIdNumber = taxIdNumber,
BillingAddressCountry = billingAddressCountry,
};
var taxInfo = new TaxInfo
{
TaxIdNumber = taxIdNumber,
BillingAddressCountry = billingAddressCountry,
};
Assert.Equal(expected, taxInfo.HasTaxId);
Assert.Equal(expected, taxInfo.HasTaxId);
}
}
}

View File

@ -4,29 +4,30 @@ using Bit.Core.Models.Business.Tokenables;
using Bit.Core.Tokens;
using Xunit;
namespace Bit.Core.Test.Models.Business.Tokenables;
public class EmergencyAccessInviteTokenableTests
namespace Bit.Core.Test.Models.Business.Tokenables
{
[Theory, AutoData]
public void SerializationSetsCorrectDateTime(EmergencyAccess emergencyAccess)
public class EmergencyAccessInviteTokenableTests
{
var token = new EmergencyAccessInviteTokenable(emergencyAccess, 2);
Assert.Equal(Tokenable.FromToken<EmergencyAccessInviteTokenable>(token.ToToken().ToString()).ExpirationDate,
token.ExpirationDate,
TimeSpan.FromMilliseconds(10));
}
[Fact]
public void IsInvalidIfIdentifierIsWrong()
{
var token = new EmergencyAccessInviteTokenable(DateTime.MaxValue)
[Theory, AutoData]
public void SerializationSetsCorrectDateTime(EmergencyAccess emergencyAccess)
{
Email = "email",
Id = Guid.NewGuid(),
Identifier = "not correct"
};
var token = new EmergencyAccessInviteTokenable(emergencyAccess, 2);
Assert.Equal(Tokenable.FromToken<EmergencyAccessInviteTokenable>(token.ToToken().ToString()).ExpirationDate,
token.ExpirationDate,
TimeSpan.FromMilliseconds(10));
}
Assert.False(token.Valid);
[Fact]
public void IsInvalidIfIdentifierIsWrong()
{
var token = new EmergencyAccessInviteTokenable(DateTime.MaxValue)
{
Email = "email",
Id = Guid.NewGuid(),
Identifier = "not correct"
};
Assert.False(token.Valid);
}
}
}

View File

@ -5,83 +5,84 @@ using Bit.Core.Tokens;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Core.Test.Models.Business.Tokenables;
public class HCaptchaTokenableTests
namespace Bit.Core.Test.Models.Business.Tokenables
{
[Fact]
public void CanHandleNullUser()
public class HCaptchaTokenableTests
{
var token = new HCaptchaTokenable(null);
Assert.Equal(default, token.Id);
Assert.Equal(default, token.Email);
}
[Fact]
public void TokenWithNullUserIsInvalid()
{
var token = new HCaptchaTokenable(null)
[Fact]
public void CanHandleNullUser()
{
ExpirationDate = DateTime.UtcNow + TimeSpan.FromDays(1)
};
var token = new HCaptchaTokenable(null);
Assert.False(token.Valid);
}
Assert.Equal(default, token.Id);
Assert.Equal(default, token.Email);
}
[Theory, BitAutoData]
public void TokenValidityCheckNullUserIdIsInvalid(User user)
{
var token = new HCaptchaTokenable(user)
[Fact]
public void TokenWithNullUserIsInvalid()
{
ExpirationDate = DateTime.UtcNow + TimeSpan.FromDays(1)
};
var token = new HCaptchaTokenable(null)
{
ExpirationDate = DateTime.UtcNow + TimeSpan.FromDays(1)
};
Assert.False(token.TokenIsValid(null));
}
Assert.False(token.Valid);
}
[Theory, AutoData]
public void CanUpdateExpirationToNonStandard(User user)
{
var token = new HCaptchaTokenable(user)
[Theory, BitAutoData]
public void TokenValidityCheckNullUserIdIsInvalid(User user)
{
ExpirationDate = DateTime.MinValue
};
var token = new HCaptchaTokenable(user)
{
ExpirationDate = DateTime.UtcNow + TimeSpan.FromDays(1)
};
Assert.Equal(DateTime.MinValue, token.ExpirationDate, TimeSpan.FromMilliseconds(10));
}
Assert.False(token.TokenIsValid(null));
}
[Theory, AutoData]
public void SetsDataFromUser(User user)
{
var token = new HCaptchaTokenable(user);
Assert.Equal(user.Id, token.Id);
Assert.Equal(user.Email, token.Email);
}
[Theory, AutoData]
public void SerializationSetsCorrectDateTime(User user)
{
var expectedDateTime = DateTime.UtcNow.AddHours(-5);
var token = new HCaptchaTokenable(user)
[Theory, AutoData]
public void CanUpdateExpirationToNonStandard(User user)
{
ExpirationDate = expectedDateTime
};
var token = new HCaptchaTokenable(user)
{
ExpirationDate = DateTime.MinValue
};
var result = Tokenable.FromToken<HCaptchaTokenable>(token.ToToken());
Assert.Equal(DateTime.MinValue, token.ExpirationDate, TimeSpan.FromMilliseconds(10));
}
Assert.Equal(expectedDateTime, result.ExpirationDate, TimeSpan.FromMilliseconds(10));
}
[Theory, AutoData]
public void IsInvalidIfIdentifierIsWrong(User user)
{
var token = new HCaptchaTokenable(user)
[Theory, AutoData]
public void SetsDataFromUser(User user)
{
Identifier = "not correct"
};
var token = new HCaptchaTokenable(user);
Assert.False(token.Valid);
Assert.Equal(user.Id, token.Id);
Assert.Equal(user.Email, token.Email);
}
[Theory, AutoData]
public void SerializationSetsCorrectDateTime(User user)
{
var expectedDateTime = DateTime.UtcNow.AddHours(-5);
var token = new HCaptchaTokenable(user)
{
ExpirationDate = expectedDateTime
};
var result = Tokenable.FromToken<HCaptchaTokenable>(token.ToToken());
Assert.Equal(expectedDateTime, result.ExpirationDate, TimeSpan.FromMilliseconds(10));
}
[Theory, AutoData]
public void IsInvalidIfIdentifierIsWrong(User user)
{
var token = new HCaptchaTokenable(user)
{
Identifier = "not correct"
};
Assert.False(token.Valid);
}
}
}

View File

@ -4,152 +4,153 @@ using Bit.Core.Models.Business.Tokenables;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Core.Test.Models.Business.Tokenables;
public class OrganizationSponsorshipOfferTokenableTests
namespace Bit.Core.Test.Models.Business.Tokenables
{
public static IEnumerable<object[]> PlanSponsorshipTypes() => Enum.GetValues<PlanSponsorshipType>().Select(x => new object[] { x });
[Fact]
public void IsInvalidIfIdentifierIsWrong()
public class OrganizationSponsorshipOfferTokenableTests
{
var token = new OrganizationSponsorshipOfferTokenable()
public static IEnumerable<object[]> PlanSponsorshipTypes() => Enum.GetValues<PlanSponsorshipType>().Select(x => new object[] { x });
[Fact]
public void IsInvalidIfIdentifierIsWrong()
{
Email = "email",
Id = Guid.NewGuid(),
Identifier = "not correct",
SponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
};
var token = new OrganizationSponsorshipOfferTokenable()
{
Email = "email",
Id = Guid.NewGuid(),
Identifier = "not correct",
SponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
};
Assert.False(token.Valid);
}
Assert.False(token.Valid);
}
[Fact]
public void IsInvalidIfIdIsDefault()
{
var token = new OrganizationSponsorshipOfferTokenable()
[Fact]
public void IsInvalidIfIdIsDefault()
{
Email = "email",
Id = default,
SponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
};
var token = new OrganizationSponsorshipOfferTokenable()
{
Email = "email",
Id = default,
SponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
};
Assert.False(token.Valid);
}
Assert.False(token.Valid);
}
[Fact]
public void IsInvalidIfEmailIsEmpty()
{
var token = new OrganizationSponsorshipOfferTokenable()
[Fact]
public void IsInvalidIfEmailIsEmpty()
{
Email = "",
Id = Guid.NewGuid(),
SponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
};
var token = new OrganizationSponsorshipOfferTokenable()
{
Email = "",
Id = Guid.NewGuid(),
SponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
};
Assert.False(token.Valid);
}
Assert.False(token.Valid);
}
[Theory, BitAutoData]
public void IsValid_Success(OrganizationSponsorship sponsorship)
{
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
[Theory, BitAutoData]
public void IsValid_Success(OrganizationSponsorship sponsorship)
{
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
Assert.True(token.IsValid(sponsorship, sponsorship.OfferedToEmail));
}
Assert.True(token.IsValid(sponsorship, sponsorship.OfferedToEmail));
}
[Theory, BitAutoData]
public void IsValid_RequiresNonNullSponsorship(OrganizationSponsorship sponsorship)
{
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
[Theory, BitAutoData]
public void IsValid_RequiresNonNullSponsorship(OrganizationSponsorship sponsorship)
{
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
Assert.False(token.IsValid(null, sponsorship.OfferedToEmail));
}
Assert.False(token.IsValid(null, sponsorship.OfferedToEmail));
}
[Theory, BitAutoData]
public void IsValid_RequiresCurrentEmailToBeSameAsOfferedToEmail(OrganizationSponsorship sponsorship, string currentEmail)
{
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
[Theory, BitAutoData]
public void IsValid_RequiresCurrentEmailToBeSameAsOfferedToEmail(OrganizationSponsorship sponsorship, string currentEmail)
{
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
Assert.False(token.IsValid(sponsorship, currentEmail));
}
Assert.False(token.IsValid(sponsorship, currentEmail));
}
[Theory, BitAutoData]
public void IsValid_RequiresSameSponsorshipId(OrganizationSponsorship sponsorship1, OrganizationSponsorship sponsorship2)
{
sponsorship1.Id = sponsorship2.Id;
[Theory, BitAutoData]
public void IsValid_RequiresSameSponsorshipId(OrganizationSponsorship sponsorship1, OrganizationSponsorship sponsorship2)
{
sponsorship1.Id = sponsorship2.Id;
var token = new OrganizationSponsorshipOfferTokenable(sponsorship1);
var token = new OrganizationSponsorshipOfferTokenable(sponsorship1);
Assert.False(token.IsValid(sponsorship2, sponsorship1.OfferedToEmail));
}
Assert.False(token.IsValid(sponsorship2, sponsorship1.OfferedToEmail));
}
[Theory, BitAutoData]
public void IsValid_RequiresSameEmail(OrganizationSponsorship sponsorship1, OrganizationSponsorship sponsorship2)
{
sponsorship1.OfferedToEmail = sponsorship2.OfferedToEmail;
[Theory, BitAutoData]
public void IsValid_RequiresSameEmail(OrganizationSponsorship sponsorship1, OrganizationSponsorship sponsorship2)
{
sponsorship1.OfferedToEmail = sponsorship2.OfferedToEmail;
var token = new OrganizationSponsorshipOfferTokenable(sponsorship1);
var token = new OrganizationSponsorshipOfferTokenable(sponsorship1);
Assert.False(token.IsValid(sponsorship2, sponsorship1.OfferedToEmail));
}
Assert.False(token.IsValid(sponsorship2, sponsorship1.OfferedToEmail));
}
[Theory, BitAutoData]
public void Constructor_GrabsIdFromSponsorship(OrganizationSponsorship sponsorship)
{
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
[Theory, BitAutoData]
public void Constructor_GrabsIdFromSponsorship(OrganizationSponsorship sponsorship)
{
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
Assert.Equal(sponsorship.Id, token.Id);
}
Assert.Equal(sponsorship.Id, token.Id);
}
[Theory, BitAutoData]
public void Constructor_GrabsEmailFromSponsorshipOfferedToEmail(OrganizationSponsorship sponsorship)
{
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
[Theory, BitAutoData]
public void Constructor_GrabsEmailFromSponsorshipOfferedToEmail(OrganizationSponsorship sponsorship)
{
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
Assert.Equal(sponsorship.OfferedToEmail, token.Email);
}
Assert.Equal(sponsorship.OfferedToEmail, token.Email);
}
[Theory, BitMemberAutoData(nameof(PlanSponsorshipTypes))]
public void Constructor_GrabsSponsorshipType(PlanSponsorshipType planSponsorshipType,
OrganizationSponsorship sponsorship)
{
sponsorship.PlanSponsorshipType = planSponsorshipType;
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
[Theory, BitMemberAutoData(nameof(PlanSponsorshipTypes))]
public void Constructor_GrabsSponsorshipType(PlanSponsorshipType planSponsorshipType,
OrganizationSponsorship sponsorship)
{
sponsorship.PlanSponsorshipType = planSponsorshipType;
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
Assert.Equal(sponsorship.PlanSponsorshipType, token.SponsorshipType);
}
Assert.Equal(sponsorship.PlanSponsorshipType, token.SponsorshipType);
}
[Theory, BitAutoData]
public void Constructor_DefaultId_Throws(OrganizationSponsorship sponsorship)
{
sponsorship.Id = default;
[Theory, BitAutoData]
public void Constructor_DefaultId_Throws(OrganizationSponsorship sponsorship)
{
sponsorship.Id = default;
Assert.Throws<ArgumentException>(() => new OrganizationSponsorshipOfferTokenable(sponsorship));
}
Assert.Throws<ArgumentException>(() => new OrganizationSponsorshipOfferTokenable(sponsorship));
}
[Theory, BitAutoData]
public void Constructor_NoOfferedToEmail_Throws(OrganizationSponsorship sponsorship)
{
sponsorship.OfferedToEmail = null;
[Theory, BitAutoData]
public void Constructor_NoOfferedToEmail_Throws(OrganizationSponsorship sponsorship)
{
sponsorship.OfferedToEmail = null;
Assert.Throws<ArgumentException>(() => new OrganizationSponsorshipOfferTokenable(sponsorship));
}
Assert.Throws<ArgumentException>(() => new OrganizationSponsorshipOfferTokenable(sponsorship));
}
[Theory, BitAutoData]
public void Constructor_EmptyOfferedToEmail_Throws(OrganizationSponsorship sponsorship)
{
sponsorship.OfferedToEmail = "";
[Theory, BitAutoData]
public void Constructor_EmptyOfferedToEmail_Throws(OrganizationSponsorship sponsorship)
{
sponsorship.OfferedToEmail = "";
Assert.Throws<ArgumentException>(() => new OrganizationSponsorshipOfferTokenable(sponsorship));
}
Assert.Throws<ArgumentException>(() => new OrganizationSponsorshipOfferTokenable(sponsorship));
}
[Theory, BitAutoData]
public void Constructor_NoPlanSponsorshipType_Throws(OrganizationSponsorship sponsorship)
{
sponsorship.PlanSponsorshipType = null;
[Theory, BitAutoData]
public void Constructor_NoPlanSponsorshipType_Throws(OrganizationSponsorship sponsorship)
{
sponsorship.PlanSponsorshipType = null;
Assert.Throws<ArgumentException>(() => new OrganizationSponsorshipOfferTokenable(sponsorship));
Assert.Throws<ArgumentException>(() => new OrganizationSponsorshipOfferTokenable(sponsorship));
}
}
}

View File

@ -5,84 +5,85 @@ using Bit.Core.Tokens;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Core.Test.Models.Business.Tokenables;
public class SsoTokenableTests
namespace Bit.Core.Test.Models.Business.Tokenables
{
[Fact]
public void CanHandleNullOrganization()
public class SsoTokenableTests
{
var token = new SsoTokenable(null, default);
Assert.Equal(default, token.OrganizationId);
Assert.Equal(default, token.DomainHint);
}
[Fact]
public void TokenWithNullOrganizationIsInvalid()
{
var token = new SsoTokenable(null, 500)
[Fact]
public void CanHandleNullOrganization()
{
ExpirationDate = DateTime.UtcNow + TimeSpan.FromDays(1)
};
var token = new SsoTokenable(null, default);
Assert.False(token.Valid);
}
Assert.Equal(default, token.OrganizationId);
Assert.Equal(default, token.DomainHint);
}
[Theory, BitAutoData]
public void TokenValidityCheckNullOrganizationIsInvalid(Organization organization)
{
var token = new SsoTokenable(organization, 500)
[Fact]
public void TokenWithNullOrganizationIsInvalid()
{
ExpirationDate = DateTime.UtcNow + TimeSpan.FromDays(1)
};
var token = new SsoTokenable(null, 500)
{
ExpirationDate = DateTime.UtcNow + TimeSpan.FromDays(1)
};
Assert.False(token.TokenIsValid(null));
}
Assert.False(token.Valid);
}
[Theory, AutoData]
public void SetsDataFromOrganization(Organization organization)
{
var token = new SsoTokenable(organization, default);
Assert.Equal(organization.Id, token.OrganizationId);
Assert.Equal(organization.Identifier, token.DomainHint);
}
[Fact]
public void SetsExpirationFromConstructor()
{
var expectedDateTime = DateTime.UtcNow.AddSeconds(500);
var token = new SsoTokenable(null, 500);
Assert.Equal(expectedDateTime, token.ExpirationDate, TimeSpan.FromMilliseconds(10));
}
[Theory, AutoData]
public void SerializationSetsCorrectDateTime(Organization organization)
{
var expectedDateTime = DateTime.UtcNow.AddHours(-5);
var token = new SsoTokenable(organization, default)
[Theory, BitAutoData]
public void TokenValidityCheckNullOrganizationIsInvalid(Organization organization)
{
ExpirationDate = expectedDateTime
};
var token = new SsoTokenable(organization, 500)
{
ExpirationDate = DateTime.UtcNow + TimeSpan.FromDays(1)
};
var result = Tokenable.FromToken<HCaptchaTokenable>(token.ToToken());
Assert.False(token.TokenIsValid(null));
}
Assert.Equal(expectedDateTime, result.ExpirationDate, TimeSpan.FromMilliseconds(10));
}
[Theory, AutoData]
public void TokenIsValidFailsWhenExpired(Organization organization)
{
var expectedDateTime = DateTime.UtcNow.AddHours(-5);
var token = new SsoTokenable(organization, default)
[Theory, AutoData]
public void SetsDataFromOrganization(Organization organization)
{
ExpirationDate = expectedDateTime
};
var token = new SsoTokenable(organization, default);
var result = token.TokenIsValid(organization);
Assert.Equal(organization.Id, token.OrganizationId);
Assert.Equal(organization.Identifier, token.DomainHint);
}
Assert.False(result);
[Fact]
public void SetsExpirationFromConstructor()
{
var expectedDateTime = DateTime.UtcNow.AddSeconds(500);
var token = new SsoTokenable(null, 500);
Assert.Equal(expectedDateTime, token.ExpirationDate, TimeSpan.FromMilliseconds(10));
}
[Theory, AutoData]
public void SerializationSetsCorrectDateTime(Organization organization)
{
var expectedDateTime = DateTime.UtcNow.AddHours(-5);
var token = new SsoTokenable(organization, default)
{
ExpirationDate = expectedDateTime
};
var result = Tokenable.FromToken<HCaptchaTokenable>(token.ToToken());
Assert.Equal(expectedDateTime, result.ExpirationDate, TimeSpan.FromMilliseconds(10));
}
[Theory, AutoData]
public void TokenIsValidFailsWhenExpired(Organization organization)
{
var expectedDateTime = DateTime.UtcNow.AddHours(-5);
var token = new SsoTokenable(organization, default)
{
ExpirationDate = expectedDateTime
};
var result = token.TokenIsValid(organization);
Assert.False(result);
}
}
}

View File

@ -3,15 +3,16 @@ using Bit.Core.Entities;
using Bit.Core.Test.AutoFixture.CipherFixtures;
using Xunit;
namespace Bit.Core.Test.Models;
public class CipherTests
namespace Bit.Core.Test.Models
{
[Theory]
[InlineUserCipherAutoData]
[InlineOrganizationCipherAutoData]
public void Clone_CreatesExactCopy(Cipher cipher)
public class CipherTests
{
Assert.Equal(JsonSerializer.Serialize(cipher), JsonSerializer.Serialize(cipher.Clone()));
[Theory]
[InlineUserCipherAutoData]
[InlineOrganizationCipherAutoData]
public void Clone_CreatesExactCopy(Cipher cipher)
{
Assert.Equal(JsonSerializer.Serialize(cipher), JsonSerializer.Serialize(cipher.Clone()));
}
}
}

View File

@ -3,25 +3,26 @@ using Bit.Core.Models.Data;
using Bit.Test.Common.Helpers;
using Xunit;
namespace Bit.Core.Test.Models.Data;
public class SendFileDataTests
namespace Bit.Core.Test.Models.Data
{
[Fact]
public void Serialize_Success()
public class SendFileDataTests
{
var sut = new SendFileData
[Fact]
public void Serialize_Success()
{
Id = "test",
Size = 100,
FileName = "thing.pdf",
Validated = true,
};
var sut = new SendFileData
{
Id = "test",
Size = 100,
FileName = "thing.pdf",
Validated = true,
};
var json = JsonSerializer.Serialize(sut);
var document = JsonDocument.Parse(json);
var root = document.RootElement;
AssertHelper.AssertJsonProperty(root, "Size", JsonValueKind.String);
Assert.False(root.TryGetProperty("SizeString", out _));
var json = JsonSerializer.Serialize(sut);
var document = JsonDocument.Parse(json);
var root = document.RootElement;
AssertHelper.AssertJsonProperty(root, "Size", JsonValueKind.String);
Assert.False(root.TryGetProperty("SizeString", out _));
}
}
}

View File

@ -3,58 +3,59 @@ using Bit.Core.Models.Data;
using Bit.Core.Utilities;
using Xunit;
namespace Bit.Core.Test.Models;
public class PermissionsTests
namespace Bit.Core.Test.Models
{
private static readonly string _exampleSerializedPermissions = string.Concat(
"{",
"\"accessEventLogs\": false,",
"\"accessImportExport\": false,",
"\"accessReports\": false,",
"\"manageAllCollections\": true,", // exists for backwards compatibility
"\"createNewCollections\": true,",
"\"editAnyCollection\": true,",
"\"deleteAnyCollection\": true,",
"\"manageAssignedCollections\": false,", // exists for backwards compatibility
"\"editAssignedCollections\": false,",
"\"deleteAssignedCollections\": false,",
"\"manageGroups\": false,",
"\"managePolicies\": false,",
"\"manageSso\": false,",
"\"manageUsers\": false,",
"\"manageResetPassword\": false,",
"\"manageScim\": false",
"}");
[Fact]
public void Serialization_Success()
public class PermissionsTests
{
var permissions = new Permissions
private static readonly string _exampleSerializedPermissions = string.Concat(
"{",
"\"accessEventLogs\": false,",
"\"accessImportExport\": false,",
"\"accessReports\": false,",
"\"manageAllCollections\": true,", // exists for backwards compatibility
"\"createNewCollections\": true,",
"\"editAnyCollection\": true,",
"\"deleteAnyCollection\": true,",
"\"manageAssignedCollections\": false,", // exists for backwards compatibility
"\"editAssignedCollections\": false,",
"\"deleteAssignedCollections\": false,",
"\"manageGroups\": false,",
"\"managePolicies\": false,",
"\"manageSso\": false,",
"\"manageUsers\": false,",
"\"manageResetPassword\": false,",
"\"manageScim\": false",
"}");
[Fact]
public void Serialization_Success()
{
AccessEventLogs = false,
AccessImportExport = false,
AccessReports = false,
CreateNewCollections = true,
EditAnyCollection = true,
DeleteAnyCollection = true,
EditAssignedCollections = false,
DeleteAssignedCollections = false,
ManageGroups = false,
ManagePolicies = false,
ManageSso = false,
ManageUsers = false,
ManageResetPassword = false,
ManageScim = false,
};
var permissions = new Permissions
{
AccessEventLogs = false,
AccessImportExport = false,
AccessReports = false,
CreateNewCollections = true,
EditAnyCollection = true,
DeleteAnyCollection = true,
EditAssignedCollections = false,
DeleteAssignedCollections = false,
ManageGroups = false,
ManagePolicies = false,
ManageSso = false,
ManageUsers = false,
ManageResetPassword = false,
ManageScim = false,
};
// minify expected json
var expected = JsonSerializer.Serialize(permissions, JsonHelpers.CamelCase);
// minify expected json
var expected = JsonSerializer.Serialize(permissions, JsonHelpers.CamelCase);
var actual = JsonSerializer.Serialize(
JsonHelpers.DeserializeOrNew<Permissions>(_exampleSerializedPermissions, JsonHelpers.CamelCase),
JsonHelpers.CamelCase);
var actual = JsonSerializer.Serialize(
JsonHelpers.DeserializeOrNew<Permissions>(_exampleSerializedPermissions, JsonHelpers.CamelCase),
JsonHelpers.CamelCase);
Assert.Equal(expected, actual);
Assert.Equal(expected, actual);
}
}
}

View File

@ -7,93 +7,94 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationApiKeys;
[SutProviderCustomize]
public class GetOrganizationApiKeyCommandTests
namespace Bit.Core.Test.OrganizationFeatures.OrganizationApiKeys
{
[Theory]
[BitAutoData]
public async Task GetOrganizationApiKey_HasOne_Returns(SutProvider<GetOrganizationApiKeyCommand> sutProvider,
Guid id, Guid organizationId, OrganizationApiKeyType keyType)
[SutProviderCustomize]
public class GetOrganizationApiKeyCommandTests
{
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
.GetManyByOrganizationIdTypeAsync(organizationId, keyType)
.Returns(new List<OrganizationApiKey>
{
new OrganizationApiKey
[Theory]
[BitAutoData]
public async Task GetOrganizationApiKey_HasOne_Returns(SutProvider<GetOrganizationApiKeyCommand> sutProvider,
Guid id, Guid organizationId, OrganizationApiKeyType keyType)
{
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
.GetManyByOrganizationIdTypeAsync(organizationId, keyType)
.Returns(new List<OrganizationApiKey>
{
Id = id,
OrganizationId = organizationId,
ApiKey = "test",
Type = keyType,
RevisionDate = DateTime.Now.AddDays(-1),
},
});
new OrganizationApiKey
{
Id = id,
OrganizationId = organizationId,
ApiKey = "test",
Type = keyType,
RevisionDate = DateTime.Now.AddDays(-1),
},
});
var apiKey = await sutProvider.Sut.GetOrganizationApiKeyAsync(organizationId, keyType);
Assert.NotNull(apiKey);
Assert.Equal(id, apiKey.Id);
}
var apiKey = await sutProvider.Sut.GetOrganizationApiKeyAsync(organizationId, keyType);
Assert.NotNull(apiKey);
Assert.Equal(id, apiKey.Id);
}
[Theory]
[BitAutoData]
public async Task GetOrganizationApiKey_HasTwo_Throws(SutProvider<GetOrganizationApiKeyCommand> sutProvider,
Guid organizationId, OrganizationApiKeyType keyType)
{
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
.GetManyByOrganizationIdTypeAsync(organizationId, keyType)
.Returns(new List<OrganizationApiKey>
{
new OrganizationApiKey
[Theory]
[BitAutoData]
public async Task GetOrganizationApiKey_HasTwo_Throws(SutProvider<GetOrganizationApiKeyCommand> sutProvider,
Guid organizationId, OrganizationApiKeyType keyType)
{
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
.GetManyByOrganizationIdTypeAsync(organizationId, keyType)
.Returns(new List<OrganizationApiKey>
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
ApiKey = "test",
Type = keyType,
RevisionDate = DateTime.Now.AddDays(-1),
},
new OrganizationApiKey
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
ApiKey = "test_other",
Type = keyType,
RevisionDate = DateTime.Now.AddDays(-1),
},
});
new OrganizationApiKey
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
ApiKey = "test",
Type = keyType,
RevisionDate = DateTime.Now.AddDays(-1),
},
new OrganizationApiKey
{
Id = Guid.NewGuid(),
OrganizationId = organizationId,
ApiKey = "test_other",
Type = keyType,
RevisionDate = DateTime.Now.AddDays(-1),
},
});
await Assert.ThrowsAsync<InvalidOperationException>(
async () => await sutProvider.Sut.GetOrganizationApiKeyAsync(organizationId, keyType));
}
await Assert.ThrowsAsync<InvalidOperationException>(
async () => await sutProvider.Sut.GetOrganizationApiKeyAsync(organizationId, keyType));
}
[Theory]
[BitAutoData]
public async Task GetOrganizationApiKey_HasNone_CreatesAndReturns(SutProvider<GetOrganizationApiKeyCommand> sutProvider,
Guid organizationId, OrganizationApiKeyType keyType)
{
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
.GetManyByOrganizationIdTypeAsync(organizationId, keyType)
.Returns(Enumerable.Empty<OrganizationApiKey>());
[Theory]
[BitAutoData]
public async Task GetOrganizationApiKey_HasNone_CreatesAndReturns(SutProvider<GetOrganizationApiKeyCommand> sutProvider,
Guid organizationId, OrganizationApiKeyType keyType)
{
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
.GetManyByOrganizationIdTypeAsync(organizationId, keyType)
.Returns(Enumerable.Empty<OrganizationApiKey>());
var apiKey = await sutProvider.Sut.GetOrganizationApiKeyAsync(organizationId, keyType);
var apiKey = await sutProvider.Sut.GetOrganizationApiKeyAsync(organizationId, keyType);
Assert.NotNull(apiKey);
Assert.Equal(organizationId, apiKey.OrganizationId);
Assert.Equal(keyType, apiKey.Type);
await sutProvider.GetDependency<IOrganizationApiKeyRepository>()
.Received(1)
.CreateAsync(Arg.Any<OrganizationApiKey>());
}
Assert.NotNull(apiKey);
Assert.Equal(organizationId, apiKey.OrganizationId);
Assert.Equal(keyType, apiKey.Type);
await sutProvider.GetDependency<IOrganizationApiKeyRepository>()
.Received(1)
.CreateAsync(Arg.Any<OrganizationApiKey>());
}
[Theory]
[BitAutoData]
public async Task GetOrganizationApiKey_BadType_Throws(SutProvider<GetOrganizationApiKeyCommand> sutProvider,
Guid organizationId, OrganizationApiKeyType keyType)
{
keyType = (OrganizationApiKeyType)byte.MaxValue;
[Theory]
[BitAutoData]
public async Task GetOrganizationApiKey_BadType_Throws(SutProvider<GetOrganizationApiKeyCommand> sutProvider,
Guid organizationId, OrganizationApiKeyType keyType)
{
keyType = (OrganizationApiKeyType)byte.MaxValue;
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(
async () => await sutProvider.Sut.GetOrganizationApiKeyAsync(organizationId, keyType));
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(
async () => await sutProvider.Sut.GetOrganizationApiKeyAsync(organizationId, keyType));
}
}
}

View File

@ -5,18 +5,19 @@ using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationApiKeys;
[SutProviderCustomize]
public class RotateOrganizationApiKeyCommandTests
namespace Bit.Core.Test.OrganizationFeatures.OrganizationApiKeys
{
[Theory, BitAutoData]
public async Task RotateApiKeyAsync_RotatesKey(SutProvider<RotateOrganizationApiKeyCommand> sutProvider,
OrganizationApiKey organizationApiKey)
[SutProviderCustomize]
public class RotateOrganizationApiKeyCommandTests
{
var existingKey = organizationApiKey.ApiKey;
organizationApiKey = await sutProvider.Sut.RotateApiKeyAsync(organizationApiKey);
Assert.NotEqual(existingKey, organizationApiKey.ApiKey);
AssertHelper.AssertRecent(organizationApiKey.RevisionDate);
[Theory, BitAutoData]
public async Task RotateApiKeyAsync_RotatesKey(SutProvider<RotateOrganizationApiKeyCommand> sutProvider,
OrganizationApiKey organizationApiKey)
{
var existingKey = organizationApiKey.ApiKey;
organizationApiKey = await sutProvider.Sut.RotateApiKeyAsync(organizationApiKey);
Assert.NotEqual(existingKey, organizationApiKey.ApiKey);
AssertHelper.AssertRecent(organizationApiKey.RevisionDate);
}
}
}

View File

@ -8,19 +8,20 @@ using Bit.Test.Common.Helpers;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationConnections;
[SutProviderCustomize]
public class CreateOrganizationConnectionCommandTests
namespace Bit.Core.Test.OrganizationFeatures.OrganizationConnections
{
[Theory]
[BitAutoData]
public async Task CreateAsync_CallsCreate(OrganizationConnectionData<BillingSyncConfig> data,
SutProvider<CreateOrganizationConnectionCommand> sutProvider)
[SutProviderCustomize]
public class CreateOrganizationConnectionCommandTests
{
await sutProvider.Sut.CreateAsync(data);
[Theory]
[BitAutoData]
public async Task CreateAsync_CallsCreate(OrganizationConnectionData<BillingSyncConfig> data,
SutProvider<CreateOrganizationConnectionCommand> sutProvider)
{
await sutProvider.Sut.CreateAsync(data);
await sutProvider.GetDependency<IOrganizationConnectionRepository>().Received(1)
.CreateAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data.ToEntity())));
await sutProvider.GetDependency<IOrganizationConnectionRepository>().Received(1)
.CreateAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data.ToEntity())));
}
}
}

View File

@ -6,19 +6,20 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationConnections;
[SutProviderCustomize]
public class DeleteOrganizationConnectionCommandTests
namespace Bit.Core.Test.OrganizationFeatures.OrganizationConnections
{
[Theory]
[BitAutoData]
public async Task DeleteAsync_CallsDelete(OrganizationConnection connection,
SutProvider<DeleteOrganizationConnectionCommand> sutProvider)
[SutProviderCustomize]
public class DeleteOrganizationConnectionCommandTests
{
await sutProvider.Sut.DeleteAsync(connection);
[Theory]
[BitAutoData]
public async Task DeleteAsync_CallsDelete(OrganizationConnection connection,
SutProvider<DeleteOrganizationConnectionCommand> sutProvider)
{
await sutProvider.Sut.DeleteAsync(connection);
await sutProvider.GetDependency<IOrganizationConnectionRepository>().Received(1)
.DeleteAsync(connection);
await sutProvider.GetDependency<IOrganizationConnectionRepository>().Received(1)
.DeleteAsync(connection);
}
}
}

View File

@ -10,49 +10,50 @@ using Bit.Test.Common.Helpers;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationConnections;
[SutProviderCustomize]
public class UpdateOrganizationConnectionCommandTests
namespace Bit.Core.Test.OrganizationFeatures.OrganizationConnections
{
[Theory]
[BitAutoData]
public async Task UpdateAsync_NoId_Fails(OrganizationConnectionData<BillingSyncConfig> data,
SutProvider<UpdateOrganizationConnectionCommand> sutProvider)
[SutProviderCustomize]
public class UpdateOrganizationConnectionCommandTests
{
data.Id = null;
[Theory]
[BitAutoData]
public async Task UpdateAsync_NoId_Fails(OrganizationConnectionData<BillingSyncConfig> data,
SutProvider<UpdateOrganizationConnectionCommand> sutProvider)
{
data.Id = null;
var exception = await Assert.ThrowsAsync<Exception>(() => sutProvider.Sut.UpdateAsync(data));
var exception = await Assert.ThrowsAsync<Exception>(() => sutProvider.Sut.UpdateAsync(data));
Assert.Contains("Cannot update connection, Connection does not exist.", exception.Message);
await sutProvider.GetDependency<IOrganizationConnectionRepository>().DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
}
Assert.Contains("Cannot update connection, Connection does not exist.", exception.Message);
await sutProvider.GetDependency<IOrganizationConnectionRepository>().DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
}
[Theory]
[BitAutoData]
public async Task UpdateAsync_ConnectionDoesNotExist_ThrowsNotFound(
OrganizationConnectionData<BillingSyncConfig> data,
SutProvider<UpdateOrganizationConnectionCommand> sutProvider)
{
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data));
[Theory]
[BitAutoData]
public async Task UpdateAsync_ConnectionDoesNotExist_ThrowsNotFound(
OrganizationConnectionData<BillingSyncConfig> data,
SutProvider<UpdateOrganizationConnectionCommand> sutProvider)
{
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data));
await sutProvider.GetDependency<IOrganizationConnectionRepository>().DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
}
await sutProvider.GetDependency<IOrganizationConnectionRepository>().DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
}
[Theory]
[BitAutoData]
public async Task UpdateAsync_CallsUpsert(OrganizationConnectionData<BillingSyncConfig> data,
OrganizationConnection existing,
SutProvider<UpdateOrganizationConnectionCommand> sutProvider)
{
data.Id = existing.Id;
[Theory]
[BitAutoData]
public async Task UpdateAsync_CallsUpsert(OrganizationConnectionData<BillingSyncConfig> data,
OrganizationConnection existing,
SutProvider<UpdateOrganizationConnectionCommand> sutProvider)
{
data.Id = existing.Id;
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByIdAsync(data.Id.Value).Returns(existing);
await sutProvider.Sut.UpdateAsync(data);
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByIdAsync(data.Id.Value).Returns(existing);
await sutProvider.Sut.UpdateAsync(data);
await sutProvider.GetDependency<IOrganizationConnectionRepository>().Received(1)
.UpsertAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data.ToEntity())));
await sutProvider.GetDependency<IOrganizationConnectionRepository>().Received(1)
.UpsertAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data.ToEntity())));
}
}
}

View File

@ -4,70 +4,71 @@ using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using NSubstitute;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise;
public abstract class CancelSponsorshipCommandTestsBase : FamiliesForEnterpriseTestsBase
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise
{
protected async Task AssertRemovedSponsoredPaymentAsync<T>(Organization sponsoredOrg,
OrganizationSponsorship sponsorship, SutProvider<T> sutProvider)
public abstract class CancelSponsorshipCommandTestsBase : FamiliesForEnterpriseTestsBase
{
await sutProvider.GetDependency<IPaymentService>().Received(1)
.RemoveOrganizationSponsorshipAsync(sponsoredOrg, sponsorship);
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).UpsertAsync(sponsoredOrg);
if (sponsorship != null)
protected async Task AssertRemovedSponsoredPaymentAsync<T>(Organization sponsoredOrg,
OrganizationSponsorship sponsorship, SutProvider<T> sutProvider)
{
await sutProvider.GetDependency<IMailService>().Received(1)
.SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(sponsoredOrg.BillingEmailAddress(), sponsorship.ValidUntil.GetValueOrDefault());
await sutProvider.GetDependency<IPaymentService>().Received(1)
.RemoveOrganizationSponsorshipAsync(sponsoredOrg, sponsorship);
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).UpsertAsync(sponsoredOrg);
if (sponsorship != null)
{
await sutProvider.GetDependency<IMailService>().Received(1)
.SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(sponsoredOrg.BillingEmailAddress(), sponsorship.ValidUntil.GetValueOrDefault());
}
}
protected async Task AssertDeletedSponsorshipAsync<T>(OrganizationSponsorship sponsorship,
SutProvider<T> sutProvider)
{
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
.DeleteAsync(sponsorship);
}
protected static async Task AssertDidNotRemoveSponsorshipAsync<T>(SutProvider<T> sutProvider)
{
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.DeleteAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
}
protected async Task AssertRemovedSponsorshipAsync<T>(OrganizationSponsorship sponsorship,
SutProvider<T> sutProvider)
{
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
.DeleteAsync(sponsorship);
}
protected static async Task AssertDidNotRemoveSponsoredPaymentAsync<T>(SutProvider<T> sutProvider)
{
await sutProvider.GetDependency<IPaymentService>().DidNotReceiveWithAnyArgs()
.RemoveOrganizationSponsorshipAsync(default, default);
await sutProvider.GetDependency<IOrganizationRepository>().DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
await sutProvider.GetDependency<IMailService>().DidNotReceiveWithAnyArgs()
.SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(default, default);
}
protected static async Task AssertDidNotDeleteSponsorshipAsync<T>(SutProvider<T> sutProvider)
{
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.DeleteAsync(default);
}
protected static async Task AssertDidNotUpdateSponsorshipAsync<T>(SutProvider<T> sutProvider)
{
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
}
protected static async Task AssertUpdatedSponsorshipAsync<T>(OrganizationSponsorship sponsorship,
SutProvider<T> sutProvider)
{
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1).UpsertAsync(sponsorship);
}
}
protected async Task AssertDeletedSponsorshipAsync<T>(OrganizationSponsorship sponsorship,
SutProvider<T> sutProvider)
{
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
.DeleteAsync(sponsorship);
}
protected static async Task AssertDidNotRemoveSponsorshipAsync<T>(SutProvider<T> sutProvider)
{
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.DeleteAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
}
protected async Task AssertRemovedSponsorshipAsync<T>(OrganizationSponsorship sponsorship,
SutProvider<T> sutProvider)
{
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
.DeleteAsync(sponsorship);
}
protected static async Task AssertDidNotRemoveSponsoredPaymentAsync<T>(SutProvider<T> sutProvider)
{
await sutProvider.GetDependency<IPaymentService>().DidNotReceiveWithAnyArgs()
.RemoveOrganizationSponsorshipAsync(default, default);
await sutProvider.GetDependency<IOrganizationRepository>().DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
await sutProvider.GetDependency<IMailService>().DidNotReceiveWithAnyArgs()
.SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(default, default);
}
protected static async Task AssertDidNotDeleteSponsorshipAsync<T>(SutProvider<T> sutProvider)
{
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.DeleteAsync(default);
}
protected static async Task AssertDidNotUpdateSponsorshipAsync<T>(SutProvider<T> sutProvider)
{
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
}
protected static async Task AssertUpdatedSponsorshipAsync<T>(OrganizationSponsorship sponsorship,
SutProvider<T> sutProvider)
{
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1).UpsertAsync(sponsorship);
}
}

View File

@ -6,45 +6,46 @@ using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
[SutProviderCustomize]
[OrganizationSponsorshipCustomize]
public class CloudRevokeSponsorshipCommandTests : CancelSponsorshipCommandTestsBase
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud
{
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_NoExistingSponsorship_ThrowsBadRequest(
SutProvider<CloudRevokeSponsorshipCommand> sutProvider)
[SutProviderCustomize]
[OrganizationSponsorshipCustomize]
public class CloudRevokeSponsorshipCommandTests : CancelSponsorshipCommandTestsBase
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RevokeSponsorshipAsync(null));
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_NoExistingSponsorship_ThrowsBadRequest(
SutProvider<CloudRevokeSponsorshipCommand> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RevokeSponsorshipAsync(null));
Assert.Contains("You are not currently sponsoring an organization.", exception.Message);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
await AssertDidNotUpdateSponsorshipAsync(sutProvider);
}
Assert.Contains("You are not currently sponsoring an organization.", exception.Message);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
await AssertDidNotUpdateSponsorshipAsync(sutProvider);
}
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_SponsorshipNotRedeemed_DeletesSponsorship(OrganizationSponsorship sponsorship,
SutProvider<CloudRevokeSponsorshipCommand> sutProvider)
{
sponsorship.SponsoredOrganizationId = null;
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_SponsorshipNotRedeemed_DeletesSponsorship(OrganizationSponsorship sponsorship,
SutProvider<CloudRevokeSponsorshipCommand> sutProvider)
{
sponsorship.SponsoredOrganizationId = null;
await sutProvider.Sut.RevokeSponsorshipAsync(sponsorship);
await AssertDeletedSponsorshipAsync(sponsorship, sutProvider);
}
await sutProvider.Sut.RevokeSponsorshipAsync(sponsorship);
await AssertDeletedSponsorshipAsync(sponsorship, sutProvider);
}
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_SponsorshipRedeemed_MarksForDelete(OrganizationSponsorship sponsorship,
SutProvider<CloudRevokeSponsorshipCommand> sutProvider)
{
await sutProvider.Sut.RevokeSponsorshipAsync(sponsorship);
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_SponsorshipRedeemed_MarksForDelete(OrganizationSponsorship sponsorship,
SutProvider<CloudRevokeSponsorshipCommand> sutProvider)
{
await sutProvider.Sut.RevokeSponsorshipAsync(sponsorship);
Assert.True(sponsorship.ToDelete);
await AssertUpdatedSponsorshipAsync(sponsorship, sutProvider);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
Assert.True(sponsorship.ToDelete);
await AssertUpdatedSponsorshipAsync(sponsorship, sutProvider);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
}
}
}

View File

@ -10,216 +10,218 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
[SutProviderCustomize]
public class CloudSyncSponsorshipsCommandTests : FamiliesForEnterpriseTestsBase
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud
{
[Theory]
[BitAutoData]
public async Task SyncOrganization_SponsoringOrgNotFound_ThrowsBadRequest(
IEnumerable<OrganizationSponsorshipData> sponsorshipsData,
SutProvider<CloudSyncSponsorshipsCommand> sutProvider)
[SutProviderCustomize]
public class CloudSyncSponsorshipsCommandTests : FamiliesForEnterpriseTestsBase
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SyncOrganization(null, sponsorshipsData));
Assert.Contains("Failed to sync sponsorship - missing organization.", exception.Message);
[Theory]
[BitAutoData]
public async Task SyncOrganization_SponsoringOrgNotFound_ThrowsBadRequest(
IEnumerable<OrganizationSponsorshipData> sponsorshipsData,
SutProvider<CloudSyncSponsorshipsCommand> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SyncOrganization(null, sponsorshipsData));
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
}
Assert.Contains("Failed to sync sponsorship - missing organization.", exception.Message);
[Theory]
[BitAutoData]
public async Task SyncOrganization_NoSponsorships_EarlyReturn(
Organization organization,
SutProvider<CloudSyncSponsorshipsCommand> sutProvider)
{
var result = await sutProvider.Sut.SyncOrganization(organization, Enumerable.Empty<OrganizationSponsorshipData>());
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
}
Assert.Empty(result.Item1.SponsorshipsBatch);
Assert.Empty(result.Item2);
[Theory]
[BitAutoData]
public async Task SyncOrganization_NoSponsorships_EarlyReturn(
Organization organization,
SutProvider<CloudSyncSponsorshipsCommand> sutProvider)
{
var result = await sutProvider.Sut.SyncOrganization(organization, Enumerable.Empty<OrganizationSponsorshipData>());
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
}
Assert.Empty(result.Item1.SponsorshipsBatch);
Assert.Empty(result.Item2);
[Theory]
[BitMemberAutoData(nameof(NonEnterprisePlanTypes))]
public async Task SyncOrganization_BadSponsoringOrgPlan_NoSync(
PlanType planType,
Organization organization, IEnumerable<OrganizationSponsorshipData> sponsorshipsData,
SutProvider<CloudSyncSponsorshipsCommand> sutProvider)
{
organization.PlanType = planType;
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
}
await sutProvider.Sut.SyncOrganization(organization, sponsorshipsData);
[Theory]
[BitMemberAutoData(nameof(NonEnterprisePlanTypes))]
public async Task SyncOrganization_BadSponsoringOrgPlan_NoSync(
PlanType planType,
Organization organization, IEnumerable<OrganizationSponsorshipData> sponsorshipsData,
SutProvider<CloudSyncSponsorshipsCommand> sutProvider)
{
organization.PlanType = planType;
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
}
await sutProvider.Sut.SyncOrganization(organization, sponsorshipsData);
[Theory]
[BitAutoData]
public async Task SyncOrganization_Success_RecordsEvent(Organization organization,
SutProvider<CloudSyncSponsorshipsCommand> sutProvider)
{
await sutProvider.Sut.SyncOrganization(organization, Array.Empty<OrganizationSponsorshipData>());
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
}
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationEventAsync(organization, EventType.Organization_SponsorshipsSynced, Arg.Any<DateTime?>());
}
[Theory]
[BitAutoData]
public async Task SyncOrganization_Success_RecordsEvent(Organization organization,
SutProvider<CloudSyncSponsorshipsCommand> sutProvider)
{
await sutProvider.Sut.SyncOrganization(organization, Array.Empty<OrganizationSponsorshipData>());
[Theory]
[BitAutoData]
public async Task SyncOrganization_OneExisting_OneNew_Success(SutProvider<CloudSyncSponsorshipsCommand> sutProvider,
Organization sponsoringOrganization, OrganizationSponsorship existingSponsorship, OrganizationSponsorship newSponsorship)
{
// Arrange
sponsoringOrganization.Enabled = true;
sponsoringOrganization.PlanType = PlanType.EnterpriseAnnually;
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationEventAsync(organization, EventType.Organization_SponsorshipsSynced, Arg.Any<DateTime?>());
}
existingSponsorship.ToDelete = false;
newSponsorship.ToDelete = false;
[Theory]
[BitAutoData]
public async Task SyncOrganization_OneExisting_OneNew_Success(SutProvider<CloudSyncSponsorshipsCommand> sutProvider,
Organization sponsoringOrganization, OrganizationSponsorship existingSponsorship, OrganizationSponsorship newSponsorship)
{
// Arrange
sponsoringOrganization.Enabled = true;
sponsoringOrganization.PlanType = PlanType.EnterpriseAnnually;
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetManyBySponsoringOrganizationAsync(sponsoringOrganization.Id)
.Returns(new List<OrganizationSponsorship>
existingSponsorship.ToDelete = false;
newSponsorship.ToDelete = false;
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetManyBySponsoringOrganizationAsync(sponsoringOrganization.Id)
.Returns(new List<OrganizationSponsorship>
{
existingSponsorship,
});
// Act
var (syncData, toEmailSponsorships) = await sutProvider.Sut.SyncOrganization(sponsoringOrganization, new[]
{
existingSponsorship,
new OrganizationSponsorshipData(existingSponsorship),
new OrganizationSponsorshipData(newSponsorship),
});
// Act
var (syncData, toEmailSponsorships) = await sutProvider.Sut.SyncOrganization(sponsoringOrganization, new[]
// Assert
// Should have updated the cloud copy for each item given
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.Received(1)
.UpsertManyAsync(Arg.Is<IEnumerable<OrganizationSponsorship>>(sponsorships => sponsorships.Count() == 2));
// Neither were marked as delete, should not have deleted
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
// Only one sponsorship was new so it should only send one
Assert.Single(toEmailSponsorships);
}
[Theory]
[BitAutoData]
public async Task SyncOrganization_TwoToDelete_OneCanDelete_Success(SutProvider<CloudSyncSponsorshipsCommand> sutProvider,
Organization sponsoringOrganization, OrganizationSponsorship canDeleteSponsorship, OrganizationSponsorship cannotDeleteSponsorship)
{
new OrganizationSponsorshipData(existingSponsorship),
new OrganizationSponsorshipData(newSponsorship),
});
// Arrange
sponsoringOrganization.PlanType = PlanType.EnterpriseAnnually;
// Assert
// Should have updated the cloud copy for each item given
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.Received(1)
.UpsertManyAsync(Arg.Is<IEnumerable<OrganizationSponsorship>>(sponsorships => sponsorships.Count() == 2));
canDeleteSponsorship.ToDelete = true;
canDeleteSponsorship.SponsoredOrganizationId = null;
// Neither were marked as delete, should not have deleted
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
cannotDeleteSponsorship.ToDelete = true;
cannotDeleteSponsorship.SponsoredOrganizationId = Guid.NewGuid();
// Only one sponsorship was new so it should only send one
Assert.Single(toEmailSponsorships);
}
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetManyBySponsoringOrganizationAsync(sponsoringOrganization.Id)
.Returns(new List<OrganizationSponsorship>
{
canDeleteSponsorship,
cannotDeleteSponsorship,
});
[Theory]
[BitAutoData]
public async Task SyncOrganization_TwoToDelete_OneCanDelete_Success(SutProvider<CloudSyncSponsorshipsCommand> sutProvider,
Organization sponsoringOrganization, OrganizationSponsorship canDeleteSponsorship, OrganizationSponsorship cannotDeleteSponsorship)
{
// Arrange
sponsoringOrganization.PlanType = PlanType.EnterpriseAnnually;
canDeleteSponsorship.ToDelete = true;
canDeleteSponsorship.SponsoredOrganizationId = null;
cannotDeleteSponsorship.ToDelete = true;
cannotDeleteSponsorship.SponsoredOrganizationId = Guid.NewGuid();
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetManyBySponsoringOrganizationAsync(sponsoringOrganization.Id)
.Returns(new List<OrganizationSponsorship>
// Act
var (syncData, toEmailSponsorships) = await sutProvider.Sut.SyncOrganization(sponsoringOrganization, new[]
{
canDeleteSponsorship,
cannotDeleteSponsorship,
new OrganizationSponsorshipData(canDeleteSponsorship),
new OrganizationSponsorshipData(cannotDeleteSponsorship),
});
// Act
var (syncData, toEmailSponsorships) = await sutProvider.Sut.SyncOrganization(sponsoringOrganization, new[]
// Assert
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.Received(1)
.UpsertManyAsync(Arg.Is<IEnumerable<OrganizationSponsorship>>(sponsorships => sponsorships.Count() == 2));
// Deletes the sponsorship that had delete requested and is not sponsoring an org
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.Received(1)
.DeleteManyAsync(Arg.Is<IEnumerable<Guid>>(toDeleteIds =>
toDeleteIds.Count() == 1 && toDeleteIds.ElementAt(0) == canDeleteSponsorship.Id));
}
[Theory]
[BitAutoData]
public async Task SyncOrganization_BadData_DoesNotSave(SutProvider<CloudSyncSponsorshipsCommand> sutProvider,
Organization sponsoringOrganization, OrganizationSponsorship badOrganizationSponsorship)
{
new OrganizationSponsorshipData(canDeleteSponsorship),
new OrganizationSponsorshipData(cannotDeleteSponsorship),
});
sponsoringOrganization.PlanType = PlanType.EnterpriseAnnually;
// Assert
badOrganizationSponsorship.ToDelete = true;
badOrganizationSponsorship.LastSyncDate = null;
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.Received(1)
.UpsertManyAsync(Arg.Is<IEnumerable<OrganizationSponsorship>>(sponsorships => sponsorships.Count() == 2));
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetManyBySponsoringOrganizationAsync(sponsoringOrganization.Id)
.Returns(new List<OrganizationSponsorship>());
// Deletes the sponsorship that had delete requested and is not sponsoring an org
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.Received(1)
.DeleteManyAsync(Arg.Is<IEnumerable<Guid>>(toDeleteIds =>
toDeleteIds.Count() == 1 && toDeleteIds.ElementAt(0) == canDeleteSponsorship.Id));
}
var (syncData, toEmailSponsorships) = await sutProvider.Sut.SyncOrganization(sponsoringOrganization, new[]
{
new OrganizationSponsorshipData(badOrganizationSponsorship),
});
[Theory]
[BitAutoData]
public async Task SyncOrganization_BadData_DoesNotSave(SutProvider<CloudSyncSponsorshipsCommand> sutProvider,
Organization sponsoringOrganization, OrganizationSponsorship badOrganizationSponsorship)
{
sponsoringOrganization.PlanType = PlanType.EnterpriseAnnually;
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
badOrganizationSponsorship.ToDelete = true;
badOrganizationSponsorship.LastSyncDate = null;
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
}
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetManyBySponsoringOrganizationAsync(sponsoringOrganization.Id)
.Returns(new List<OrganizationSponsorship>());
var (syncData, toEmailSponsorships) = await sutProvider.Sut.SyncOrganization(sponsoringOrganization, new[]
[Theory]
[BitAutoData]
public async Task SyncOrganization_OrgDisabledForFourMonths_DoesNotSave(SutProvider<CloudSyncSponsorshipsCommand> sutProvider,
Organization sponsoringOrganization, OrganizationSponsorship organizationSponsorship)
{
new OrganizationSponsorshipData(badOrganizationSponsorship),
});
sponsoringOrganization.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrganization.Enabled = false;
sponsoringOrganization.ExpirationDate = DateTime.UtcNow.AddDays(-120);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
organizationSponsorship.ToDelete = false;
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
}
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetManyBySponsoringOrganizationAsync(sponsoringOrganization.Id)
.Returns(new List<OrganizationSponsorship>());
[Theory]
[BitAutoData]
public async Task SyncOrganization_OrgDisabledForFourMonths_DoesNotSave(SutProvider<CloudSyncSponsorshipsCommand> sutProvider,
Organization sponsoringOrganization, OrganizationSponsorship organizationSponsorship)
{
sponsoringOrganization.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrganization.Enabled = false;
sponsoringOrganization.ExpirationDate = DateTime.UtcNow.AddDays(-120);
var (syncData, toEmailSponsorships) = await sutProvider.Sut.SyncOrganization(sponsoringOrganization, new[]
{
new OrganizationSponsorshipData(organizationSponsorship),
});
organizationSponsorship.ToDelete = false;
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetManyBySponsoringOrganizationAsync(sponsoringOrganization.Id)
.Returns(new List<OrganizationSponsorship>());
var (syncData, toEmailSponsorships) = await sutProvider.Sut.SyncOrganization(sponsoringOrganization, new[]
{
new OrganizationSponsorshipData(organizationSponsorship),
});
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
}
}
}

View File

@ -6,21 +6,22 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
[SutProviderCustomize]
public class OrganizationSponsorshipRenewCommandTests
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud
{
[Theory]
[BitAutoData]
public async Task UpdateExpirationDate_UpdatesValidUntil(OrganizationSponsorship sponsorship, DateTime expireDate,
SutProvider<OrganizationSponsorshipRenewCommand> sutProvider)
[SutProviderCustomize]
public class OrganizationSponsorshipRenewCommandTests
{
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().GetBySponsoredOrganizationIdAsync(sponsorship.SponsoredOrganizationId.Value).Returns(sponsorship);
[Theory]
[BitAutoData]
public async Task UpdateExpirationDate_UpdatesValidUntil(OrganizationSponsorship sponsorship, DateTime expireDate,
SutProvider<OrganizationSponsorshipRenewCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().GetBySponsoredOrganizationIdAsync(sponsorship.SponsoredOrganizationId.Value).Returns(sponsorship);
await sutProvider.Sut.UpdateExpirationDateAsync(sponsorship.SponsoredOrganizationId.Value, expireDate);
await sutProvider.Sut.UpdateExpirationDateAsync(sponsorship.SponsoredOrganizationId.Value, expireDate);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
.UpsertAsync(sponsorship);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
.UpsertAsync(sponsorship);
}
}
}

View File

@ -6,37 +6,38 @@ using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise;
[SutProviderCustomize]
[OrganizationSponsorshipCustomize]
public class RemoveSponsorshipCommandTests : CancelSponsorshipCommandTestsBase
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise
{
[Theory]
[BitAutoData]
public async Task RemoveSponsorship_SponsoredOrgNull_ThrowsBadRequest(OrganizationSponsorship sponsorship,
SutProvider<RemoveSponsorshipCommand> sutProvider)
[SutProviderCustomize]
[OrganizationSponsorshipCustomize]
public class RemoveSponsorshipCommandTests : CancelSponsorshipCommandTestsBase
{
sponsorship.SponsoredOrganizationId = null;
[Theory]
[BitAutoData]
public async Task RemoveSponsorship_SponsoredOrgNull_ThrowsBadRequest(OrganizationSponsorship sponsorship,
SutProvider<RemoveSponsorshipCommand> sutProvider)
{
sponsorship.SponsoredOrganizationId = null;
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RemoveSponsorshipAsync(sponsorship));
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RemoveSponsorshipAsync(sponsorship));
Assert.Contains("The requested organization is not currently being sponsored.", exception.Message);
Assert.False(sponsorship.ToDelete);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
await AssertDidNotUpdateSponsorshipAsync(sutProvider);
}
Assert.Contains("The requested organization is not currently being sponsored.", exception.Message);
Assert.False(sponsorship.ToDelete);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
await AssertDidNotUpdateSponsorshipAsync(sutProvider);
}
[Theory]
[BitAutoData]
public async Task RemoveSponsorship_SponsorshipNotFound_ThrowsBadRequest(SutProvider<RemoveSponsorshipCommand> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RemoveSponsorshipAsync(null));
[Theory]
[BitAutoData]
public async Task RemoveSponsorship_SponsorshipNotFound_ThrowsBadRequest(SutProvider<RemoveSponsorshipCommand> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RemoveSponsorshipAsync(null));
Assert.Contains("The requested organization is not currently being sponsored.", exception.Message);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
await AssertDidNotUpdateSponsorshipAsync(sutProvider);
Assert.Contains("The requested organization is not currently being sponsored.", exception.Message);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
await AssertDidNotUpdateSponsorshipAsync(sutProvider);
}
}
}

View File

@ -10,114 +10,115 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise;
[SutProviderCustomize]
[OrganizationSponsorshipCustomize]
public class SendSponsorshipOfferCommandTests : FamiliesForEnterpriseTestsBase
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise
{
[Theory]
[BitAutoData]
public async Task SendSponsorshipOffer_SendSponsorshipOfferAsync_ExistingAccount_Success(OrganizationSponsorship sponsorship, string sponsoringOrgName, User user, SutProvider<SendSponsorshipOfferCommand> sutProvider)
[SutProviderCustomize]
[OrganizationSponsorshipCustomize]
public class SendSponsorshipOfferCommandTests : FamiliesForEnterpriseTestsBase
{
sutProvider.GetDependency<IUserRepository>().GetByEmailAsync(sponsorship.OfferedToEmail).Returns(user);
[Theory]
[BitAutoData]
public async Task SendSponsorshipOffer_SendSponsorshipOfferAsync_ExistingAccount_Success(OrganizationSponsorship sponsorship, string sponsoringOrgName, User user, SutProvider<SendSponsorshipOfferCommand> sutProvider)
{
sutProvider.GetDependency<IUserRepository>().GetByEmailAsync(sponsorship.OfferedToEmail).Returns(user);
await sutProvider.Sut.SendSponsorshipOfferAsync(sponsorship, sponsoringOrgName);
await sutProvider.Sut.SendSponsorshipOfferAsync(sponsorship, sponsoringOrgName);
await sutProvider.GetDependency<IMailService>().Received(1).SendFamiliesForEnterpriseOfferEmailAsync(sponsoringOrgName, sponsorship.OfferedToEmail, true, Arg.Any<string>());
}
await sutProvider.GetDependency<IMailService>().Received(1).SendFamiliesForEnterpriseOfferEmailAsync(sponsoringOrgName, sponsorship.OfferedToEmail, true, Arg.Any<string>());
}
[Theory]
[BitAutoData]
public async Task SendSponsorshipOffer_SendSponsorshipOfferAsync_NewAccount_Success(OrganizationSponsorship sponsorship, string sponsoringOrgName, SutProvider<SendSponsorshipOfferCommand> sutProvider)
{
sutProvider.GetDependency<IUserRepository>().GetByEmailAsync(sponsorship.OfferedToEmail).Returns((User)null);
[Theory]
[BitAutoData]
public async Task SendSponsorshipOffer_SendSponsorshipOfferAsync_NewAccount_Success(OrganizationSponsorship sponsorship, string sponsoringOrgName, SutProvider<SendSponsorshipOfferCommand> sutProvider)
{
sutProvider.GetDependency<IUserRepository>().GetByEmailAsync(sponsorship.OfferedToEmail).Returns((User)null);
await sutProvider.Sut.SendSponsorshipOfferAsync(sponsorship, sponsoringOrgName);
await sutProvider.Sut.SendSponsorshipOfferAsync(sponsorship, sponsoringOrgName);
await sutProvider.GetDependency<IMailService>().Received(1).SendFamiliesForEnterpriseOfferEmailAsync(sponsoringOrgName, sponsorship.OfferedToEmail, false, Arg.Any<string>());
}
await sutProvider.GetDependency<IMailService>().Received(1).SendFamiliesForEnterpriseOfferEmailAsync(sponsoringOrgName, sponsorship.OfferedToEmail, false, Arg.Any<string>());
}
[Theory]
[BitAutoData]
public async Task ResendSponsorshipOffer_SponsoringOrgNotFound_ThrowsBadRequest(
OrganizationUser orgUser, OrganizationSponsorship sponsorship,
SutProvider<SendSponsorshipOfferCommand> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SendSponsorshipOfferAsync(null, orgUser, sponsorship));
[Theory]
[BitAutoData]
public async Task ResendSponsorshipOffer_SponsoringOrgNotFound_ThrowsBadRequest(
OrganizationUser orgUser, OrganizationSponsorship sponsorship,
SutProvider<SendSponsorshipOfferCommand> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SendSponsorshipOfferAsync(null, orgUser, sponsorship));
Assert.Contains("Cannot find the requested sponsoring organization.", exception.Message);
await sutProvider.GetDependency<IMailService>()
.DidNotReceiveWithAnyArgs()
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
}
Assert.Contains("Cannot find the requested sponsoring organization.", exception.Message);
await sutProvider.GetDependency<IMailService>()
.DidNotReceiveWithAnyArgs()
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
}
[Theory]
[BitAutoData]
public async Task ResendSponsorshipOffer_SponsoringOrgUserNotFound_ThrowsBadRequest(Organization org,
OrganizationSponsorship sponsorship, SutProvider<SendSponsorshipOfferCommand> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SendSponsorshipOfferAsync(org, null, sponsorship));
[Theory]
[BitAutoData]
public async Task ResendSponsorshipOffer_SponsoringOrgUserNotFound_ThrowsBadRequest(Organization org,
OrganizationSponsorship sponsorship, SutProvider<SendSponsorshipOfferCommand> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SendSponsorshipOfferAsync(org, null, sponsorship));
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
await sutProvider.GetDependency<IMailService>()
.DidNotReceiveWithAnyArgs()
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
}
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
await sutProvider.GetDependency<IMailService>()
.DidNotReceiveWithAnyArgs()
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
}
[Theory]
[BitAutoData]
[BitMemberAutoData(nameof(NonConfirmedOrganizationUsersStatuses))]
public async Task ResendSponsorshipOffer_SponsoringOrgUserNotConfirmed_ThrowsBadRequest(OrganizationUserStatusType status,
Organization org, OrganizationUser orgUser, OrganizationSponsorship sponsorship,
SutProvider<SendSponsorshipOfferCommand> sutProvider)
{
orgUser.Status = status;
[Theory]
[BitAutoData]
[BitMemberAutoData(nameof(NonConfirmedOrganizationUsersStatuses))]
public async Task ResendSponsorshipOffer_SponsoringOrgUserNotConfirmed_ThrowsBadRequest(OrganizationUserStatusType status,
Organization org, OrganizationUser orgUser, OrganizationSponsorship sponsorship,
SutProvider<SendSponsorshipOfferCommand> sutProvider)
{
orgUser.Status = status;
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SendSponsorshipOfferAsync(org, orgUser, sponsorship));
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SendSponsorshipOfferAsync(org, orgUser, sponsorship));
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
await sutProvider.GetDependency<IMailService>()
.DidNotReceiveWithAnyArgs()
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
}
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
await sutProvider.GetDependency<IMailService>()
.DidNotReceiveWithAnyArgs()
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
}
[Theory]
[BitAutoData]
public async Task ResendSponsorshipOffer_SponsorshipNotFound_ThrowsBadRequest(Organization org,
OrganizationUser orgUser,
SutProvider<SendSponsorshipOfferCommand> sutProvider)
{
orgUser.Status = OrganizationUserStatusType.Confirmed;
[Theory]
[BitAutoData]
public async Task ResendSponsorshipOffer_SponsorshipNotFound_ThrowsBadRequest(Organization org,
OrganizationUser orgUser,
SutProvider<SendSponsorshipOfferCommand> sutProvider)
{
orgUser.Status = OrganizationUserStatusType.Confirmed;
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SendSponsorshipOfferAsync(org, orgUser, null));
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SendSponsorshipOfferAsync(org, orgUser, null));
Assert.Contains("Cannot find an outstanding sponsorship offer for this organization.", exception.Message);
await sutProvider.GetDependency<IMailService>()
.DidNotReceiveWithAnyArgs()
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
}
Assert.Contains("Cannot find an outstanding sponsorship offer for this organization.", exception.Message);
await sutProvider.GetDependency<IMailService>()
.DidNotReceiveWithAnyArgs()
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
}
[Theory]
[BitAutoData]
public async Task ResendSponsorshipOffer_NoOfferToEmail_ThrowsBadRequest(Organization org,
OrganizationUser orgUser, OrganizationSponsorship sponsorship,
SutProvider<SendSponsorshipOfferCommand> sutProvider)
{
orgUser.Status = OrganizationUserStatusType.Confirmed;
sponsorship.OfferedToEmail = null;
[Theory]
[BitAutoData]
public async Task ResendSponsorshipOffer_NoOfferToEmail_ThrowsBadRequest(Organization org,
OrganizationUser orgUser, OrganizationSponsorship sponsorship,
SutProvider<SendSponsorshipOfferCommand> sutProvider)
{
orgUser.Status = OrganizationUserStatusType.Confirmed;
sponsorship.OfferedToEmail = null;
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SendSponsorshipOfferAsync(org, orgUser, sponsorship));
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SendSponsorshipOfferAsync(org, orgUser, sponsorship));
Assert.Contains("Cannot find an outstanding sponsorship offer for this organization.", exception.Message);
await sutProvider.GetDependency<IMailService>()
.DidNotReceiveWithAnyArgs()
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
Assert.Contains("Cannot find an outstanding sponsorship offer for this organization.", exception.Message);
await sutProvider.GetDependency<IMailService>()
.DidNotReceiveWithAnyArgs()
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
}
}
}

View File

@ -10,85 +10,86 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
[SutProviderCustomize]
[OrganizationSponsorshipCustomize]
public class SetUpSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud
{
[Theory]
[BitAutoData]
public async Task SetUpSponsorship_SponsorshipNotFound_ThrowsBadRequest(Organization org,
SutProvider<SetUpSponsorshipCommand> sutProvider)
[SutProviderCustomize]
[OrganizationSponsorshipCustomize]
public class SetUpSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SetUpSponsorshipAsync(null, org));
[Theory]
[BitAutoData]
public async Task SetUpSponsorship_SponsorshipNotFound_ThrowsBadRequest(Organization org,
SutProvider<SetUpSponsorshipCommand> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SetUpSponsorshipAsync(null, org));
Assert.Contains("No unredeemed sponsorship offer exists for you.", exception.Message);
await AssertDidNotSetUpAsync(sutProvider);
}
Assert.Contains("No unredeemed sponsorship offer exists for you.", exception.Message);
await AssertDidNotSetUpAsync(sutProvider);
}
[Theory]
[BitAutoData]
public async Task SetUpSponsorship_OrgAlreadySponsored_ThrowsBadRequest(Organization org,
OrganizationSponsorship sponsorship, OrganizationSponsorship existingSponsorship,
SutProvider<SetUpSponsorshipCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(org.Id).Returns(existingSponsorship);
[Theory]
[BitAutoData]
public async Task SetUpSponsorship_OrgAlreadySponsored_ThrowsBadRequest(Organization org,
OrganizationSponsorship sponsorship, OrganizationSponsorship existingSponsorship,
SutProvider<SetUpSponsorshipCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(org.Id).Returns(existingSponsorship);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SetUpSponsorshipAsync(sponsorship, org));
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SetUpSponsorshipAsync(sponsorship, org));
Assert.Contains("Cannot redeem a sponsorship offer for an organization that is already sponsored. Revoke existing sponsorship first.", exception.Message);
await AssertDidNotSetUpAsync(sutProvider);
}
Assert.Contains("Cannot redeem a sponsorship offer for an organization that is already sponsored. Revoke existing sponsorship first.", exception.Message);
await AssertDidNotSetUpAsync(sutProvider);
}
[Theory]
[BitMemberAutoData(nameof(FamiliesPlanTypes))]
public async Task SetUpSponsorship_TooLongSinceLastSync_ThrowsBadRequest(PlanType planType, Organization org,
OrganizationSponsorship sponsorship,
SutProvider<SetUpSponsorshipCommand> sutProvider)
{
org.PlanType = planType;
sponsorship.LastSyncDate = DateTime.UtcNow.AddDays(-365);
[Theory]
[BitMemberAutoData(nameof(FamiliesPlanTypes))]
public async Task SetUpSponsorship_TooLongSinceLastSync_ThrowsBadRequest(PlanType planType, Organization org,
OrganizationSponsorship sponsorship,
SutProvider<SetUpSponsorshipCommand> sutProvider)
{
org.PlanType = planType;
sponsorship.LastSyncDate = DateTime.UtcNow.AddDays(-365);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SetUpSponsorshipAsync(sponsorship, org));
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SetUpSponsorshipAsync(sponsorship, org));
Assert.Contains("This sponsorship offer is more than 6 months old and has expired.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.Received(1)
.DeleteAsync(sponsorship);
await AssertDidNotSetUpAsync(sutProvider);
}
Assert.Contains("This sponsorship offer is more than 6 months old and has expired.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.Received(1)
.DeleteAsync(sponsorship);
await AssertDidNotSetUpAsync(sutProvider);
}
[Theory]
[BitMemberAutoData(nameof(NonFamiliesPlanTypes))]
public async Task SetUpSponsorship_OrgNotFamiles_ThrowsBadRequest(PlanType planType,
OrganizationSponsorship sponsorship, Organization org,
SutProvider<SetUpSponsorshipCommand> sutProvider)
{
org.PlanType = planType;
sponsorship.LastSyncDate = DateTime.UtcNow;
[Theory]
[BitMemberAutoData(nameof(NonFamiliesPlanTypes))]
public async Task SetUpSponsorship_OrgNotFamiles_ThrowsBadRequest(PlanType planType,
OrganizationSponsorship sponsorship, Organization org,
SutProvider<SetUpSponsorshipCommand> sutProvider)
{
org.PlanType = planType;
sponsorship.LastSyncDate = DateTime.UtcNow;
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SetUpSponsorshipAsync(sponsorship, org));
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SetUpSponsorshipAsync(sponsorship, org));
Assert.Contains("Can only redeem sponsorship offer on families organizations.", exception.Message);
await AssertDidNotSetUpAsync(sutProvider);
}
Assert.Contains("Can only redeem sponsorship offer on families organizations.", exception.Message);
await AssertDidNotSetUpAsync(sutProvider);
}
private static async Task AssertDidNotSetUpAsync(SutProvider<SetUpSponsorshipCommand> sutProvider)
{
await sutProvider.GetDependency<IPaymentService>()
.DidNotReceiveWithAnyArgs()
.SponsorOrganizationAsync(default, default);
await sutProvider.GetDependency<IOrganizationRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
private static async Task AssertDidNotSetUpAsync(SutProvider<SetUpSponsorshipCommand> sutProvider)
{
await sutProvider.GetDependency<IPaymentService>()
.DidNotReceiveWithAnyArgs()
.SponsorOrganizationAsync(default, default);
await sutProvider.GetDependency<IOrganizationRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
}
}
}

View File

@ -8,50 +8,51 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
[SutProviderCustomize]
public class ValidateBillingSyncKeyCommandTests
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud
{
[Theory]
[BitAutoData]
public async Task ValidateBillingSyncKeyAsync_NullOrganization_Throws(SutProvider<ValidateBillingSyncKeyCommand> sutProvider)
[SutProviderCustomize]
public class ValidateBillingSyncKeyCommandTests
{
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.ValidateBillingSyncKeyAsync(null, null));
}
[Theory]
[BitAutoData]
public async Task ValidateBillingSyncKeyAsync_NullOrganization_Throws(SutProvider<ValidateBillingSyncKeyCommand> sutProvider)
{
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.ValidateBillingSyncKeyAsync(null, null));
}
[Theory]
[BitAutoData((string)null)]
[BitAutoData("")]
[BitAutoData(" ")]
public async Task ValidateBillingSyncKeyAsync_BadString_ReturnsFalse(string billingSyncKey, SutProvider<ValidateBillingSyncKeyCommand> sutProvider)
{
Assert.False(await sutProvider.Sut.ValidateBillingSyncKeyAsync(new Organization(), billingSyncKey));
}
[Theory]
[BitAutoData((string)null)]
[BitAutoData("")]
[BitAutoData(" ")]
public async Task ValidateBillingSyncKeyAsync_BadString_ReturnsFalse(string billingSyncKey, SutProvider<ValidateBillingSyncKeyCommand> sutProvider)
{
Assert.False(await sutProvider.Sut.ValidateBillingSyncKeyAsync(new Organization(), billingSyncKey));
}
[Theory]
[BitAutoData]
public async Task ValidateBillingSyncKeyAsync_KeyEquals_ReturnsTrue(SutProvider<ValidateBillingSyncKeyCommand> sutProvider,
Organization organization, OrganizationApiKey orgApiKey, string billingSyncKey)
{
orgApiKey.ApiKey = billingSyncKey;
[Theory]
[BitAutoData]
public async Task ValidateBillingSyncKeyAsync_KeyEquals_ReturnsTrue(SutProvider<ValidateBillingSyncKeyCommand> sutProvider,
Organization organization, OrganizationApiKey orgApiKey, string billingSyncKey)
{
orgApiKey.ApiKey = billingSyncKey;
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
.GetManyByOrganizationIdTypeAsync(organization.Id, OrganizationApiKeyType.BillingSync)
.Returns(new[] { orgApiKey });
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
.GetManyByOrganizationIdTypeAsync(organization.Id, OrganizationApiKeyType.BillingSync)
.Returns(new[] { orgApiKey });
Assert.True(await sutProvider.Sut.ValidateBillingSyncKeyAsync(organization, billingSyncKey));
}
Assert.True(await sutProvider.Sut.ValidateBillingSyncKeyAsync(organization, billingSyncKey));
}
[Theory]
[BitAutoData]
public async Task ValidateBillingSyncKeyAsync_KeyDoesNotEqual_ReturnsFalse(SutProvider<ValidateBillingSyncKeyCommand> sutProvider,
Organization organization, OrganizationApiKey orgApiKey, string billingSyncKey)
{
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
.GetManyByOrganizationIdTypeAsync(organization.Id, OrganizationApiKeyType.BillingSync)
.Returns(new[] { orgApiKey });
[Theory]
[BitAutoData]
public async Task ValidateBillingSyncKeyAsync_KeyDoesNotEqual_ReturnsFalse(SutProvider<ValidateBillingSyncKeyCommand> sutProvider,
Organization organization, OrganizationApiKey orgApiKey, string billingSyncKey)
{
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
.GetManyByOrganizationIdTypeAsync(organization.Id, OrganizationApiKeyType.BillingSync)
.Returns(new[] { orgApiKey });
Assert.False(await sutProvider.Sut.ValidateBillingSyncKeyAsync(organization, billingSyncKey));
Assert.False(await sutProvider.Sut.ValidateBillingSyncKeyAsync(organization, billingSyncKey));
}
}
}

View File

@ -9,78 +9,79 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
[SutProviderCustomize]
public class ValidateRedemptionTokenCommandTests
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud
{
[Theory]
[BitAutoData]
public async Task ValidateRedemptionTokenAsync_CannotUnprotect_ReturnsFalse(SutProvider<ValidateRedemptionTokenCommand> sutProvider,
string encryptedString)
[SutProviderCustomize]
public class ValidateRedemptionTokenCommandTests
{
sutProvider
.GetDependency<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>()
.TryUnprotect(encryptedString, out _)
.Returns(call =>
{
call[1] = null;
return false;
});
[Theory]
[BitAutoData]
public async Task ValidateRedemptionTokenAsync_CannotUnprotect_ReturnsFalse(SutProvider<ValidateRedemptionTokenCommand> sutProvider,
string encryptedString)
{
sutProvider
.GetDependency<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>()
.TryUnprotect(encryptedString, out _)
.Returns(call =>
{
call[1] = null;
return false;
});
var (valid, sponsorship) = await sutProvider.Sut.ValidateRedemptionTokenAsync(encryptedString, null);
Assert.False(valid);
Assert.Null(sponsorship);
}
var (valid, sponsorship) = await sutProvider.Sut.ValidateRedemptionTokenAsync(encryptedString, null);
Assert.False(valid);
Assert.Null(sponsorship);
}
[Theory]
[BitAutoData]
public async Task ValidateRedemptionTokenAsync_NoSponsorship_ReturnsFalse(SutProvider<ValidateRedemptionTokenCommand> sutProvider,
string encryptedString, OrganizationSponsorshipOfferTokenable tokenable)
{
sutProvider
.GetDependency<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>()
.TryUnprotect(encryptedString, out _)
.Returns(call =>
{
call[1] = tokenable;
return true;
});
[Theory]
[BitAutoData]
public async Task ValidateRedemptionTokenAsync_NoSponsorship_ReturnsFalse(SutProvider<ValidateRedemptionTokenCommand> sutProvider,
string encryptedString, OrganizationSponsorshipOfferTokenable tokenable)
{
sutProvider
.GetDependency<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>()
.TryUnprotect(encryptedString, out _)
.Returns(call =>
{
call[1] = tokenable;
return true;
});
var (valid, sponsorship) = await sutProvider.Sut.ValidateRedemptionTokenAsync(encryptedString, "test@email.com");
Assert.False(valid);
Assert.Null(sponsorship);
}
var (valid, sponsorship) = await sutProvider.Sut.ValidateRedemptionTokenAsync(encryptedString, "test@email.com");
Assert.False(valid);
Assert.Null(sponsorship);
}
[Theory]
[BitAutoData]
public async Task ValidateRedemptionTokenAsync_ValidSponsorship_ReturnsFalse(SutProvider<ValidateRedemptionTokenCommand> sutProvider,
string encryptedString, string email, OrganizationSponsorshipOfferTokenable tokenable)
{
tokenable.Email = email;
[Theory]
[BitAutoData]
public async Task ValidateRedemptionTokenAsync_ValidSponsorship_ReturnsFalse(SutProvider<ValidateRedemptionTokenCommand> sutProvider,
string encryptedString, string email, OrganizationSponsorshipOfferTokenable tokenable)
{
tokenable.Email = email;
sutProvider
.GetDependency<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>()
.TryUnprotect(encryptedString, out _)
.Returns(call =>
{
call[1] = tokenable;
return true;
});
sutProvider
.GetDependency<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>()
.TryUnprotect(encryptedString, out _)
.Returns(call =>
{
call[1] = tokenable;
return true;
});
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetByIdAsync(tokenable.Id)
.Returns(new OrganizationSponsorship
{
Id = tokenable.Id,
PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
OfferedToEmail = email
});
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetByIdAsync(tokenable.Id)
.Returns(new OrganizationSponsorship
{
Id = tokenable.Id,
PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
OfferedToEmail = email
});
var (valid, sponsorship) = await sutProvider.Sut
.ValidateRedemptionTokenAsync(encryptedString, email);
var (valid, sponsorship) = await sutProvider.Sut
.ValidateRedemptionTokenAsync(encryptedString, email);
Assert.True(valid);
Assert.NotNull(sponsorship);
Assert.True(valid);
Assert.NotNull(sponsorship);
}
}
}

View File

@ -8,246 +8,247 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
[SutProviderCustomize]
[OrganizationSponsorshipCustomize]
public class ValidateSponsorshipCommandTests : CancelSponsorshipCommandTestsBase
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud
{
[Theory]
[BitAutoData]
public async Task ValidateSponsorshipAsync_NoSponsoredOrg_EarlyReturn(Guid sponsoredOrgId,
SutProvider<ValidateSponsorshipCommand> sutProvider)
[SutProviderCustomize]
[OrganizationSponsorshipCustomize]
public class ValidateSponsorshipCommandTests : CancelSponsorshipCommandTestsBase
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrgId).Returns((Organization)null);
[Theory]
[BitAutoData]
public async Task ValidateSponsorshipAsync_NoSponsoredOrg_EarlyReturn(Guid sponsoredOrgId,
SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrgId).Returns((Organization)null);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrgId);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrgId);
Assert.False(result);
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
}
Assert.False(result);
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
}
[Theory]
[BitAutoData]
public async Task ValidateSponsorshipAsync_NoExistingSponsorship_UpdatesStripePlan(Organization sponsoredOrg,
SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
[Theory]
[BitAutoData]
public async Task ValidateSponsorshipAsync_NoExistingSponsorship_UpdatesStripePlan(Organization sponsoredOrg,
SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, null, sutProvider);
}
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, null, sutProvider);
}
[Theory]
[BitAutoData]
public async Task ValidateSponsorshipAsync_SponsoringOrgDefault_UpdatesStripePlan(Organization sponsoredOrg,
OrganizationSponsorship existingSponsorship, SutProvider<ValidateSponsorshipCommand> sutProvider)
{
existingSponsorship.SponsoringOrganizationId = default;
[Theory]
[BitAutoData]
public async Task ValidateSponsorshipAsync_SponsoringOrgDefault_UpdatesStripePlan(Organization sponsoredOrg,
OrganizationSponsorship existingSponsorship, SutProvider<ValidateSponsorshipCommand> sutProvider)
{
existingSponsorship.SponsoringOrganizationId = default;
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
}
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
}
[Theory]
[BitAutoData]
public async Task ValidateSponsorshipAsync_SponsoringOrgUserDefault_UpdatesStripePlan(Organization sponsoredOrg,
OrganizationSponsorship existingSponsorship, SutProvider<ValidateSponsorshipCommand> sutProvider)
{
existingSponsorship.SponsoringOrganizationUserId = default;
[Theory]
[BitAutoData]
public async Task ValidateSponsorshipAsync_SponsoringOrgUserDefault_UpdatesStripePlan(Organization sponsoredOrg,
OrganizationSponsorship existingSponsorship, SutProvider<ValidateSponsorshipCommand> sutProvider)
{
existingSponsorship.SponsoringOrganizationUserId = default;
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
}
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
}
[Theory]
[BitAutoData]
public async Task ValidateSponsorshipAsync_SponsorshipTypeNull_UpdatesStripePlan(Organization sponsoredOrg,
OrganizationSponsorship existingSponsorship, SutProvider<ValidateSponsorshipCommand> sutProvider)
{
existingSponsorship.PlanSponsorshipType = null;
[Theory]
[BitAutoData]
public async Task ValidateSponsorshipAsync_SponsorshipTypeNull_UpdatesStripePlan(Organization sponsoredOrg,
OrganizationSponsorship existingSponsorship, SutProvider<ValidateSponsorshipCommand> sutProvider)
{
existingSponsorship.PlanSponsorshipType = null;
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
}
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
}
[Theory]
[BitAutoData]
public async Task ValidateSponsorshipAsync_SponsoringOrgNotFound_UpdatesStripePlan(Organization sponsoredOrg,
OrganizationSponsorship existingSponsorship, SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
[Theory]
[BitAutoData]
public async Task ValidateSponsorshipAsync_SponsoringOrgNotFound_UpdatesStripePlan(Organization sponsoredOrg,
OrganizationSponsorship existingSponsorship, SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
}
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
}
[Theory]
[BitMemberAutoData(nameof(NonEnterprisePlanTypes))]
public async Task ValidateSponsorshipAsync_SponsoringOrgNotEnterprise_UpdatesStripePlan(PlanType planType,
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = planType;
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
[Theory]
[BitMemberAutoData(nameof(NonEnterprisePlanTypes))]
public async Task ValidateSponsorshipAsync_SponsoringOrgNotEnterprise_UpdatesStripePlan(PlanType planType,
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = planType;
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
}
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
}
[Theory]
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
public async Task ValidateSponsorshipAsync_SponsoringOrgDisabledLongerThanGrace_UpdatesStripePlan(PlanType planType,
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = planType;
sponsoringOrg.Enabled = false;
sponsoringOrg.ExpirationDate = DateTime.UtcNow.AddDays(-100);
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
[Theory]
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
public async Task ValidateSponsorshipAsync_SponsoringOrgDisabledLongerThanGrace_UpdatesStripePlan(PlanType planType,
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = planType;
sponsoringOrg.Enabled = false;
sponsoringOrg.ExpirationDate = DateTime.UtcNow.AddDays(-100);
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
}
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
}
[Theory]
[OrganizationSponsorshipCustomize(ToDelete = true)]
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
public async Task ValidateSponsorshipAsync_ToDeleteSponsorship_IsInvalid(PlanType planType,
Organization sponsoredOrg, OrganizationSponsorship sponsorship, Organization sponsoringOrg,
SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = planType;
sponsoringOrg.Enabled = true;
sponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
[Theory]
[OrganizationSponsorshipCustomize(ToDelete = true)]
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
public async Task ValidateSponsorshipAsync_ToDeleteSponsorship_IsInvalid(PlanType planType,
Organization sponsoredOrg, OrganizationSponsorship sponsorship, Organization sponsoringOrg,
SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = planType;
sponsoringOrg.Enabled = true;
sponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(sponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(sponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
Assert.False(result);
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, sponsorship, sutProvider);
await AssertDeletedSponsorshipAsync(sponsorship, sutProvider);
}
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, sponsorship, sutProvider);
await AssertDeletedSponsorshipAsync(sponsorship, sutProvider);
}
[Theory]
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
public async Task ValidateSponsorshipAsync_SponsoringOrgDisabledUnknownTime_UpdatesStripePlan(PlanType planType,
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = planType;
sponsoringOrg.Enabled = false;
sponsoringOrg.ExpirationDate = null;
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
[Theory]
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
public async Task ValidateSponsorshipAsync_SponsoringOrgDisabledUnknownTime_UpdatesStripePlan(PlanType planType,
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = planType;
sponsoringOrg.Enabled = false;
sponsoringOrg.ExpirationDate = null;
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
await AssertRemovedSponsorshipAsync(existingSponsorship, sutProvider);
}
Assert.False(result);
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
await AssertRemovedSponsorshipAsync(existingSponsorship, sutProvider);
}
[Theory]
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
public async Task ValidateSponsorshipAsync_SponsoringOrgDisabledLessThanGrace_Valid(PlanType planType,
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = planType;
sponsoringOrg.Enabled = true;
sponsoringOrg.ExpirationDate = DateTime.UtcNow.AddDays(-1);
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
[Theory]
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
public async Task ValidateSponsorshipAsync_SponsoringOrgDisabledLessThanGrace_Valid(PlanType planType,
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = planType;
sponsoringOrg.Enabled = true;
sponsoringOrg.ExpirationDate = DateTime.UtcNow.AddDays(-1);
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
Assert.True(result);
Assert.True(result);
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
await AssertDidNotRemoveSponsorshipAsync(sutProvider);
}
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
await AssertDidNotRemoveSponsorshipAsync(sutProvider);
}
[Theory]
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
public async Task ValidateSponsorshipAsync_Valid(PlanType planType,
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = planType;
sponsoringOrg.Enabled = true;
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
[Theory]
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
public async Task ValidateSponsorshipAsync_Valid(PlanType planType,
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
SutProvider<ValidateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = planType;
sponsoringOrg.Enabled = true;
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
Assert.True(result);
Assert.True(result);
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
}
}
}

View File

@ -13,166 +13,167 @@ using NSubstitute.ExceptionExtensions;
using NSubstitute.ReturnsExtensions;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise;
[SutProviderCustomize]
public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise
{
private bool SponsorshipValidator(OrganizationSponsorship sponsorship, OrganizationSponsorship expectedSponsorship)
[SutProviderCustomize]
public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
{
try
private bool SponsorshipValidator(OrganizationSponsorship sponsorship, OrganizationSponsorship expectedSponsorship)
{
AssertHelper.AssertPropertyEqual(sponsorship, expectedSponsorship, nameof(OrganizationSponsorship.Id));
return true;
try
{
AssertHelper.AssertPropertyEqual(sponsorship, expectedSponsorship, nameof(OrganizationSponsorship.Id));
return true;
}
catch
{
return false;
}
}
catch
[Theory, BitAutoData]
public async Task CreateSponsorship_OfferedToNotFound_ThrowsBadRequest(OrganizationUser orgUser, SutProvider<CreateSponsorshipCommand> sutProvider)
{
return false;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).ReturnsNull();
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateSponsorshipAsync(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default));
Assert.Contains("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
}
}
[Theory, BitAutoData]
public async Task CreateSponsorship_OfferedToNotFound_ThrowsBadRequest(OrganizationUser orgUser, SutProvider<CreateSponsorshipCommand> sutProvider)
{
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).ReturnsNull();
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateSponsorshipAsync(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default));
Assert.Contains("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
}
[Theory, BitAutoData]
public async Task CreateSponsorship_OfferedToSelf_ThrowsBadRequest(OrganizationUser orgUser, string sponsoredEmail, User user, SutProvider<CreateSponsorshipCommand> sutProvider)
{
user.Email = sponsoredEmail;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateSponsorshipAsync(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, default));
Assert.Contains("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
}
[Theory, BitMemberAutoData(nameof(NonEnterprisePlanTypes))]
public async Task CreateSponsorship_BadSponsoringOrgPlan_ThrowsBadRequest(PlanType sponsoringOrgPlan,
Organization org, OrganizationUser orgUser, User user, SutProvider<CreateSponsorshipCommand> sutProvider)
{
org.PlanType = sponsoringOrgPlan;
orgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default));
Assert.Contains("Specified Organization cannot sponsor other organizations.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
}
[Theory]
[BitMemberAutoData(nameof(NonConfirmedOrganizationUsersStatuses))]
public async Task CreateSponsorship_BadSponsoringUserStatus_ThrowsBadRequest(
OrganizationUserStatusType statusType, Organization org, OrganizationUser orgUser, User user,
SutProvider<CreateSponsorshipCommand> sutProvider)
{
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.Status = statusType;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default));
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
}
[Theory]
[OrganizationSponsorshipCustomize]
[BitAutoData]
public async Task CreateSponsorship_AlreadySponsoring_Throws(Organization org,
OrganizationUser orgUser, User user, OrganizationSponsorship sponsorship,
SutProvider<CreateSponsorshipCommand> sutProvider)
{
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoringOrganizationUserIdAsync(orgUser.Id).Returns(sponsorship);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, sponsorship.PlanSponsorshipType.Value, default, default));
Assert.Contains("Can only sponsor one organization per Organization User.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
}
[Theory]
[BitAutoData]
public async Task CreateSponsorship_CreatesSponsorship(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, User user,
string sponsoredEmail, string friendlyName, Guid sponsorshipId, SutProvider<CreateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(default)).Do(callInfo =>
[Theory, BitAutoData]
public async Task CreateSponsorship_OfferedToSelf_ThrowsBadRequest(OrganizationUser orgUser, string sponsoredEmail, User user, SutProvider<CreateSponsorshipCommand> sutProvider)
{
var sponsorship = callInfo.Arg<OrganizationSponsorship>();
sponsorship.Id = sponsorshipId;
});
user.Email = sponsoredEmail;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateSponsorshipAsync(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, default));
await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName);
Assert.Contains("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
}
var expectedSponsorship = new OrganizationSponsorship
[Theory, BitMemberAutoData(nameof(NonEnterprisePlanTypes))]
public async Task CreateSponsorship_BadSponsoringOrgPlan_ThrowsBadRequest(PlanType sponsoringOrgPlan,
Organization org, OrganizationUser orgUser, User user, SutProvider<CreateSponsorshipCommand> sutProvider)
{
Id = sponsorshipId,
SponsoringOrganizationId = sponsoringOrg.Id,
SponsoringOrganizationUserId = sponsoringOrgUser.Id,
FriendlyName = friendlyName,
OfferedToEmail = sponsoredEmail,
PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
};
org.PlanType = sponsoringOrgPlan;
orgUser.Status = OrganizationUserStatusType.Confirmed;
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
.UpsertAsync(Arg.Is<OrganizationSponsorship>(s => SponsorshipValidator(s, expectedSponsorship)));
}
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
[Theory]
[BitAutoData]
public async Task CreateSponsorship_CreateSponsorshipThrows_RevertsDatabase(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, User user,
string sponsoredEmail, string friendlyName, SutProvider<CreateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default));
var expectedException = new Exception();
OrganizationSponsorship createdSponsorship = null;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().UpsertAsync(default).ThrowsForAnyArgs(callInfo =>
Assert.Contains("Specified Organization cannot sponsor other organizations.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
}
[Theory]
[BitMemberAutoData(nameof(NonConfirmedOrganizationUsersStatuses))]
public async Task CreateSponsorship_BadSponsoringUserStatus_ThrowsBadRequest(
OrganizationUserStatusType statusType, Organization org, OrganizationUser orgUser, User user,
SutProvider<CreateSponsorshipCommand> sutProvider)
{
createdSponsorship = callInfo.ArgAt<OrganizationSponsorship>(0);
createdSponsorship.Id = Guid.NewGuid();
return expectedException;
});
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.Status = statusType;
var actualException = await Assert.ThrowsAsync<Exception>(() =>
sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName));
Assert.Same(expectedException, actualException);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
.DeleteAsync(createdSponsorship);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default));
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
}
[Theory]
[OrganizationSponsorshipCustomize]
[BitAutoData]
public async Task CreateSponsorship_AlreadySponsoring_Throws(Organization org,
OrganizationUser orgUser, User user, OrganizationSponsorship sponsorship,
SutProvider<CreateSponsorshipCommand> sutProvider)
{
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoringOrganizationUserIdAsync(orgUser.Id).Returns(sponsorship);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, sponsorship.PlanSponsorshipType.Value, default, default));
Assert.Contains("Can only sponsor one organization per Organization User.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default);
}
[Theory]
[BitAutoData]
public async Task CreateSponsorship_CreatesSponsorship(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, User user,
string sponsoredEmail, string friendlyName, Guid sponsorshipId, SutProvider<CreateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(default)).Do(callInfo =>
{
var sponsorship = callInfo.Arg<OrganizationSponsorship>();
sponsorship.Id = sponsorshipId;
});
await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName);
var expectedSponsorship = new OrganizationSponsorship
{
Id = sponsorshipId,
SponsoringOrganizationId = sponsoringOrg.Id,
SponsoringOrganizationUserId = sponsoringOrgUser.Id,
FriendlyName = friendlyName,
OfferedToEmail = sponsoredEmail,
PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
};
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
.UpsertAsync(Arg.Is<OrganizationSponsorship>(s => SponsorshipValidator(s, expectedSponsorship)));
}
[Theory]
[BitAutoData]
public async Task CreateSponsorship_CreateSponsorshipThrows_RevertsDatabase(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, User user,
string sponsoredEmail, string friendlyName, SutProvider<CreateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
var expectedException = new Exception();
OrganizationSponsorship createdSponsorship = null;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().UpsertAsync(default).ThrowsForAnyArgs(callInfo =>
{
createdSponsorship = callInfo.ArgAt<OrganizationSponsorship>(0);
createdSponsorship.Id = Guid.NewGuid();
return expectedException;
});
var actualException = await Assert.ThrowsAsync<Exception>(() =>
sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName));
Assert.Same(expectedException, actualException);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
.DeleteAsync(createdSponsorship);
}
}
}

View File

@ -1,24 +1,25 @@
using Bit.Core.Enums;
using Bit.Core.Utilities;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise;
public abstract class FamiliesForEnterpriseTestsBase
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise
{
public static IEnumerable<object[]> EnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
public abstract class FamiliesForEnterpriseTestsBase
{
public static IEnumerable<object[]> EnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonEnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonEnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> FamiliesPlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Families).Select(p => new object[] { p });
public static IEnumerable<object[]> FamiliesPlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Families).Select(p => new object[] { p });
public static IEnumerable<object[]> NonFamiliesPlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
public static IEnumerable<object[]> NonFamiliesPlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
Enum.GetValues<OrganizationUserStatusType>()
.Where(s => s != OrganizationUserStatusType.Confirmed)
.Select(s => new object[] { s });
public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
Enum.GetValues<OrganizationUserStatusType>()
.Where(s => s != OrganizationUserStatusType.Confirmed)
.Select(s => new object[] { s });
}
}

View File

@ -6,47 +6,48 @@ using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SelfHosted;
[SutProviderCustomize]
[OrganizationSponsorshipCustomize]
public class SelfHostedRevokeSponsorshipCommandTests : CancelSponsorshipCommandTestsBase
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SelfHosted
{
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_NoExistingSponsorship_ThrowsBadRequest(
SutProvider<SelfHostedRevokeSponsorshipCommand> sutProvider)
[SutProviderCustomize]
[OrganizationSponsorshipCustomize]
public class SelfHostedRevokeSponsorshipCommandTests : CancelSponsorshipCommandTestsBase
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RevokeSponsorshipAsync(null));
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_NoExistingSponsorship_ThrowsBadRequest(
SutProvider<SelfHostedRevokeSponsorshipCommand> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RevokeSponsorshipAsync(null));
Assert.Contains("You are not currently sponsoring an organization.", exception.Message);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
await AssertDidNotUpdateSponsorshipAsync(sutProvider);
}
Assert.Contains("You are not currently sponsoring an organization.", exception.Message);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
await AssertDidNotUpdateSponsorshipAsync(sutProvider);
}
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_SponsorshipNotSynced_DeletesSponsorship(OrganizationSponsorship sponsorship,
SutProvider<SelfHostedRevokeSponsorshipCommand> sutProvider)
{
sponsorship.LastSyncDate = null;
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_SponsorshipNotSynced_DeletesSponsorship(OrganizationSponsorship sponsorship,
SutProvider<SelfHostedRevokeSponsorshipCommand> sutProvider)
{
sponsorship.LastSyncDate = null;
await sutProvider.Sut.RevokeSponsorshipAsync(sponsorship);
await AssertDeletedSponsorshipAsync(sponsorship, sutProvider);
}
await sutProvider.Sut.RevokeSponsorshipAsync(sponsorship);
await AssertDeletedSponsorshipAsync(sponsorship, sutProvider);
}
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_SponsorshipSynced_MarksForDeletion(OrganizationSponsorship sponsorship,
SutProvider<SelfHostedRevokeSponsorshipCommand> sutProvider)
{
sponsorship.LastSyncDate = DateTime.UtcNow;
[Theory]
[BitAutoData]
public async Task RevokeSponsorship_SponsorshipSynced_MarksForDeletion(OrganizationSponsorship sponsorship,
SutProvider<SelfHostedRevokeSponsorshipCommand> sutProvider)
{
sponsorship.LastSyncDate = DateTime.UtcNow;
await sutProvider.Sut.RevokeSponsorshipAsync(sponsorship);
await sutProvider.Sut.RevokeSponsorshipAsync(sponsorship);
Assert.True(sponsorship.ToDelete);
await AssertUpdatedSponsorshipAsync(sponsorship, sutProvider);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
Assert.True(sponsorship.ToDelete);
await AssertUpdatedSponsorshipAsync(sponsorship, sutProvider);
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
}
}
}

View File

@ -15,172 +15,174 @@ using NSubstitute;
using RichardSzalay.MockHttp;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SelfHosted;
public class SelfHostedSyncSponsorshipsCommandTests : FamiliesForEnterpriseTestsBase
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SelfHosted
{
public static SutProvider<SelfHostedSyncSponsorshipsCommand> GetSutProvider(bool enableCloudCommunication = true, string identityResponse = null, string apiResponse = null)
public class SelfHostedSyncSponsorshipsCommandTests : FamiliesForEnterpriseTestsBase
{
var fixture = new Fixture().WithAutoNSubstitutionsAutoPopulatedProperties();
fixture.AddMockHttp();
var settings = fixture.Create<IGlobalSettings>();
settings.SelfHosted = true;
settings.EnableCloudCommunication = enableCloudCommunication;
var apiUri = fixture.Create<Uri>();
var identityUri = fixture.Create<Uri>();
settings.Installation.ApiUri.Returns(apiUri.ToString());
settings.Installation.IdentityUri.Returns(identityUri.ToString());
var apiHandler = new MockHttpMessageHandler();
var identityHandler = new MockHttpMessageHandler();
var syncUri = string.Concat(apiUri, "organization/sponsorship/sync");
var tokenUri = string.Concat(identityUri, "connect/token");
apiHandler.When(HttpMethod.Post, syncUri)
.Respond("application/json", apiResponse);
identityHandler.When(HttpMethod.Post, tokenUri)
.Respond("application/json", identityResponse ?? "{\"access_token\":\"string\",\"expires_in\":3600,\"token_type\":\"Bearer\",\"scope\":\"string\"}");
var apiHttp = apiHandler.ToHttpClient();
var identityHttp = identityHandler.ToHttpClient();
var mockHttpClientFactory = Substitute.For<IHttpClientFactory>();
mockHttpClientFactory.CreateClient(Arg.Is("client")).Returns(apiHttp);
mockHttpClientFactory.CreateClient(Arg.Is("identity")).Returns(identityHttp);
return new SutProvider<SelfHostedSyncSponsorshipsCommand>(fixture)
.SetDependency(settings)
.SetDependency(mockHttpClientFactory)
.Create();
}
[Theory]
[BitAutoData]
public async Task SyncOrganization_BillingSyncKeyDisabled_ThrowsBadRequest(
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection)
{
var sutProvider = GetSutProvider();
billingSyncConnection.Enabled = false;
billingSyncConnection.SetConfig(new BillingSyncConfig
public static SutProvider<SelfHostedSyncSponsorshipsCommand> GetSutProvider(bool enableCloudCommunication = true, string identityResponse = null, string apiResponse = null)
{
BillingSyncKey = "okslkcslkjf"
});
var fixture = new Fixture().WithAutoNSubstitutionsAutoPopulatedProperties();
fixture.AddMockHttp();
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection));
var settings = fixture.Create<IGlobalSettings>();
settings.SelfHosted = true;
settings.EnableCloudCommunication = enableCloudCommunication;
Assert.Contains($"Billing Sync Key disabled", exception.Message);
var apiUri = fixture.Create<Uri>();
var identityUri = fixture.Create<Uri>();
settings.Installation.ApiUri.Returns(apiUri.ToString());
settings.Installation.IdentityUri.Returns(identityUri.ToString());
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
}
var apiHandler = new MockHttpMessageHandler();
var identityHandler = new MockHttpMessageHandler();
var syncUri = string.Concat(apiUri, "organization/sponsorship/sync");
var tokenUri = string.Concat(identityUri, "connect/token");
[Theory]
[BitAutoData]
public async Task SyncOrganization_BillingSyncKeyEmpty_ThrowsBadRequest(
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection)
{
var sutProvider = GetSutProvider();
billingSyncConnection.Config = "";
apiHandler.When(HttpMethod.Post, syncUri)
.Respond("application/json", apiResponse);
identityHandler.When(HttpMethod.Post, tokenUri)
.Respond("application/json", identityResponse ?? "{\"access_token\":\"string\",\"expires_in\":3600,\"token_type\":\"Bearer\",\"scope\":\"string\"}");
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection));
Assert.Contains($"No Billing Sync Key known", exception.Message);
var apiHttp = apiHandler.ToHttpClient();
var identityHttp = identityHandler.ToHttpClient();
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
}
var mockHttpClientFactory = Substitute.For<IHttpClientFactory>();
mockHttpClientFactory.CreateClient(Arg.Is("client")).Returns(apiHttp);
mockHttpClientFactory.CreateClient(Arg.Is("identity")).Returns(identityHttp);
[Theory]
[BitAutoData]
public async Task SyncOrganization_CloudCommunicationDisabled_EarlyReturn(
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection)
{
var sutProvider = GetSutProvider(false);
return new SutProvider<SelfHostedSyncSponsorshipsCommand>(fixture)
.SetDependency(settings)
.SetDependency(mockHttpClientFactory)
.Create();
}
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection));
Assert.Contains($"Cloud communication is disabled", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
}
[Theory]
[OrganizationSponsorshipCustomize]
[BitAutoData]
public async Task SyncOrganization_SyncsSponsorships(
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection, IEnumerable<OrganizationSponsorship> sponsorships)
{
var syncJsonResponse = JsonSerializer.Serialize(new OrganizationSponsorshipSyncResponseModel(
new OrganizationSponsorshipSyncData
[Theory]
[BitAutoData]
public async Task SyncOrganization_BillingSyncKeyDisabled_ThrowsBadRequest(
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection)
{
var sutProvider = GetSutProvider();
billingSyncConnection.Enabled = false;
billingSyncConnection.SetConfig(new BillingSyncConfig
{
SponsorshipsBatch = sponsorships.Select(o => new OrganizationSponsorshipData(o))
}));
BillingSyncKey = "okslkcslkjf"
});
var sutProvider = GetSutProvider(apiResponse: syncJsonResponse);
billingSyncConnection.SetConfig(new BillingSyncConfig
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection));
Assert.Contains($"Billing Sync Key disabled", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
}
[Theory]
[BitAutoData]
public async Task SyncOrganization_BillingSyncKeyEmpty_ThrowsBadRequest(
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection)
{
BillingSyncKey = "okslkcslkjf"
});
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetManyBySponsoringOrganizationAsync(Arg.Any<Guid>()).Returns(sponsorships.ToList());
var sutProvider = GetSutProvider();
billingSyncConnection.Config = "";
await sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection));
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.Received(1)
.UpsertManyAsync(Arg.Any<IEnumerable<OrganizationSponsorship>>());
}
Assert.Contains($"No Billing Sync Key known", exception.Message);
[Theory]
[OrganizationSponsorshipCustomize(ToDelete = true)]
[BitAutoData]
public async Task SyncOrganization_DeletesSponsorships(
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection, IEnumerable<OrganizationSponsorship> sponsorships)
{
var syncJsonResponse = JsonSerializer.Serialize(new OrganizationSponsorshipSyncResponseModel(
new OrganizationSponsorshipSyncData
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
}
[Theory]
[BitAutoData]
public async Task SyncOrganization_CloudCommunicationDisabled_EarlyReturn(
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection)
{
var sutProvider = GetSutProvider(false);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection));
Assert.Contains($"Cloud communication is disabled", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
}
[Theory]
[OrganizationSponsorshipCustomize]
[BitAutoData]
public async Task SyncOrganization_SyncsSponsorships(
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection, IEnumerable<OrganizationSponsorship> sponsorships)
{
var syncJsonResponse = JsonSerializer.Serialize(new OrganizationSponsorshipSyncResponseModel(
new OrganizationSponsorshipSyncData
{
SponsorshipsBatch = sponsorships.Select(o => new OrganizationSponsorshipData(o))
}));
var sutProvider = GetSutProvider(apiResponse: syncJsonResponse);
billingSyncConnection.SetConfig(new BillingSyncConfig
{
SponsorshipsBatch = sponsorships.Select(o => new OrganizationSponsorshipData(o) { CloudSponsorshipRemoved = true })
}));
BillingSyncKey = "okslkcslkjf"
});
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetManyBySponsoringOrganizationAsync(Arg.Any<Guid>()).Returns(sponsorships.ToList());
var sutProvider = GetSutProvider(apiResponse: syncJsonResponse);
billingSyncConnection.SetConfig(new BillingSyncConfig
await sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.DeleteManyAsync(default);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.Received(1)
.UpsertManyAsync(Arg.Any<IEnumerable<OrganizationSponsorship>>());
}
[Theory]
[OrganizationSponsorshipCustomize(ToDelete = true)]
[BitAutoData]
public async Task SyncOrganization_DeletesSponsorships(
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection, IEnumerable<OrganizationSponsorship> sponsorships)
{
BillingSyncKey = "okslkcslkjf"
});
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetManyBySponsoringOrganizationAsync(Arg.Any<Guid>()).Returns(sponsorships.ToList());
var syncJsonResponse = JsonSerializer.Serialize(new OrganizationSponsorshipSyncResponseModel(
new OrganizationSponsorshipSyncData
{
SponsorshipsBatch = sponsorships.Select(o => new OrganizationSponsorshipData(o) { CloudSponsorshipRemoved = true })
}));
await sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection);
var sutProvider = GetSutProvider(apiResponse: syncJsonResponse);
billingSyncConnection.SetConfig(new BillingSyncConfig
{
BillingSyncKey = "okslkcslkjf"
});
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetManyBySponsoringOrganizationAsync(Arg.Any<Guid>()).Returns(sponsorships.ToList());
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.Received(1)
.DeleteManyAsync(Arg.Any<IEnumerable<Guid>>());
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
await sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.Received(1)
.DeleteManyAsync(Arg.Any<IEnumerable<Guid>>());
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertManyAsync(default);
}
}
}

View File

@ -1,27 +1,28 @@
using Bit.Core.Utilities;
using Xunit;
namespace Bit.Core.Test.Resources;
public class VerifyResources
namespace Bit.Core.Test.Resources
{
[Theory]
[MemberData(nameof(GetResources))]
public void Resource_FoundAndReadable(string resourceName)
public class VerifyResources
{
var assembly = typeof(CoreHelpers).Assembly;
using (var resource = assembly.GetManifestResourceStream(resourceName))
[Theory]
[MemberData(nameof(GetResources))]
public void Resource_FoundAndReadable(string resourceName)
{
Assert.NotNull(resource);
Assert.True(resource.CanRead);
var assembly = typeof(CoreHelpers).Assembly;
using (var resource = assembly.GetManifestResourceStream(resourceName))
{
Assert.NotNull(resource);
Assert.True(resource.CanRead);
}
}
public static IEnumerable<object[]> GetResources()
{
yield return new[] { "Bit.Core.licensing.cer" };
yield return new[] { "Bit.Core.MailTemplates.Handlebars.AddedCredit.html.hbs" };
yield return new[] { "Bit.Core.MailTemplates.Handlebars.Layouts.Basic.html.hbs" };
}
}
public static IEnumerable<object[]> GetResources()
{
yield return new[] { "Bit.Core.licensing.cer" };
yield return new[] { "Bit.Core.MailTemplates.Handlebars.AddedCredit.html.hbs" };
yield return new[] { "Bit.Core.MailTemplates.Handlebars.Layouts.Basic.html.hbs" };
}
}

View File

@ -8,79 +8,80 @@ using Microsoft.Extensions.Logging;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services;
public class AmazonSesMailDeliveryServiceTests : IDisposable
namespace Bit.Core.Test.Services
{
private readonly AmazonSesMailDeliveryService _sut;
private readonly GlobalSettings _globalSettings;
private readonly IWebHostEnvironment _hostingEnvironment;
private readonly ILogger<AmazonSesMailDeliveryService> _logger;
private readonly IAmazonSimpleEmailService _amazonSimpleEmailService;
public AmazonSesMailDeliveryServiceTests()
public class AmazonSesMailDeliveryServiceTests : IDisposable
{
_globalSettings = new GlobalSettings
private readonly AmazonSesMailDeliveryService _sut;
private readonly GlobalSettings _globalSettings;
private readonly IWebHostEnvironment _hostingEnvironment;
private readonly ILogger<AmazonSesMailDeliveryService> _logger;
private readonly IAmazonSimpleEmailService _amazonSimpleEmailService;
public AmazonSesMailDeliveryServiceTests()
{
Amazon =
{
AccessKeyId = "AccessKeyId-AmazonSesMailDeliveryServiceTests",
AccessKeySecret = "AccessKeySecret-AmazonSesMailDeliveryServiceTests",
Region = "Region-AmazonSesMailDeliveryServiceTests"
}
};
_hostingEnvironment = Substitute.For<IWebHostEnvironment>();
_logger = Substitute.For<ILogger<AmazonSesMailDeliveryService>>();
_amazonSimpleEmailService = Substitute.For<IAmazonSimpleEmailService>();
_sut = new AmazonSesMailDeliveryService(
_globalSettings,
_hostingEnvironment,
_logger,
_amazonSimpleEmailService
);
}
public void Dispose()
{
_sut?.Dispose();
}
[Fact]
public async Task SendEmailAsync_CallsSendEmailAsync_WhenMessageIsValid()
{
var mailMessage = new MailMessage
{
ToEmails = new List<string> { "ToEmails" },
BccEmails = new List<string> { "BccEmails" },
Subject = "Subject",
HtmlContent = "HtmlContent",
TextContent = "TextContent",
Category = "Category"
};
await _sut.SendEmailAsync(mailMessage);
await _amazonSimpleEmailService.Received(1).SendEmailAsync(
Arg.Do<SendEmailRequest>(request =>
_globalSettings = new GlobalSettings
{
Assert.False(string.IsNullOrEmpty(request.Source));
Amazon =
{
AccessKeyId = "AccessKeyId-AmazonSesMailDeliveryServiceTests",
AccessKeySecret = "AccessKeySecret-AmazonSesMailDeliveryServiceTests",
Region = "Region-AmazonSesMailDeliveryServiceTests"
}
};
Assert.Single(request.Destination.ToAddresses);
Assert.Equal(mailMessage.ToEmails.First(), request.Destination.ToAddresses.First());
_hostingEnvironment = Substitute.For<IWebHostEnvironment>();
_logger = Substitute.For<ILogger<AmazonSesMailDeliveryService>>();
_amazonSimpleEmailService = Substitute.For<IAmazonSimpleEmailService>();
Assert.Equal(mailMessage.Subject, request.Message.Subject.Data);
Assert.Equal(mailMessage.HtmlContent, request.Message.Body.Html.Data);
Assert.Equal(mailMessage.TextContent, request.Message.Body.Text.Data);
_sut = new AmazonSesMailDeliveryService(
_globalSettings,
_hostingEnvironment,
_logger,
_amazonSimpleEmailService
);
}
Assert.Single(request.Destination.BccAddresses);
Assert.Equal(mailMessage.BccEmails.First(), request.Destination.BccAddresses.First());
public void Dispose()
{
_sut?.Dispose();
}
Assert.Contains(request.Tags, x => x.Name == "Environment");
Assert.Contains(request.Tags, x => x.Name == "Sender");
Assert.Contains(request.Tags, x => x.Name == "Category");
}));
[Fact]
public async Task SendEmailAsync_CallsSendEmailAsync_WhenMessageIsValid()
{
var mailMessage = new MailMessage
{
ToEmails = new List<string> { "ToEmails" },
BccEmails = new List<string> { "BccEmails" },
Subject = "Subject",
HtmlContent = "HtmlContent",
TextContent = "TextContent",
Category = "Category"
};
await _sut.SendEmailAsync(mailMessage);
await _amazonSimpleEmailService.Received(1).SendEmailAsync(
Arg.Do<SendEmailRequest>(request =>
{
Assert.False(string.IsNullOrEmpty(request.Source));
Assert.Single(request.Destination.ToAddresses);
Assert.Equal(mailMessage.ToEmails.First(), request.Destination.ToAddresses.First());
Assert.Equal(mailMessage.Subject, request.Message.Subject.Data);
Assert.Equal(mailMessage.HtmlContent, request.Message.Body.Html.Data);
Assert.Equal(mailMessage.TextContent, request.Message.Body.Text.Data);
Assert.Single(request.Destination.BccAddresses);
Assert.Equal(mailMessage.BccEmails.First(), request.Destination.BccAddresses.First());
Assert.Contains(request.Tags, x => x.Name == "Environment");
Assert.Contains(request.Tags, x => x.Name == "Sender");
Assert.Contains(request.Tags, x => x.Name == "Category");
}));
}
}
}

View File

@ -4,73 +4,74 @@ using Bit.Core.Settings;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services;
public class AmazonSqsBlockIpServiceTests : IDisposable
namespace Bit.Core.Test.Services
{
private readonly AmazonSqsBlockIpService _sut;
private readonly GlobalSettings _globalSettings;
private readonly IAmazonSQS _amazonSqs;
public AmazonSqsBlockIpServiceTests()
public class AmazonSqsBlockIpServiceTests : IDisposable
{
_globalSettings = new GlobalSettings
private readonly AmazonSqsBlockIpService _sut;
private readonly GlobalSettings _globalSettings;
private readonly IAmazonSQS _amazonSqs;
public AmazonSqsBlockIpServiceTests()
{
Amazon =
_globalSettings = new GlobalSettings
{
AccessKeyId = "AccessKeyId-AmazonSesMailDeliveryServiceTests",
AccessKeySecret = "AccessKeySecret-AmazonSesMailDeliveryServiceTests",
Region = "Region-AmazonSesMailDeliveryServiceTests"
}
};
Amazon =
{
AccessKeyId = "AccessKeyId-AmazonSesMailDeliveryServiceTests",
AccessKeySecret = "AccessKeySecret-AmazonSesMailDeliveryServiceTests",
Region = "Region-AmazonSesMailDeliveryServiceTests"
}
};
_amazonSqs = Substitute.For<IAmazonSQS>();
_amazonSqs = Substitute.For<IAmazonSQS>();
_sut = new AmazonSqsBlockIpService(_globalSettings, _amazonSqs);
}
_sut = new AmazonSqsBlockIpService(_globalSettings, _amazonSqs);
}
public void Dispose()
{
_sut?.Dispose();
}
public void Dispose()
{
_sut?.Dispose();
}
[Fact]
public async Task BlockIpAsync_UnblockCalled_WhenNotPermanent()
{
const string expectedIp = "ip";
[Fact]
public async Task BlockIpAsync_UnblockCalled_WhenNotPermanent()
{
const string expectedIp = "ip";
await _sut.BlockIpAsync(expectedIp, false);
await _sut.BlockIpAsync(expectedIp, false);
await _amazonSqs.Received(2).SendMessageAsync(
Arg.Any<string>(),
Arg.Is(expectedIp));
}
await _amazonSqs.Received(2).SendMessageAsync(
Arg.Any<string>(),
Arg.Is(expectedIp));
}
[Fact]
public async Task BlockIpAsync_UnblockNotCalled_WhenPermanent()
{
const string expectedIp = "ip";
[Fact]
public async Task BlockIpAsync_UnblockNotCalled_WhenPermanent()
{
const string expectedIp = "ip";
await _sut.BlockIpAsync(expectedIp, true);
await _sut.BlockIpAsync(expectedIp, true);
await _amazonSqs.Received(1).SendMessageAsync(
Arg.Any<string>(),
Arg.Is(expectedIp));
}
await _amazonSqs.Received(1).SendMessageAsync(
Arg.Any<string>(),
Arg.Is(expectedIp));
}
[Fact]
public async Task BlockIpAsync_NotBlocked_WhenAlreadyBlockedRecently()
{
const string expectedIp = "ip";
[Fact]
public async Task BlockIpAsync_NotBlocked_WhenAlreadyBlockedRecently()
{
const string expectedIp = "ip";
await _sut.BlockIpAsync(expectedIp, true);
await _sut.BlockIpAsync(expectedIp, true);
// The second call should hit the already blocked guard clause
await _sut.BlockIpAsync(expectedIp, true);
// The second call should hit the already blocked guard clause
await _sut.BlockIpAsync(expectedIp, true);
await _amazonSqs.Received(1).SendMessageAsync(
Arg.Any<string>(),
Arg.Is(expectedIp));
await _amazonSqs.Received(1).SendMessageAsync(
Arg.Any<string>(),
Arg.Is(expectedIp));
}
}
}

View File

@ -6,35 +6,36 @@ using NSubstitute;
using NSubstitute.Core;
using Xunit;
namespace Bit.Core.Test.Services;
[SutProviderCustomize]
public class AppleIapServiceTests
namespace Bit.Core.Test.Services
{
[Theory, BitAutoData]
public async Task GetReceiptStatusAsync_MoreThanFourAttempts_Throws(SutProvider<AppleIapService> sutProvider)
[SutProviderCustomize]
public class AppleIapServiceTests
{
var result = await sutProvider.Sut.GetReceiptStatusAsync("test", false, 5, null);
Assert.Null(result);
var errorLog = sutProvider.GetDependency<ILogger<AppleIapService>>()
.ReceivedCalls()
.SingleOrDefault(LogOneWarning);
Assert.True(errorLog != null, "Must contain one error log of warning level containing 'null'");
static bool LogOneWarning(ICall call)
[Theory, BitAutoData]
public async Task GetReceiptStatusAsync_MoreThanFourAttempts_Throws(SutProvider<AppleIapService> sutProvider)
{
if (call.GetMethodInfo().Name != "Log")
var result = await sutProvider.Sut.GetReceiptStatusAsync("test", false, 5, null);
Assert.Null(result);
var errorLog = sutProvider.GetDependency<ILogger<AppleIapService>>()
.ReceivedCalls()
.SingleOrDefault(LogOneWarning);
Assert.True(errorLog != null, "Must contain one error log of warning level containing 'null'");
static bool LogOneWarning(ICall call)
{
return false;
if (call.GetMethodInfo().Name != "Log")
{
return false;
}
var args = call.GetArguments();
var logLevel = (LogLevel)args[0];
var exception = (Exception)args[3];
return logLevel == LogLevel.Warning && exception.Message.Contains("null");
}
var args = call.GetArguments();
var logLevel = (LogLevel)args[0];
var exception = (Exception)args[3];
return logLevel == LogLevel.Warning && exception.Message.Contains("null");
}
}
}

View File

@ -4,28 +4,29 @@ using Microsoft.Extensions.Logging;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services;
public class AzureAttachmentStorageServiceTests
namespace Bit.Core.Test.Services
{
private readonly AzureAttachmentStorageService _sut;
private readonly GlobalSettings _globalSettings;
private readonly ILogger<AzureAttachmentStorageService> _logger;
public AzureAttachmentStorageServiceTests()
public class AzureAttachmentStorageServiceTests
{
_globalSettings = new GlobalSettings();
_logger = Substitute.For<ILogger<AzureAttachmentStorageService>>();
private readonly AzureAttachmentStorageService _sut;
_sut = new AzureAttachmentStorageService(_globalSettings, _logger);
}
private readonly GlobalSettings _globalSettings;
private readonly ILogger<AzureAttachmentStorageService> _logger;
// Remove this test when we add actual tests. It only proves that
// we've properly constructed the system under test.
[Fact(Skip = "Needs additional work")]
public void ServiceExists()
{
Assert.NotNull(_sut);
public AzureAttachmentStorageServiceTests()
{
_globalSettings = new GlobalSettings();
_logger = Substitute.For<ILogger<AzureAttachmentStorageService>>();
_sut = new AzureAttachmentStorageService(_globalSettings, _logger);
}
// Remove this test when we add actual tests. It only proves that
// we've properly constructed the system under test.
[Fact(Skip = "Needs additional work")]
public void ServiceExists()
{
Assert.NotNull(_sut);
}
}
}

View File

@ -2,26 +2,27 @@
using Bit.Core.Settings;
using Xunit;
namespace Bit.Core.Test.Services;
public class AzureQueueBlockIpServiceTests
namespace Bit.Core.Test.Services
{
private readonly AzureQueueBlockIpService _sut;
private readonly GlobalSettings _globalSettings;
public AzureQueueBlockIpServiceTests()
public class AzureQueueBlockIpServiceTests
{
_globalSettings = new GlobalSettings();
private readonly AzureQueueBlockIpService _sut;
_sut = new AzureQueueBlockIpService(_globalSettings);
}
private readonly GlobalSettings _globalSettings;
// Remove this test when we add actual tests. It only proves that
// we've properly constructed the system under test.
[Fact(Skip = "Needs additional work")]
public void ServiceExists()
{
Assert.NotNull(_sut);
public AzureQueueBlockIpServiceTests()
{
_globalSettings = new GlobalSettings();
_sut = new AzureQueueBlockIpService(_globalSettings);
}
// Remove this test when we add actual tests. It only proves that
// we've properly constructed the system under test.
[Fact(Skip = "Needs additional work")]
public void ServiceExists()
{
Assert.NotNull(_sut);
}
}
}

View File

@ -4,30 +4,31 @@ using Bit.Core.Settings;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services;
public class AzureQueueEventWriteServiceTests
namespace Bit.Core.Test.Services
{
private readonly AzureQueueEventWriteService _sut;
private readonly GlobalSettings _globalSettings;
private readonly IEventRepository _eventRepository;
public AzureQueueEventWriteServiceTests()
public class AzureQueueEventWriteServiceTests
{
_globalSettings = new GlobalSettings();
_eventRepository = Substitute.For<IEventRepository>();
private readonly AzureQueueEventWriteService _sut;
_sut = new AzureQueueEventWriteService(
_globalSettings
);
}
private readonly GlobalSettings _globalSettings;
private readonly IEventRepository _eventRepository;
// Remove this test when we add actual tests. It only proves that
// we've properly constructed the system under test.
[Fact(Skip = "Needs additional work")]
public void ServiceExists()
{
Assert.NotNull(_sut);
public AzureQueueEventWriteServiceTests()
{
_globalSettings = new GlobalSettings();
_eventRepository = Substitute.For<IEventRepository>();
_sut = new AzureQueueEventWriteService(
_globalSettings
);
}
// Remove this test when we add actual tests. It only proves that
// we've properly constructed the system under test.
[Fact(Skip = "Needs additional work")]
public void ServiceExists()
{
Assert.NotNull(_sut);
}
}
}

View File

@ -4,31 +4,32 @@ using Microsoft.AspNetCore.Http;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services;
public class AzureQueuePushNotificationServiceTests
namespace Bit.Core.Test.Services
{
private readonly AzureQueuePushNotificationService _sut;
private readonly GlobalSettings _globalSettings;
private readonly IHttpContextAccessor _httpContextAccessor;
public AzureQueuePushNotificationServiceTests()
public class AzureQueuePushNotificationServiceTests
{
_globalSettings = new GlobalSettings();
_httpContextAccessor = Substitute.For<IHttpContextAccessor>();
private readonly AzureQueuePushNotificationService _sut;
_sut = new AzureQueuePushNotificationService(
_globalSettings,
_httpContextAccessor
);
}
private readonly GlobalSettings _globalSettings;
private readonly IHttpContextAccessor _httpContextAccessor;
// Remove this test when we add actual tests. It only proves that
// we've properly constructed the system under test.
[Fact(Skip = "Needs additional work")]
public void ServiceExists()
{
Assert.NotNull(_sut);
public AzureQueuePushNotificationServiceTests()
{
_globalSettings = new GlobalSettings();
_httpContextAccessor = Substitute.For<IHttpContextAccessor>();
_sut = new AzureQueuePushNotificationService(
_globalSettings,
_httpContextAccessor
);
}
// Remove this test when we add actual tests. It only proves that
// we've properly constructed the system under test.
[Fact(Skip = "Needs additional work")]
public void ServiceExists()
{
Assert.NotNull(_sut);
}
}
}

View File

@ -9,208 +9,209 @@ using Core.Models.Data;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services;
public class CipherServiceTests
namespace Bit.Core.Test.Services
{
[Theory, UserCipherAutoData]
public async Task SaveAsync_WrongRevisionDate_Throws(SutProvider<CipherService> sutProvider, Cipher cipher)
public class CipherServiceTests
{
var lastKnownRevisionDate = cipher.RevisionDate.AddDays(-1);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveAsync(cipher, cipher.UserId.Value, lastKnownRevisionDate));
Assert.Contains("out of date", exception.Message);
}
[Theory, UserCipherAutoData]
public async Task SaveDetailsAsync_WrongRevisionDate_Throws(SutProvider<CipherService> sutProvider,
CipherDetails cipherDetails)
{
var lastKnownRevisionDate = cipherDetails.RevisionDate.AddDays(-1);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveDetailsAsync(cipherDetails, cipherDetails.UserId.Value, lastKnownRevisionDate));
Assert.Contains("out of date", exception.Message);
}
[Theory, UserCipherAutoData]
public async Task ShareAsync_WrongRevisionDate_Throws(SutProvider<CipherService> sutProvider, Cipher cipher,
Organization organization, List<Guid> collectionIds)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var lastKnownRevisionDate = cipher.RevisionDate.AddDays(-1);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ShareAsync(cipher, cipher, organization.Id, collectionIds, cipher.UserId.Value,
lastKnownRevisionDate));
Assert.Contains("out of date", exception.Message);
}
[Theory, UserCipherAutoData("99ab4f6c-44f8-4ff5-be7a-75c37c33c69e")]
public async Task ShareManyAsync_WrongRevisionDate_Throws(SutProvider<CipherService> sutProvider,
IEnumerable<Cipher> ciphers, Guid organizationId, List<Guid> collectionIds)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId)
.Returns(new Organization
{
PlanType = Enums.PlanType.EnterpriseAnnually,
MaxStorageGb = 100
});
var cipherInfos = ciphers.Select(c => (c, (DateTime?)c.RevisionDate.AddDays(-1)));
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ShareManyAsync(cipherInfos, organizationId, collectionIds, ciphers.First().UserId.Value));
Assert.Contains("out of date", exception.Message);
}
[Theory]
[InlineUserCipherAutoData("")]
[InlineUserCipherAutoData("Correct Time")]
public async Task SaveAsync_CorrectRevisionDate_Passes(string revisionDateString,
SutProvider<CipherService> sutProvider, Cipher cipher)
{
var lastKnownRevisionDate = string.IsNullOrEmpty(revisionDateString) ? (DateTime?)null : cipher.RevisionDate;
await sutProvider.Sut.SaveAsync(cipher, cipher.UserId.Value, lastKnownRevisionDate);
await sutProvider.GetDependency<ICipherRepository>().Received(1).ReplaceAsync(cipher);
}
[Theory]
[InlineUserCipherAutoData("")]
[InlineUserCipherAutoData("Correct Time")]
public async Task SaveDetailsAsync_CorrectRevisionDate_Passes(string revisionDateString,
SutProvider<CipherService> sutProvider, CipherDetails cipherDetails)
{
var lastKnownRevisionDate = string.IsNullOrEmpty(revisionDateString) ? (DateTime?)null : cipherDetails.RevisionDate;
await sutProvider.Sut.SaveDetailsAsync(cipherDetails, cipherDetails.UserId.Value, lastKnownRevisionDate);
await sutProvider.GetDependency<ICipherRepository>().Received(1).ReplaceAsync(cipherDetails);
}
[Theory]
[InlineUserCipherAutoData("")]
[InlineUserCipherAutoData("Correct Time")]
public async Task ShareAsync_CorrectRevisionDate_Passes(string revisionDateString,
SutProvider<CipherService> sutProvider, Cipher cipher, Organization organization, List<Guid> collectionIds)
{
var lastKnownRevisionDate = string.IsNullOrEmpty(revisionDateString) ? (DateTime?)null : cipher.RevisionDate;
var cipherRepository = sutProvider.GetDependency<ICipherRepository>();
cipherRepository.ReplaceAsync(cipher, collectionIds).Returns(true);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
await sutProvider.Sut.ShareAsync(cipher, cipher, organization.Id, collectionIds, cipher.UserId.Value,
lastKnownRevisionDate);
await cipherRepository.Received(1).ReplaceAsync(cipher, collectionIds);
}
[Theory]
[InlineKnownUserCipherAutoData(userId: "99ab4f6c-44f8-4ff5-be7a-75c37c33c69e", "")]
[InlineKnownUserCipherAutoData(userId: "99ab4f6c-44f8-4ff5-be7a-75c37c33c69e", "CorrectTime")]
public async Task ShareManyAsync_CorrectRevisionDate_Passes(string revisionDateString,
SutProvider<CipherService> sutProvider, IEnumerable<Cipher> ciphers, Organization organization, List<Guid> collectionIds)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id)
.Returns(new Organization
{
PlanType = Enums.PlanType.EnterpriseAnnually,
MaxStorageGb = 100
});
var cipherInfos = ciphers.Select(c => (c,
string.IsNullOrEmpty(revisionDateString) ? null : (DateTime?)c.RevisionDate));
var sharingUserId = ciphers.First().UserId.Value;
await sutProvider.Sut.ShareManyAsync(cipherInfos, organization.Id, collectionIds, sharingUserId);
await sutProvider.GetDependency<ICipherRepository>().Received(1).UpdateCiphersAsync(sharingUserId,
Arg.Is<IEnumerable<Cipher>>(arg => arg.Except(ciphers).IsNullOrEmpty()));
}
[Theory]
[InlineKnownUserCipherAutoData("c64d8a15-606e-41d6-9c7e-174d4d8f3b2e", "c64d8a15-606e-41d6-9c7e-174d4d8f3b2e")]
[InlineOrganizationCipherAutoData("c64d8a15-606e-41d6-9c7e-174d4d8f3b2e")]
public async Task RestoreAsync_UpdatesCipher(Guid restoringUserId, Cipher cipher, SutProvider<CipherService> sutProvider)
{
sutProvider.GetDependency<ICipherRepository>().GetCanEditByIdAsync(restoringUserId, cipher.Id).Returns(true);
var initialRevisionDate = new DateTime(1970, 1, 1, 0, 0, 0);
cipher.DeletedDate = initialRevisionDate;
cipher.RevisionDate = initialRevisionDate;
await sutProvider.Sut.RestoreAsync(cipher, restoringUserId, cipher.OrganizationId.HasValue);
Assert.Null(cipher.DeletedDate);
Assert.NotEqual(initialRevisionDate, cipher.RevisionDate);
}
[Theory]
[InlineKnownUserCipherAutoData("c64d8a15-606e-41d6-9c7e-174d4d8f3b2e", "c64d8a15-606e-41d6-9c7e-174d4d8f3b2e")]
public async Task RestoreManyAsync_UpdatesCiphers(Guid restoringUserId, IEnumerable<CipherDetails> ciphers,
SutProvider<CipherService> sutProvider)
{
var previousRevisionDate = DateTime.UtcNow;
foreach (var cipher in ciphers)
[Theory, UserCipherAutoData]
public async Task SaveAsync_WrongRevisionDate_Throws(SutProvider<CipherService> sutProvider, Cipher cipher)
{
cipher.RevisionDate = previousRevisionDate;
var lastKnownRevisionDate = cipher.RevisionDate.AddDays(-1);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveAsync(cipher, cipher.UserId.Value, lastKnownRevisionDate));
Assert.Contains("out of date", exception.Message);
}
var revisionDate = previousRevisionDate + TimeSpan.FromMinutes(1);
sutProvider.GetDependency<ICipherRepository>().RestoreAsync(Arg.Any<IEnumerable<Guid>>(), restoringUserId)
.Returns(revisionDate);
await sutProvider.Sut.RestoreManyAsync(ciphers, restoringUserId);
foreach (var cipher in ciphers)
[Theory, UserCipherAutoData]
public async Task SaveDetailsAsync_WrongRevisionDate_Throws(SutProvider<CipherService> sutProvider,
CipherDetails cipherDetails)
{
var lastKnownRevisionDate = cipherDetails.RevisionDate.AddDays(-1);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveDetailsAsync(cipherDetails, cipherDetails.UserId.Value, lastKnownRevisionDate));
Assert.Contains("out of date", exception.Message);
}
[Theory, UserCipherAutoData]
public async Task ShareAsync_WrongRevisionDate_Throws(SutProvider<CipherService> sutProvider, Cipher cipher,
Organization organization, List<Guid> collectionIds)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var lastKnownRevisionDate = cipher.RevisionDate.AddDays(-1);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ShareAsync(cipher, cipher, organization.Id, collectionIds, cipher.UserId.Value,
lastKnownRevisionDate));
Assert.Contains("out of date", exception.Message);
}
[Theory, UserCipherAutoData("99ab4f6c-44f8-4ff5-be7a-75c37c33c69e")]
public async Task ShareManyAsync_WrongRevisionDate_Throws(SutProvider<CipherService> sutProvider,
IEnumerable<Cipher> ciphers, Guid organizationId, List<Guid> collectionIds)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId)
.Returns(new Organization
{
PlanType = Enums.PlanType.EnterpriseAnnually,
MaxStorageGb = 100
});
var cipherInfos = ciphers.Select(c => (c, (DateTime?)c.RevisionDate.AddDays(-1)));
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ShareManyAsync(cipherInfos, organizationId, collectionIds, ciphers.First().UserId.Value));
Assert.Contains("out of date", exception.Message);
}
[Theory]
[InlineUserCipherAutoData("")]
[InlineUserCipherAutoData("Correct Time")]
public async Task SaveAsync_CorrectRevisionDate_Passes(string revisionDateString,
SutProvider<CipherService> sutProvider, Cipher cipher)
{
var lastKnownRevisionDate = string.IsNullOrEmpty(revisionDateString) ? (DateTime?)null : cipher.RevisionDate;
await sutProvider.Sut.SaveAsync(cipher, cipher.UserId.Value, lastKnownRevisionDate);
await sutProvider.GetDependency<ICipherRepository>().Received(1).ReplaceAsync(cipher);
}
[Theory]
[InlineUserCipherAutoData("")]
[InlineUserCipherAutoData("Correct Time")]
public async Task SaveDetailsAsync_CorrectRevisionDate_Passes(string revisionDateString,
SutProvider<CipherService> sutProvider, CipherDetails cipherDetails)
{
var lastKnownRevisionDate = string.IsNullOrEmpty(revisionDateString) ? (DateTime?)null : cipherDetails.RevisionDate;
await sutProvider.Sut.SaveDetailsAsync(cipherDetails, cipherDetails.UserId.Value, lastKnownRevisionDate);
await sutProvider.GetDependency<ICipherRepository>().Received(1).ReplaceAsync(cipherDetails);
}
[Theory]
[InlineUserCipherAutoData("")]
[InlineUserCipherAutoData("Correct Time")]
public async Task ShareAsync_CorrectRevisionDate_Passes(string revisionDateString,
SutProvider<CipherService> sutProvider, Cipher cipher, Organization organization, List<Guid> collectionIds)
{
var lastKnownRevisionDate = string.IsNullOrEmpty(revisionDateString) ? (DateTime?)null : cipher.RevisionDate;
var cipherRepository = sutProvider.GetDependency<ICipherRepository>();
cipherRepository.ReplaceAsync(cipher, collectionIds).Returns(true);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
await sutProvider.Sut.ShareAsync(cipher, cipher, organization.Id, collectionIds, cipher.UserId.Value,
lastKnownRevisionDate);
await cipherRepository.Received(1).ReplaceAsync(cipher, collectionIds);
}
[Theory]
[InlineKnownUserCipherAutoData(userId: "99ab4f6c-44f8-4ff5-be7a-75c37c33c69e", "")]
[InlineKnownUserCipherAutoData(userId: "99ab4f6c-44f8-4ff5-be7a-75c37c33c69e", "CorrectTime")]
public async Task ShareManyAsync_CorrectRevisionDate_Passes(string revisionDateString,
SutProvider<CipherService> sutProvider, IEnumerable<Cipher> ciphers, Organization organization, List<Guid> collectionIds)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id)
.Returns(new Organization
{
PlanType = Enums.PlanType.EnterpriseAnnually,
MaxStorageGb = 100
});
var cipherInfos = ciphers.Select(c => (c,
string.IsNullOrEmpty(revisionDateString) ? null : (DateTime?)c.RevisionDate));
var sharingUserId = ciphers.First().UserId.Value;
await sutProvider.Sut.ShareManyAsync(cipherInfos, organization.Id, collectionIds, sharingUserId);
await sutProvider.GetDependency<ICipherRepository>().Received(1).UpdateCiphersAsync(sharingUserId,
Arg.Is<IEnumerable<Cipher>>(arg => arg.Except(ciphers).IsNullOrEmpty()));
}
[Theory]
[InlineKnownUserCipherAutoData("c64d8a15-606e-41d6-9c7e-174d4d8f3b2e", "c64d8a15-606e-41d6-9c7e-174d4d8f3b2e")]
[InlineOrganizationCipherAutoData("c64d8a15-606e-41d6-9c7e-174d4d8f3b2e")]
public async Task RestoreAsync_UpdatesCipher(Guid restoringUserId, Cipher cipher, SutProvider<CipherService> sutProvider)
{
sutProvider.GetDependency<ICipherRepository>().GetCanEditByIdAsync(restoringUserId, cipher.Id).Returns(true);
var initialRevisionDate = new DateTime(1970, 1, 1, 0, 0, 0);
cipher.DeletedDate = initialRevisionDate;
cipher.RevisionDate = initialRevisionDate;
await sutProvider.Sut.RestoreAsync(cipher, restoringUserId, cipher.OrganizationId.HasValue);
Assert.Null(cipher.DeletedDate);
Assert.Equal(revisionDate, cipher.RevisionDate);
Assert.NotEqual(initialRevisionDate, cipher.RevisionDate);
}
}
[Theory]
[InlineUserCipherAutoData]
public async Task ShareManyAsync_FreeOrgWithAttachment_Throws(SutProvider<CipherService> sutProvider,
IEnumerable<Cipher> ciphers, Guid organizationId, List<Guid> collectionIds)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(new Organization
[Theory]
[InlineKnownUserCipherAutoData("c64d8a15-606e-41d6-9c7e-174d4d8f3b2e", "c64d8a15-606e-41d6-9c7e-174d4d8f3b2e")]
public async Task RestoreManyAsync_UpdatesCiphers(Guid restoringUserId, IEnumerable<CipherDetails> ciphers,
SutProvider<CipherService> sutProvider)
{
PlanType = Enums.PlanType.Free
});
ciphers.FirstOrDefault().Attachments =
"{\"attachment1\":{\"Size\":\"250\",\"FileName\":\"superCoolFile\","
+ "\"Key\":\"superCoolFile\",\"ContainerName\":\"testContainer\",\"Validated\":false}}";
var cipherInfos = ciphers.Select(c => (c,
(DateTime?)c.RevisionDate));
var sharingUserId = ciphers.First().UserId.Value;
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ShareManyAsync(cipherInfos, organizationId, collectionIds, sharingUserId));
Assert.Contains("This organization cannot use attachments", exception.Message);
}
[Theory]
[InlineUserCipherAutoData]
public async Task ShareManyAsync_PaidOrgWithAttachment_Passes(SutProvider<CipherService> sutProvider,
IEnumerable<Cipher> ciphers, Guid organizationId, List<Guid> collectionIds)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId)
.Returns(new Organization
var previousRevisionDate = DateTime.UtcNow;
foreach (var cipher in ciphers)
{
PlanType = Enums.PlanType.EnterpriseAnnually,
MaxStorageGb = 100
cipher.RevisionDate = previousRevisionDate;
}
var revisionDate = previousRevisionDate + TimeSpan.FromMinutes(1);
sutProvider.GetDependency<ICipherRepository>().RestoreAsync(Arg.Any<IEnumerable<Guid>>(), restoringUserId)
.Returns(revisionDate);
await sutProvider.Sut.RestoreManyAsync(ciphers, restoringUserId);
foreach (var cipher in ciphers)
{
Assert.Null(cipher.DeletedDate);
Assert.Equal(revisionDate, cipher.RevisionDate);
}
}
[Theory]
[InlineUserCipherAutoData]
public async Task ShareManyAsync_FreeOrgWithAttachment_Throws(SutProvider<CipherService> sutProvider,
IEnumerable<Cipher> ciphers, Guid organizationId, List<Guid> collectionIds)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(new Organization
{
PlanType = Enums.PlanType.Free
});
ciphers.FirstOrDefault().Attachments =
"{\"attachment1\":{\"Size\":\"250\",\"FileName\":\"superCoolFile\","
+ "\"Key\":\"superCoolFile\",\"ContainerName\":\"testContainer\",\"Validated\":false}}";
ciphers.FirstOrDefault().Attachments =
"{\"attachment1\":{\"Size\":\"250\",\"FileName\":\"superCoolFile\","
+ "\"Key\":\"superCoolFile\",\"ContainerName\":\"testContainer\",\"Validated\":false}}";
var cipherInfos = ciphers.Select(c => (c,
(DateTime?)c.RevisionDate));
var sharingUserId = ciphers.First().UserId.Value;
var cipherInfos = ciphers.Select(c => (c,
(DateTime?)c.RevisionDate));
var sharingUserId = ciphers.First().UserId.Value;
await sutProvider.Sut.ShareManyAsync(cipherInfos, organizationId, collectionIds, sharingUserId);
await sutProvider.GetDependency<ICipherRepository>().Received(1).UpdateCiphersAsync(sharingUserId,
Arg.Is<IEnumerable<Cipher>>(arg => arg.Except(ciphers).IsNullOrEmpty()));
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ShareManyAsync(cipherInfos, organizationId, collectionIds, sharingUserId));
Assert.Contains("This organization cannot use attachments", exception.Message);
}
[Theory]
[InlineUserCipherAutoData]
public async Task ShareManyAsync_PaidOrgWithAttachment_Passes(SutProvider<CipherService> sutProvider,
IEnumerable<Cipher> ciphers, Guid organizationId, List<Guid> collectionIds)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId)
.Returns(new Organization
{
PlanType = Enums.PlanType.EnterpriseAnnually,
MaxStorageGb = 100
});
ciphers.FirstOrDefault().Attachments =
"{\"attachment1\":{\"Size\":\"250\",\"FileName\":\"superCoolFile\","
+ "\"Key\":\"superCoolFile\",\"ContainerName\":\"testContainer\",\"Validated\":false}}";
var cipherInfos = ciphers.Select(c => (c,
(DateTime?)c.RevisionDate));
var sharingUserId = ciphers.First().UserId.Value;
await sutProvider.Sut.ShareManyAsync(cipherInfos, organizationId, collectionIds, sharingUserId);
await sutProvider.GetDependency<ICipherRepository>().Received(1).UpdateCiphersAsync(sharingUserId,
Arg.Is<IEnumerable<Cipher>>(arg => arg.Except(ciphers).IsNullOrEmpty()));
}
}
}

View File

@ -10,160 +10,161 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services;
public class CollectionServiceTest
namespace Bit.Core.Test.Services
{
[Theory, CollectionAutoData]
public async Task SaveAsync_DefaultId_CreatesCollectionInTheRepository(Collection collection, Organization organization, SutProvider<CollectionService> sutProvider)
public class CollectionServiceTest
{
collection.Id = default;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var utcNow = DateTime.UtcNow;
[Theory, CollectionAutoData]
public async Task SaveAsync_DefaultId_CreatesCollectionInTheRepository(Collection collection, Organization organization, SutProvider<CollectionService> sutProvider)
{
collection.Id = default;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var utcNow = DateTime.UtcNow;
await sutProvider.Sut.SaveAsync(collection);
await sutProvider.Sut.SaveAsync(collection);
await sutProvider.GetDependency<ICollectionRepository>().Received().CreateAsync(collection);
await sutProvider.GetDependency<IEventService>().Received()
.LogCollectionEventAsync(collection, EventType.Collection_Created);
Assert.True(collection.CreationDate - utcNow < TimeSpan.FromSeconds(1));
Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
}
await sutProvider.GetDependency<ICollectionRepository>().Received().CreateAsync(collection);
await sutProvider.GetDependency<IEventService>().Received()
.LogCollectionEventAsync(collection, EventType.Collection_Created);
Assert.True(collection.CreationDate - utcNow < TimeSpan.FromSeconds(1));
Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
}
[Theory, CollectionAutoData]
public async Task SaveAsync_DefaultIdWithGroups_CreateCollectionWithGroupsInRepository(Collection collection,
IEnumerable<SelectionReadOnly> groups, Organization organization, SutProvider<CollectionService> sutProvider)
{
collection.Id = default;
organization.UseGroups = true;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var utcNow = DateTime.UtcNow;
[Theory, CollectionAutoData]
public async Task SaveAsync_DefaultIdWithGroups_CreateCollectionWithGroupsInRepository(Collection collection,
IEnumerable<SelectionReadOnly> groups, Organization organization, SutProvider<CollectionService> sutProvider)
{
collection.Id = default;
organization.UseGroups = true;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var utcNow = DateTime.UtcNow;
await sutProvider.Sut.SaveAsync(collection, groups);
await sutProvider.Sut.SaveAsync(collection, groups);
await sutProvider.GetDependency<ICollectionRepository>().Received().CreateAsync(collection, groups);
await sutProvider.GetDependency<IEventService>().Received()
.LogCollectionEventAsync(collection, EventType.Collection_Created);
Assert.True(collection.CreationDate - utcNow < TimeSpan.FromSeconds(1));
Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
}
await sutProvider.GetDependency<ICollectionRepository>().Received().CreateAsync(collection, groups);
await sutProvider.GetDependency<IEventService>().Received()
.LogCollectionEventAsync(collection, EventType.Collection_Created);
Assert.True(collection.CreationDate - utcNow < TimeSpan.FromSeconds(1));
Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
}
[Theory, CollectionAutoData]
public async Task SaveAsync_NonDefaultId_ReplacesCollectionInRepository(Collection collection, Organization organization, SutProvider<CollectionService> sutProvider)
{
var creationDate = collection.CreationDate;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var utcNow = DateTime.UtcNow;
[Theory, CollectionAutoData]
public async Task SaveAsync_NonDefaultId_ReplacesCollectionInRepository(Collection collection, Organization organization, SutProvider<CollectionService> sutProvider)
{
var creationDate = collection.CreationDate;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var utcNow = DateTime.UtcNow;
await sutProvider.Sut.SaveAsync(collection);
await sutProvider.Sut.SaveAsync(collection);
await sutProvider.GetDependency<ICollectionRepository>().Received().ReplaceAsync(collection);
await sutProvider.GetDependency<IEventService>().Received()
.LogCollectionEventAsync(collection, EventType.Collection_Updated);
Assert.Equal(collection.CreationDate, creationDate);
Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
}
await sutProvider.GetDependency<ICollectionRepository>().Received().ReplaceAsync(collection);
await sutProvider.GetDependency<IEventService>().Received()
.LogCollectionEventAsync(collection, EventType.Collection_Updated);
Assert.Equal(collection.CreationDate, creationDate);
Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
}
[Theory, CollectionAutoData]
public async Task SaveAsync_OrganizationNotUseGroup_CreateCollectionWithoutGroupsInRepository(Collection collection, IEnumerable<SelectionReadOnly> groups,
Organization organization, SutProvider<CollectionService> sutProvider)
{
collection.Id = default;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var utcNow = DateTime.UtcNow;
[Theory, CollectionAutoData]
public async Task SaveAsync_OrganizationNotUseGroup_CreateCollectionWithoutGroupsInRepository(Collection collection, IEnumerable<SelectionReadOnly> groups,
Organization organization, SutProvider<CollectionService> sutProvider)
{
collection.Id = default;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var utcNow = DateTime.UtcNow;
await sutProvider.Sut.SaveAsync(collection, groups);
await sutProvider.Sut.SaveAsync(collection, groups);
await sutProvider.GetDependency<ICollectionRepository>().Received().CreateAsync(collection);
await sutProvider.GetDependency<IEventService>().Received()
.LogCollectionEventAsync(collection, EventType.Collection_Created);
Assert.True(collection.CreationDate - utcNow < TimeSpan.FromSeconds(1));
Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
}
await sutProvider.GetDependency<ICollectionRepository>().Received().CreateAsync(collection);
await sutProvider.GetDependency<IEventService>().Received()
.LogCollectionEventAsync(collection, EventType.Collection_Created);
Assert.True(collection.CreationDate - utcNow < TimeSpan.FromSeconds(1));
Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
}
[Theory, CollectionAutoData]
public async Task SaveAsync_DefaultIdWithUserId_UpdateUserInCollectionRepository(Collection collection,
Organization organization, OrganizationUser organizationUser, SutProvider<CollectionService> sutProvider)
{
collection.Id = default;
organizationUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<IOrganizationUserRepository>().GetByOrganizationAsync(organization.Id, organizationUser.Id)
.Returns(organizationUser);
var utcNow = DateTime.UtcNow;
[Theory, CollectionAutoData]
public async Task SaveAsync_DefaultIdWithUserId_UpdateUserInCollectionRepository(Collection collection,
Organization organization, OrganizationUser organizationUser, SutProvider<CollectionService> sutProvider)
{
collection.Id = default;
organizationUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<IOrganizationUserRepository>().GetByOrganizationAsync(organization.Id, organizationUser.Id)
.Returns(organizationUser);
var utcNow = DateTime.UtcNow;
await sutProvider.Sut.SaveAsync(collection, null, organizationUser.Id);
await sutProvider.Sut.SaveAsync(collection, null, organizationUser.Id);
await sutProvider.GetDependency<ICollectionRepository>().Received().CreateAsync(collection);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received()
.GetByOrganizationAsync(organization.Id, organizationUser.Id);
await sutProvider.GetDependency<ICollectionRepository>().Received().UpdateUsersAsync(collection.Id, Arg.Any<List<SelectionReadOnly>>());
await sutProvider.GetDependency<IEventService>().Received()
.LogCollectionEventAsync(collection, EventType.Collection_Created);
Assert.True(collection.CreationDate - utcNow < TimeSpan.FromSeconds(1));
Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
}
await sutProvider.GetDependency<ICollectionRepository>().Received().CreateAsync(collection);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received()
.GetByOrganizationAsync(organization.Id, organizationUser.Id);
await sutProvider.GetDependency<ICollectionRepository>().Received().UpdateUsersAsync(collection.Id, Arg.Any<List<SelectionReadOnly>>());
await sutProvider.GetDependency<IEventService>().Received()
.LogCollectionEventAsync(collection, EventType.Collection_Created);
Assert.True(collection.CreationDate - utcNow < TimeSpan.FromSeconds(1));
Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task SaveAsync_NonExistingOrganizationId_ThrowsBadRequest(Collection collection, SutProvider<CollectionService> sutProvider)
{
var ex = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SaveAsync(collection));
Assert.Contains("Organization not found", ex.Message);
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default);
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default, default);
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogCollectionEventAsync(default, default);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task SaveAsync_NonExistingOrganizationId_ThrowsBadRequest(Collection collection, SutProvider<CollectionService> sutProvider)
{
var ex = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SaveAsync(collection));
Assert.Contains("Organization not found", ex.Message);
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default);
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default, default);
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogCollectionEventAsync(default, default);
}
[Theory, CollectionAutoData]
public async Task SaveAsync_ExceedsOrganizationMaxCollections_ThrowsBadRequest(Collection collection, Organization organization, SutProvider<CollectionService> sutProvider)
{
collection.Id = default;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<ICollectionRepository>().GetCountByOrganizationIdAsync(organization.Id)
.Returns(organization.MaxCollections.Value);
[Theory, CollectionAutoData]
public async Task SaveAsync_ExceedsOrganizationMaxCollections_ThrowsBadRequest(Collection collection, Organization organization, SutProvider<CollectionService> sutProvider)
{
collection.Id = default;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<ICollectionRepository>().GetCountByOrganizationIdAsync(organization.Id)
.Returns(organization.MaxCollections.Value);
var ex = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SaveAsync(collection));
Assert.Equal($@"You have reached the maximum number of collections ({organization.MaxCollections.Value}) for this organization.", ex.Message);
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default);
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default, default);
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogCollectionEventAsync(default, default);
}
var ex = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SaveAsync(collection));
Assert.Equal($@"You have reached the maximum number of collections ({organization.MaxCollections.Value}) for this organization.", ex.Message);
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default);
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default, default);
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogCollectionEventAsync(default, default);
}
[Theory, CollectionAutoData]
public async Task DeleteUserAsync_DeletesValidUserWhoBelongsToCollection(Collection collection,
Organization organization, OrganizationUser organizationUser, SutProvider<CollectionService> sutProvider)
{
collection.OrganizationId = organization.Id;
organizationUser.OrganizationId = organization.Id;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(organizationUser.Id)
.Returns(organizationUser);
[Theory, CollectionAutoData]
public async Task DeleteUserAsync_DeletesValidUserWhoBelongsToCollection(Collection collection,
Organization organization, OrganizationUser organizationUser, SutProvider<CollectionService> sutProvider)
{
collection.OrganizationId = organization.Id;
organizationUser.OrganizationId = organization.Id;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(organizationUser.Id)
.Returns(organizationUser);
await sutProvider.Sut.DeleteUserAsync(collection, organizationUser.Id);
await sutProvider.Sut.DeleteUserAsync(collection, organizationUser.Id);
await sutProvider.GetDependency<ICollectionRepository>().Received()
.DeleteUserAsync(collection.Id, organizationUser.Id);
await sutProvider.GetDependency<IEventService>().Received().LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Updated);
}
await sutProvider.GetDependency<ICollectionRepository>().Received()
.DeleteUserAsync(collection.Id, organizationUser.Id);
await sutProvider.GetDependency<IEventService>().Received().LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Updated);
}
[Theory, CollectionAutoData]
public async Task DeleteUserAsync_InvalidUser_ThrowsNotFound(Collection collection, Organization organization,
OrganizationUser organizationUser, SutProvider<CollectionService> sutProvider)
{
collection.OrganizationId = organization.Id;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(organizationUser.Id)
.Returns(organizationUser);
[Theory, CollectionAutoData]
public async Task DeleteUserAsync_InvalidUser_ThrowsNotFound(Collection collection, Organization organization,
OrganizationUser organizationUser, SutProvider<CollectionService> sutProvider)
{
collection.OrganizationId = organization.Id;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(organizationUser.Id)
.Returns(organizationUser);
// user not in organization
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.DeleteUserAsync(collection, organizationUser.Id));
// invalid user
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteUserAsync(collection, Guid.NewGuid()));
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().DeleteUserAsync(default, default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs()
.LogOrganizationUserEventAsync(default, default);
// user not in organization
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.DeleteUserAsync(collection, organizationUser.Id));
// invalid user
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteUserAsync(collection, Guid.NewGuid()));
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().DeleteUserAsync(default, default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs()
.LogOrganizationUserEventAsync(default, default);
}
}
}

View File

@ -5,32 +5,33 @@ using Bit.Core.Services;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services;
public class DeviceServiceTests
namespace Bit.Core.Test.Services
{
[Fact]
public async Task DeviceSaveShouldUpdateRevisionDateAndPushRegistration()
public class DeviceServiceTests
{
var deviceRepo = Substitute.For<IDeviceRepository>();
var pushRepo = Substitute.For<IPushRegistrationService>();
var deviceService = new DeviceService(deviceRepo, pushRepo);
var id = Guid.NewGuid();
var userId = Guid.NewGuid();
var device = new Device
[Fact]
public async Task DeviceSaveShouldUpdateRevisionDateAndPushRegistration()
{
Id = id,
Name = "test device",
Type = DeviceType.Android,
UserId = userId,
PushToken = "testtoken",
Identifier = "testid"
};
await deviceService.SaveAsync(device);
var deviceRepo = Substitute.For<IDeviceRepository>();
var pushRepo = Substitute.For<IPushRegistrationService>();
var deviceService = new DeviceService(deviceRepo, pushRepo);
Assert.True(device.RevisionDate - DateTime.UtcNow < TimeSpan.FromSeconds(1));
await pushRepo.Received().CreateOrUpdateRegistrationAsync("testtoken", id.ToString(),
userId.ToString(), "testid", DeviceType.Android);
var id = Guid.NewGuid();
var userId = Guid.NewGuid();
var device = new Device
{
Id = id,
Name = "test device",
Type = DeviceType.Android,
UserId = userId,
PushToken = "testtoken",
Identifier = "testid"
};
await deviceService.SaveAsync(device);
Assert.True(device.RevisionDate - DateTime.UtcNow < TimeSpan.FromSeconds(1));
await pushRepo.Received().CreateOrUpdateRegistrationAsync("testtoken", id.ToString(),
userId.ToString(), "testid", DeviceType.Android);
}
}
}

View File

@ -9,162 +9,163 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services;
public class EmergencyAccessServiceTests
namespace Bit.Core.Test.Services
{
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task SaveAsync_PremiumCannotUpdate(
SutProvider<EmergencyAccessService> sutProvider, User savingUser)
public class EmergencyAccessServiceTests
{
savingUser.Premium = false;
var emergencyAccess = new EmergencyAccess
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task SaveAsync_PremiumCannotUpdate(
SutProvider<EmergencyAccessService> sutProvider, User savingUser)
{
Type = Enums.EmergencyAccessType.Takeover,
GrantorId = savingUser.Id,
};
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(savingUser.Id).Returns(savingUser);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveAsync(emergencyAccess, savingUser));
Assert.Contains("Not a premium user.", exception.Message);
await sutProvider.GetDependency<IEmergencyAccessRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task InviteAsync_UserWithKeyConnectorCannotUseTakeover(
SutProvider<EmergencyAccessService> sutProvider, User invitingUser, string email, int waitTime)
{
invitingUser.UsesKeyConnector = true;
sutProvider.GetDependency<IUserService>().CanAccessPremium(invitingUser).Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InviteAsync(invitingUser, email, Enums.EmergencyAccessType.Takeover, waitTime));
Assert.Contains("You cannot use Emergency Access Takeover because you are using Key Connector", exception.Message);
await sutProvider.GetDependency<IEmergencyAccessRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task ConfirmUserAsync_UserWithKeyConnectorCannotUseTakeover(
SutProvider<EmergencyAccessService> sutProvider, User confirmingUser, string key)
{
confirmingUser.UsesKeyConnector = true;
var emergencyAccess = new EmergencyAccess
{
Status = Enums.EmergencyAccessStatusType.Accepted,
GrantorId = confirmingUser.Id,
Type = Enums.EmergencyAccessType.Takeover,
};
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(confirmingUser.Id).Returns(confirmingUser);
sutProvider.GetDependency<IEmergencyAccessRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(emergencyAccess);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ConfirmUserAsync(new Guid(), key, confirmingUser.Id));
Assert.Contains("You cannot use Emergency Access Takeover because you are using Key Connector", exception.Message);
await sutProvider.GetDependency<IEmergencyAccessRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task SaveAsync_UserWithKeyConnectorCannotUseTakeover(
SutProvider<EmergencyAccessService> sutProvider, User savingUser)
{
savingUser.UsesKeyConnector = true;
var emergencyAccess = new EmergencyAccess
{
Type = Enums.EmergencyAccessType.Takeover,
GrantorId = savingUser.Id,
};
var userService = sutProvider.GetDependency<IUserService>();
userService.GetUserByIdAsync(savingUser.Id).Returns(savingUser);
userService.CanAccessPremium(savingUser).Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveAsync(emergencyAccess, savingUser));
Assert.Contains("You cannot use Emergency Access Takeover because you are using Key Connector", exception.Message);
await sutProvider.GetDependency<IEmergencyAccessRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task InitiateAsync_UserWithKeyConnectorCannotUseTakeover(
SutProvider<EmergencyAccessService> sutProvider, User initiatingUser, User grantor)
{
grantor.UsesKeyConnector = true;
var emergencyAccess = new EmergencyAccess
{
Status = Enums.EmergencyAccessStatusType.Confirmed,
GranteeId = initiatingUser.Id,
GrantorId = grantor.Id,
Type = Enums.EmergencyAccessType.Takeover,
};
sutProvider.GetDependency<IEmergencyAccessRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(emergencyAccess);
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(grantor.Id).Returns(grantor);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InitiateAsync(new Guid(), initiatingUser));
Assert.Contains("You cannot takeover an account that is using Key Connector", exception.Message);
await sutProvider.GetDependency<IEmergencyAccessRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task TakeoverAsync_UserWithKeyConnectorCannotUseTakeover(
SutProvider<EmergencyAccessService> sutProvider, User requestingUser, User grantor)
{
grantor.UsesKeyConnector = true;
var emergencyAccess = new EmergencyAccess
{
GrantorId = grantor.Id,
GranteeId = requestingUser.Id,
Status = Enums.EmergencyAccessStatusType.RecoveryApproved,
Type = Enums.EmergencyAccessType.Takeover,
};
sutProvider.GetDependency<IEmergencyAccessRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(emergencyAccess);
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(grantor.Id).Returns(grantor);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.TakeoverAsync(new Guid(), requestingUser));
Assert.Contains("You cannot takeover an account that is using Key Connector", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task PasswordAsync_Disables_2FA_Providers_And_Unknown_Device_Verification_On_The_Grantor(
SutProvider<EmergencyAccessService> sutProvider, User requestingUser, User grantor)
{
grantor.UsesKeyConnector = true;
grantor.UnknownDeviceVerificationEnabled = true;
grantor.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
{
[TwoFactorProviderType.Email] = new TwoFactorProvider
savingUser.Premium = false;
var emergencyAccess = new EmergencyAccess
{
MetaData = new Dictionary<string, object> { ["Email"] = "asdfasf" },
Enabled = true
}
});
var emergencyAccess = new EmergencyAccess
Type = Enums.EmergencyAccessType.Takeover,
GrantorId = savingUser.Id,
};
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(savingUser.Id).Returns(savingUser);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveAsync(emergencyAccess, savingUser));
Assert.Contains("Not a premium user.", exception.Message);
await sutProvider.GetDependency<IEmergencyAccessRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task InviteAsync_UserWithKeyConnectorCannotUseTakeover(
SutProvider<EmergencyAccessService> sutProvider, User invitingUser, string email, int waitTime)
{
GrantorId = grantor.Id,
GranteeId = requestingUser.Id,
Status = Enums.EmergencyAccessStatusType.RecoveryApproved,
Type = Enums.EmergencyAccessType.Takeover,
};
invitingUser.UsesKeyConnector = true;
sutProvider.GetDependency<IUserService>().CanAccessPremium(invitingUser).Returns(true);
sutProvider.GetDependency<IEmergencyAccessRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(emergencyAccess);
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(grantor.Id).Returns(grantor);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InviteAsync(invitingUser, email, Enums.EmergencyAccessType.Takeover, waitTime));
await sutProvider.Sut.PasswordAsync(Guid.NewGuid(), requestingUser, "blablahash", "blablakey");
Assert.Contains("You cannot use Emergency Access Takeover because you are using Key Connector", exception.Message);
await sutProvider.GetDependency<IEmergencyAccessRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default);
}
Assert.False(grantor.UnknownDeviceVerificationEnabled);
Assert.Empty(grantor.GetTwoFactorProviders());
await sutProvider.GetDependency<IUserRepository>().Received().ReplaceAsync(grantor);
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task ConfirmUserAsync_UserWithKeyConnectorCannotUseTakeover(
SutProvider<EmergencyAccessService> sutProvider, User confirmingUser, string key)
{
confirmingUser.UsesKeyConnector = true;
var emergencyAccess = new EmergencyAccess
{
Status = Enums.EmergencyAccessStatusType.Accepted,
GrantorId = confirmingUser.Id,
Type = Enums.EmergencyAccessType.Takeover,
};
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(confirmingUser.Id).Returns(confirmingUser);
sutProvider.GetDependency<IEmergencyAccessRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(emergencyAccess);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ConfirmUserAsync(new Guid(), key, confirmingUser.Id));
Assert.Contains("You cannot use Emergency Access Takeover because you are using Key Connector", exception.Message);
await sutProvider.GetDependency<IEmergencyAccessRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task SaveAsync_UserWithKeyConnectorCannotUseTakeover(
SutProvider<EmergencyAccessService> sutProvider, User savingUser)
{
savingUser.UsesKeyConnector = true;
var emergencyAccess = new EmergencyAccess
{
Type = Enums.EmergencyAccessType.Takeover,
GrantorId = savingUser.Id,
};
var userService = sutProvider.GetDependency<IUserService>();
userService.GetUserByIdAsync(savingUser.Id).Returns(savingUser);
userService.CanAccessPremium(savingUser).Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveAsync(emergencyAccess, savingUser));
Assert.Contains("You cannot use Emergency Access Takeover because you are using Key Connector", exception.Message);
await sutProvider.GetDependency<IEmergencyAccessRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task InitiateAsync_UserWithKeyConnectorCannotUseTakeover(
SutProvider<EmergencyAccessService> sutProvider, User initiatingUser, User grantor)
{
grantor.UsesKeyConnector = true;
var emergencyAccess = new EmergencyAccess
{
Status = Enums.EmergencyAccessStatusType.Confirmed,
GranteeId = initiatingUser.Id,
GrantorId = grantor.Id,
Type = Enums.EmergencyAccessType.Takeover,
};
sutProvider.GetDependency<IEmergencyAccessRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(emergencyAccess);
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(grantor.Id).Returns(grantor);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InitiateAsync(new Guid(), initiatingUser));
Assert.Contains("You cannot takeover an account that is using Key Connector", exception.Message);
await sutProvider.GetDependency<IEmergencyAccessRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task TakeoverAsync_UserWithKeyConnectorCannotUseTakeover(
SutProvider<EmergencyAccessService> sutProvider, User requestingUser, User grantor)
{
grantor.UsesKeyConnector = true;
var emergencyAccess = new EmergencyAccess
{
GrantorId = grantor.Id,
GranteeId = requestingUser.Id,
Status = Enums.EmergencyAccessStatusType.RecoveryApproved,
Type = Enums.EmergencyAccessType.Takeover,
};
sutProvider.GetDependency<IEmergencyAccessRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(emergencyAccess);
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(grantor.Id).Returns(grantor);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.TakeoverAsync(new Guid(), requestingUser));
Assert.Contains("You cannot takeover an account that is using Key Connector", exception.Message);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task PasswordAsync_Disables_2FA_Providers_And_Unknown_Device_Verification_On_The_Grantor(
SutProvider<EmergencyAccessService> sutProvider, User requestingUser, User grantor)
{
grantor.UsesKeyConnector = true;
grantor.UnknownDeviceVerificationEnabled = true;
grantor.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
{
[TwoFactorProviderType.Email] = new TwoFactorProvider
{
MetaData = new Dictionary<string, object> { ["Email"] = "asdfasf" },
Enabled = true
}
});
var emergencyAccess = new EmergencyAccess
{
GrantorId = grantor.Id,
GranteeId = requestingUser.Id,
Status = Enums.EmergencyAccessStatusType.RecoveryApproved,
Type = Enums.EmergencyAccessType.Takeover,
};
sutProvider.GetDependency<IEmergencyAccessRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(emergencyAccess);
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(grantor.Id).Returns(grantor);
await sutProvider.Sut.PasswordAsync(Guid.NewGuid(), requestingUser, "blablahash", "blablakey");
Assert.False(grantor.UnknownDeviceVerificationEnabled);
Assert.Empty(grantor.GetTwoFactorProviders());
await sutProvider.GetDependency<IUserRepository>().Received().ReplaceAsync(grantor);
}
}
}

View File

@ -11,98 +11,99 @@ using Bit.Test.Common.Helpers;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services;
[SutProviderCustomize]
public class EventServiceTests
namespace Bit.Core.Test.Services
{
public static IEnumerable<object[]> InstallationIdTestCases => TestCaseHelper.GetCombinationsOfMultipleLists(
new object[] { Guid.NewGuid(), null },
Enum.GetValues<EventType>().Select(e => (object)e)
).Select(p => p.ToArray());
[Theory]
[BitMemberAutoData(nameof(InstallationIdTestCases))]
public async Task LogOrganizationEvent_ProvidesInstallationId(Guid? installationId, EventType eventType,
Organization organization, SutProvider<EventService> sutProvider)
[SutProviderCustomize]
public class EventServiceTests
{
organization.Enabled = true;
organization.UseEvents = true;
public static IEnumerable<object[]> InstallationIdTestCases => TestCaseHelper.GetCombinationsOfMultipleLists(
new object[] { Guid.NewGuid(), null },
Enum.GetValues<EventType>().Select(e => (object)e)
).Select(p => p.ToArray());
sutProvider.GetDependency<ICurrentContext>().InstallationId.Returns(installationId);
await sutProvider.Sut.LogOrganizationEventAsync(organization, eventType);
await sutProvider.GetDependency<IEventWriteService>().Received(1).CreateAsync(Arg.Is<IEvent>(e =>
e.OrganizationId == organization.Id &&
e.Type == eventType &&
e.InstallationId == installationId));
}
[Theory, BitAutoData]
public async Task LogOrganizationUserEvent_LogsRequiredInfo(OrganizationUser orgUser, EventType eventType, DateTime date,
Guid actingUserId, Guid providerId, string ipAddress, DeviceType deviceType, SutProvider<EventService> sutProvider)
{
var orgAbilities = new Dictionary<Guid, OrganizationAbility>()
[Theory]
[BitMemberAutoData(nameof(InstallationIdTestCases))]
public async Task LogOrganizationEvent_ProvidesInstallationId(Guid? installationId, EventType eventType,
Organization organization, SutProvider<EventService> sutProvider)
{
{orgUser.OrganizationId, new OrganizationAbility() { UseEvents = true, Enabled = true } }
};
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilitiesAsync().Returns(orgAbilities);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().IpAddress.Returns(ipAddress);
sutProvider.GetDependency<ICurrentContext>().DeviceType.Returns(deviceType);
sutProvider.GetDependency<ICurrentContext>().ProviderIdForOrg(Arg.Any<Guid>()).Returns(providerId);
organization.Enabled = true;
organization.UseEvents = true;
await sutProvider.Sut.LogOrganizationUserEventAsync(orgUser, eventType, date);
sutProvider.GetDependency<ICurrentContext>().InstallationId.Returns(installationId);
var expected = new List<IEvent>() {
new EventMessage()
{
IpAddress = ipAddress,
DeviceType = deviceType,
OrganizationId = orgUser.OrganizationId,
UserId = orgUser.UserId,
OrganizationUserId = orgUser.Id,
ProviderId = providerId,
Type = eventType,
ActingUserId = actingUserId,
Date = date
}
};
await sutProvider.Sut.LogOrganizationEventAsync(organization, eventType);
await sutProvider.GetDependency<IEventWriteService>().Received(1).CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual<IEvent>(expected, new[] { "IdempotencyId" })));
}
await sutProvider.GetDependency<IEventWriteService>().Received(1).CreateAsync(Arg.Is<IEvent>(e =>
e.OrganizationId == organization.Id &&
e.Type == eventType &&
e.InstallationId == installationId));
}
[Theory, BitAutoData]
public async Task LogProviderUserEvent_LogsRequiredInfo(ProviderUser providerUser, EventType eventType, DateTime date,
Guid actingUserId, Guid providerId, string ipAddress, DeviceType deviceType, SutProvider<EventService> sutProvider)
{
var providerAbilities = new Dictionary<Guid, ProviderAbility>()
[Theory, BitAutoData]
public async Task LogOrganizationUserEvent_LogsRequiredInfo(OrganizationUser orgUser, EventType eventType, DateTime date,
Guid actingUserId, Guid providerId, string ipAddress, DeviceType deviceType, SutProvider<EventService> sutProvider)
{
{providerUser.ProviderId, new ProviderAbility() { UseEvents = true, Enabled = true } }
};
sutProvider.GetDependency<IApplicationCacheService>().GetProviderAbilitiesAsync().Returns(providerAbilities);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().IpAddress.Returns(ipAddress);
sutProvider.GetDependency<ICurrentContext>().DeviceType.Returns(deviceType);
sutProvider.GetDependency<ICurrentContext>().ProviderIdForOrg(Arg.Any<Guid>()).Returns(providerId);
await sutProvider.Sut.LogProviderUserEventAsync(providerUser, eventType, date);
var expected = new List<IEvent>() {
new EventMessage()
var orgAbilities = new Dictionary<Guid, OrganizationAbility>()
{
IpAddress = ipAddress,
DeviceType = deviceType,
ProviderId = providerUser.ProviderId,
UserId = providerUser.UserId,
ProviderUserId = providerUser.Id,
Type = eventType,
ActingUserId = actingUserId,
Date = date
}
};
{orgUser.OrganizationId, new OrganizationAbility() { UseEvents = true, Enabled = true } }
};
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilitiesAsync().Returns(orgAbilities);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().IpAddress.Returns(ipAddress);
sutProvider.GetDependency<ICurrentContext>().DeviceType.Returns(deviceType);
sutProvider.GetDependency<ICurrentContext>().ProviderIdForOrg(Arg.Any<Guid>()).Returns(providerId);
await sutProvider.GetDependency<IEventWriteService>().Received(1).CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual<IEvent>(expected, new[] { "IdempotencyId" })));
await sutProvider.Sut.LogOrganizationUserEventAsync(orgUser, eventType, date);
var expected = new List<IEvent>() {
new EventMessage()
{
IpAddress = ipAddress,
DeviceType = deviceType,
OrganizationId = orgUser.OrganizationId,
UserId = orgUser.UserId,
OrganizationUserId = orgUser.Id,
ProviderId = providerId,
Type = eventType,
ActingUserId = actingUserId,
Date = date
}
};
await sutProvider.GetDependency<IEventWriteService>().Received(1).CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual<IEvent>(expected, new[] { "IdempotencyId" })));
}
[Theory, BitAutoData]
public async Task LogProviderUserEvent_LogsRequiredInfo(ProviderUser providerUser, EventType eventType, DateTime date,
Guid actingUserId, Guid providerId, string ipAddress, DeviceType deviceType, SutProvider<EventService> sutProvider)
{
var providerAbilities = new Dictionary<Guid, ProviderAbility>()
{
{providerUser.ProviderId, new ProviderAbility() { UseEvents = true, Enabled = true } }
};
sutProvider.GetDependency<IApplicationCacheService>().GetProviderAbilitiesAsync().Returns(providerAbilities);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
sutProvider.GetDependency<ICurrentContext>().IpAddress.Returns(ipAddress);
sutProvider.GetDependency<ICurrentContext>().DeviceType.Returns(deviceType);
sutProvider.GetDependency<ICurrentContext>().ProviderIdForOrg(Arg.Any<Guid>()).Returns(providerId);
await sutProvider.Sut.LogProviderUserEventAsync(providerUser, eventType, date);
var expected = new List<IEvent>() {
new EventMessage()
{
IpAddress = ipAddress,
DeviceType = deviceType,
ProviderId = providerUser.ProviderId,
UserId = providerUser.UserId,
ProviderUserId = providerUser.Id,
Type = eventType,
ActingUserId = actingUserId,
Date = date
}
};
await sutProvider.GetDependency<IEventWriteService>().Received(1).CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual<IEvent>(expected, new[] { "IdempotencyId" })));
}
}
}

View File

@ -10,127 +10,128 @@ using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services;
public class GroupServiceTests
namespace Bit.Core.Test.Services
{
[Theory, GroupOrganizationAutoData]
public async Task SaveAsync_DefaultGroupId_CreatesGroupInRepository(Group group, Organization organization, SutProvider<GroupService> sutProvider)
public class GroupServiceTests
{
group.Id = default(Guid);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
organization.UseGroups = true;
var utcNow = DateTime.UtcNow;
[Theory, GroupOrganizationAutoData]
public async Task SaveAsync_DefaultGroupId_CreatesGroupInRepository(Group group, Organization organization, SutProvider<GroupService> sutProvider)
{
group.Id = default(Guid);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
organization.UseGroups = true;
var utcNow = DateTime.UtcNow;
await sutProvider.Sut.SaveAsync(group);
await sutProvider.Sut.SaveAsync(group);
await sutProvider.GetDependency<IGroupRepository>().Received().CreateAsync(group);
await sutProvider.GetDependency<IEventService>().Received()
.LogGroupEventAsync(group, EventType.Group_Created);
Assert.True(group.CreationDate - utcNow < TimeSpan.FromSeconds(1));
Assert.True(group.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
}
await sutProvider.GetDependency<IGroupRepository>().Received().CreateAsync(group);
await sutProvider.GetDependency<IEventService>().Received()
.LogGroupEventAsync(group, EventType.Group_Created);
Assert.True(group.CreationDate - utcNow < TimeSpan.FromSeconds(1));
Assert.True(group.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
}
[Theory, GroupOrganizationAutoData]
public async Task SaveAsync_DefaultGroupIdAndCollections_CreatesGroupInRepository(Group group, Organization organization, List<SelectionReadOnly> collections, SutProvider<GroupService> sutProvider)
{
group.Id = default(Guid);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
organization.UseGroups = true;
var utcNow = DateTime.UtcNow;
[Theory, GroupOrganizationAutoData]
public async Task SaveAsync_DefaultGroupIdAndCollections_CreatesGroupInRepository(Group group, Organization organization, List<SelectionReadOnly> collections, SutProvider<GroupService> sutProvider)
{
group.Id = default(Guid);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
organization.UseGroups = true;
var utcNow = DateTime.UtcNow;
await sutProvider.Sut.SaveAsync(group, collections);
await sutProvider.Sut.SaveAsync(group, collections);
await sutProvider.GetDependency<IGroupRepository>().Received().CreateAsync(group, collections);
await sutProvider.GetDependency<IEventService>().Received()
.LogGroupEventAsync(group, EventType.Group_Created);
Assert.True(group.CreationDate - utcNow < TimeSpan.FromSeconds(1));
Assert.True(group.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
}
await sutProvider.GetDependency<IGroupRepository>().Received().CreateAsync(group, collections);
await sutProvider.GetDependency<IEventService>().Received()
.LogGroupEventAsync(group, EventType.Group_Created);
Assert.True(group.CreationDate - utcNow < TimeSpan.FromSeconds(1));
Assert.True(group.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
}
[Theory, GroupOrganizationAutoData]
public async Task SaveAsync_NonDefaultGroupId_ReplaceGroupInRepository(Group group, Organization organization, List<SelectionReadOnly> collections, SutProvider<GroupService> sutProvider)
{
organization.UseGroups = true;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
[Theory, GroupOrganizationAutoData]
public async Task SaveAsync_NonDefaultGroupId_ReplaceGroupInRepository(Group group, Organization organization, List<SelectionReadOnly> collections, SutProvider<GroupService> sutProvider)
{
organization.UseGroups = true;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
await sutProvider.Sut.SaveAsync(group, collections);
await sutProvider.Sut.SaveAsync(group, collections);
await sutProvider.GetDependency<IGroupRepository>().Received().ReplaceAsync(group, collections);
await sutProvider.GetDependency<IEventService>().Received()
.LogGroupEventAsync(group, EventType.Group_Updated);
Assert.True(group.RevisionDate - DateTime.UtcNow < TimeSpan.FromSeconds(1));
}
await sutProvider.GetDependency<IGroupRepository>().Received().ReplaceAsync(group, collections);
await sutProvider.GetDependency<IEventService>().Received()
.LogGroupEventAsync(group, EventType.Group_Updated);
Assert.True(group.RevisionDate - DateTime.UtcNow < TimeSpan.FromSeconds(1));
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task SaveAsync_NonExistingOrganizationId_ThrowsBadRequest(Group group, SutProvider<GroupService> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveAsync(group));
Assert.Contains("Organization not found", exception.Message);
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default);
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task SaveAsync_NonExistingOrganizationId_ThrowsBadRequest(Group group, SutProvider<GroupService> sutProvider)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveAsync(group));
Assert.Contains("Organization not found", exception.Message);
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default);
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default);
}
[Theory, GroupOrganizationNotUseGroupsAutoData]
public async Task SaveAsync_OrganizationDoesNotUseGroups_ThrowsBadRequest(Group group, Organization organization, SutProvider<GroupService> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
[Theory, GroupOrganizationNotUseGroupsAutoData]
public async Task SaveAsync_OrganizationDoesNotUseGroups_ThrowsBadRequest(Group group, Organization organization, SutProvider<GroupService> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveAsync(group));
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveAsync(group));
Assert.Contains("This organization cannot use groups", exception.Message);
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default);
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default);
}
Assert.Contains("This organization cannot use groups", exception.Message);
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default);
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task DeleteAsync_ValidData_DeletesGroup(Group group, SutProvider<GroupService> sutProvider)
{
await sutProvider.Sut.DeleteAsync(group);
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task DeleteAsync_ValidData_DeletesGroup(Group group, SutProvider<GroupService> sutProvider)
{
await sutProvider.Sut.DeleteAsync(group);
await sutProvider.GetDependency<IGroupRepository>().Received().DeleteAsync(group);
await sutProvider.GetDependency<IEventService>().Received()
.LogGroupEventAsync(group, EventType.Group_Deleted);
}
await sutProvider.GetDependency<IGroupRepository>().Received().DeleteAsync(group);
await sutProvider.GetDependency<IEventService>().Received()
.LogGroupEventAsync(group, EventType.Group_Deleted);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task DeleteUserAsync_ValidData_DeletesUserInGroupRepository(Group group, Organization organization, OrganizationUser organizationUser, SutProvider<GroupService> sutProvider)
{
group.OrganizationId = organization.Id;
organization.UseGroups = true;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
organizationUser.OrganizationId = organization.Id;
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(organizationUser.Id)
.Returns(organizationUser);
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task DeleteUserAsync_ValidData_DeletesUserInGroupRepository(Group group, Organization organization, OrganizationUser organizationUser, SutProvider<GroupService> sutProvider)
{
group.OrganizationId = organization.Id;
organization.UseGroups = true;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
organizationUser.OrganizationId = organization.Id;
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(organizationUser.Id)
.Returns(organizationUser);
await sutProvider.Sut.DeleteUserAsync(group, organizationUser.Id);
await sutProvider.Sut.DeleteUserAsync(group, organizationUser.Id);
await sutProvider.GetDependency<IGroupRepository>().Received().DeleteUserAsync(group.Id, organizationUser.Id);
await sutProvider.GetDependency<IEventService>().Received()
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UpdatedGroups);
}
await sutProvider.GetDependency<IGroupRepository>().Received().DeleteUserAsync(group.Id, organizationUser.Id);
await sutProvider.GetDependency<IEventService>().Received()
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UpdatedGroups);
}
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task DeleteUserAsync_InvalidUser_ThrowsNotFound(Group group, Organization organization, OrganizationUser organizationUser, SutProvider<GroupService> sutProvider)
{
group.OrganizationId = organization.Id;
organization.UseGroups = true;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
// organizationUser.OrganizationId = organization.Id;
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(organizationUser.Id)
.Returns(organizationUser);
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
public async Task DeleteUserAsync_InvalidUser_ThrowsNotFound(Group group, Organization organization, OrganizationUser organizationUser, SutProvider<GroupService> sutProvider)
{
group.OrganizationId = organization.Id;
organization.UseGroups = true;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
// organizationUser.OrganizationId = organization.Id;
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(organizationUser.Id)
.Returns(organizationUser);
// user not in organization
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteUserAsync(group, organizationUser.Id));
// invalid user
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteUserAsync(group, Guid.NewGuid()));
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs()
.DeleteUserAsync(default, default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs()
.LogOrganizationUserEventAsync(default, default);
// user not in organization
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteUserAsync(group, organizationUser.Id));
// invalid user
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteUserAsync(group, Guid.NewGuid()));
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs()
.DeleteUserAsync(default, default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs()
.LogOrganizationUserEventAsync(default, default);
}
}
}

View File

@ -8,168 +8,169 @@ using Microsoft.Extensions.Logging;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services;
public class HandlebarsMailServiceTests
namespace Bit.Core.Test.Services
{
private readonly HandlebarsMailService _sut;
private readonly GlobalSettings _globalSettings;
private readonly IMailDeliveryService _mailDeliveryService;
private readonly IMailEnqueuingService _mailEnqueuingService;
public HandlebarsMailServiceTests()
public class HandlebarsMailServiceTests
{
_globalSettings = new GlobalSettings();
_mailDeliveryService = Substitute.For<IMailDeliveryService>();
_mailEnqueuingService = Substitute.For<IMailEnqueuingService>();
private readonly HandlebarsMailService _sut;
_sut = new HandlebarsMailService(
_globalSettings,
_mailDeliveryService,
_mailEnqueuingService
);
}
private readonly GlobalSettings _globalSettings;
private readonly IMailDeliveryService _mailDeliveryService;
private readonly IMailEnqueuingService _mailEnqueuingService;
[Fact(Skip = "For local development")]
public async Task SendAllEmails()
{
// This test is only opt in and is more for development purposes.
// This will send all emails to the test email address so that they can be viewed.
var namedParameters = new Dictionary<(string, Type), object>
public HandlebarsMailServiceTests()
{
// TODO: Swith to use env variable
{ ("email", typeof(string)), "test@bitwarden.com" },
{ ("user", typeof(User)), new User
{
Id = Guid.NewGuid(),
Email = "test@bitwarden.com",
}},
{ ("userId", typeof(Guid)), Guid.NewGuid() },
{ ("token", typeof(string)), "test_token" },
{ ("fromEmail", typeof(string)), "test@bitwarden.com" },
{ ("toEmail", typeof(string)), "test@bitwarden.com" },
{ ("newEmailAddress", typeof(string)), "test@bitwarden.com" },
{ ("hint", typeof(string)), "Test Hint" },
{ ("organizationName", typeof(string)), "Test Organization Name" },
{ ("orgUser", typeof(OrganizationUser)), new OrganizationUser
{
Id = Guid.NewGuid(),
Email = "test@bitwarden.com",
OrganizationId = Guid.NewGuid(),
_globalSettings = new GlobalSettings();
_mailDeliveryService = Substitute.For<IMailDeliveryService>();
_mailEnqueuingService = Substitute.For<IMailEnqueuingService>();
}},
{ ("token", typeof(ExpiringToken)), new ExpiringToken("test_token", DateTime.UtcNow.AddDays(1))},
{ ("organization", typeof(Organization)), new Organization
{
Id = Guid.NewGuid(),
Name = "Test Organization Name",
Seats = 5
}},
{ ("initialSeatCount", typeof(int)), 5},
{ ("ownerEmails", typeof(IEnumerable<string>)), new [] { "test@bitwarden.com" }},
{ ("maxSeatCount", typeof(int)), 5 },
{ ("userIdentifier", typeof(string)), "test_user" },
{ ("adminEmails", typeof(IEnumerable<string>)), new [] { "test@bitwarden.com" }},
{ ("returnUrl", typeof(string)), "https://bitwarden.com/" },
{ ("amount", typeof(decimal)), 1.00M },
{ ("dueDate", typeof(DateTime)), DateTime.UtcNow.AddDays(1) },
{ ("items", typeof(List<string>)), new List<string> { "test@bitwarden.com" }},
{ ("mentionInvoices", typeof(bool)), true },
{ ("emails", typeof(IEnumerable<string>)), new [] { "test@bitwarden.com" }},
{ ("deviceType", typeof(string)), "Mobile" },
{ ("timestamp", typeof(DateTime)), DateTime.UtcNow.AddDays(1)},
{ ("ip", typeof(string)), "127.0.0.1" },
{ ("emergencyAccess", typeof(EmergencyAccess)), new EmergencyAccess
{
Id = Guid.NewGuid(),
Email = "test@bitwarden.com",
}},
{ ("granteeEmail", typeof(string)), "test@bitwarden.com" },
{ ("grantorName", typeof(string)), "Test User" },
{ ("initiatingName", typeof(string)), "Test" },
{ ("approvingName", typeof(string)), "Test Name" },
{ ("rejectingName", typeof(string)), "Test Name" },
{ ("provider", typeof(Provider)), new Provider
{
Id = Guid.NewGuid(),
}},
{ ("name", typeof(string)), "Test Name" },
{ ("ea", typeof(EmergencyAccess)), new EmergencyAccess
{
Id = Guid.NewGuid(),
Email = "test@bitwarden.com",
}},
{ ("userName", typeof(string)), "testUser" },
{ ("orgName", typeof(string)), "Test Org Name" },
{ ("providerName", typeof(string)), "testProvider" },
{ ("providerUser", typeof(ProviderUser)), new ProviderUser
{
ProviderId = Guid.NewGuid(),
Id = Guid.NewGuid(),
}},
{ ("familyUserEmail", typeof(string)), "test@bitwarden.com" },
{ ("sponsorEmail", typeof(string)), "test@bitwarden.com" },
{ ("familyOrgName", typeof(string)), "Test Org Name" },
// Swap existingAccount to true or false to generate different versions of the SendFamiliesForEnterpriseOfferEmailAsync emails.
{ ("existingAccount", typeof(bool)), false },
{ ("sponsorshipEndDate", typeof(DateTime)), DateTime.UtcNow.AddDays(1)},
{ ("sponsorOrgName", typeof(string)), "Sponsor Test Org Name" },
{ ("expirationDate", typeof(DateTime)), DateTime.Now.AddDays(3) },
{ ("utcNow", typeof(DateTime)), DateTime.UtcNow },
};
var globalSettings = new GlobalSettings
{
Mail = new GlobalSettings.MailSettings
{
Smtp = new GlobalSettings.MailSettings.SmtpSettings
{
Host = "localhost",
TrustServer = true,
Port = 10250,
},
ReplyToEmail = "noreply@bitwarden.com",
},
SiteName = "Bitwarden",
};
var mailDeliveryService = new MailKitSmtpMailDeliveryService(globalSettings, Substitute.For<ILogger<MailKitSmtpMailDeliveryService>>());
var handlebarsService = new HandlebarsMailService(globalSettings, mailDeliveryService, new BlockingMailEnqueuingService());
var sendMethods = typeof(IMailService).GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(m => m.Name.StartsWith("Send") && m.Name != "SendEnqueuedMailMessageAsync");
foreach (var sendMethod in sendMethods)
{
await InvokeMethod(sendMethod);
_sut = new HandlebarsMailService(
_globalSettings,
_mailDeliveryService,
_mailEnqueuingService
);
}
async Task InvokeMethod(MethodInfo method)
[Fact(Skip = "For local development")]
public async Task SendAllEmails()
{
var parameters = method.GetParameters();
var args = new object[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
// This test is only opt in and is more for development purposes.
// This will send all emails to the test email address so that they can be viewed.
var namedParameters = new Dictionary<(string, Type), object>
{
if (!namedParameters.TryGetValue((parameters[i].Name, parameters[i].ParameterType), out var value))
// TODO: Swith to use env variable
{ ("email", typeof(string)), "test@bitwarden.com" },
{ ("user", typeof(User)), new User
{
throw new InvalidOperationException($"Couldn't find a parameter for name '{parameters[i].Name}' and type '{parameters[i].ParameterType.FullName}'");
}
Id = Guid.NewGuid(),
Email = "test@bitwarden.com",
}},
{ ("userId", typeof(Guid)), Guid.NewGuid() },
{ ("token", typeof(string)), "test_token" },
{ ("fromEmail", typeof(string)), "test@bitwarden.com" },
{ ("toEmail", typeof(string)), "test@bitwarden.com" },
{ ("newEmailAddress", typeof(string)), "test@bitwarden.com" },
{ ("hint", typeof(string)), "Test Hint" },
{ ("organizationName", typeof(string)), "Test Organization Name" },
{ ("orgUser", typeof(OrganizationUser)), new OrganizationUser
{
Id = Guid.NewGuid(),
Email = "test@bitwarden.com",
OrganizationId = Guid.NewGuid(),
args[i] = value;
}},
{ ("token", typeof(ExpiringToken)), new ExpiringToken("test_token", DateTime.UtcNow.AddDays(1))},
{ ("organization", typeof(Organization)), new Organization
{
Id = Guid.NewGuid(),
Name = "Test Organization Name",
Seats = 5
}},
{ ("initialSeatCount", typeof(int)), 5},
{ ("ownerEmails", typeof(IEnumerable<string>)), new [] { "test@bitwarden.com" }},
{ ("maxSeatCount", typeof(int)), 5 },
{ ("userIdentifier", typeof(string)), "test_user" },
{ ("adminEmails", typeof(IEnumerable<string>)), new [] { "test@bitwarden.com" }},
{ ("returnUrl", typeof(string)), "https://bitwarden.com/" },
{ ("amount", typeof(decimal)), 1.00M },
{ ("dueDate", typeof(DateTime)), DateTime.UtcNow.AddDays(1) },
{ ("items", typeof(List<string>)), new List<string> { "test@bitwarden.com" }},
{ ("mentionInvoices", typeof(bool)), true },
{ ("emails", typeof(IEnumerable<string>)), new [] { "test@bitwarden.com" }},
{ ("deviceType", typeof(string)), "Mobile" },
{ ("timestamp", typeof(DateTime)), DateTime.UtcNow.AddDays(1)},
{ ("ip", typeof(string)), "127.0.0.1" },
{ ("emergencyAccess", typeof(EmergencyAccess)), new EmergencyAccess
{
Id = Guid.NewGuid(),
Email = "test@bitwarden.com",
}},
{ ("granteeEmail", typeof(string)), "test@bitwarden.com" },
{ ("grantorName", typeof(string)), "Test User" },
{ ("initiatingName", typeof(string)), "Test" },
{ ("approvingName", typeof(string)), "Test Name" },
{ ("rejectingName", typeof(string)), "Test Name" },
{ ("provider", typeof(Provider)), new Provider
{
Id = Guid.NewGuid(),
}},
{ ("name", typeof(string)), "Test Name" },
{ ("ea", typeof(EmergencyAccess)), new EmergencyAccess
{
Id = Guid.NewGuid(),
Email = "test@bitwarden.com",
}},
{ ("userName", typeof(string)), "testUser" },
{ ("orgName", typeof(string)), "Test Org Name" },
{ ("providerName", typeof(string)), "testProvider" },
{ ("providerUser", typeof(ProviderUser)), new ProviderUser
{
ProviderId = Guid.NewGuid(),
Id = Guid.NewGuid(),
}},
{ ("familyUserEmail", typeof(string)), "test@bitwarden.com" },
{ ("sponsorEmail", typeof(string)), "test@bitwarden.com" },
{ ("familyOrgName", typeof(string)), "Test Org Name" },
// Swap existingAccount to true or false to generate different versions of the SendFamiliesForEnterpriseOfferEmailAsync emails.
{ ("existingAccount", typeof(bool)), false },
{ ("sponsorshipEndDate", typeof(DateTime)), DateTime.UtcNow.AddDays(1)},
{ ("sponsorOrgName", typeof(string)), "Sponsor Test Org Name" },
{ ("expirationDate", typeof(DateTime)), DateTime.Now.AddDays(3) },
{ ("utcNow", typeof(DateTime)), DateTime.UtcNow },
};
var globalSettings = new GlobalSettings
{
Mail = new GlobalSettings.MailSettings
{
Smtp = new GlobalSettings.MailSettings.SmtpSettings
{
Host = "localhost",
TrustServer = true,
Port = 10250,
},
ReplyToEmail = "noreply@bitwarden.com",
},
SiteName = "Bitwarden",
};
var mailDeliveryService = new MailKitSmtpMailDeliveryService(globalSettings, Substitute.For<ILogger<MailKitSmtpMailDeliveryService>>());
var handlebarsService = new HandlebarsMailService(globalSettings, mailDeliveryService, new BlockingMailEnqueuingService());
var sendMethods = typeof(IMailService).GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(m => m.Name.StartsWith("Send") && m.Name != "SendEnqueuedMailMessageAsync");
foreach (var sendMethod in sendMethods)
{
await InvokeMethod(sendMethod);
}
await (Task)method.Invoke(handlebarsService, args);
async Task InvokeMethod(MethodInfo method)
{
var parameters = method.GetParameters();
var args = new object[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
{
if (!namedParameters.TryGetValue((parameters[i].Name, parameters[i].ParameterType), out var value))
{
throw new InvalidOperationException($"Couldn't find a parameter for name '{parameters[i].Name}' and type '{parameters[i].ParameterType.FullName}'");
}
args[i] = value;
}
await (Task)method.Invoke(handlebarsService, args);
}
}
// Remove this test when we add actual tests. It only proves that
// we've properly constructed the system under test.
[Fact]
public void ServiceExists()
{
Assert.NotNull(_sut);
}
}
// Remove this test when we add actual tests. It only proves that
// we've properly constructed the system under test.
[Fact]
public void ServiceExists()
{
Assert.NotNull(_sut);
}
}

Some files were not shown because too many files have changed in this diff Show More