Browse Source

Refactor token provider tests to use a common base class for single active token providers and improve token verification logic

pull/24926/head
maliming 1 month ago
parent
commit
6e97ceb0df
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 13
      modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProvider.cs
  2. 139
      modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpChangeEmailTokenProvider_Tests.cs
  3. 145
      modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpEmailConfirmationTokenProvider_Tests.cs
  4. 139
      modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpPasswordResetTokenProvider_Tests.cs
  5. 138
      modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProviderTestBase.cs

13
modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProvider.cs

@ -62,7 +62,18 @@ public abstract class AbpSingleActiveTokenProvider : DataProtectorTokenProvider<
}
var inputHash = ComputeSha256Hash(token);
return string.Equals(storedHash, inputHash, StringComparison.Ordinal);
try
{
var storedHashBytes = Convert.FromHexString(storedHash);
var inputHashBytes = Convert.FromHexString(inputHash);
return CryptographicOperations.FixedTimeEquals(storedHashBytes, inputHashBytes);
}
catch (FormatException)
{
// In case the stored hash is corrupted or not a valid hex string,
// treat the token as invalid rather than throwing.
return false;
}
}
protected virtual string ComputeSha256Hash(string input)

139
modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpChangeEmailTokenProvider_Tests.cs

@ -7,22 +7,25 @@ using Xunit;
namespace Volo.Abp.Identity.AspNetCore;
public class AbpChangeEmailTokenProvider_Tests : AbpIdentityAspNetCoreTestBase
public class AbpChangeEmailTokenProvider_Tests : AbpSingleActiveTokenProviderTestBase
{
private const string NewEmail = "newemail@example.com";
protected IIdentityUserRepository UserRepository { get; }
protected IdentityUserManager UserManager { get; }
protected IdentityTestData TestData { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected override Task<string> GenerateTokenAsync(IdentityUser user)
=> UserManager.GenerateChangeEmailTokenAsync(user, NewEmail);
public AbpChangeEmailTokenProvider_Tests()
{
UserRepository = GetRequiredService<IIdentityUserRepository>();
UserManager = GetRequiredService<IdentityUserManager>();
TestData = GetRequiredService<IdentityTestData>();
UnitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
}
protected override Task<bool> VerifyTokenAsync(IdentityUser user, string token)
=> UserManager.VerifyUserTokenAsync(
user,
UserManager.Options.Tokens.ChangeEmailTokenProvider,
UserManager<IdentityUser>.GetChangeEmailTokenPurpose(NewEmail),
token);
protected override string GetProviderName()
=> UserManager.Options.Tokens.ChangeEmailTokenProvider;
protected override string GetPurpose()
=> UserManager<IdentityUser>.GetChangeEmailTokenPurpose(NewEmail);
[Fact]
public void AbpChangeEmailTokenProvider_Should_Be_Registered()
@ -42,82 +45,6 @@ public class AbpChangeEmailTokenProvider_Tests : AbpIdentityAspNetCoreTestBase
identityOptions.Tokens.ChangeEmailTokenProvider.ShouldBe(AbpChangeEmailTokenProvider.ProviderName);
}
[Fact]
public async Task Generate_And_Verify_Change_Email_Token_Should_Succeed()
{
using (var uow = UnitOfWorkManager.Begin())
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
var token = await UserManager.GenerateChangeEmailTokenAsync(john, NewEmail);
token.ShouldNotBeNullOrEmpty();
var isValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.ChangeEmailTokenProvider,
UserManager<IdentityUser>.GetChangeEmailTokenPurpose(NewEmail),
token);
isValid.ShouldBeTrue();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Invalid_Token_Should_Fail_Verification()
{
using (var uow = UnitOfWorkManager.Begin())
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
await UserManager.GenerateChangeEmailTokenAsync(john, NewEmail);
var isValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.ChangeEmailTokenProvider,
UserManager<IdentityUser>.GetChangeEmailTokenPurpose(NewEmail),
"invalid-token-value");
isValid.ShouldBeFalse();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Second_Token_Should_Invalidate_First_Token()
{
using (var uow = UnitOfWorkManager.Begin())
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
var firstToken = await UserManager.GenerateChangeEmailTokenAsync(john, NewEmail);
john = await UserRepository.GetAsync(TestData.UserJohnId);
var secondToken = await UserManager.GenerateChangeEmailTokenAsync(john, NewEmail);
john = await UserRepository.GetAsync(TestData.UserJohnId);
var firstTokenValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.ChangeEmailTokenProvider,
UserManager<IdentityUser>.GetChangeEmailTokenPurpose(NewEmail),
firstToken);
var secondTokenValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.ChangeEmailTokenProvider,
UserManager<IdentityUser>.GetChangeEmailTokenPurpose(NewEmail),
secondToken);
firstTokenValid.ShouldBeFalse();
secondTokenValid.ShouldBeTrue();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Token_Should_Become_Invalid_After_Email_Change()
{
@ -133,42 +60,8 @@ public class AbpChangeEmailTokenProvider_Tests : AbpIdentityAspNetCoreTestBase
// SecurityStamp has changed after the email change, so the old token must be invalid.
john = await UserRepository.GetAsync(TestData.UserJohnId);
var isValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.ChangeEmailTokenProvider,
UserManager<IdentityUser>.GetChangeEmailTokenPurpose(NewEmail),
token);
isValid.ShouldBeFalse();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Token_Hash_Should_Persist_Across_UnitOfWork_Boundaries()
{
string token;
// UoW 1: generate the token; UpdateAsync inside GenerateAsync must persist the hash.
using (var uow = UnitOfWorkManager.Begin(requiresNew: true))
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
token = await UserManager.GenerateChangeEmailTokenAsync(john, NewEmail);
await uow.CompleteAsync();
}
// UoW 2: validate using a fresh DbContext to confirm the hash was written to the DB.
using (var uow = UnitOfWorkManager.Begin(requiresNew: true))
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
var isValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.ChangeEmailTokenProvider,
UserManager<IdentityUser>.GetChangeEmailTokenPurpose(NewEmail),
token);
(await VerifyTokenAsync(john, token)).ShouldBeFalse();
isValid.ShouldBeTrue();
await uow.CompleteAsync();
}
}

