Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moq usermanager missing IUserEmailStore

what i am testing.

This is an identity server project with a login to federated gateway. I do not control this gateway and am having issues with them not returning the proper claims back to me that i need to verify the users logins. I would like to be able to test that i can handle these errors.

For example email claim is missing without that i can not login a user.

I have created a test that tests the email claim is missing returns an error.(Works fine)

Now I am trying to test the other side of things. If the claims are in fact there it should return the user that matches to the claims returned.

The method we are testing

public static async Task<(ApplicationUser user, string provider, string providerUserUserName, IEnumerable<Claim> claims, string message)> FindUserFromExternalProvider(AuthenticateResult result, UserManager<ApplicationUser> userManager, ILogger<SegesExternalController> logger)
    {
        var externalUser = result.Principal;

        // try to determine the unique id of the external user (issued by the provider)
        var eMailClaim = externalUser.FindFirst(SegesSettingsConstants.SegesEmailClaimName);

        if(eMailClaim == null) return (null, null, null, null, $"{SegesSettingsConstants.SegesEmailClaimName} claim not found.");

        // remove the user id claim so we don't include it as an extra claim if/when we provision the user
        var claims = externalUser.Claims.ToList();
        claims.LogSegesClaims(logger);

        claims.Remove(eMailClaim);
        // Should we remove more claims
        var provider = result.Properties.Items["scheme"];
        var providerUserUserName = eMailClaim.Value;

        var user = await userManager.FindByEmailAsync(providerUserUserName);  // Test Breaks here

        return (user, provider, providerUserUserName, claims, null);
    }

Test

[Fact]
public async void Federated_login_with_email_claim_return_no_error()
    {
        // Arrange

        var principal = new ClaimsPrincipal();
        principal.AddIdentity(new ClaimsIdentity(
            new Claim[] {
                new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "Testbruger til André"),
                new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", @"PROD\Salg43"),
                new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/postalcode", "8200"),
                new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/locality", "Aarhus N"),
                new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "[email protected]"),
            },
            "FakeScheme"));
        var authenticateResult = AuthenticateResult.Success(new AuthenticationTicket(principal, new AuthenticationProperties() { Items = { { "scheme", "fed" } } }, "FakeScheme"));


        var exprectUser = new ApplicationUser()
        {
            UserName = "[email protected]",
            NormalizedUserName = "[email protected]",
            NormalizedEmail = "[email protected]",
            Email = "[email protected]",
            Id = 123,
            EmailConfirmed = true
        };

        var mockEmailStore = new Mock<IUserEmailStore<ApplicationUser>>();
        var mockQueryableUserStore = new Mock<IQueryableUserStore<ApplicationUser>>();

        var mockUserStore = new Mock<IUserStore<ApplicationUser>>();
        mockUserStore.Setup(x => x.FindByIdAsync(exprectUser.Id.ToString(), CancellationToken.None)).ReturnsAsync(exprectUser);
        var userManager = new UserManager<ApplicationUser>(mockUserStore.Object, null, null, null,  null, null, null, null, null);


        var logger = new Logger<ExternalController>(new LoggerFactory());

        // Act
        var (user, provider, providerUserUserName, claims, errorMessage) = await AuthorizationHelpers.FindUserFromExternalProvider(authenticateResult, userManager, logger);

        // Assert
        user.ShouldNotBeNull();
    }

The issue with above.

I am trying to moq a usermanager for my unit test

var exprectUser = new ApplicationUser()
        {
            UserName = "[email protected]",
            NormalizedUserName = "[email protected]",
            NormalizedEmail = "[email protected]",
            Email = "[email protected]",
            Id = 123,
            EmailConfirmed = true
        };

var mockUserStore = new Mock<IUserStore<ApplicationUser>>();
mockUserStore.Setup(x => x.FindByIdAsync(exprectUser.Id.ToString(), CancellationToken.None)).ReturnsAsync(exprectUser);
var userManager = new UserManager<ApplicationUser>(mockUserStore.Object, null, null, null,  null, null, null, null, null);

however when the method i am testing tries to find the user.

var findUser = await userManager.FindByEmailAsync("[email protected]");

it throws an error

Message: System.NotSupportedException : Store does not implement IUserEmailStore.

How do i implement IUserEmailStore in my moq usermanager?

My unit test project does contain the newest EntityFramework package.

Trying another way.

var founduser = userManager.Users.FirstOrDefault(e => e.Email.Equals("[email protected]", StringComparison.InvariantCultureIgnoreCase));

results in

System.NotSupportedException : Store does not implement IQueryableUserStore.

I think i must be moqing this wrong.

Update From comment

Ok i can moq the IUserEmailStore but I am not sure what i should do with it

var mockEmailStore = new Mock<IUserEmailStore<ApplicationUser>>();
like image 712
DaImTo Avatar asked Apr 10 '19 10:04

DaImTo


2 Answers

I managed to create a full moq usermanager that lets me search on email

 public class MoqUserManager : UserManager<ApplicationUser>
    {
        public MoqUserManager(IUserStore<ApplicationUser> userStore) : base(userStore,
                new Mock<IOptions<IdentityOptions>>().Object,
                new Mock<IPasswordHasher<ApplicationUser>>().Object,
                new IUserValidator<ApplicationUser>[0],
                new IPasswordValidator<ApplicationUser>[0],
                new Mock<ILookupNormalizer>().Object,
                new Mock<IdentityErrorDescriber>().Object,
                new Mock<IServiceProvider>().Object,
                new Mock<ILogger<UserManager<ApplicationUser>>>().Object)
        { }

        public override Task<ApplicationUser> FindByEmailAsync(string email)
        {
            return Task.FromResult(new ApplicationUser { Email = email });
        }    
    }

which gives me

var mockUserStore = new Mock<IUserStore<ApplicationUser>>();
mockUserStore.Setup(x => x.FindByIdAsync(exprectUser.Id.ToString(), CancellationToken.None)).ReturnsAsync(exprectUser);
var userManager = new FakeUserManager(mockUserStore.Object);

So now i can verify that the proper user is returned from my identity server matching the federated login user.

like image 91
DaImTo Avatar answered Nov 15 '22 01:11

DaImTo


Okay your with the updated question the issue lies in

var userManager = new UserManager<ApplicationUser>(mockUserStore.Object, null, null, null,  null, null, null, null, null);

This is not creating a mock, but an actual instance of UserManager<T>.

You will have to do

var userManagerMock = new Mock<UserManager<ApplicationUser>>(mockUserStore.Object, null, null, null,  null, null, null, null, null);

then do an setup

userManagerMock.Setup(um => um.FindByEmailAsync("[email protected])).Returns(exprectUser)

and pass userManagerMock.Object to your

var (user, provider, providerUserUserName, claims, errorMessage) = await AuthorizationHelpers.FindUserFromExternalProvider(authenticateResult, userManagerMock.Object, logger);

When mocking, you never want to call new on the external dependency and instead mock it, since then you can't change its behavior for a specific test. UserManager<T> should have all or most public properties as virtual, so you can override them.

like image 30
Tseng Avatar answered Nov 15 '22 01:11

Tseng