I have some service that's responsible for changing user password while using UserManager
and everything works fine, but when I want to write some tests, then it starts failing at method CheckPassword
, which basically checks if current (old password)
is correct
myService:
public async bool TryChangePassword(User user, string OldPassword, string NewPassword)
{
(...)
// it returns false
var checkOldPassword = await _userManager.CheckPasswordAsync(user, OldPassword);
if (!checkOldPassword)
{
return false;
}
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
var result = await _userManager.ResetPasswordAsync(user, token, NewPassword);
return result.Succeeded;
}
myTests:
private readonly UserManager<User> _userManager;
[Fact]
public void password_change_attempt_1()
{
var service = new myService(_context, _userManager);
var user = new User("john");
var register = _userManager.CreateAsync(user, "123456");
_context.SaveChanges();
Assert.True(_context.Users.Any());
var result = service.TryChangePassword(user, "123456", "newPassword");
}
but for some reason it fails at:
var checkOldPassword = await _userManager.CheckPasswordAsync(user, OldPassword);
it returns false, but as you can see, provided password is correct, probably there's something wrong with mocking user manager
Here's how do I create mock of UserManager
in Tests'
constructor
public Tests()
{
var o = new DbContextOptionsBuilder<Context>();
o.UseInMemoryDatabase(Guid.NewGuid().ToString());
_context = new Context(o.Options);
_context.Database.EnsureCreated();
var userStore = new MockUserStore(_context);
_userManager = new MockUserManager(userStore,
new Mock<IOptions<IdentityOptions>>().Object,
new Mock<IPasswordHasher<User>>().Object,
new IUserValidator<User>[0],
new IPasswordValidator<User>[0],
new Mock<ILookupNormalizer>().Object,
new Mock<IdentityErrorDescriber>().Object,
new Mock<IServiceProvider>().Object,
new Mock<ILogger<UserManager<User>>>().Object);
}
public class MockUserManager : UserManager<User>
{
public MockUserManager(IUserStore<User> store, IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<User> passwordHasher, IEnumerable<IUserValidator<User>> userValidators,
IEnumerable<IPasswordValidator<User>> passwordValidators, ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<User>> logger)
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
}
public override Task<IdentityResult> CreateAsync(User user)
{
this.Store.CreateAsync(user, new CancellationToken());
return Task.FromResult(IdentityResult.Success);
}
}
public class MockUserStore : IUserStore<User>, IUserPasswordStore<User>
{
public readonly Context _ctx;
public MockUserStore(Context ctx)
{
_ctx = ctx;
}
public Task<IdentityResult> CreateAsync(User user, CancellationToken cancellationToken)
{
_ctx.Users.Add(user);
return Task.FromResult(IdentityResult.Success);
}
public Task<IdentityResult> DeleteAsync(User user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public void Dispose()
{
throw new NotImplementedException();
}
public Task<User> FindByIdAsync(string userId, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<User> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<string> GetNormalizedUserNameAsync(User user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<string> GetPasswordHashAsync(User user, CancellationToken cancellationToken)
{
return Task.FromResult<string>(user.PasswordHash);
}
public Task<string> GetUserIdAsync(User user, CancellationToken cancellationToken)
{
return Task.FromResult<string>(user.Id);
}
public Task<string> GetUserNameAsync(User user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<bool> HasPasswordAsync(User user, CancellationToken cancellationToken)
{
return Task.FromResult<bool>(!String.IsNullOrEmpty(user.PasswordHash));
}
public Task SetNormalizedUserNameAsync(User user, string normalizedName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task SetPasswordHashAsync(User user, string passwordHash, CancellationToken cancellationToken)
{
user.PasswordHash = passwordHash;
return Task.FromResult(0);
}
public Task SetUserNameAsync(User user, string userName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<IdentityResult> UpdateAsync(User user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
I suppose it can be fixed here:
public Task<IdentityResult> CreateAsync(User user, CancellationToken cancellationToken)
{
_ctx.Users.Add(user);
return Task.FromResult(IdentityResult.Success);
}
by adding something like
user.PasswordHash = generateHash(password)
but how can I know how many iterations do ASP.NET Core Identity does use?
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password,
salt: salt,
prf: KeyDerivationPrf.HMACSHA1,
iterationCount: 10000,
numBytesRequested: 256 / 8));
Aucun commentaire:
Enregistrer un commentaire