145
modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpEmailConfirmationTokenProvider_Tests.cs

@ -7,20 +7,23 @@ using Xunit;
namespace Volo.Abp.Identity.AspNetCore;
public class AbpEmailConfirmationTokenProvider_Tests : AbpIdentityAspNetCoreTestBase
public class AbpEmailConfirmationTokenProvider_Tests : AbpSingleActiveTokenProviderTestBase
{
protected IIdentityUserRepository UserRepository { get; }
protected IdentityUserManager UserManager { get; }
protected IdentityTestData TestData { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected override Task<string> GenerateTokenAsync(IdentityUser user)
=> UserManager.GenerateEmailConfirmationTokenAsync(user);
public AbpEmailConfirmationTokenProvider_Tests()
{
UserRepository = GetRequiredService<IIdentityUserRepository>();
UserManager = GetRequiredService<IdentityUserManager>();
TestData = GetRequiredService<IdentityTestData>();
UnitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
}
protected override Task<bool> VerifyTokenAsync(IdentityUser user, string token)
=> UserManager.VerifyUserTokenAsync(
user,
UserManager.Options.Tokens.EmailConfirmationTokenProvider,
UserManager<IdentityUser>.ConfirmEmailTokenPurpose,
token);
protected override string GetProviderName()
=> UserManager.Options.Tokens.EmailConfirmationTokenProvider;
protected override string GetPurpose()
=> UserManager<IdentityUser>.ConfirmEmailTokenPurpose;
[Fact]
public void AbpEmailConfirmationTokenProvider_Should_Be_Registered()
@ -40,82 +43,6 @@ public class AbpEmailConfirmationTokenProvider_Tests : AbpIdentityAspNetCoreTest
identityOptions.Tokens.EmailConfirmationTokenProvider.ShouldBe(AbpEmailConfirmationTokenProvider.ProviderName);
}
[Fact]
public async Task Generate_And_Verify_Email_Confirmation_Token_Should_Succeed()
{
using (var uow = UnitOfWorkManager.Begin())
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
var token = await UserManager.GenerateEmailConfirmationTokenAsync(john);
token.ShouldNotBeNullOrEmpty();
var isValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.EmailConfirmationTokenProvider,
UserManager<IdentityUser>.ConfirmEmailTokenPurpose,
token);
isValid.ShouldBeTrue();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Invalid_Token_Should_Fail_Verification()
{
using (var uow = UnitOfWorkManager.Begin())
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
await UserManager.GenerateEmailConfirmationTokenAsync(john);
var isValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.EmailConfirmationTokenProvider,
UserManager<IdentityUser>.ConfirmEmailTokenPurpose,
"invalid-token-value");
isValid.ShouldBeFalse();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Second_Token_Should_Invalidate_First_Token()
{
using (var uow = UnitOfWorkManager.Begin())
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
var firstToken = await UserManager.GenerateEmailConfirmationTokenAsync(john);
john = await UserRepository.GetAsync(TestData.UserJohnId);
var secondToken = await UserManager.GenerateEmailConfirmationTokenAsync(john);
john = await UserRepository.GetAsync(TestData.UserJohnId);
var firstTokenValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.EmailConfirmationTokenProvider,
UserManager<IdentityUser>.ConfirmEmailTokenPurpose,
firstToken);
var secondTokenValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.EmailConfirmationTokenProvider,
UserManager<IdentityUser>.ConfirmEmailTokenPurpose,
secondToken);
firstTokenValid.ShouldBeFalse();
secondTokenValid.ShouldBeTrue();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Token_Should_Become_Invalid_After_Email_Confirmation_With_Explicit_Revocation()
{
@ -129,49 +56,13 @@ public class AbpEmailConfirmationTokenProvider_Tests : AbpIdentityAspNetCoreTest
result.Succeeded.ShouldBeTrue();
// ConfirmEmailAsync does NOT update SecurityStamp, so the hash is not
// automatically invalidated. Callers that require single-use semantics must
// explicitly revoke the stored token hash after a successful confirmation.
// automatically invalidated. Callers must explicitly revoke the hash.
john = await UserRepository.GetAsync(TestData.UserJohnId);
var removeResult = await UserManager.RemoveEmailConfirmationTokenAsync(john);
removeResult.Succeeded.ShouldBeTrue();
(await UserManager.RemoveEmailConfirmationTokenAsync(john)).Succeeded.ShouldBeTrue();
john = await UserRepository.GetAsync(TestData.UserJohnId);
var isValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.EmailConfirmationTokenProvider,
UserManager<IdentityUser>.ConfirmEmailTokenPurpose,
token);
isValid.ShouldBeFalse();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Token_Hash_Should_Persist_Across_UnitOfWork_Boundaries()
{
string token;
// UoW 1: generate the token; UpdateAsync inside GenerateAsync must persist the hash.
using (var uow = UnitOfWorkManager.Begin(requiresNew: true))
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
token = await UserManager.GenerateEmailConfirmationTokenAsync(john);
await uow.CompleteAsync();
}
// UoW 2: validate using a fresh DbContext to confirm the hash was written to the DB.
using (var uow = UnitOfWorkManager.Begin(requiresNew: true))
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
var isValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.EmailConfirmationTokenProvider,
UserManager<IdentityUser>.ConfirmEmailTokenPurpose,
token);
(await VerifyTokenAsync(john, token)).ShouldBeFalse();
isValid.ShouldBeTrue();
await uow.CompleteAsync();
}
}

