// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Data.SqlClient; using System.Linq; using System.Linq.Expressions; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNet.Identity.Test; using Microsoft.AspNet.Testing; using Microsoft.AspNet.Testing.xunit; using Microsoft.Data.Entity; using Microsoft.Data.Entity.Storage; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Microsoft.AspNet.Identity.EntityFramework.Test { public abstract class SqlStoreTestBase : UserManagerTestBase where TUser : IdentityUser, new() where TRole : IdentityRole, new() where TKey : IEquatable { public abstract string ConnectionString { get; } protected override bool ShouldSkipDbTests() { return TestPlatformHelper.IsMono || !TestPlatformHelper.IsWindows; } public class TestDbContext : IdentityDbContext { } protected override TUser CreateTestUser(string namePrefix = "", string email = "", string phoneNumber = "", bool lockoutEnabled = false, DateTimeOffset? lockoutEnd = default(DateTimeOffset?), bool useNamePrefixAsUserName = false) { return new TUser { UserName = useNamePrefixAsUserName ? namePrefix : string.Format("{0}{1}", namePrefix, Guid.NewGuid()), Email = email, PhoneNumber = phoneNumber, LockoutEnabled = lockoutEnabled, LockoutEnd = lockoutEnd }; } protected override TRole CreateTestRole(string roleNamePrefix = "", bool useRoleNamePrefixAsRoleName = false) { var roleName = useRoleNamePrefixAsRoleName ? roleNamePrefix : string.Format("{0}{1}", roleNamePrefix, Guid.NewGuid()); return new TRole() { Name = roleName }; } protected override Expression> RoleNameEqualsPredicate(string roleName) => r => r.Name == roleName; protected override Expression> UserNameEqualsPredicate(string userName) => u => u.UserName == userName; protected override Expression> RoleNameStartsWithPredicate(string roleName) => r => r.Name.StartsWith(roleName); protected override Expression> UserNameStartsWithPredicate(string userName) => u => u.UserName.StartsWith(userName); [TestPriority(-1000)] [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] public void DropDatabaseStart() { DropDb(); } [TestPriority(10000)] [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] public void DropDatabaseDone() { DropDb(); } public void DropDb() { var db = DbUtil.Create(ConnectionString); db.Database.EnsureDeleted(); } public TestDbContext CreateContext(bool delete = false) { var db = DbUtil.Create(ConnectionString); if (delete) { db.Database.EnsureDeleted(); } db.Database.EnsureCreated(); return db; } protected override object CreateTestContext() { return CreateContext(); } protected override void AddUserStore(IServiceCollection services, object context = null) { services.AddSingleton>(new UserStore((TestDbContext)context)); } protected override void AddRoleStore(IServiceCollection services, object context = null) { services.AddSingleton>(new RoleStore((TestDbContext)context)); } protected override void SetUserPasswordHash(TUser user, string hashedPassword) { user.PasswordHash = hashedPassword; } public void EnsureDatabase() { CreateContext(); } [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] public void EnsureDefaultSchema() { VerifyDefaultSchema(CreateContext()); } internal static void VerifyDefaultSchema(TestDbContext dbContext) { var sqlConn = dbContext.Database.GetDbConnection(); using (var db = new SqlConnection(sqlConn.ConnectionString)) { db.Open(); Assert.True(VerifyColumns(db, "AspNetUsers", "Id", "UserName", "Email", "PasswordHash", "SecurityStamp", "EmailConfirmed", "PhoneNumber", "PhoneNumberConfirmed", "TwoFactorEnabled", "LockoutEnabled", "LockoutEnd", "AccessFailedCount", "ConcurrencyStamp", "NormalizedUserName", "NormalizedEmail")); Assert.True(VerifyColumns(db, "AspNetRoles", "Id", "Name", "NormalizedName", "ConcurrencyStamp")); Assert.True(VerifyColumns(db, "AspNetUserRoles", "UserId", "RoleId")); Assert.True(VerifyColumns(db, "AspNetUserClaims", "Id", "UserId", "ClaimType", "ClaimValue")); Assert.True(VerifyColumns(db, "AspNetUserLogins", "UserId", "ProviderKey", "LoginProvider", "ProviderDisplayName")); VerifyIndex(db, "AspNetRoles", "RoleNameIndex"); VerifyIndex(db, "AspNetUsers", "UserNameIndex"); VerifyIndex(db, "AspNetUsers", "EmailIndex"); db.Close(); } } internal static bool VerifyColumns(SqlConnection conn, string table, params string[] columns) { var count = 0; using ( var command = new SqlCommand("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS where TABLE_NAME=@Table", conn)) { command.Parameters.Add(new SqlParameter("Table", table)); using (var reader = command.ExecuteReader()) { while (reader.Read()) { count++; if (!columns.Contains(reader.GetString(0))) { return false; } } return count == columns.Length; } } } internal static void VerifyIndex(SqlConnection conn, string table, string index) { using ( var command = new SqlCommand( "SELECT COUNT(*) FROM sys.indexes where NAME=@Index AND object_id = OBJECT_ID(@Table)", conn)) { command.Parameters.Add(new SqlParameter("Index", index)); command.Parameters.Add(new SqlParameter("Table", table)); using (var reader = command.ExecuteReader()) { Assert.True(reader.Read()); Assert.True(reader.GetInt32(0) > 0); } } } [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] public async Task DeleteRoleNonEmptySucceedsTest() { // Need fail if not empty? var context = CreateTestContext(); var userMgr = CreateManager(context); var roleMgr = CreateRoleManager(context); var roleName = "delete" + Guid.NewGuid().ToString(); var role = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true); Assert.False(await roleMgr.RoleExistsAsync(roleName)); IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); var user = CreateTestUser(); IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user)); IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, roleName)); var roles = await userMgr.GetRolesAsync(user); Assert.Equal(1, roles.Count()); IdentityResultAssert.IsSuccess(await roleMgr.DeleteAsync(role)); Assert.Null(await roleMgr.FindByNameAsync(roleName)); Assert.False(await roleMgr.RoleExistsAsync(roleName)); // REVIEW: We should throw if deleteing a non empty role? roles = await userMgr.GetRolesAsync(user); Assert.Equal(0, roles.Count()); } [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] public async Task DeleteUserRemovesFromRoleTest() { // Need fail if not empty? var userMgr = CreateManager(); var roleMgr = CreateRoleManager(); var roleName = "deleteUserRemove" + Guid.NewGuid().ToString(); var role = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true); Assert.False(await roleMgr.RoleExistsAsync(roleName)); IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); var user = CreateTestUser(); IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user)); IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, roleName)); var roles = await userMgr.GetRolesAsync(user); Assert.Equal(1, roles.Count()); IdentityResultAssert.IsSuccess(await userMgr.DeleteAsync(user)); roles = await userMgr.GetRolesAsync(user); Assert.Equal(0, roles.Count()); } [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] public void CanCreateUserUsingEF() { using (var db = CreateContext()) { var user = CreateTestUser(); db.Users.Add(user); db.SaveChanges(); Assert.True(db.Users.Any(u => u.UserName == user.UserName)); Assert.NotNull(db.Users.FirstOrDefault(u => u.UserName == user.UserName)); } } [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] public async Task CanCreateUsingManager() { var manager = CreateManager(); var user = CreateTestUser(); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); } private async Task LazyLoadTestSetup(TestDbContext db, TUser user) { var context = CreateContext(); var manager = CreateManager(context); var role = CreateRoleManager(context); var admin = CreateTestRole("Admin" + Guid.NewGuid().ToString()); var local = CreateTestRole("Local" + Guid.NewGuid().ToString()); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, new UserLoginInfo("provider", user.Id.ToString(), "display"))); IdentityResultAssert.IsSuccess(await role.CreateAsync(admin)); IdentityResultAssert.IsSuccess(await role.CreateAsync(local)); IdentityResultAssert.IsSuccess(await manager.AddToRoleAsync(user, admin.Name)); IdentityResultAssert.IsSuccess(await manager.AddToRoleAsync(user, local.Name)); Claim[] userClaims = { new Claim("Whatever", "Value"), new Claim("Whatever2", "Value2") }; foreach (var c in userClaims) { IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); } } [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] public async Task LoadFromDbFindByIdTest() { var db = CreateContext(); var user = CreateTestUser(); await LazyLoadTestSetup(db, user); db = CreateContext(); var manager = CreateManager(db); var userById = await manager.FindByIdAsync(user.Id.ToString()); Assert.Equal(2, (await manager.GetClaimsAsync(userById)).Count); Assert.Equal(1, (await manager.GetLoginsAsync(userById)).Count); Assert.Equal(2, (await manager.GetRolesAsync(userById)).Count); } [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] public async Task LoadFromDbFindByNameTest() { var db = CreateContext(); var user = CreateTestUser(); await LazyLoadTestSetup(db, user); db = CreateContext(); var manager = CreateManager(db); var userByName = await manager.FindByNameAsync(user.UserName); Assert.Equal(2, (await manager.GetClaimsAsync(userByName)).Count); Assert.Equal(1, (await manager.GetLoginsAsync(userByName)).Count); Assert.Equal(2, (await manager.GetRolesAsync(userByName)).Count); } [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] public async Task LoadFromDbFindByLoginTest() { var db = CreateContext(); var user = CreateTestUser(); await LazyLoadTestSetup(db, user); db = CreateContext(); var manager = CreateManager(db); var userByLogin = await manager.FindByLoginAsync("provider", user.Id.ToString()); Assert.Equal(2, (await manager.GetClaimsAsync(userByLogin)).Count); Assert.Equal(1, (await manager.GetLoginsAsync(userByLogin)).Count); Assert.Equal(2, (await manager.GetRolesAsync(userByLogin)).Count); } [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] public async Task LoadFromDbFindByEmailTest() { var db = CreateContext(); var user = CreateTestUser(); user.Email = "fooz@fizzy.pop"; await LazyLoadTestSetup(db, user); db = CreateContext(); var manager = CreateManager(db); var userByEmail = await manager.FindByEmailAsync(user.Email); Assert.Equal(2, (await manager.GetClaimsAsync(userByEmail)).Count); Assert.Equal(1, (await manager.GetLoginsAsync(userByEmail)).Count); Assert.Equal(2, (await manager.GetRolesAsync(userByEmail)).Count); } } }