From e00f53369fac090d2d4f4a0eb9f8400837fc83c2 Mon Sep 17 00:00:00 2001 From: Joshua Ford Date: Thu, 30 Apr 2020 23:47:38 -0500 Subject: [PATCH] Create tests for AccountsController Partial test suite included to minimize the amount necessary to review. --- test/Api.Test/Api.Test.csproj | 6 +- .../Controllers/AccountsControllerTests.cs | 352 ++++++++++++++++++ 2 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 test/Api.Test/Controllers/AccountsControllerTests.cs diff --git a/test/Api.Test/Api.Test.csproj b/test/Api.Test/Api.Test.csproj index b4cebb8bd..515ad4c9f 100644 --- a/test/Api.Test/Api.Test.csproj +++ b/test/Api.Test/Api.Test.csproj @@ -8,12 +8,14 @@ + - - + + + diff --git a/test/Api.Test/Controllers/AccountsControllerTests.cs b/test/Api.Test/Controllers/AccountsControllerTests.cs new file mode 100644 index 000000000..14ca7987e --- /dev/null +++ b/test/Api.Test/Controllers/AccountsControllerTests.cs @@ -0,0 +1,352 @@ +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Bit.Api.Controllers; +using Bit.Core; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Api; +using Bit.Core.Models.Data; +using Bit.Core.Models.Table; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Microsoft.AspNetCore.Identity; +using NSubstitute; +using Xunit; + +namespace Bit.Api.Test.Controllers +{ + public class AccountsControllerTests : IDisposable + { + private readonly AccountsController _sut; + + private readonly IUserService _userService; + private readonly IUserRepository _userRepository; + private readonly ICipherRepository _cipherRepository; + private readonly IFolderRepository _folderRepository; + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly IPaymentService _paymentService; + private readonly GlobalSettings _globalSettings; + + public AccountsControllerTests() + { + _userService = Substitute.For(); + _userRepository = Substitute.For(); + _cipherRepository = Substitute.For(); + _folderRepository = Substitute.For(); + _organizationUserRepository = Substitute.For(); + _paymentService = Substitute.For(); + _globalSettings = new GlobalSettings(); + _sut = new AccountsController( + _userService, + _userRepository, + _cipherRepository, + _folderRepository, + _organizationUserRepository, + _paymentService, + _globalSettings + ); + } + + public void Dispose() + { + _sut?.Dispose(); + } + + [Fact] + public async Task PostPrelogin_WhenUserExists_ShouldReturnUserKdfInfo() + { + var userKdfInfo = new UserKdfInformation + { + Kdf = KdfType.PBKDF2_SHA256, + KdfIterations = 5000 + }; + _userRepository.GetKdfInformationByEmailAsync(Arg.Any()).Returns(Task.FromResult(userKdfInfo)); + + 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()).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(), 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 + }; + + await _sut.PostRegister(request); + + await _userService.Received(1).RegisterUserAsync(Arg.Any(), passwordHash, token, userGuid); + } + + [Fact] + public async Task PostRegister_WhenUserServiceFails_ShouldThrowBadRequestException() + { + var passwordHash = "abcdef"; + var token = "123456"; + var userGuid = new Guid(); + _userService.RegisterUserAsync(Arg.Any(), 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(() => _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( + () => _sut.PostEmailToken(new EmailTokenRequestModel()) + ); + } + + [Fact] + public async Task PostEmailToken_WhenInvalidPasssword_ShouldThrowBadRequestException() + { + var user = GenerateExampleUser(); + ConfigureUserServiceToReturnValidPrincipalFor(user); + ConfigureUserServiceToRejectPasswordFor(user); + + await Assert.ThrowsAsync( + () => _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( + () => _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( + () => _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( + () => _sut.PostVerifyEmail() + ); + } + + [Fact] + public async Task PostVerifyEmailToken_ShouldConfirmEmail() + { + var user = GenerateExampleUser(); + ConfigureUserServiceToReturnValidIdFor(user); + _userService.ConfirmEmailAsync(user, Arg.Any()) + .Returns(Task.FromResult(IdentityResult.Success)); + + await _sut.PostVerifyEmailToken(new VerifyEmailRequestModel { UserId = "12345678-1234-1234-1234-123456789012" }); + + await _userService.Received(1).ConfirmEmailAsync(user, Arg.Any()); + } + + [Fact] + public async Task PostVerifyEmailToken_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException() + { + var user = GenerateExampleUser(); + ConfigureUserServiceToReturnNullUserId(); + + await Assert.ThrowsAsync( + () => _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()) + .Returns(Task.FromResult(IdentityResult.Failed())); + + await Assert.ThrowsAsync( + () => _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) + .Returns(Task.FromResult(IdentityResult.Success)); + + await _sut.PostPassword(new PasswordRequestModel()); + + await _userService.Received(1).ChangePasswordAsync(user, default, default, default); + } + + [Fact] + public async Task PostPassword_WhenNotAuthorized_ShouldThrowUnauthorizedAccessException() + { + ConfigureUserServiceToReturnNullPrincipal(); + + await Assert.ThrowsAsync( + () => _sut.PostPassword(new PasswordRequestModel()) + ); + } + + [Fact] + public async Task PostPassword_WhenPasswordChangeFails_ShouldBadRequestException() + { + var user = GenerateExampleUser(); + ConfigureUserServiceToReturnValidPrincipalFor(user); + _userService.ChangePasswordAsync(user, default, default, default) + .Returns(Task.FromResult(IdentityResult.Failed())); + + await Assert.ThrowsAsync( + () => _sut.PostPassword(new PasswordRequestModel()) + ); + } + + // 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()) + .Returns(Task.FromResult((User)null)); + } + + private void ConfigureUserServiceToReturnValidPrincipalFor(User user) + { + _userService.GetUserByPrincipalAsync(Arg.Any()) + .Returns(Task.FromResult(user)); + } + + private void ConfigureUserServiceToRejectPasswordFor(User user) + { + _userService.CheckPasswordAsync(user, Arg.Any()) + .Returns(Task.FromResult(false)); + } + + private void ConfigureUserServiceToAcceptPasswordFor(User user) + { + _userService.CheckPasswordAsync(user, Arg.Any()) + .Returns(Task.FromResult(true)); + } + + private void ConfigureUserServiceToReturnValidIdFor(User user) + { + _userService.GetUserByIdAsync(Arg.Any()) + .Returns(Task.FromResult(user)); + } + + private void ConfigureUserServiceToReturnNullUserId() + { + _userService.GetUserByIdAsync(Arg.Any()) + .Returns(Task.FromResult((User)null)); + } + } +} +