Files
StoreWeb/Tests/Store.Users.Tests/UserAuthenticationTests.cs
T

99 lines
4.1 KiB
C#

using Store.Users.Entities;
using Store.Users.Services;
using MAX.Framework.Commons;
namespace Store.Users.Tests;
public sealed class UserAuthenticationTests
{
[Theory]
[InlineData("", "password")]
[InlineData(" ", "password")]
[InlineData("main", "")]
[InlineData("missing", "legacy-password")]
[InlineData("main", "wrong")]
[InlineData("main' OR 1=1 --", "legacy-password")]
public async Task InvalidCredentials_ReturnNullWithoutChangingPassword(string name, string password)
{
using var context = new UserTestContext();
Assert.Null(await context.Authentication.Authenticate(name, password));
Assert.Equal("legacy-password", (await context.Read("main")).Password);
}
[Fact]
public async Task LegacyPassword_RemainsUnchangedAfterRepeatedLogins()
{
using var context = new UserTestContext();
var info = await context.Authentication.Authenticate(" main ", "legacy-password");
Assert.Equal("main", info!.UserName);
var user = await context.Read("main");
Assert.Equal("legacy-password", user.Password);
Assert.True(UserPassword.Verify(user, "legacy-password"));
Assert.NotNull(await context.Authentication.Authenticate("main", "legacy-password"));
Assert.Equal("legacy-password", (await context.Read("main")).Password);
}
[Fact]
public async Task HashedPassword_RemainsUnchangedAfterLogin()
{
using var context = new UserTestContext();
context.CurrentUser.UserName = "main";
await context.Users.ChangePassword(new() { OldPassword = "legacy-password", NewPassword = "new-password" }, default);
var hash = (await context.Read("main")).Password;
Assert.NotNull(await context.Authentication.Authenticate("main", "new-password"));
Assert.Equal(hash, (await context.Read("main")).Password);
}
[Fact]
public void PasswordHashes_AreSalted_AndRejectWrongPassword()
{
var user = new UserEntity();
user.Password = UserPassword.Hash(user, "test-password");
Assert.NotEqual(user.Password, UserPassword.Hash(user, "test-password"));
Assert.True(UserPassword.Verify(user, "test-password"));
Assert.False(UserPassword.Verify(user, "wrong-password"));
}
[Theory]
[InlineData("AQAAAAinvalid!")]
[InlineData("AAinvalid!")]
[InlineData("")]
public void MalformedPasswordHash_IsRejected(string hash) =>
Assert.False(UserPassword.Verify(new UserEntity { Password = hash }, "test-password"));
[Fact]
public async Task WrongOldPassword_DoesNotChangePasswordOrSignOut()
{
using var context = new UserTestContext();
context.CurrentUser.UserName = "main";
var error = await Assert.ThrowsAsync<AppException>(() => context.Users.ChangePassword(
new() { OldPassword = "wrong", NewPassword = "new-password" }, default));
Assert.Equal("原密码错误", error.Message);
Assert.Equal("legacy-password", (await context.Read("main")).Password);
Assert.Empty(context.Sessions.SignedOut);
}
[Fact]
public async Task ChangePassword_InvalidatesOldPasswordAndSession()
{
using var context = new UserTestContext();
context.CurrentUser.UserName = "main";
await context.Users.ChangePassword(new() { OldPassword = "legacy-password", NewPassword = "new-password" }, default);
Assert.Contains("main", context.Sessions.SignedOut);
Assert.Null(await context.Authentication.Authenticate("main", "legacy-password"));
Assert.NotNull(await context.Authentication.Authenticate("main", "new-password"));
}
[Fact]
public async Task CancelledAuthentication_ThrowsBeforeChangingPassword()
{
using var context = new UserTestContext();
using var cancellation = new CancellationTokenSource();
cancellation.Cancel();
await Assert.ThrowsAnyAsync<OperationCanceledException>(() =>
context.Authentication.Authenticate("main", "legacy-password", cancellation.Token));
Assert.Equal("legacy-password", (await context.Read("main")).Password);
}
}