diff --git a/All.sln b/All.sln index c0b3c620aa5..31037f88143 100644 --- a/All.sln +++ b/All.sln @@ -112,6 +112,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.Sqlite.sqlit EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.OData.FunctionalTests", "test\EFCore.OData.FunctionalTests\EFCore.OData.FunctionalTests.csproj", "{7C0E5443-FE44-4436-8A7D-CE64D1F889BD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFCore.AspNet.Specification.Tests", "test\EFCore.AspNet.Specification.Tests\EFCore.AspNet.Specification.Tests.csproj", "{80A812BF-8AB7-4197-AC1C-712BA5E818FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFCore.AspNet.SqlServer.FunctionalTests", "test\EFCore.AspNet.SqlServer.FunctionalTests\EFCore.AspNet.SqlServer.FunctionalTests.csproj", "{F956A344-5C8D-4015-A3BF-7A8304C58BE4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFCore.AspNet.Sqlite.FunctionalTests", "test\EFCore.AspNet.Sqlite.FunctionalTests\EFCore.AspNet.Sqlite.FunctionalTests.csproj", "{CC93C465-F5AC-4CB9-A064-3675955962F4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFCore.AspNet.InMemory.FunctionalTests", "test\EFCore.AspNet.InMemory.FunctionalTests\EFCore.AspNet.InMemory.FunctionalTests.csproj", "{F1B2E5A0-8C74-414A-B262-353FEE325E9F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -294,6 +302,22 @@ Global {7C0E5443-FE44-4436-8A7D-CE64D1F889BD}.Debug|Any CPU.Build.0 = Debug|Any CPU {7C0E5443-FE44-4436-8A7D-CE64D1F889BD}.Release|Any CPU.ActiveCfg = Release|Any CPU {7C0E5443-FE44-4436-8A7D-CE64D1F889BD}.Release|Any CPU.Build.0 = Release|Any CPU + {80A812BF-8AB7-4197-AC1C-712BA5E818FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80A812BF-8AB7-4197-AC1C-712BA5E818FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80A812BF-8AB7-4197-AC1C-712BA5E818FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80A812BF-8AB7-4197-AC1C-712BA5E818FB}.Release|Any CPU.Build.0 = Release|Any CPU + {F956A344-5C8D-4015-A3BF-7A8304C58BE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F956A344-5C8D-4015-A3BF-7A8304C58BE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F956A344-5C8D-4015-A3BF-7A8304C58BE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F956A344-5C8D-4015-A3BF-7A8304C58BE4}.Release|Any CPU.Build.0 = Release|Any CPU + {CC93C465-F5AC-4CB9-A064-3675955962F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC93C465-F5AC-4CB9-A064-3675955962F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC93C465-F5AC-4CB9-A064-3675955962F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC93C465-F5AC-4CB9-A064-3675955962F4}.Release|Any CPU.Build.0 = Release|Any CPU + {F1B2E5A0-8C74-414A-B262-353FEE325E9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1B2E5A0-8C74-414A-B262-353FEE325E9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1B2E5A0-8C74-414A-B262-353FEE325E9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1B2E5A0-8C74-414A-B262-353FEE325E9F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -343,6 +367,10 @@ Global {B163761D-FB4A-4C80-BAB9-01905E1351EF} = {258D5057-81B9-40EC-A872-D21E27452749} {E0FF35C8-8038-4394-9C2A-AF34BE3CC61F} = {258D5057-81B9-40EC-A872-D21E27452749} {7C0E5443-FE44-4436-8A7D-CE64D1F889BD} = {258D5057-81B9-40EC-A872-D21E27452749} + {80A812BF-8AB7-4197-AC1C-712BA5E818FB} = {258D5057-81B9-40EC-A872-D21E27452749} + {F956A344-5C8D-4015-A3BF-7A8304C58BE4} = {258D5057-81B9-40EC-A872-D21E27452749} + {CC93C465-F5AC-4CB9-A064-3675955962F4} = {258D5057-81B9-40EC-A872-D21E27452749} + {F1B2E5A0-8C74-414A-B262-353FEE325E9F} = {258D5057-81B9-40EC-A872-D21E27452749} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {285A5EB4-BCF4-40EB-B9E1-DF6DBCB5E705} diff --git a/EFCore.Runtime.slnf b/EFCore.Runtime.slnf index 30dfd3597f9..ab9c14f8060 100644 --- a/EFCore.Runtime.slnf +++ b/EFCore.Runtime.slnf @@ -33,6 +33,10 @@ "test\\EFCore.Sqlite.FunctionalTests\\EFCore.Sqlite.FunctionalTests.csproj", "test\\EFCore.Sqlite.Tests\\EFCore.Sqlite.Tests.csproj", "test\\EFCore.Tests\\EFCore.Tests.csproj", + "test\\EFCore.AspNet.Specification.Tests\\EFCore.AspNet.Specification.Tests.csproj", + "test\\EFCore.AspNet.SqlServer.FunctionalTests\\EFCore.AspNet.SqlServer.FunctionalTests.csproj", + "test\\EFCore.AspNet.Sqlite.FunctionalTests\\EFCore.AspNet.Sqlite.FunctionalTests.csproj", + "test\\EFCore.AspNet.InMemory.FunctionalTests\\EFCore.AspNet.InMemory.FunctionalTests.csproj" ] } } \ No newline at end of file diff --git a/test/EFCore.AspNet.InMemory.FunctionalTests/AspNetIdentityCustomTypesDefaultInMemoryTest.cs b/test/EFCore.AspNet.InMemory.FunctionalTests/AspNetIdentityCustomTypesDefaultInMemoryTest.cs new file mode 100644 index 00000000000..134462bcb9e --- /dev/null +++ b/test/EFCore.AspNet.InMemory.FunctionalTests/AspNetIdentityCustomTypesDefaultInMemoryTest.cs @@ -0,0 +1,55 @@ +// 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.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore +{ + public class AspNetIdentityCustomTypesDefaultInMemoryTest + : AspNetIdentityCustomTypesDefaultTestBase< + AspNetIdentityCustomTypesDefaultInMemoryTest.AspNetIdentityCustomTypesDefaultInMemoryFixture> + { + public AspNetIdentityCustomTypesDefaultInMemoryTest(AspNetIdentityCustomTypesDefaultInMemoryFixture fixture) + : base(fixture) + { + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + { + } + + protected override async Task ExecuteWithStrategyInTransactionAsync( + Func testOperation, + Func nestedTestOperation1 = null, + Func nestedTestOperation2 = null, + Func nestedTestOperation3 = null) + { + await base.ExecuteWithStrategyInTransactionAsync( + testOperation, nestedTestOperation1, nestedTestOperation2, nestedTestOperation3); + await Fixture.ReseedAsync(); + } + + public class AspNetIdentityCustomTypesDefaultInMemoryFixture : AspNetIdentityFixtureBase + { + protected override IServiceCollection AddServices(IServiceCollection serviceCollection) + => base.AddServices(serviceCollection).AddEntityFrameworkProxies(); + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder) + .UseLazyLoadingProxies() + .ConfigureWarnings(e => e.Ignore(InMemoryEventId.TransactionIgnoredWarning)); + + protected override ITestStoreFactory TestStoreFactory + => InMemoryTestStoreFactory.Instance; + + protected override string StoreName + => "AspNetCustomTypesDefaultIdentity"; + } + } +} diff --git a/test/EFCore.AspNet.InMemory.FunctionalTests/AspNetIdentityCustomTypesIntKeyInMemoryTest.cs b/test/EFCore.AspNet.InMemory.FunctionalTests/AspNetIdentityCustomTypesIntKeyInMemoryTest.cs new file mode 100644 index 00000000000..89950a25659 --- /dev/null +++ b/test/EFCore.AspNet.InMemory.FunctionalTests/AspNetIdentityCustomTypesIntKeyInMemoryTest.cs @@ -0,0 +1,49 @@ +// 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.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore +{ + public class AspNetIdentityCustomTypesIntKeyInMemoryTest + : AspNetIdentityCustomTypesIntKeyTestBase + { + public AspNetIdentityCustomTypesIntKeyInMemoryTest(AspNetIdentityCustomTypesIntKeyInMemoryFixture fixture) + : base(fixture) + { + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + { + } + + protected override async Task ExecuteWithStrategyInTransactionAsync( + Func testOperation, + Func nestedTestOperation1 = null, + Func nestedTestOperation2 = null, + Func nestedTestOperation3 = null) + { + await base.ExecuteWithStrategyInTransactionAsync( + testOperation, nestedTestOperation1, nestedTestOperation2, nestedTestOperation3); + await Fixture.ReseedAsync(); + } + + public class AspNetIdentityCustomTypesIntKeyInMemoryFixture : AspNetIdentityFixtureBase + { + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder).ConfigureWarnings(e => e.Ignore(InMemoryEventId.TransactionIgnoredWarning)); + + protected override ITestStoreFactory TestStoreFactory + => InMemoryTestStoreFactory.Instance; + + protected override string StoreName + => "AspNetCustomTypesIntKeyIdentity"; + } + } +} diff --git a/test/EFCore.AspNet.InMemory.FunctionalTests/AspNetIdentityDefaultInMemoryTest.cs b/test/EFCore.AspNet.InMemory.FunctionalTests/AspNetIdentityDefaultInMemoryTest.cs new file mode 100644 index 00000000000..7e1b8cb430a --- /dev/null +++ b/test/EFCore.AspNet.InMemory.FunctionalTests/AspNetIdentityDefaultInMemoryTest.cs @@ -0,0 +1,49 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore +{ + public class AspNetIdentityDefaultInMemoryTest + : AspNetIdentityDefaultTestBase + { + public AspNetIdentityDefaultInMemoryTest(AspNetDefaultIdentityInMemoryFixture fixture) + : base(fixture) + { + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + { + } + + protected override async Task ExecuteWithStrategyInTransactionAsync( + Func testOperation, + Func nestedTestOperation1 = null, + Func nestedTestOperation2 = null, + Func nestedTestOperation3 = null) + { + await base.ExecuteWithStrategyInTransactionAsync( + testOperation, nestedTestOperation1, nestedTestOperation2, nestedTestOperation3); + await Fixture.ReseedAsync(); + } + + public class AspNetDefaultIdentityInMemoryFixture : AspNetIdentityFixtureBase + { + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder).ConfigureWarnings(e => e.Ignore(InMemoryEventId.TransactionIgnoredWarning)); + + protected override ITestStoreFactory TestStoreFactory + => InMemoryTestStoreFactory.Instance; + + protected override string StoreName + => "AspNetDefaultIdentity"; + } + } +} diff --git a/test/EFCore.AspNet.InMemory.FunctionalTests/AspNetIdentityIntKeyInMemoryTest.cs b/test/EFCore.AspNet.InMemory.FunctionalTests/AspNetIdentityIntKeyInMemoryTest.cs new file mode 100644 index 00000000000..bb521c77d10 --- /dev/null +++ b/test/EFCore.AspNet.InMemory.FunctionalTests/AspNetIdentityIntKeyInMemoryTest.cs @@ -0,0 +1,50 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore +{ + public class AspNetIdentityIntKeyInMemoryTest + : AspNetIdentityIntKeyTestBase + { + public AspNetIdentityIntKeyInMemoryTest(AspNetIdentityIntKeyInMemoryFixture fixture) + : base(fixture) + { + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + { + } + + protected override async Task ExecuteWithStrategyInTransactionAsync( + Func, IdentityRole, int>, Task> testOperation, + Func, IdentityRole, int>, Task> nestedTestOperation1 = null, + Func, IdentityRole, int>, Task> nestedTestOperation2 = null, + Func, IdentityRole, int>, Task> nestedTestOperation3 = null) + { + await base.ExecuteWithStrategyInTransactionAsync( + testOperation, nestedTestOperation1, nestedTestOperation2, nestedTestOperation3); + await Fixture.ReseedAsync(); + } + + public class AspNetIdentityIntKeyInMemoryFixture : AspNetIdentityFixtureBase + { + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder).ConfigureWarnings(e => e.Ignore(InMemoryEventId.TransactionIgnoredWarning)); + + protected override ITestStoreFactory TestStoreFactory + => InMemoryTestStoreFactory.Instance; + + protected override string StoreName + => "AspNetIntKeyIdentity"; + } + } +} diff --git a/test/EFCore.AspNet.InMemory.FunctionalTests/EFCore.AspNet.InMemory.FunctionalTests.csproj b/test/EFCore.AspNet.InMemory.FunctionalTests/EFCore.AspNet.InMemory.FunctionalTests.csproj new file mode 100644 index 00000000000..e9482460911 --- /dev/null +++ b/test/EFCore.AspNet.InMemory.FunctionalTests/EFCore.AspNet.InMemory.FunctionalTests.csproj @@ -0,0 +1,24 @@ + + + + net5.0 + Microsoft.EntityFrameworkCore.AspNet.InMemory.FunctionalTests + Microsoft.EntityFrameworkCore + True + + + + + PreserveNewest + + + PreserveNewest + + + + + + + + + diff --git a/test/EFCore.AspNet.Specification.Tests/AspNetIdentityCustomTypesDefaultTestBase.cs b/test/EFCore.AspNet.Specification.Tests/AspNetIdentityCustomTypesDefaultTestBase.cs new file mode 100644 index 00000000000..bbef03e1afe --- /dev/null +++ b/test/EFCore.AspNet.Specification.Tests/AspNetIdentityCustomTypesDefaultTestBase.cs @@ -0,0 +1,446 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Xunit; + +namespace Microsoft.EntityFrameworkCore +{ + public abstract class AspNetIdentityCustomTypesDefaultTestBase + : AspNetIdentityTestBase + where TFixture : AspNetIdentityTestBase. + AspNetIdentityFixtureBase + { + protected AspNetIdentityCustomTypesDefaultTestBase(TFixture fixture) + : base(fixture) + { + } + + [ConditionalFact] + public void Can_build_string_key_identity_model_with_custom_types() + { + using (var context = CreateContext()) + { + var entityTypeMappings = context.Model.GetEntityTypes().Select(e => new EntityTypeMapping(e)).ToList(); + + EntityTypeMapping.AssertEqual(CustomTypesDefaultMappings, entityTypeMappings); + //throw new Exception(EntityTypeMapping.Serialize(entityTypeMappings)); + } + } + + [ConditionalFact] + public async Task Can_lazy_load_User_navigations() + { + var userId = ""; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + var user = new CustomUserString { NormalizedUserName = "wendy" }; + await CreateUser(context, user); + userId = user.Id; + }, + async context => + { + var user = await context.Users.SingleAsync(u => u.Id == userId); + + Assert.Equal(3, user.Claims.Count); + Assert.Equal(2, user.Logins.Count); + Assert.Equal(1, user.Tokens.Count); + Assert.Equal(2, user.UserRoles.Count); + }); + } + + [ConditionalFact] + public async Task Can_lazy_load_Role_navigations() + { + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, new CustomUserString { NormalizedUserName = "wendy" }); + }, + async context => + { + var role = await context.Roles.OrderBy(e => e.NormalizedName).FirstAsync(); + + Assert.Equal(2, role.RoleClaims.Count); + Assert.Equal(1, role.UserRoles.Count); + }); + } + + [ConditionalFact] + public async Task Can_lazy_load_UserRole_navigations() + { + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, new CustomUserString { NormalizedUserName = "wendy" }); + }, + async context => + { + var userRole = await context.UserRoles.OrderBy(e => e.Role.Name).FirstAsync(); + + Assert.NotNull(userRole.Role); + Assert.NotNull(userRole.User); + }); + } + + [ConditionalFact] + public async Task Can_lazy_load_UserClaim_navigations() + { + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, new CustomUserString { NormalizedUserName = "wendy" }); + }, + async context => + { + var userClaim = await context.UserClaims.OrderBy(e => e.ClaimType).ThenBy(e => e.ClaimValue).FirstAsync(); + Assert.NotNull(userClaim.User); + }); + } + + [ConditionalFact] + public async Task Can_lazy_load_UserLogin_navigations() + { + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, new CustomUserString { NormalizedUserName = "wendy" }); + }, + async context => + { + var userLogin = await context.UserLogins.OrderBy(e => e.LoginProvider).FirstAsync(); + Assert.NotNull(userLogin.User); + }); + } + + [ConditionalFact] + public async Task Can_lazy_load_RoleClaim_navigations() + { + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, new CustomUserString { NormalizedUserName = "wendy" }); + }, + async context => + { + var roleClaim = await context.RoleClaims.OrderBy(e => e.Role.Name).FirstAsync(); + Assert.NotNull(roleClaim.Role); + }); + } + + [ConditionalFact] + public async Task Can_lazy_load_UserToken_navigations() + { + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, new CustomUserString { NormalizedUserName = "wendy" }); + }, + async context => + { + var userToken = await context.UserTokens.OrderBy(e => e.Name).FirstAsync(); + Assert.NotNull(userToken.User); + }); + } + + protected readonly List CustomTypesDefaultMappings = new() + { + new() + { + Name = "Microsoft.EntityFrameworkCore.CustomRoleClaimString", + TableName = "MyRoleClaims", + PrimaryKey = "Key: CustomRoleClaimString.Id PK", + Properties = + { + "Property: CustomRoleClaimString.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd", + "Property: CustomRoleClaimString.ClaimType (string)", + "Property: CustomRoleClaimString.ClaimValue (string)", + "Property: CustomRoleClaimString.RoleId (string) Required FK Index", + }, + Indexes = { "{'RoleId'} ", }, + FKs = + { + "ForeignKey: CustomRoleClaimString {'RoleId'} -> CustomRoleString {'Id'} ToDependent: RoleClaims ToPrincipal: Role Cascade", + }, + Navigations = + { + "Navigation: CustomRoleClaimString.Role (CustomRoleString) ToPrincipal CustomRoleString Inverse: RoleClaims PropertyAccessMode.Field", + }, + }, + new() + { + Name = "Microsoft.EntityFrameworkCore.CustomRoleString", + TableName = "MyRoles", + PrimaryKey = "Key: CustomRoleString.Id PK", + Properties = + { + "Property: CustomRoleString.Id (string) Required PK AfterSave:Throw", + "Property: CustomRoleString.ConcurrencyStamp (string) Concurrency", + "Property: CustomRoleString.Name (string) MaxLength(256)", + "Property: CustomRoleString.NormalizedName (string) Index MaxLength(256)", + }, + Indexes = { "{'NormalizedName'} Unique", }, + Navigations = + { + "Navigation: CustomRoleString.RoleClaims (ICollection) Collection ToDependent CustomRoleClaimString Inverse: Role PropertyAccessMode.Field", + "Navigation: CustomRoleString.UserRoles (ICollection) Collection ToDependent CustomUserRoleString Inverse: Role PropertyAccessMode.Field", + }, + }, + new() + { + Name = "Microsoft.EntityFrameworkCore.CustomUserClaimString", + TableName = "MyUserClaims", + PrimaryKey = "Key: CustomUserClaimString.Id PK", + Properties = + { + "Property: CustomUserClaimString.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd", + "Property: CustomUserClaimString.ClaimType (string)", + "Property: CustomUserClaimString.ClaimValue (string)", + "Property: CustomUserClaimString.UserId (string) Required FK Index", + }, + Indexes = { "{'UserId'} ", }, + FKs = + { + "ForeignKey: CustomUserClaimString {'UserId'} -> CustomUserString {'Id'} ToDependent: Claims ToPrincipal: User Cascade", + }, + Navigations = + { + "Navigation: CustomUserClaimString.User (CustomUserString) ToPrincipal CustomUserString Inverse: Claims PropertyAccessMode.Field", + }, + }, + new() + { + Name = "Microsoft.EntityFrameworkCore.CustomUserLoginString", + TableName = "MyUserLogins", + PrimaryKey = "Key: CustomUserLoginString.LoginProvider, CustomUserLoginString.ProviderKey PK", + Properties = + { + "Property: CustomUserLoginString.LoginProvider (string) Required PK AfterSave:Throw", + "Property: CustomUserLoginString.ProviderKey (string) Required PK AfterSave:Throw", + "Property: CustomUserLoginString.ProviderDisplayName (string)", + "Property: CustomUserLoginString.UserId (string) Required FK Index", + }, + Indexes = { "{'UserId'} ", }, + FKs = + { + "ForeignKey: CustomUserLoginString {'UserId'} -> CustomUserString {'Id'} ToDependent: Logins ToPrincipal: User Cascade", + }, + Navigations = + { + "Navigation: CustomUserLoginString.User (CustomUserString) ToPrincipal CustomUserString Inverse: Logins PropertyAccessMode.Field", + }, + }, + new() + { + Name = "Microsoft.EntityFrameworkCore.CustomUserRoleString", + TableName = "MyUserRoles", + PrimaryKey = "Key: CustomUserRoleString.UserId, CustomUserRoleString.RoleId PK", + Properties = + { + "Property: CustomUserRoleString.UserId (string) Required PK FK AfterSave:Throw", + "Property: CustomUserRoleString.RoleId (string) Required PK FK Index AfterSave:Throw", + }, + Indexes = { "{'RoleId'} ", }, + FKs = + { + "ForeignKey: CustomUserRoleString {'RoleId'} -> CustomRoleString {'Id'} ToDependent: UserRoles ToPrincipal: Role Cascade", + "ForeignKey: CustomUserRoleString {'UserId'} -> CustomUserString {'Id'} ToDependent: UserRoles ToPrincipal: User Cascade", + }, + Navigations = + { + "Navigation: CustomUserRoleString.Role (CustomRoleString) ToPrincipal CustomRoleString Inverse: UserRoles PropertyAccessMode.Field", + "Navigation: CustomUserRoleString.User (CustomUserString) ToPrincipal CustomUserString Inverse: UserRoles PropertyAccessMode.Field", + }, + }, + new() + { + Name = "Microsoft.EntityFrameworkCore.CustomUserString", + TableName = "MyUsers", + PrimaryKey = "Key: CustomUserString.Id PK", + Properties = + { + "Property: CustomUserString.Id (string) Required PK AfterSave:Throw", + "Property: CustomUserString.AccessFailedCount (int) Required", + "Property: CustomUserString.ConcurrencyStamp (string) Concurrency", + "Property: CustomUserString.CustomTag (string)", + "Property: CustomUserString.Email (string) MaxLength(128)", + "Property: CustomUserString.EmailConfirmed (bool) Required", + "Property: CustomUserString.LockoutEnabled (bool) Required", + "Property: CustomUserString.LockoutEnd (Nullable)", + "Property: CustomUserString.NormalizedEmail (string) Index MaxLength(128)", + "Property: CustomUserString.NormalizedUserName (string) Index MaxLength(128)", + "Property: CustomUserString.PasswordHash (string)", + "Property: CustomUserString.PhoneNumber (string)", + "Property: CustomUserString.PhoneNumberConfirmed (bool) Required", + "Property: CustomUserString.SecurityStamp (string)", + "Property: CustomUserString.TwoFactorEnabled (bool) Required", + "Property: CustomUserString.UserName (string) MaxLength(128)", + }, + Indexes = + { + "{'NormalizedEmail'} ", "{'NormalizedUserName'} Unique", + }, + Navigations = + { + "Navigation: CustomUserString.Claims (ICollection) Collection ToDependent CustomUserClaimString Inverse: User PropertyAccessMode.Field", + "Navigation: CustomUserString.Logins (ICollection) Collection ToDependent CustomUserLoginString Inverse: User PropertyAccessMode.Field", + "Navigation: CustomUserString.Tokens (ICollection) Collection ToDependent CustomUserTokenString Inverse: User PropertyAccessMode.Field", + "Navigation: CustomUserString.UserRoles (ICollection) Collection ToDependent CustomUserRoleString Inverse: User PropertyAccessMode.Field", + }, + }, + new() + { + Name = "Microsoft.EntityFrameworkCore.CustomUserTokenString", + TableName = "MyUserTokens", + PrimaryKey = "Key: CustomUserTokenString.UserId, CustomUserTokenString.LoginProvider, CustomUserTokenString.Name PK", + Properties = + { + "Property: CustomUserTokenString.UserId (string) Required PK FK AfterSave:Throw", + "Property: CustomUserTokenString.LoginProvider (string) Required PK AfterSave:Throw MaxLength(128)", + "Property: CustomUserTokenString.Name (string) Required PK AfterSave:Throw MaxLength(128)", + "Property: CustomUserTokenString.Value (string)", + }, + FKs = + { + "ForeignKey: CustomUserTokenString {'UserId'} -> CustomUserString {'Id'} ToDependent: Tokens ToPrincipal: User Cascade", + }, + Navigations = + { + "Navigation: CustomUserTokenString.User (CustomUserString) ToPrincipal CustomUserString Inverse: Tokens PropertyAccessMode.Field", + }, + }, + }; + } + + public class CustomTypesIdentityContext : IdentityDbContext + { + public CustomTypesIdentityContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.HasDefaultSchema("notdbo"); + + modelBuilder.Entity( + b => + { + b.HasMany(e => e.Claims).WithOne(e => e.User).HasForeignKey(uc => uc.UserId).IsRequired(); + b.HasMany(e => e.Logins).WithOne(e => e.User).HasForeignKey(ul => ul.UserId).IsRequired(); + b.HasMany(e => e.Tokens).WithOne(e => e.User).HasForeignKey(ut => ut.UserId).IsRequired(); + b.HasMany(e => e.UserRoles).WithOne(e => e.User).HasForeignKey(ur => ur.UserId).IsRequired(); + b.ToTable("MyUsers"); + b.Property(u => u.UserName).HasMaxLength(128); + b.Property(u => u.NormalizedUserName).HasMaxLength(128); + b.Property(u => u.Email).HasMaxLength(128); + b.Property(u => u.NormalizedEmail).HasMaxLength(128); + }); + + modelBuilder.Entity( + b => + { + b.HasMany(e => e.UserRoles).WithOne(e => e.Role).HasForeignKey(ur => ur.RoleId).IsRequired(); + b.HasMany(e => e.RoleClaims).WithOne(e => e.Role).HasForeignKey(rc => rc.RoleId).IsRequired(); + b.ToTable("MyRoles"); + }); + + modelBuilder.Entity( + b => + { + b.ToTable("MyUserClaims"); + }); + + modelBuilder.Entity( + b => + { + b.ToTable("MyUserLogins"); + }); + + modelBuilder.Entity( + b => + { + b.Property(t => t.LoginProvider).HasMaxLength(128); + b.Property(t => t.Name).HasMaxLength(128); + b.ToTable("MyUserTokens"); + }); + + modelBuilder.Entity( + b => + { + b.ToTable("MyRoleClaims"); + }); + + modelBuilder.Entity( + b => + { + b.ToTable("MyUserRoles"); + }); + } + } + + public class CustomUserString : IdentityUser + { + public CustomUserString() + { + Id = Guid.NewGuid().ToString(); + } + + public string CustomTag { get; set; } + + public virtual ICollection Claims { get; set; } + public virtual ICollection Logins { get; set; } + public virtual ICollection Tokens { get; set; } + public virtual ICollection UserRoles { get; set; } + } + + public class CustomRoleString : IdentityRole + { + public CustomRoleString() + { + Id = Guid.NewGuid().ToString(); + } + + public virtual ICollection UserRoles { get; set; } + public virtual ICollection RoleClaims { get; set; } + } + + public class CustomUserRoleString : IdentityUserRole + { + public virtual CustomUserString User { get; set; } + public virtual CustomRoleString Role { get; set; } + } + + public class CustomUserClaimString : IdentityUserClaim + { + public virtual CustomUserString User { get; set; } + } + + public class CustomUserLoginString : IdentityUserLogin + { + public virtual CustomUserString User { get; set; } + } + + public class CustomRoleClaimString : IdentityRoleClaim + { + public virtual CustomRoleString Role { get; set; } + } + + public class CustomUserTokenString : IdentityUserToken + { + public virtual CustomUserString User { get; set; } + } +} diff --git a/test/EFCore.AspNet.Specification.Tests/AspNetIdentityCustomTypesIntKeyTestBase.cs b/test/EFCore.AspNet.Specification.Tests/AspNetIdentityCustomTypesIntKeyTestBase.cs new file mode 100644 index 00000000000..5efc3273db2 --- /dev/null +++ b/test/EFCore.AspNet.Specification.Tests/AspNetIdentityCustomTypesIntKeyTestBase.cs @@ -0,0 +1,250 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Xunit; + +namespace Microsoft.EntityFrameworkCore +{ + public abstract class AspNetIdentityCustomTypesIntKeyTestBase + : AspNetIdentityTestBase + where TFixture : AspNetIdentityTestBase.AspNetIdentityFixtureBase + { + protected AspNetIdentityCustomTypesIntKeyTestBase(TFixture fixture) + : base(fixture) + { + } + + [ConditionalFact] + public void Can_build_int_key_identity_model_with_custom_types() + { + using (var context = CreateContext()) + { + var entityTypeMappings = context.Model.GetEntityTypes().Select(e => new EntityTypeMapping(e)).ToList(); + + EntityTypeMapping.AssertEqual(CustomTypesIntKeyMappings, entityTypeMappings); + //throw new Exception(EntityTypeMapping.Serialize(entityTypeMappings)); + } + } + + [ConditionalFact] + public async Task Can_use_navigation_properties_on_User() + { + var userId = 0; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + var user = new CustomUserInt { NormalizedUserName = "wendy" }; + await CreateUser(context, user); + userId = user.Id; + }, + async context => + { + var user = await context.Users + .Include(e => e.Claims) + .Include(e => e.Logins) + .Include(e => e.Tokens) + .Include(e => e.UserRoles) + .SingleAsync(u => u.Id == userId); + + Assert.Equal(3, user.Claims.Count); + Assert.Equal(2, user.Logins.Count); + Assert.Equal(1, user.Tokens.Count); + Assert.Equal(2, user.UserRoles.Count); + }); + } + + protected readonly List CustomTypesIntKeyMappings = new() + { + new() + { + Name = "Microsoft.EntityFrameworkCore.CustomRoleClaimInt", + TableName = "AspNetRoleClaims", + PrimaryKey = "Key: CustomRoleClaimInt.Id PK", + Properties = + { + "Property: CustomRoleClaimInt.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd", + "Property: CustomRoleClaimInt.ClaimType (string)", + "Property: CustomRoleClaimInt.ClaimValue (string)", + "Property: CustomRoleClaimInt.RoleId (int) Required FK Index", + }, + Indexes = { "{'RoleId'} ", }, + FKs = { "ForeignKey: CustomRoleClaimInt {'RoleId'} -> CustomRoleInt {'Id'} Cascade", }, + }, + new() + { + Name = "Microsoft.EntityFrameworkCore.CustomRoleInt", + TableName = "AspNetRoles", + PrimaryKey = "Key: CustomRoleInt.Id PK", + Properties = + { + "Property: CustomRoleInt.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd", + "Property: CustomRoleInt.ConcurrencyStamp (string) Concurrency", + "Property: CustomRoleInt.Name (string) MaxLength(256)", + "Property: CustomRoleInt.NormalizedName (string) Index MaxLength(256)", + }, + Indexes = { "{'NormalizedName'} Unique", }, + }, + new() + { + Name = "Microsoft.EntityFrameworkCore.CustomUserClaimInt", + TableName = "AspNetUserClaims", + PrimaryKey = "Key: CustomUserClaimInt.Id PK", + Properties = + { + "Property: CustomUserClaimInt.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd", + "Property: CustomUserClaimInt.ClaimType (string)", + "Property: CustomUserClaimInt.ClaimValue (string)", + "Property: CustomUserClaimInt.UserId (int) Required FK Index", + }, + Indexes = { "{'UserId'} ", }, + FKs = { "ForeignKey: CustomUserClaimInt {'UserId'} -> CustomUserInt {'Id'} ToDependent: Claims Cascade", }, + }, + new() + { + Name = "Microsoft.EntityFrameworkCore.CustomUserInt", + TableName = "AspNetUsers", + PrimaryKey = "Key: CustomUserInt.Id PK", + Properties = + { + "Property: CustomUserInt.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd", + "Property: CustomUserInt.AccessFailedCount (int) Required", + "Property: CustomUserInt.ConcurrencyStamp (string) Concurrency", + "Property: CustomUserInt.CustomTag (string)", + "Property: CustomUserInt.Email (string) MaxLength(256)", + "Property: CustomUserInt.EmailConfirmed (bool) Required", + "Property: CustomUserInt.LockoutEnabled (bool) Required", + "Property: CustomUserInt.LockoutEnd (Nullable)", + "Property: CustomUserInt.NormalizedEmail (string) Index MaxLength(256)", + "Property: CustomUserInt.NormalizedUserName (string) Index MaxLength(256)", + "Property: CustomUserInt.PasswordHash (string)", + "Property: CustomUserInt.PhoneNumber (string)", + "Property: CustomUserInt.PhoneNumberConfirmed (bool) Required", + "Property: CustomUserInt.SecurityStamp (string)", + "Property: CustomUserInt.TwoFactorEnabled (bool) Required", + "Property: CustomUserInt.UserName (string) MaxLength(256)", + }, + Indexes = + { + "{'NormalizedEmail'} ", "{'NormalizedUserName'} Unique", + }, + Navigations = + { + "Navigation: CustomUserInt.Claims (ICollection) Collection ToDependent CustomUserClaimInt", + "Navigation: CustomUserInt.Logins (ICollection) Collection ToDependent CustomUserLoginInt", + "Navigation: CustomUserInt.Tokens (ICollection) Collection ToDependent CustomUserTokenInt", + "Navigation: CustomUserInt.UserRoles (ICollection) Collection ToDependent CustomUserRoleInt", + }, + }, + new() + { + Name = "Microsoft.EntityFrameworkCore.CustomUserLoginInt", + TableName = "AspNetUserLogins", + PrimaryKey = "Key: CustomUserLoginInt.LoginProvider, CustomUserLoginInt.ProviderKey PK", + Properties = + { + "Property: CustomUserLoginInt.LoginProvider (string) Required PK AfterSave:Throw", + "Property: CustomUserLoginInt.ProviderKey (string) Required PK AfterSave:Throw", + "Property: CustomUserLoginInt.ProviderDisplayName (string)", + "Property: CustomUserLoginInt.UserId (int) Required FK Index", + }, + Indexes = { "{'UserId'} ", }, + FKs = { "ForeignKey: CustomUserLoginInt {'UserId'} -> CustomUserInt {'Id'} ToDependent: Logins Cascade", }, + }, + new() + { + Name = "Microsoft.EntityFrameworkCore.CustomUserRoleInt", + TableName = "AspNetUserRoles", + PrimaryKey = "Key: CustomUserRoleInt.UserId, CustomUserRoleInt.RoleId PK", + Properties = + { + "Property: CustomUserRoleInt.UserId (int) Required PK FK AfterSave:Throw", + "Property: CustomUserRoleInt.RoleId (int) Required PK FK Index AfterSave:Throw", + }, + Indexes = { "{'RoleId'} ", }, + FKs = + { + "ForeignKey: CustomUserRoleInt {'RoleId'} -> CustomRoleInt {'Id'} Cascade", + "ForeignKey: CustomUserRoleInt {'UserId'} -> CustomUserInt {'Id'} ToDependent: UserRoles Cascade", + }, + }, + new() + { + Name = "Microsoft.EntityFrameworkCore.CustomUserTokenInt", + TableName = "AspNetUserTokens", + PrimaryKey = "Key: CustomUserTokenInt.UserId, CustomUserTokenInt.LoginProvider, CustomUserTokenInt.Name PK", + Properties = + { + "Property: CustomUserTokenInt.UserId (int) Required PK FK AfterSave:Throw", + "Property: CustomUserTokenInt.LoginProvider (string) Required PK AfterSave:Throw", + "Property: CustomUserTokenInt.Name (string) Required PK AfterSave:Throw", + "Property: CustomUserTokenInt.Value (string)", + }, + FKs = { "ForeignKey: CustomUserTokenInt {'UserId'} -> CustomUserInt {'Id'} ToDependent: Tokens Cascade", }, + }, + }; + } + + public class CustomTypesIdentityContextInt : IdentityDbContext + { + public CustomTypesIdentityContextInt(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity( + b => + { + b.HasMany(e => e.Claims).WithOne().HasForeignKey(uc => uc.UserId).IsRequired(); + b.HasMany(e => e.Logins).WithOne().HasForeignKey(ul => ul.UserId).IsRequired(); + b.HasMany(e => e.Tokens).WithOne().HasForeignKey(ut => ut.UserId).IsRequired(); + b.HasMany(e => e.UserRoles).WithOne().HasForeignKey(ur => ur.UserId).IsRequired(); + }); + } + } + + public class CustomUserInt : IdentityUser + { + public string CustomTag { get; set; } + public virtual ICollection Claims { get; set; } + public virtual ICollection Logins { get; set; } + public virtual ICollection Tokens { get; set; } + public virtual ICollection UserRoles { get; set; } + } + + public class CustomRoleInt : IdentityRole + { + } + + public class CustomUserClaimInt : IdentityUserClaim + { + } + + public class CustomUserRoleInt : IdentityUserRole + { + } + + public class CustomUserLoginInt : IdentityUserLogin + { + } + + public class CustomRoleClaimInt : IdentityRoleClaim + { + } + + public class CustomUserTokenInt : IdentityUserToken + { + } +} diff --git a/test/EFCore.AspNet.Specification.Tests/AspNetIdentityDefaultTestBase.cs b/test/EFCore.AspNet.Specification.Tests/AspNetIdentityDefaultTestBase.cs new file mode 100644 index 00000000000..46852b07e9d --- /dev/null +++ b/test/EFCore.AspNet.Specification.Tests/AspNetIdentityDefaultTestBase.cs @@ -0,0 +1,159 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Xunit; + +namespace Microsoft.EntityFrameworkCore +{ + public abstract class AspNetIdentityDefaultTestBase + : AspNetIdentityTestBase, + IdentityUserRole, IdentityUserLogin, IdentityRoleClaim, IdentityUserToken> + where TFixture : AspNetIdentityTestBase, + IdentityUserRole, IdentityUserLogin, IdentityRoleClaim, IdentityUserToken>. + AspNetIdentityFixtureBase + { + protected AspNetIdentityDefaultTestBase(TFixture fixture) + : base(fixture) + { + } + + [ConditionalFact] + public void Can_build_default_identity_model() + { + using (var context = CreateContext()) + { + var entityTypeMappings = context.Model.GetEntityTypes().Select(e => new EntityTypeMapping(e)).ToList(); + + EntityTypeMapping.AssertEqual(DefaultMappings, entityTypeMappings); + //throw new Exception(EntityTypeMapping.Serialize(entityTypeMappings)); + } + } + + protected readonly List DefaultMappings = new() + { + new() + { + Name = "Microsoft.AspNetCore.Identity.IdentityRole", + TableName = "AspNetRoles", + PrimaryKey = "Key: IdentityRole.Id PK", + Properties = + { + "Property: IdentityRole.Id (string) Required PK AfterSave:Throw", + "Property: IdentityRole.ConcurrencyStamp (string) Concurrency", + "Property: IdentityRole.Name (string) MaxLength(256)", + "Property: IdentityRole.NormalizedName (string) Index MaxLength(256)", + }, + Indexes = { "{'NormalizedName'} Unique", }, + }, + new() + { + Name = "Microsoft.AspNetCore.Identity.IdentityRoleClaim", + TableName = "AspNetRoleClaims", + PrimaryKey = "Key: IdentityRoleClaim.Id PK", + Properties = + { + "Property: IdentityRoleClaim.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd", + "Property: IdentityRoleClaim.ClaimType (string)", + "Property: IdentityRoleClaim.ClaimValue (string)", + "Property: IdentityRoleClaim.RoleId (string) Required FK Index", + }, + Indexes = { "{'RoleId'} ", }, + FKs = { "ForeignKey: IdentityRoleClaim {'RoleId'} -> IdentityRole {'Id'} Cascade", }, + }, + new() + { + Name = "Microsoft.AspNetCore.Identity.IdentityUser", + TableName = "AspNetUsers", + PrimaryKey = "Key: IdentityUser.Id PK", + Properties = + { + "Property: IdentityUser.Id (string) Required PK AfterSave:Throw", + "Property: IdentityUser.AccessFailedCount (int) Required", + "Property: IdentityUser.ConcurrencyStamp (string) Concurrency", + "Property: IdentityUser.Email (string) MaxLength(256)", + "Property: IdentityUser.EmailConfirmed (bool) Required", + "Property: IdentityUser.LockoutEnabled (bool) Required", + "Property: IdentityUser.LockoutEnd (Nullable)", + "Property: IdentityUser.NormalizedEmail (string) Index MaxLength(256)", + "Property: IdentityUser.NormalizedUserName (string) Index MaxLength(256)", + "Property: IdentityUser.PasswordHash (string)", + "Property: IdentityUser.PhoneNumber (string)", + "Property: IdentityUser.PhoneNumberConfirmed (bool) Required", + "Property: IdentityUser.SecurityStamp (string)", + "Property: IdentityUser.TwoFactorEnabled (bool) Required", + "Property: IdentityUser.UserName (string) MaxLength(256)", + }, + Indexes = + { + "{'NormalizedEmail'} ", "{'NormalizedUserName'} Unique", + }, + }, + new() + { + Name = "Microsoft.AspNetCore.Identity.IdentityUserClaim", + TableName = "AspNetUserClaims", + PrimaryKey = "Key: IdentityUserClaim.Id PK", + Properties = + { + "Property: IdentityUserClaim.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd", + "Property: IdentityUserClaim.ClaimType (string)", + "Property: IdentityUserClaim.ClaimValue (string)", + "Property: IdentityUserClaim.UserId (string) Required FK Index", + }, + Indexes = { "{'UserId'} ", }, + FKs = { "ForeignKey: IdentityUserClaim {'UserId'} -> IdentityUser {'Id'} Cascade", }, + }, + new() + { + Name = "Microsoft.AspNetCore.Identity.IdentityUserLogin", + TableName = "AspNetUserLogins", + PrimaryKey = "Key: IdentityUserLogin.LoginProvider, IdentityUserLogin.ProviderKey PK", + Properties = + { + "Property: IdentityUserLogin.LoginProvider (string) Required PK AfterSave:Throw", + "Property: IdentityUserLogin.ProviderKey (string) Required PK AfterSave:Throw", + "Property: IdentityUserLogin.ProviderDisplayName (string)", + "Property: IdentityUserLogin.UserId (string) Required FK Index", + }, + Indexes = { "{'UserId'} ", }, + FKs = { "ForeignKey: IdentityUserLogin {'UserId'} -> IdentityUser {'Id'} Cascade", }, + }, + new() + { + Name = "Microsoft.AspNetCore.Identity.IdentityUserRole", + TableName = "AspNetUserRoles", + PrimaryKey = "Key: IdentityUserRole.UserId, IdentityUserRole.RoleId PK", + Properties = + { + "Property: IdentityUserRole.UserId (string) Required PK FK AfterSave:Throw", + "Property: IdentityUserRole.RoleId (string) Required PK FK Index AfterSave:Throw", + }, + Indexes = { "{'RoleId'} ", }, + FKs = + { + "ForeignKey: IdentityUserRole {'RoleId'} -> IdentityRole {'Id'} Cascade", + "ForeignKey: IdentityUserRole {'UserId'} -> IdentityUser {'Id'} Cascade", + }, + }, + new() + { + Name = "Microsoft.AspNetCore.Identity.IdentityUserToken", + TableName = "AspNetUserTokens", + PrimaryKey = + "Key: IdentityUserToken.UserId, IdentityUserToken.LoginProvider, IdentityUserToken.Name PK", + Properties = + { + "Property: IdentityUserToken.UserId (string) Required PK FK AfterSave:Throw", + "Property: IdentityUserToken.LoginProvider (string) Required PK AfterSave:Throw", + "Property: IdentityUserToken.Name (string) Required PK AfterSave:Throw", + "Property: IdentityUserToken.Value (string)", + }, + FKs = { "ForeignKey: IdentityUserToken {'UserId'} -> IdentityUser {'Id'} Cascade", }, + }, + }; + } +} diff --git a/test/EFCore.AspNet.Specification.Tests/AspNetIdentityIntKeyTestBase.cs b/test/EFCore.AspNet.Specification.Tests/AspNetIdentityIntKeyTestBase.cs new file mode 100644 index 00000000000..0dcdb9d0ef9 --- /dev/null +++ b/test/EFCore.AspNet.Specification.Tests/AspNetIdentityIntKeyTestBase.cs @@ -0,0 +1,159 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Xunit; + +namespace Microsoft.EntityFrameworkCore +{ + public abstract class AspNetIdentityIntKeyTestBase + : AspNetIdentityTestBase, IdentityRole, int>, + IdentityUser, IdentityRole, int, IdentityUserClaim, IdentityUserRole, IdentityUserLogin, + IdentityRoleClaim, IdentityUserToken> + where TFixture : AspNetIdentityTestBase, IdentityRole, int>, IdentityUser, + IdentityRole, int, IdentityUserClaim, IdentityUserRole, IdentityUserLogin, IdentityRoleClaim, + IdentityUserToken>.AspNetIdentityFixtureBase + { + protected AspNetIdentityIntKeyTestBase(TFixture fixture) + : base(fixture) + { + } + + [ConditionalFact] + public void Can_build_int_key_identity_model() + { + using (var context = CreateContext()) + { + var entityTypeMappings = context.Model.GetEntityTypes().Select(e => new EntityTypeMapping(e)).ToList(); + + EntityTypeMapping.AssertEqual(IntKeyMappings, entityTypeMappings); + // throw new Exception(EntityTypeMapping.Serialize(entityTypeMappings)); + } + } + + protected readonly List IntKeyMappings = new() + { + new() + { + Name = "Microsoft.AspNetCore.Identity.IdentityRole", + TableName = "AspNetRoles", + PrimaryKey = "Key: IdentityRole.Id PK", + Properties = + { + "Property: IdentityRole.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd", + "Property: IdentityRole.ConcurrencyStamp (string) Concurrency", + "Property: IdentityRole.Name (string) MaxLength(256)", + "Property: IdentityRole.NormalizedName (string) Index MaxLength(256)", + }, + Indexes = { "{'NormalizedName'} Unique", }, + }, + new() + { + Name = "Microsoft.AspNetCore.Identity.IdentityRoleClaim", + TableName = "AspNetRoleClaims", + PrimaryKey = "Key: IdentityRoleClaim.Id PK", + Properties = + { + "Property: IdentityRoleClaim.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd", + "Property: IdentityRoleClaim.ClaimType (string)", + "Property: IdentityRoleClaim.ClaimValue (string)", + "Property: IdentityRoleClaim.RoleId (int) Required FK Index", + }, + Indexes = { "{'RoleId'} ", }, + FKs = { "ForeignKey: IdentityRoleClaim {'RoleId'} -> IdentityRole {'Id'} Cascade", }, + }, + new() + { + Name = "Microsoft.AspNetCore.Identity.IdentityUser", + TableName = "AspNetUsers", + PrimaryKey = "Key: IdentityUser.Id PK", + Properties = + { + "Property: IdentityUser.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd", + "Property: IdentityUser.AccessFailedCount (int) Required", + "Property: IdentityUser.ConcurrencyStamp (string) Concurrency", + "Property: IdentityUser.Email (string) MaxLength(256)", + "Property: IdentityUser.EmailConfirmed (bool) Required", + "Property: IdentityUser.LockoutEnabled (bool) Required", + "Property: IdentityUser.LockoutEnd (Nullable)", + "Property: IdentityUser.NormalizedEmail (string) Index MaxLength(256)", + "Property: IdentityUser.NormalizedUserName (string) Index MaxLength(256)", + "Property: IdentityUser.PasswordHash (string)", + "Property: IdentityUser.PhoneNumber (string)", + "Property: IdentityUser.PhoneNumberConfirmed (bool) Required", + "Property: IdentityUser.SecurityStamp (string)", + "Property: IdentityUser.TwoFactorEnabled (bool) Required", + "Property: IdentityUser.UserName (string) MaxLength(256)", + }, + Indexes = + { + "{'NormalizedEmail'} ", "{'NormalizedUserName'} Unique", + }, + }, + new() + { + Name = "Microsoft.AspNetCore.Identity.IdentityUserClaim", + TableName = "AspNetUserClaims", + PrimaryKey = "Key: IdentityUserClaim.Id PK", + Properties = + { + "Property: IdentityUserClaim.Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd", + "Property: IdentityUserClaim.ClaimType (string)", + "Property: IdentityUserClaim.ClaimValue (string)", + "Property: IdentityUserClaim.UserId (int) Required FK Index", + }, + Indexes = { "{'UserId'} ", }, + FKs = { "ForeignKey: IdentityUserClaim {'UserId'} -> IdentityUser {'Id'} Cascade", }, + }, + new() + { + Name = "Microsoft.AspNetCore.Identity.IdentityUserLogin", + TableName = "AspNetUserLogins", + PrimaryKey = "Key: IdentityUserLogin.LoginProvider, IdentityUserLogin.ProviderKey PK", + Properties = + { + "Property: IdentityUserLogin.LoginProvider (string) Required PK AfterSave:Throw", + "Property: IdentityUserLogin.ProviderKey (string) Required PK AfterSave:Throw", + "Property: IdentityUserLogin.ProviderDisplayName (string)", + "Property: IdentityUserLogin.UserId (int) Required FK Index", + }, + Indexes = { "{'UserId'} ", }, + FKs = { "ForeignKey: IdentityUserLogin {'UserId'} -> IdentityUser {'Id'} Cascade", }, + }, + new() + { + Name = "Microsoft.AspNetCore.Identity.IdentityUserRole", + TableName = "AspNetUserRoles", + PrimaryKey = "Key: IdentityUserRole.UserId, IdentityUserRole.RoleId PK", + Properties = + { + "Property: IdentityUserRole.UserId (int) Required PK FK AfterSave:Throw", + "Property: IdentityUserRole.RoleId (int) Required PK FK Index AfterSave:Throw", + }, + Indexes = { "{'RoleId'} ", }, + FKs = + { + "ForeignKey: IdentityUserRole {'RoleId'} -> IdentityRole {'Id'} Cascade", + "ForeignKey: IdentityUserRole {'UserId'} -> IdentityUser {'Id'} Cascade", + }, + }, + new() + { + Name = "Microsoft.AspNetCore.Identity.IdentityUserToken", + TableName = "AspNetUserTokens", + PrimaryKey = "Key: IdentityUserToken.UserId, IdentityUserToken.LoginProvider, IdentityUserToken.Name PK", + Properties = + { + "Property: IdentityUserToken.UserId (int) Required PK FK AfterSave:Throw", + "Property: IdentityUserToken.LoginProvider (string) Required PK AfterSave:Throw", + "Property: IdentityUserToken.Name (string) Required PK AfterSave:Throw", + "Property: IdentityUserToken.Value (string)", + }, + FKs = { "ForeignKey: IdentityUserToken {'UserId'} -> IdentityUser {'Id'} Cascade", }, + }, + }; + } +} diff --git a/test/EFCore.AspNet.Specification.Tests/AspNetIdentityTestBase.cs b/test/EFCore.AspNet.Specification.Tests/AspNetIdentityTestBase.cs new file mode 100644 index 00000000000..24b3c005737 --- /dev/null +++ b/test/EFCore.AspNet.Specification.Tests/AspNetIdentityTestBase.cs @@ -0,0 +1,605 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; + +namespace Microsoft.EntityFrameworkCore +{ + public abstract class + AspNetIdentityTestBase : IClassFixture + where TFixture : AspNetIdentityTestBase.AspNetIdentityFixtureBase + where TUser : IdentityUser, new() + where TRole : IdentityRole, new() + where TKey : IEquatable + where TUserClaim : IdentityUserClaim, new() + where TUserRole : IdentityUserRole, new() + where TUserLogin : IdentityUserLogin, new() + where TUserToken : IdentityUserToken, new() + where TRoleClaim : IdentityRoleClaim, new() + where TContext : IdentityUserContext + { + protected AspNetIdentityTestBase(TFixture fixture) + => Fixture = fixture; + + [ConditionalFact] + public async Task Can_call_UserStore_FindByNameAsync() + { + var user = new TUser { NormalizedUserName = "wendy" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var userStore = + new UserStore(context); + + Assert.Equal(user.Id, (await userStore.FindByNameAsync("wendy")).Id); + }); + } + + [ConditionalFact] + public async Task Can_call_UserStore_FindByEmailAsync() + { + var user = new TUser { NormalizedEmail = "wendy@example.com" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var userStore = + new UserStore(context); + + Assert.Equal(user.Id, (await userStore.FindByEmailAsync("wendy@example.com")).Id); + }); + } + + [ConditionalFact] + public async Task Can_call_UserStore_GetRolesAsync() + { + var user = new TUser { NormalizedEmail = "wendy@example.com" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var userStore = + new UserStore(context); + + var roles = await userStore.GetRolesAsync(user); + Assert.Equal(2, roles.Count); + Assert.Contains("Admin", roles); + Assert.Contains("Moderator", roles); + }); + } + + [ConditionalFact] + public async Task Can_call_UserStore_ReplaceClaimAsync() + { + var user = new TUser { NormalizedEmail = "wendy@example.com" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var userStore = + new UserStore(context); + + await userStore.ReplaceClaimAsync(user, new Claim("T1", "V2"), new Claim("T1", "V4")); + + await context.SaveChangesAsync(); + }, + async context => + { + using var userStore = + new UserStore(context); + + var claims = (await userStore.GetClaimsAsync(user)).OrderBy(e => e.Type).ThenBy(e => e.Value).ToList(); + Assert.Equal(3, claims.Count); + Assert.Equal("T1", claims[0].Type); + Assert.Equal("V1", claims[0].Value); + Assert.Equal("T1", claims[1].Type); + Assert.Equal("V4", claims[1].Value); + Assert.Equal("T2", claims[2].Type); + Assert.Equal("V3", claims[2].Value); + }); + } + + [ConditionalFact] + public async Task Can_call_UserStore_RemoveClaimsAsync() + { + var user = new TUser { NormalizedEmail = "wendy@example.com" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var userStore = + new UserStore(context); + + await userStore.RemoveClaimsAsync(user, new[] { new Claim("T1", "V1"), new Claim("T2", "V3") }); + + await context.SaveChangesAsync(); + }, + async context => + { + using var userStore = + new UserStore(context); + + var claims = (await userStore.GetClaimsAsync(user)).OrderBy(e => e.Type).ThenBy(e => e.Value).ToList(); + Assert.Equal(1, claims.Count); + Assert.Equal("T1", claims[0].Type); + Assert.Equal("V2", claims[0].Value); + }); + } + + [ConditionalFact] + public async Task Can_call_UserStore_GetLoginsAsync() + { + var user = new TUser { NormalizedEmail = "wendy@example.com" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var userStore = + new UserStore(context); + + var logins = (await userStore.GetLoginsAsync(user)).OrderBy(e => e.LoginProvider).ToList(); + Assert.Equal(2, logins.Count); + Assert.Equal("ISCABBS", logins[0].LoginProvider); + Assert.Equal("Local", logins[1].LoginProvider); + }); + } + + [ConditionalFact] + public async Task Can_call_UserStore_GetUsersForClaimAsync() + { + var user = new TUser { NormalizedEmail = "wendy@example.com" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var userStore = + new UserStore(context); + + var users = await userStore.GetUsersForClaimAsync(new Claim("T1", "V1")); + Assert.Equal(1, users.Count); + Assert.Equal("wendy@example.com", users[0].NormalizedEmail); + }); + } + + [ConditionalFact] + public async Task Can_call_UserStore_GetUsersInRoleAsync() + { + var user = new TUser { NormalizedEmail = "wendy@example.com" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var userStore = + new UserStore(context); + + var users = await userStore.GetUsersInRoleAsync("admin"); + Assert.Equal(1, users.Count); + Assert.Equal("wendy@example.com", users[0].NormalizedEmail); + }); + } + + [ConditionalFact] + public async Task Can_call_UserOnlyStore_FindByNameAsync() + { + var user = new TUser { NormalizedUserName = "wendy" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var userStore = new UserOnlyStore(context); + + Assert.Equal(user.Id, (await userStore.FindByNameAsync("wendy")).Id); + }); + } + + [ConditionalFact] + public async Task Can_call_UserOnlyStore_FindByEmailAsync() + { + var user = new TUser { NormalizedEmail = "wendy@example.com" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var userStore = new UserOnlyStore(context); + + Assert.Equal(user.Id, (await userStore.FindByEmailAsync("wendy@example.com")).Id); + }); + } + + [ConditionalFact] + public async Task Can_call_UserOnlyStore_GetClaimsAsync() + { + var user = new TUser { NormalizedEmail = "wendy@example.com" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var userStore = new UserOnlyStore(context); + + var claims = (await userStore.GetClaimsAsync(user)).OrderBy(e => e.Type).ThenBy(e => e.Value).ToList(); + Assert.Equal(3, claims.Count); + Assert.Equal("T1", claims[0].Type); + Assert.Equal("V1", claims[0].Value); + Assert.Equal("T1", claims[1].Type); + Assert.Equal("V2", claims[1].Value); + Assert.Equal("T2", claims[2].Type); + Assert.Equal("V3", claims[2].Value); + }); + } + + [ConditionalFact] + public async Task Can_call_UserOnlyStore_ReplaceClaimAsync() + { + var user = new TUser { NormalizedEmail = "wendy@example.com" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var userStore = new UserOnlyStore(context); + + await userStore.ReplaceClaimAsync(user, new Claim("T1", "V2"), new Claim("T1", "V4")); + + await context.SaveChangesAsync(); + }, + async context => + { + using var userStore = new UserOnlyStore(context); + + var claims = (await userStore.GetClaimsAsync(user)).OrderBy(e => e.Type).ThenBy(e => e.Value).ToList(); + Assert.Equal(3, claims.Count); + Assert.Equal("T1", claims[0].Type); + Assert.Equal("V1", claims[0].Value); + Assert.Equal("T1", claims[1].Type); + Assert.Equal("V4", claims[1].Value); + Assert.Equal("T2", claims[2].Type); + Assert.Equal("V3", claims[2].Value); + }); + } + + [ConditionalFact] + public async Task Can_call_UserOnlyStore_RemoveClaimsAsync() + { + var user = new TUser { NormalizedEmail = "wendy@example.com" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var userStore = new UserOnlyStore(context); + + await userStore.RemoveClaimsAsync(user, new[] { new Claim("T1", "V1"), new Claim("T2", "V3") }); + + await context.SaveChangesAsync(); + }, + async context => + { + using var userStore = new UserOnlyStore(context); + + var claims = (await userStore.GetClaimsAsync(user)).OrderBy(e => e.Type).ThenBy(e => e.Value).ToList(); + Assert.Equal(1, claims.Count); + Assert.Equal("T1", claims[0].Type); + Assert.Equal("V2", claims[0].Value); + }); + } + + [ConditionalFact] + public async Task Can_call_UserOnlyStore_GetLoginsAsync() + { + var user = new TUser { NormalizedEmail = "wendy@example.com" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var userStore = new UserOnlyStore(context); + + var logins = (await userStore.GetLoginsAsync(user)).OrderBy(e => e.LoginProvider).ToList(); + Assert.Equal(2, logins.Count); + Assert.Equal("ISCABBS", logins[0].LoginProvider); + Assert.Equal("Local", logins[1].LoginProvider); + }); + } + + [ConditionalFact] + public async Task Can_call_UserOnlyStore_GetUsersForClaimAsync() + { + var user = new TUser { NormalizedEmail = "wendy@example.com" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var userStore = new UserOnlyStore(context); + + var users = await userStore.GetUsersForClaimAsync(new Claim("T1", "V1")); + Assert.Equal(1, users.Count); + Assert.Equal("wendy@example.com", users[0].NormalizedEmail); + }); + } + + [ConditionalFact] + public async Task Can_call_RoleStore_GetClaimsAsync() + { + var user = new TUser { NormalizedEmail = "wendy@example.com" }; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + await CreateUser(context, user); + }, + async context => + { + using var roleStore = new RoleStore(context); + var adminRole = roleStore.Roles.Single(r => r.NormalizedName == "admin"); + + var claims = (await roleStore.GetClaimsAsync(adminRole)).OrderBy(e => e.Type).ThenBy(e => e.Value).ToList(); + Assert.Equal(2, claims.Count); + Assert.Equal("AC1", claims[0].Type); + Assert.Equal("V1", claims[0].Value); + Assert.Equal("AC2", claims[1].Type); + Assert.Equal("V1", claims[1].Value); + }); + } + + protected static async Task CreateUser(TContext context, TUser user) + { + using var userStore = + new UserStore(context); + using var roleStore = new RoleStore(context); + + await userStore.CreateAsync(user); + await userStore.AddClaimsAsync(user, new[] { new Claim("T1", "V1"), new Claim("T1", "V2"), new Claim("T2", "V3") }); + + var adminRole = new TRole { NormalizedName = "admin", Name = "Admin" }; + await roleStore.CreateAsync(adminRole); + await userStore.AddToRoleAsync(user, "admin"); + await roleStore.AddClaimAsync(adminRole, new Claim("AC1", "V1")); + await roleStore.AddClaimAsync(adminRole, new Claim("AC2", "V1")); + + var moderatorRole = new TRole { NormalizedName = "moderator", Name = "Moderator" }; + await roleStore.CreateAsync(moderatorRole); + await userStore.AddToRoleAsync(user, "moderator"); + await roleStore.AddClaimAsync(moderatorRole, new Claim("MC1", "V1")); + await roleStore.AddClaimAsync(moderatorRole, new Claim("MC2", "V1")); + + await userStore.AddLoginAsync(user, new UserLoginInfo("ISCABBS", "DrDave", "SSHFTW")); + await userStore.AddLoginAsync(user, new UserLoginInfo("Local", "EekyBear", "PPS")); + + await userStore.SetTokenAsync(user, "ISCABBS", "DrDave", "SSHFTW", CancellationToken.None); + + await context.SaveChangesAsync(); + } + + protected TFixture Fixture { get; } + + public abstract class AspNetIdentityFixtureBase + : SharedStoreFixtureBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + } + + protected override void Seed(TContext context) + { + } + } + + protected TContext CreateContext() + => Fixture.CreateContext(); + + protected virtual Task ExecuteWithStrategyInTransactionAsync( + Func testOperation, + Func nestedTestOperation1 = null, + Func nestedTestOperation2 = null, + Func nestedTestOperation3 = null) + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, UseTransaction, + testOperation, nestedTestOperation1, nestedTestOperation2, nestedTestOperation3); + + protected virtual void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + protected class EntityTypeMapping + { + public EntityTypeMapping() + { + } + + public EntityTypeMapping(IEntityType entityType) + { + Name = entityType.Name; + TableName = entityType.GetTableName(); + PrimaryKey = entityType.FindPrimaryKey()!.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + Properties.AddRange( + entityType.GetProperties() + .Select(p => p.ToDebugString(MetadataDebugStringOptions.SingleLineDefault))); + + Indexes.AddRange( + entityType.GetIndexes().Select(i => $"{i.Properties.Format()} {(i.IsUnique ? "Unique" : "")}")); + + FKs.AddRange( + entityType.GetForeignKeys().Select(f => f.ToDebugString(MetadataDebugStringOptions.SingleLineDefault))); + + Navigations.AddRange( + entityType.GetNavigations().Select(n => n.ToDebugString(MetadataDebugStringOptions.SingleLineDefault))); + + SkipNavigations.AddRange( + entityType.GetSkipNavigations().Select(n => n.ToDebugString(MetadataDebugStringOptions.SingleLineDefault))); + } + + public string Name { get; set; } + public string TableName { get; set; } + public string PrimaryKey { get; set; } + public List Properties { get; } = new(); + public List Indexes { get; } = new(); + public List FKs { get; } = new(); + public List Navigations { get; } = new(); + public List SkipNavigations { get; } = new(); + + public override string ToString() + { + var builder = new StringBuilder(); + + builder.AppendLine("new()"); + builder.AppendLine("{"); + + builder.AppendLine($" Name = \"{Name}\","); + builder.AppendLine($" TableName = \"{TableName}\","); + builder.AppendLine($" PrimaryKey = \"{PrimaryKey}\","); + + builder.AppendLine(" Properties ="); + builder.AppendLine(" {"); + foreach (var property in Properties) + { + builder.AppendLine($" \"{property}\","); + } + + builder.AppendLine(" },"); + + if (Indexes.Any()) + { + builder.AppendLine(" Indexes ="); + builder.AppendLine(" {"); + foreach (var index in Indexes) + { + builder.AppendLine($" \"{index}\","); + } + + builder.AppendLine(" },"); + } + + if (FKs.Any()) + { + builder.AppendLine(" FKs ="); + builder.AppendLine(" {"); + foreach (var fk in FKs) + { + builder.AppendLine($" \"{fk}\","); + } + + builder.AppendLine(" },"); + } + + if (Navigations.Any()) + { + builder.AppendLine(" Navigations ="); + builder.AppendLine(" {"); + foreach (var navigation in Navigations) + { + builder.AppendLine($" \"{navigation}\","); + } + + builder.AppendLine(" },"); + } + + builder.AppendLine("},"); + + return builder.ToString(); + } + + public static void AssertEqual(IReadOnlyList expected, IReadOnlyList actual) + { + Assert.Equal(expected.Count, actual.Count); + for (var i = 0; i < expected.Count; i++) + { + var e = expected[i]; + var a = actual[i]; + + Assert.Equal(e.Name, a.Name); + Assert.Equal(e.TableName, a.TableName); + Assert.Equal(e.PrimaryKey, a.PrimaryKey); + Assert.Equal(e.Properties, a.Properties); + Assert.Equal(e.Indexes, a.Indexes); + Assert.Equal(e.FKs, a.FKs); + Assert.Equal(e.Navigations, a.Navigations); + Assert.Equal(e.SkipNavigations, a.SkipNavigations); + } + } + + public static string Serialize(IEnumerable mappings) + { + var builder = new StringBuilder(); + foreach (var mapping in mappings) + { + builder.Append(mapping); + } + + return builder.ToString(); + } + } + } +} diff --git a/test/EFCore.AspNet.Specification.Tests/EFCore.AspNet.Specification.Tests.csproj b/test/EFCore.AspNet.Specification.Tests/EFCore.AspNet.Specification.Tests.csproj new file mode 100644 index 00000000000..f0da7cde7d2 --- /dev/null +++ b/test/EFCore.AspNet.Specification.Tests/EFCore.AspNet.Specification.Tests.csproj @@ -0,0 +1,23 @@ + + + + Shared ASP.NET test suite for Entity Framework Core database providers. + net5.0 + Microsoft.EntityFrameworkCore.AspNet.Specification.Tests + Microsoft.EntityFrameworkCore + true + true + true + + + + + + + + + + + + + diff --git a/test/EFCore.AspNet.SqlServer.FunctionalTests/AspNetIdentityCustomTypesDefaultSqlServerTest.cs b/test/EFCore.AspNet.SqlServer.FunctionalTests/AspNetIdentityCustomTypesDefaultSqlServerTest.cs new file mode 100644 index 00000000000..ed2ac25f401 --- /dev/null +++ b/test/EFCore.AspNet.SqlServer.FunctionalTests/AspNetIdentityCustomTypesDefaultSqlServerTest.cs @@ -0,0 +1,36 @@ +// 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 Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore +{ + public class AspNetIdentityCustomTypesDefaultSqlServerTest + : AspNetIdentityCustomTypesDefaultTestBase< + AspNetIdentityCustomTypesDefaultSqlServerTest.AspNetIdentityCustomTypesDefaultSqlServerFixture> + { + public AspNetIdentityCustomTypesDefaultSqlServerTest(AspNetIdentityCustomTypesDefaultSqlServerFixture fixture) + : base(fixture) + { + } + + public class AspNetIdentityCustomTypesDefaultSqlServerFixture : AspNetIdentityFixtureBase + { + protected override IServiceCollection AddServices(IServiceCollection serviceCollection) + => base.AddServices(serviceCollection).AddEntityFrameworkProxies(); + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder).UseLazyLoadingProxies(); + + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected override ITestStoreFactory TestStoreFactory + => SqlServerTestStoreFactory.Instance; + + protected override string StoreName + => "AspNetCustomTypesDefaultIdentity"; + } + } +} diff --git a/test/EFCore.AspNet.SqlServer.FunctionalTests/AspNetIdentityCustomTypesIntKeySqlServerTest.cs b/test/EFCore.AspNet.SqlServer.FunctionalTests/AspNetIdentityCustomTypesIntKeySqlServerTest.cs new file mode 100644 index 00000000000..7d3be542d44 --- /dev/null +++ b/test/EFCore.AspNet.SqlServer.FunctionalTests/AspNetIdentityCustomTypesIntKeySqlServerTest.cs @@ -0,0 +1,29 @@ +// 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 Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore +{ + public class AspNetIdentityCustomTypesIntKeySqlServerTest + : AspNetIdentityCustomTypesIntKeyTestBase< + AspNetIdentityCustomTypesIntKeySqlServerTest.AspNetIdentityCustomTypesIntKeySqlServerFixture> + { + public AspNetIdentityCustomTypesIntKeySqlServerTest(AspNetIdentityCustomTypesIntKeySqlServerFixture fixture) + : base(fixture) + { + } + + public class AspNetIdentityCustomTypesIntKeySqlServerFixture : AspNetIdentityFixtureBase + { + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected override ITestStoreFactory TestStoreFactory + => SqlServerTestStoreFactory.Instance; + + protected override string StoreName + => "AspNetCustomTypesIntKeyIdentity"; + } + } +} diff --git a/test/EFCore.AspNet.SqlServer.FunctionalTests/AspNetIdentityDefaultSqlServerTest.cs b/test/EFCore.AspNet.SqlServer.FunctionalTests/AspNetIdentityDefaultSqlServerTest.cs new file mode 100644 index 00000000000..4bfac1f563b --- /dev/null +++ b/test/EFCore.AspNet.SqlServer.FunctionalTests/AspNetIdentityDefaultSqlServerTest.cs @@ -0,0 +1,28 @@ +// 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 Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore +{ + public class AspNetIdentityDefaultSqlServerTest + : AspNetIdentityDefaultTestBase + { + public AspNetIdentityDefaultSqlServerTest(AspNetDefaultIdentitySqlServerFixture fixture) + : base(fixture) + { + } + + public class AspNetDefaultIdentitySqlServerFixture : AspNetIdentityFixtureBase + { + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected override ITestStoreFactory TestStoreFactory + => SqlServerTestStoreFactory.Instance; + + protected override string StoreName + => "AspNetDefaultIdentity"; + } + } +} diff --git a/test/EFCore.AspNet.SqlServer.FunctionalTests/AspNetIdentityIntKeySqlServerTest.cs b/test/EFCore.AspNet.SqlServer.FunctionalTests/AspNetIdentityIntKeySqlServerTest.cs new file mode 100644 index 00000000000..4fc511b902f --- /dev/null +++ b/test/EFCore.AspNet.SqlServer.FunctionalTests/AspNetIdentityIntKeySqlServerTest.cs @@ -0,0 +1,28 @@ +// 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 Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore +{ + public class AspNetIdentityIntKeySqlServerTest + : AspNetIdentityIntKeyTestBase + { + public AspNetIdentityIntKeySqlServerTest(AspNetIdentityIntKeySqlServerFixture fixture) + : base(fixture) + { + } + + public class AspNetIdentityIntKeySqlServerFixture : AspNetIdentityFixtureBase + { + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected override ITestStoreFactory TestStoreFactory + => SqlServerTestStoreFactory.Instance; + + protected override string StoreName + => "AspNetIntKeyIdentity"; + } + } +} diff --git a/test/EFCore.AspNet.SqlServer.FunctionalTests/EFCore.AspNet.SqlServer.FunctionalTests.csproj b/test/EFCore.AspNet.SqlServer.FunctionalTests/EFCore.AspNet.SqlServer.FunctionalTests.csproj new file mode 100644 index 00000000000..e9548af8b9b --- /dev/null +++ b/test/EFCore.AspNet.SqlServer.FunctionalTests/EFCore.AspNet.SqlServer.FunctionalTests.csproj @@ -0,0 +1,24 @@ + + + + net5.0 + Microsoft.EntityFrameworkCore.AspNet.SqlServer.FunctionalTests + Microsoft.EntityFrameworkCore + True + + + + + PreserveNewest + + + PreserveNewest + + + + + + + + + diff --git a/test/EFCore.AspNet.Sqlite.FunctionalTests/AspNetIdentityCustomTypesDefaultSqliteTest.cs b/test/EFCore.AspNet.Sqlite.FunctionalTests/AspNetIdentityCustomTypesDefaultSqliteTest.cs new file mode 100644 index 00000000000..b1204794b79 --- /dev/null +++ b/test/EFCore.AspNet.Sqlite.FunctionalTests/AspNetIdentityCustomTypesDefaultSqliteTest.cs @@ -0,0 +1,38 @@ +// 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 Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore +{ + public class AspNetIdentityCustomTypesDefaultSqliteTest + : AspNetIdentityCustomTypesDefaultTestBase + { + public AspNetIdentityCustomTypesDefaultSqliteTest(AspNetIdentityCustomTypesDefaultSqliteFixture fixture) + : base(fixture) + { + } + + public class AspNetIdentityCustomTypesDefaultSqliteFixture : AspNetIdentityFixtureBase + { + protected override IServiceCollection AddServices(IServiceCollection serviceCollection) + => base.AddServices(serviceCollection).AddEntityFrameworkProxies(); + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder) + .UseLazyLoadingProxies() + .ConfigureWarnings(e => e.Ignore(SqliteEventId.SchemaConfiguredWarning)); + + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected override ITestStoreFactory TestStoreFactory + => SqliteTestStoreFactory.Instance; + + protected override string StoreName + => "AspNetCustomTypesDefaultIdentity"; + } + } +} diff --git a/test/EFCore.AspNet.Sqlite.FunctionalTests/AspNetIdentityCustomTypesIntKeySqliteTest.cs b/test/EFCore.AspNet.Sqlite.FunctionalTests/AspNetIdentityCustomTypesIntKeySqliteTest.cs new file mode 100644 index 00000000000..7cf2133662b --- /dev/null +++ b/test/EFCore.AspNet.Sqlite.FunctionalTests/AspNetIdentityCustomTypesIntKeySqliteTest.cs @@ -0,0 +1,28 @@ +// 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 Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore +{ + public class AspNetIdentityCustomTypesIntKeySqliteTest + : AspNetIdentityCustomTypesIntKeyTestBase + { + public AspNetIdentityCustomTypesIntKeySqliteTest(AspNetIdentityCustomTypesIntKeySqliteFixture fixture) + : base(fixture) + { + } + + public class AspNetIdentityCustomTypesIntKeySqliteFixture : AspNetIdentityFixtureBase + { + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected override ITestStoreFactory TestStoreFactory + => SqliteTestStoreFactory.Instance; + + protected override string StoreName + => "AspNetCustomTypesIntKeyIdentity"; + } + } +} diff --git a/test/EFCore.AspNet.Sqlite.FunctionalTests/AspNetIdentityDefaultSqliteTest.cs b/test/EFCore.AspNet.Sqlite.FunctionalTests/AspNetIdentityDefaultSqliteTest.cs new file mode 100644 index 00000000000..079b1963513 --- /dev/null +++ b/test/EFCore.AspNet.Sqlite.FunctionalTests/AspNetIdentityDefaultSqliteTest.cs @@ -0,0 +1,28 @@ +// 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 Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore +{ + public class AspNetIdentityDefaultSqliteTest + : AspNetIdentityDefaultTestBase + { + public AspNetIdentityDefaultSqliteTest(AspNetDefaultIdentitySqliteFixture fixture) + : base(fixture) + { + } + + public class AspNetDefaultIdentitySqliteFixture : AspNetIdentityFixtureBase + { + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected override ITestStoreFactory TestStoreFactory + => SqliteTestStoreFactory.Instance; + + protected override string StoreName + => "AspNetDefaultIdentity"; + } + } +} diff --git a/test/EFCore.AspNet.Sqlite.FunctionalTests/AspNetIdentityIntKeySqliteTest.cs b/test/EFCore.AspNet.Sqlite.FunctionalTests/AspNetIdentityIntKeySqliteTest.cs new file mode 100644 index 00000000000..d67a1dade1f --- /dev/null +++ b/test/EFCore.AspNet.Sqlite.FunctionalTests/AspNetIdentityIntKeySqliteTest.cs @@ -0,0 +1,28 @@ +// 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 Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore +{ + public class AspNetIdentityIntKeySqliteTest + : AspNetIdentityIntKeyTestBase + { + public AspNetIdentityIntKeySqliteTest(AspNetIdentityIntKeySqliteFixture fixture) + : base(fixture) + { + } + + public class AspNetIdentityIntKeySqliteFixture : AspNetIdentityFixtureBase + { + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected override ITestStoreFactory TestStoreFactory + => SqliteTestStoreFactory.Instance; + + protected override string StoreName + => "AspNetIntKeyIdentity"; + } + } +} diff --git a/test/EFCore.AspNet.Sqlite.FunctionalTests/EFCore.AspNet.Sqlite.FunctionalTests.csproj b/test/EFCore.AspNet.Sqlite.FunctionalTests/EFCore.AspNet.Sqlite.FunctionalTests.csproj new file mode 100644 index 00000000000..67e3151db5e --- /dev/null +++ b/test/EFCore.AspNet.Sqlite.FunctionalTests/EFCore.AspNet.Sqlite.FunctionalTests.csproj @@ -0,0 +1,24 @@ + + + + net5.0 + Microsoft.EntityFrameworkCore.AspNet.Sqlite.FunctionalTests + Microsoft.EntityFrameworkCore + True + + + + + PreserveNewest + + + PreserveNewest + + + + + + + + +