// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. namespace ProductivityApiUnitTests { using System; using System.Collections; using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.Core.Objects.DataClasses; using System.Data.Entity.Infrastructure; using System.Data.Entity.Internal; using System.Data.Entity.ModelConfiguration.Configuration.Types; using System.Data.Entity.Resources; using System.Linq; using System.Reflection; using Moq; using Xunit; public class FakeEntity1 { } public class FakeEntity2 { } public class FakeEntity3 { } public class FakeEntity4 { } public class FakeEntity5 { } public class FakeEntity6 { } public class FakeEntity7 { } public class FakeEntity8 { } public class FakeEntity9 { } public class FakeEntity10 { } public class FakeEntity11 { } public class FakeEntity12 { } public class FakeEntity13 { } public class FakeEntity14 { } public class FakeEntity15 { } public class FakeEntity16 { } public class FakeEntity17 { } public class FakeEntity18 { } /// /// Unit tests for the DbSet/ObjectSet discovery service. /// public class SetDiscoveryTests : TestBase { #region Positive DbContext discovery and initialization tests private class FakeDbContextWithDbSets : DbContext { public FakeDbContextWithDbSets() : base("this=is=not=valid") { } // Should be detected: DbSets with no modifiers public DbSet PublicGetSet { get; set; } protected IDbSet ProtectedGetSet { get; set; } internal DbSet InternalGetSet { get; set; } protected internal IDbSet InternalProtectedGetSet { get; set; } private DbSet PrivateGetSet { get; set; } // Should be detected: Public DbSets setter modifiers public IDbSet PrivateSet { get; private set; } public DbSet ProtectedSet { get; protected set; } public IDbSet InternalSet { get; internal set; } public DbSet InternalProtectedSet { get; protected internal set; } // Should be detected: Public DbSets getter modifiers public DbSet PrivateGet { private get; set; } public IDbSet ProtectedGet { protected get; set; } public DbSet InternalGet { internal get; set; } public IDbSet InternalProtectedGet { protected internal get; set; } // Should be detected: DbSets with no setters public DbSet PublicGetNoSet { get { return null; } } protected IDbSet ProtectedGetNoSet { get { return null; } } internal IDbSet InternalGetNoSet { get { return null; } } protected internal DbSet InternalProtectedGetNoSet { get { return null; } } private IDbSet PrivateGetNoSet { get { return null; } } } [Fact] public void DbSet_and_IDbSet_properties_on_derived_DbContext_are_discovered() { var expected = new[] { "PublicGetSet", "ProtectedGetSet", "InternalGetSet", "InternalProtectedGetSet", "PrivateGetSet", "PrivateSet", "ProtectedSet", "InternalSet", "InternalProtectedSet", "PrivateGet", "ProtectedGet", "InternalGet", "InternalProtectedGet", "PublicGetNoSet", "ProtectedGetNoSet", "InternalGetNoSet", "InternalProtectedGetNoSet", "PrivateGetNoSet" , }; AssertExpectedSetsDiscovered(new FakeDbContextWithDbSets(), expected); } private class FakeDbContextWithNonSets : DbContext { public FakeDbContextWithNonSets() : base("this=is=not=valid") { } public DbSet Control { get; set; } public IQueryable QueryableT { get; set; } public IQueryable Queryable { get; set; } public IEnumerable EnumerableT { get; set; } public IEnumerable Enumerable { get; set; } public DbQuery DbQuery { get; set; } } [Fact] public void IQueryable_and_DbQuery_properties_on_derived_DbContext_are_ignored() { AssertExpectedSetsDiscovered(new FakeDbContextWithNonSets(), new[] { "Control" }); } [Fact] public void DbSet_and_IDbSet_properties_with_public_setters_are_initialized() { using (var context = new FakeDbContextWithDbSets()) { var contextType = typeof(FakeDbContextWithDbSets); const BindingFlags binding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; Assert.NotNull(contextType.GetProperty("PublicGetSet", binding).GetValue(context, null)); Assert.Null(contextType.GetProperty("ProtectedGetSet", binding).GetValue(context, null)); Assert.Null(contextType.GetProperty("InternalGetSet", binding).GetValue(context, null)); Assert.Null(contextType.GetProperty("InternalProtectedGetSet", binding).GetValue(context, null)); Assert.Null(contextType.GetProperty("PrivateGetSet", binding).GetValue(context, null)); Assert.Null(contextType.GetProperty("PrivateSet", binding).GetValue(context, null)); Assert.Null(contextType.GetProperty("ProtectedSet", binding).GetValue(context, null)); Assert.Null(contextType.GetProperty("InternalSet", binding).GetValue(context, null)); Assert.Null(contextType.GetProperty("InternalProtectedSet", binding).GetValue(context, null)); Assert.NotNull(contextType.GetProperty("PrivateGet", binding).GetValue(context, null)); Assert.NotNull(contextType.GetProperty("ProtectedGet", binding).GetValue(context, null)); Assert.NotNull(contextType.GetProperty("InternalGet", binding).GetValue(context, null)); Assert.NotNull(contextType.GetProperty("InternalProtectedGet", binding).GetValue(context, null)); Assert.Null(contextType.GetProperty("PublicGetNoSet", binding).GetValue(context, null)); Assert.Null(contextType.GetProperty("ProtectedGetNoSet", binding).GetValue(context, null)); Assert.Null(contextType.GetProperty("InternalGetNoSet", binding).GetValue(context, null)); Assert.Null(contextType.GetProperty("InternalProtectedGetNoSet", binding).GetValue(context, null)); Assert.Null(contextType.GetProperty("PrivateGetNoSet", binding).GetValue(context, null)); } } [Fact] public void IQueryable_and_DbQuery_properties_with_public_setters_are_not_initialized() { using (var context = new FakeDbContextWithNonSets()) { Assert.NotNull(context.Control); Assert.Null(context.DbQuery); Assert.Null(context.Enumerable); Assert.Null(context.EnumerableT); Assert.Null(context.Queryable); Assert.Null(context.QueryableT); } } #endregion #region Negative entity types private class FakeDbContextWithInterfaceDbSet : DbContext { public DbSet SetProp { get; set; } } [Fact] public void Discovery_of_DbSet_of_interface_type_throws() { Assert.Equal( Strings.InvalidEntityType(typeof(IList)), Assert.Throws( () => new DbSetDiscoveryService(new FakeDbContextWithInterfaceDbSet()).InitializeSets()).Message); } private class FakeDbContextWithGenericDbSet : DbContext { public DbSet> SetProp { get; set; } } [Fact] public void Discovery_of_DbSet_of_generic_type_throws() { Assert.Equal( Strings.InvalidEntityType(typeof(List)), Assert.Throws( () => new DbSetDiscoveryService(new FakeDbContextWithGenericDbSet()).InitializeSets()).Message); } private class FakeDbContextWithNestedDbSet : DbContext { public DbSet SetProp { get; set; } } [Fact] public void Discovery_of_DbSet_of_nested_type_throws() { Assert.Equal( Strings.InvalidEntityType(typeof(FakeDbContextWithDbSets)), Assert.Throws( () => new DbSetDiscoveryService(new FakeDbContextWithNestedDbSet()).InitializeSets()).Message); } private class FakeDbContextWithObjectDbSet : DbContext { public DbSet SetProp { get; set; } } [Fact] public void Discovery_of_DbSet_of_object_throws() { Assert.Equal( Strings.InvalidEntityType(typeof(Object)), Assert.Throws( () => new DbSetDiscoveryService(new FakeDbContextWithObjectDbSet()).InitializeSets()).Message); } private class FakeDbContextWithEntityObjectDbSet : DbContext { public DbSet SetProp { get; set; } } [Fact] public void Discovery_of_DbSet_of_EntityObject_throws() { Assert.Equal( Strings.InvalidEntityType(typeof(EntityObject)), Assert.Throws( () => new DbSetDiscoveryService(new FakeDbContextWithEntityObjectDbSet()).InitializeSets()).Message); } private class FakeDbContextWithStructuralObjectDbSet : DbContext { public DbSet SetProp { get; set; } } [Fact] public void Discovery_of_DbSet_of_StructuralObject_throws() { Assert.Equal( Strings.InvalidEntityType(typeof(StructuralObject)), Assert.Throws( () => new DbSetDiscoveryService(new FakeDbContextWithStructuralObjectDbSet()).InitializeSets()).Message); } private class FakeDbContextWithComplexObjectDbSet : DbContext { public DbSet SetProp { get; set; } } [Fact] public void Discovery_of_DbSet_of_ComplexObject_throws() { Assert.Equal( Strings.InvalidEntityType(typeof(ComplexObject)), Assert.Throws( () => new DbSetDiscoveryService(new FakeDbContextWithComplexObjectDbSet()).InitializeSets()).Message); } private class FakeDbContextWithStringDbSet : DbContext { public DbSet SetProp { get; set; } } [Fact] public void Discovery_of_DbSet_of_string_throws() { Assert.Equal( Strings.InvalidEntityType(typeof(String)), Assert.Throws( () => new DbSetDiscoveryService(new FakeDbContextWithStringDbSet()).InitializeSets()).Message); } #endregion #region Positive tests for discovery attributes at the class level [SuppressDbSetInitialization] private class DerivedDbContextWithClassLevelSetDiscoverOnly : DbContext { public DbSet FakeSet1 { get; set; } public DbSet FakeSet2 { get; set; } } [Fact] public void SuppressDbSetInitializationAttribute_on_context_class_results_in_all_sets_being_found() { var context = new DerivedDbContextWithClassLevelSetDiscoverOnly(); DiscoverAndInitializeSets(context, 2); Assert.Null(context.FakeSet1); Assert.Null(context.FakeSet2); } #endregion #region Positive tests for discovery attributes at the property level private class DerivedDbContextWithPropertyLevelSetDiscoverOnly : DbContext { public DbSet FakeSet1 { get; set; } [SuppressDbSetInitialization] public DbSet FakeSet2 { get; set; } } [Fact] public void SuppressDbSetInitializationAttribute_on_property_results_in_property_still_being_discovered() { var context = new DerivedDbContextWithPropertyLevelSetDiscoverOnly(); DiscoverAndInitializeSets(context, 2); Assert.NotNull(context.FakeSet1); Assert.Null(context.FakeSet2); } #endregion #region Positive tests for discovery attributes on contexts with inheritance [SuppressDbSetInitialization] private class DerivedDbContextWithInheritanceLevel1 : DbContext { public DbSet FakeSet1 { get; set; } } private class DerivedDbContextWithInheritanceLevel2 : DerivedDbContextWithInheritanceLevel1 { public DbSet FakeSet2 { get; set; } } [SuppressDbSetInitialization] private class DerivedDbContextWithInheritanceLevel3 : DerivedDbContextWithInheritanceLevel2 { public DbSet FakeSet3 { get; set; } } [Fact] public void DbSetDiscoveryAttribute_on_class_applies_only_to_properties_in_that_class() { var context = new DerivedDbContextWithInheritanceLevel3(); DiscoverAndInitializeSets(context, 3); Assert.Null(context.FakeSet1); Assert.NotNull(context.FakeSet2); Assert.Null(context.FakeSet3); } private class DerivedDbContextForPropertyOverrideLevel1 : DbContext { public virtual DbSet FakeSet1 { get; set; } } private class DerivedDbContextForPropertyOverrideLevel2 : DerivedDbContextForPropertyOverrideLevel1 { [SuppressDbSetInitialization] public override DbSet FakeSet1 { get; set; } } [Fact] public void DbSetDiscoveryAttribute_on_overriden_property_is_used() { var context = new DerivedDbContextForPropertyOverrideLevel2(); DiscoverAndInitializeSets(context, 1); Assert.Null(context.FakeSet1); } private class DerivedDbContextForPropertyHideLevel1 : DbContext { public DbSet FakeSet1 { get; set; } } private class DerivedDbContextForPropertyHideLevel2 : DerivedDbContextForPropertyHideLevel1 { [SuppressDbSetInitialization] public new DbSet FakeSet1 { get; set; } } [Fact] public void DbSetDiscoveryAttribute_on_hiding_property_is_used() { var context = new DerivedDbContextForPropertyHideLevel2(); DiscoverAndInitializeSets(context, 1); Assert.Null(context.FakeSet1); } private class DerivedDbContextWithInheritanceBLevel1 : DbContext { public virtual DbSet FakeSet1 { get; set; } } [SuppressDbSetInitialization] private class DerivedDbContextWithInheritanceBLevel2 : DerivedDbContextWithInheritanceBLevel1 { public override DbSet FakeSet1 { get; set; } } [Fact] public void DbSetDiscoveryAttribute_on_class_applies_to_properties_overriden_in_that_class() { var context = new DerivedDbContextWithInheritanceBLevel2(); DiscoverAndInitializeSets(context, 1); Assert.Null(context.FakeSet1); } #endregion #region Static tests for SuppressDbSetInitializationAttribute usage [Fact] public void DbSetDiscoveryAttribute_does_not_allow_multiple_uses() { Assert.False(GetDbSetDiscoveryAttributeUsage().AllowMultiple); } [Fact] public void DbSetDiscoveryAttribute_can_be_applied_to_classes_or_properties_only() { Assert.Equal(AttributeTargets.Class | AttributeTargets.Property, GetDbSetDiscoveryAttributeUsage().ValidOn); } private static AttributeUsageAttribute GetDbSetDiscoveryAttributeUsage() { return (AttributeUsageAttribute)typeof(SuppressDbSetInitializationAttribute).GetCustomAttributes( typeof(AttributeUsageAttribute), inherit: false).Single(); } #endregion #region Helpers internal class EntityTypeConfigurationForMock : EntityTypeConfiguration { public EntityTypeConfigurationForMock() : base(typeof(FakeEntity)) { } } private void AssertExpectedSetsDiscovered(DbContext context, IEnumerable expected) { var mockBuilder = new Mock(); var mockConfig = new Mock(); mockBuilder.Setup(b => b.Entity(It.IsAny())).Returns(mockConfig.Object); var discoveryService = new DbSetDiscoveryService(context); discoveryService.RegisterSets(mockBuilder.Object); foreach (var setName in expected) { var name = setName; mockConfig.VerifySet(c => c.EntitySetName = name, Times.Once()); } } private static void DiscoverAndInitializeSets(DbContext context, int setCount) { var mockBuilder = new Mock(); var mockConfig = new Mock(); mockBuilder.Setup(b => b.Entity(It.IsAny())).Returns(mockConfig.Object); var discoveryService = new DbSetDiscoveryService(context); discoveryService.RegisterSets(mockBuilder.Object); mockBuilder.Verify(b => b.Entity(It.IsAny()), Times.Exactly(setCount)); mockConfig.VerifySet(c => c.EntitySetName = It.IsAny(), Times.Exactly(setCount)); } #endregion } }