139
modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpPasswordResetTokenProvider_Tests.cs

@ -7,20 +7,23 @@ using Xunit;
namespace Volo.Abp.Identity.AspNetCore;
public class AbpPasswordResetTokenProvider_Tests : AbpIdentityAspNetCoreTestBase
public class AbpPasswordResetTokenProvider_Tests : AbpSingleActiveTokenProviderTestBase
{
protected IIdentityUserRepository UserRepository { get; }
protected IdentityUserManager UserManager { get; }
protected IdentityTestData TestData { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected override Task<string> GenerateTokenAsync(IdentityUser user)
=> UserManager.GeneratePasswordResetTokenAsync(user);
public AbpPasswordResetTokenProvider_Tests()
{
UserRepository = GetRequiredService<IIdentityUserRepository>();
UserManager = GetRequiredService<IdentityUserManager>();
TestData = GetRequiredService<IdentityTestData>();
UnitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
}
protected override Task<bool> VerifyTokenAsync(IdentityUser user, string token)
=> UserManager.VerifyUserTokenAsync(
user,
UserManager.Options.Tokens.PasswordResetTokenProvider,
UserManager<IdentityUser>.ResetPasswordTokenPurpose,
token);
protected override string GetProviderName()
=> UserManager.Options.Tokens.PasswordResetTokenProvider;
protected override string GetPurpose()
=> UserManager<IdentityUser>.ResetPasswordTokenPurpose;
[Fact]
public void AbpPasswordResetTokenProvider_Should_Be_Registered()
@ -40,82 +43,6 @@ public class AbpPasswordResetTokenProvider_Tests : AbpIdentityAspNetCoreTestBase
identityOptions.Tokens.PasswordResetTokenProvider.ShouldBe(AbpPasswordResetTokenProvider.ProviderName);
}
[Fact]
public async Task Generate_And_Verify_Password_Reset_Token_Should_Succeed()
{
using (var uow = UnitOfWorkManager.Begin())
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
var token = await UserManager.GeneratePasswordResetTokenAsync(john);
token.ShouldNotBeNullOrEmpty();
var isValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.PasswordResetTokenProvider,
UserManager<IdentityUser>.ResetPasswordTokenPurpose,
token);
isValid.ShouldBeTrue();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Invalid_Token_Should_Fail_Verification()
{
using (var uow = UnitOfWorkManager.Begin())
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
await UserManager.GeneratePasswordResetTokenAsync(john);
var isValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.PasswordResetTokenProvider,
UserManager<IdentityUser>.ResetPasswordTokenPurpose,
"invalid-token-value");
isValid.ShouldBeFalse();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Second_Token_Should_Invalidate_First_Token()
{
using (var uow = UnitOfWorkManager.Begin())
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
var firstToken = await UserManager.GeneratePasswordResetTokenAsync(john);
john = await UserRepository.GetAsync(TestData.UserJohnId);
var secondToken = await UserManager.GeneratePasswordResetTokenAsync(john);
john = await UserRepository.GetAsync(TestData.UserJohnId);
var firstTokenValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.PasswordResetTokenProvider,
UserManager<IdentityUser>.ResetPasswordTokenPurpose,
firstToken);
var secondTokenValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.PasswordResetTokenProvider,
UserManager<IdentityUser>.ResetPasswordTokenPurpose,
secondToken);
firstTokenValid.ShouldBeFalse();
secondTokenValid.ShouldBeTrue();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Token_Should_Become_Invalid_After_Password_Reset()
{
@ -131,42 +58,8 @@ public class AbpPasswordResetTokenProvider_Tests : AbpIdentityAspNetCoreTestBase
// SecurityStamp has changed after reset, so the old token must be invalid.
john = await UserRepository.GetAsync(TestData.UserJohnId);
var isValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.PasswordResetTokenProvider,
UserManager<IdentityUser>.ResetPasswordTokenPurpose,
token);
isValid.ShouldBeFalse();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Token_Hash_Should_Persist_Across_UnitOfWork_Boundaries()
{
string token;
// UoW 1: generate the token; UpdateAsync inside GenerateAsync must persist the hash.
using (var uow = UnitOfWorkManager.Begin(requiresNew: true))
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
token = await UserManager.GeneratePasswordResetTokenAsync(john);
await uow.CompleteAsync();
}
// UoW 2: validate using a fresh DbContext to confirm the hash was written to the DB.
using (var uow = UnitOfWorkManager.Begin(requiresNew: true))
{
var john = await UserRepository.GetAsync(TestData.UserJohnId);
var isValid = await UserManager.VerifyUserTokenAsync(
john,
UserManager.Options.Tokens.PasswordResetTokenProvider,
UserManager<IdentityUser>.ResetPasswordTokenPurpose,
token);
(await VerifyTokenAsync(john, token)).ShouldBeFalse();
isValid.ShouldBeTrue();
await uow.CompleteAsync();
}
}

