// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using FakeItEasy; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Squidex.Domain.Users { public class DefaultUserResolverTests { private readonly IUserFactory userFactory = A.Fake(); private readonly UserManager userManager = A.Fake>(); private readonly DefaultUserResolver sut; public DefaultUserResolverTests() { A.CallTo(() => userFactory.IsId(A.That.StartsWith("id"))) .Returns(true); A.CallTo(() => userManager.NormalizeEmail(A._)) .ReturnsLazily(c => c.GetArgument(0)!.ToUpperInvariant()); var serviceProvider = A.Fake(); var scope = A.Fake(); var scopeFactory = A.Fake(); A.CallTo(() => scopeFactory.CreateScope()) .Returns(scope); A.CallTo(() => scope.ServiceProvider) .Returns(serviceProvider); A.CallTo(() => serviceProvider.GetService(typeof(IServiceScopeFactory))) .Returns(scopeFactory); A.CallTo(() => serviceProvider.GetService(typeof(IUserFactory))) .Returns(userFactory); A.CallTo(() => serviceProvider.GetService(typeof(UserManager))) .Returns(userManager); sut = new DefaultUserResolver(serviceProvider); } [Fact] public async Task Should_create_user_and_return_true_when_created() { var (user, claims) = GenerateUser("id1"); A.CallTo(() => userFactory.Create(user.Email)) .Returns(user); A.CallTo(() => userManager.CreateAsync(user)) .Returns(IdentityResult.Success); SetupUser(user, claims); var (result, created) = await sut.CreateUserIfNotExistsAsync(user.Email, false); Assert.Equal(user.Id, result!.Id); Assert.Equal(user.Email, result!.Email); Assert.True(created); } [Fact] public async Task Should_create_user_and_return_false_when_already_exists() { var (user, claims) = GenerateUser("id1"); A.CallTo(() => userFactory.Create(user.Email)) .Returns(user); A.CallTo(() => userManager.CreateAsync(user)) .Returns(IdentityResult.Failed()); SetupUser(user, claims); var (result, created) = await sut.CreateUserIfNotExistsAsync(user.Email, false); Assert.Equal(user.Id, result!.Id); Assert.Equal(user.Email, result!.Email); Assert.False(created); } [Fact] public async Task Should_create_user_and_return_false_when_exception_thrown() { var (user, claims) = GenerateUser("id1"); A.CallTo(() => userFactory.Create(user.Email)) .Throws(new InvalidOperationException()); A.CallTo(() => userManager.CreateAsync(user)) .Returns(IdentityResult.Failed()); SetupUser(user, claims); var (result, created) = await sut.CreateUserIfNotExistsAsync(user.Email, false); Assert.Equal(user.Id, result!.Id); Assert.Equal(user.Email, result!.Email); Assert.False(created); } [Fact] public async Task Should_add_claim_when_not_added_yet() { var (user, claims) = GenerateUser("id2"); A.CallTo(() => userManager.AddClaimsAsync(user, A>._)) .Returns(IdentityResult.Success); SetupUser(user, claims); await sut.SetClaimAsync("id2", "my-claim", "new-value"); A.CallTo(() => userManager.AddClaimsAsync(user, A>.That.Matches(x => x.Any(y => y.Type == "my-claim" && y.Value == "new-value")))) .MustHaveHappened(); } [Fact] public async Task Should_remove_previous_claim() { var (user, claims) = GenerateUser("id2"); claims.Add(new Claim("my-claim", "old-value")); A.CallTo(() => userManager.AddClaimsAsync(user, A>._)) .Returns(IdentityResult.Success); A.CallTo(() => userManager.RemoveClaimsAsync(user, A>._)) .Returns(IdentityResult.Success); SetupUser(user, claims); await sut.SetClaimAsync("id2", "my-claim", "new-value"); A.CallTo(() => userManager.AddClaimsAsync(user, A>.That.Matches(x => x.Any(y => y.Type == "my-claim" && y.Value == "new-value")))) .MustHaveHappened(); A.CallTo(() => userManager.RemoveClaimsAsync(user, A>.That.Matches(x => x.Any(y => y.Type == "my-claim" && y.Value == "old-value")))) .MustHaveHappened(); } [Fact] public async Task Should_resolve_user_by_email() { var (user, claims) = GenerateUser("id1"); SetupUser(user, claims); var result = await sut.FindByIdOrEmailAsync(user.Email); Assert.Equal(user.Id, result!.Id); Assert.Equal(user.Email, result!.Email); Assert.Equal(claims, result!.Claims); } [Fact] public async Task Should_resolve_user_by_id() { var (user, claims) = GenerateUser("id2"); SetupUser(user, claims); var result = await sut.FindByIdOrEmailAsync(user.Id)!; Assert.Equal(user.Id, result!.Id); Assert.Equal(user.Email, result!.Email); Assert.Equal(claims, result!.Claims); } [Fact] public async Task Should_resolve_user_by_id_only() { var (user, claims) = GenerateUser("id2"); SetupUser(user, claims); var result = await sut.FindByIdAsync(user.Id)!; Assert.Equal(user.Id, result!.Id); Assert.Equal(user.Email, result!.Email); Assert.Equal(claims, result!.Claims); } [Fact] public async Task Should_query_many_by_email_async() { var (user1, claims1) = GenerateUser("id1"); var (user2, claims2) = GenerateUser("id2"); var list = new List { user1, user2 }; A.CallTo(() => userManager.Users) .Returns(list.AsQueryable()); A.CallTo(() => userManager.GetClaimsAsync(user2)) .Returns(claims2); var result = await sut.QueryByEmailAsync("2"); Assert.Equal(user2.Id, result[0].Id); Assert.Equal(user2.Email, result[0].Email); Assert.Equal(claims2, result[0].Claims); A.CallTo(() => userManager.GetClaimsAsync(user1)) .MustNotHaveHappened(); } private static (IdentityUser, List) GenerateUser(string id) { var user = new IdentityUser { Id = id, Email = $"email_{id}", NormalizedEmail = $"EMAIL_{id}" }; var claims = new List { new Claim($"{id}_a", "1"), new Claim($"{id}_b", "2") }; return (user, claims); } private void SetupUser(IdentityUser user, List claims) { A.CallTo(() => userManager.FindByEmailAsync(user.Email)) .Returns(user); A.CallTo(() => userManager.FindByIdAsync(user.Id)) .Returns(user); A.CallTo(() => userManager.GetClaimsAsync(user)) .Returns(claims); } } }