138
modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProviderTestBase.cs

@ -0,0 +1,138 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Shouldly;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.Identity.AspNetCore;
/// <summary>
/// Abstract base class that exercises the common behaviour of every
/// <see cref="AbpSingleActiveTokenProvider"/> subclass.
/// Concrete subclasses inject their provider-specific generate/verify helpers
/// so the same test suite runs against each provider.
/// </summary>
public abstract class AbpSingleActiveTokenProviderTestBase : AbpIdentityAspNetCoreTestBase
{
protected IIdentityUserRepository UserRepository { get; }
protected IdentityUserManager UserManager { get; }
protected IdentityTestData TestData { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected AbpSingleActiveTokenProviderTestBase()
{
UserRepository = GetRequiredService<IIdentityUserRepository>();
UserManager = GetRequiredService<IdentityUserManager>();
TestData = GetRequiredService<IdentityTestData>();
UnitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
}
/// <summary>Generates a token for <paramref name="user"/> via the provider under test.</summary>
protected abstract Task<string> GenerateTokenAsync(IdentityUser user);
/// <summary>Verifies <paramref name="token"/> for <paramref name="user"/> via the provider under test.</summary>
protected abstract Task<bool> VerifyTokenAsync(IdentityUser user, string token);
/// <summary>Returns the provider name used to look up the stored hash.</summary>
protected abstract string GetProviderName();
/// <summary>Returns the token purpose used as the hash key prefix.</summary>
protected abstract string GetPurpose();
private string GetHashKey() => GetPurpose() + AbpSingleActiveTokenProvider.TokenHashSuffix;
[Fact]
public async Task Generate_And_Verify_Token_Should_Succeed()
{
using (var uow = UnitOfWorkManager.Begin())
{
var user = await UserRepository.GetAsync(TestData.UserJohnId);
var token = await GenerateTokenAsync(user);
token.ShouldNotBeNullOrEmpty();
user = await UserRepository.GetAsync(TestData.UserJohnId);
(await VerifyTokenAsync(user, token)).ShouldBeTrue();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Invalid_Token_Should_Fail_Verification()
{
using (var uow = UnitOfWorkManager.Begin())
{
var user = await UserRepository.GetAsync(TestData.UserJohnId);
await GenerateTokenAsync(user);
user = await UserRepository.GetAsync(TestData.UserJohnId);
(await VerifyTokenAsync(user, "invalid-token-value")).ShouldBeFalse();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Second_Token_Should_Invalidate_First_Token()
{
using (var uow = UnitOfWorkManager.Begin())
{
var user = await UserRepository.GetAsync(TestData.UserJohnId);
var firstToken = await GenerateTokenAsync(user);
user = await UserRepository.GetAsync(TestData.UserJohnId);
var secondToken = await GenerateTokenAsync(user);
user = await UserRepository.GetAsync(TestData.UserJohnId);
(await VerifyTokenAsync(user, firstToken)).ShouldBeFalse();
(await VerifyTokenAsync(user, secondToken)).ShouldBeTrue();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Corrupted_Hash_Should_Return_False_Instead_Of_Throwing()
{
using (var uow = UnitOfWorkManager.Begin())
{
var user = await UserRepository.GetAsync(TestData.UserJohnId);
var token = await GenerateTokenAsync(user);
// Overwrite with a non-hex string to simulate data corruption.
user = await UserRepository.GetAsync(TestData.UserJohnId);
await UserManager.SetAuthenticationTokenAsync(user, GetProviderName(), GetHashKey(), "not-valid-hex!!!");
user = await UserRepository.GetAsync(TestData.UserJohnId);
// ValidateAsync must catch FormatException internally and return false.
(await VerifyTokenAsync(user, token)).ShouldBeFalse();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Token_Hash_Should_Persist_Across_UnitOfWork_Boundaries()
{
string token;
// UoW 1: generate; UpdateAsync inside GenerateAsync must write the hash to the DB.
using (var uow = UnitOfWorkManager.Begin(requiresNew: true))
{
var user = await UserRepository.GetAsync(TestData.UserJohnId);
token = await GenerateTokenAsync(user);
await uow.CompleteAsync();
}
// UoW 2: validate with a fresh DbContext to confirm the hash was persisted.
using (var uow = UnitOfWorkManager.Begin(requiresNew: true))
{
var user = await UserRepository.GetAsync(TestData.UserJohnId);
(await VerifyTokenAsync(user, token)).ShouldBeTrue();
await uow.CompleteAsync();
}
}
}
Loading…
Cancel
Save