// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

namespace System.Data.Entity.Metadata
{
    using System;
    using System.Collections.Generic;
    using System.Data.Entity.Core;
    using System.Data.Entity.Core.Metadata.Edm;
    using System.Data.Entity.Functionals.Utilities;
    using System.Data.Entity.TestHelpers;
    using System.IO;
    using System.Linq;
    using System.Xml;
    using System.Xml.Linq;
    using System.Xml.Schema;
    using Xunit;
    using Xunit.Sdk;

    public class MetadataEnumTests : FunctionalTestBase, IUseFixture<MetadataEnumFixture>
    {
        private const string enumCsdl =
@"<Schema xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"" Namespace=""EnumTestModel"">
  <EntityContainer Name=""EnumTestContainer"">
    <EntitySet Name=""EnumTestSet"" EntityType=""EnumTestModel.EnumTestEntity"" />
  </EntityContainer>
  <EntityType Name=""EnumTestEntity"">
    <Key>
      <PropertyRef Name=""Id"" />
    </Key>
    <Property Name=""Id"" Nullable=""false"" Type=""Int32"" />
    <Property Name=""Color"" Type=""EnumTestModel.Color"" Nullable=""false""/>
  </EntityType>
  <EnumType Name=""Color"" IsFlags=""false"">
    <Member Name=""Yellow"" Value=""1"" />
    <Member Name=""Green"" Value=""2"" />
    <Member Name=""Blue"" Value=""3""/>
  </EnumType>
</Schema>";

        private const string edmNamespace = "http://schemas.microsoft.com/ado/2009/11/edm";

        private XmlSchemaSet csdlSchemaSet;

        public void SetFixture(MetadataEnumFixture data)
        {
            csdlSchemaSet = data.CsdlSchemaSet;
        }

        [Fact]
        public void Empty_enum_is_valid()
        {
            var emptyEnum =
@"<Schema xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"" Namespace=""EnumModel"">
  <EnumType Name=""Color"" IsFlags=""false"">
  </EnumType>
</Schema>";

            var edmItemCollection = new EdmItemCollection(new[] { XmlReader.Create(new StringReader(emptyEnum)) });
            var enumType = edmItemCollection.GetItems<EnumType>().Single();

            Assert.Equal(0, enumType.Members.Count);
        }

        [Fact]
        public void SchemaSource_metadata_property_is_populated()
        {
            var edmItemCollection = new EdmItemCollection(new[] { XmlReader.Create(new StringReader(enumCsdl)) });
            var enumType = edmItemCollection.GetItems<EnumType>().Single();
            
            var schemaSource = enumType.MetadataProperties["SchemaSource"];
            Assert.Equal(PropertyKind.System, schemaSource.PropertyKind);
        }

        [Fact]
        public void Documenation_is_populated_correctly_for_enum_type()
        {
            var enumWithDocumentation =
@"<Schema xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"" Namespace=""EnumModel"">
  <EnumType Name=""Color"" IsFlags=""false"">
    <Documentation>
      <Summary>Documentation - summary</Summary>
      <LongDescription>Documentation - long description</LongDescription>
    </Documentation>
    <Member Name=""Yellow"" Value=""1"" />
  </EnumType>
</Schema>";

            var edmItemCollection = new EdmItemCollection(new[] { XmlReader.Create(new StringReader(enumWithDocumentation)) });
            var enumType = edmItemCollection.GetItems<EnumType>().Single();

            Assert.NotNull(enumType.Documentation);
            Assert.Equal("Documentation - summary", enumType.Documentation.Summary);
            Assert.Equal("Documentation - long description", enumType.Documentation.LongDescription);
        }
        
        [Fact]
        public void Documenation_is_populated_correctly_for_members_of_enum_type()
        {
            var enumWithDocumentation =
@"<Schema xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"" Namespace=""EnumTestModel"">
  <EnumType Name=""Color"" IsFlags=""false"">
    <Member Name=""Yellow"" Value=""1"">
      <Documentation>
        <Summary>Documentation - summary</Summary>
        <LongDescription>Documentation - long description</LongDescription>
      </Documentation>
    </Member>
  </EnumType>
</Schema>";

            var edmItemCollection = new EdmItemCollection(new[] { XmlReader.Create(new StringReader(enumWithDocumentation)) });
            var enumType = edmItemCollection.GetItems<EnumType>().Single();
            var enumMemberDocumetation = enumType.Members.Single().Documentation;

            Assert.NotNull(enumMemberDocumetation);
            Assert.Equal("Documentation - summary", enumMemberDocumetation.Summary);
            Assert.Equal("Documentation - long description", enumMemberDocumetation.LongDescription);
        }

        [Fact]
        public void EnumType_contains_MetadataProperties()
        {
            var enumCsdl =
@"<Schema xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"" Namespace=""EnumTestModel"">
  <EnumType Name=""Color"" IsFlags=""false"">
    <Member Name=""Yellow"" Value=""1"" />
    <Member Name=""Green"" Value=""2"" />
    <Member Name=""Blue"" Value=""3""/>
  </EnumType>
</Schema>";

            var edmItemCollection = new EdmItemCollection(new[] { XmlReader.Create(new StringReader(enumCsdl)) });

            var enumType = edmItemCollection.GetItems<EnumType>().Single();

            Assert.Equal("Color", enumType.MetadataProperties.Where(p => p.Name == "Name").Single().Value);
            Assert.False((bool)enumType.MetadataProperties.Where(p => p.Name == "IsFlags").Single().Value);
            Assert.Equal("Edm.Int32", enumType.MetadataProperties.Where(p => p.Name == "UnderlyingType").Single().Value.ToString());

            var membersProperty = ((IEnumerable<EnumMember>)enumType.MetadataProperties.Where(p => p.Name == "Members").Single().Value).ToList();
            
            Assert.Equal(3, membersProperty.Count());
            Assert.Equal("Yellow", membersProperty[0].Name);
            Assert.Equal(1, membersProperty[0].Value);

            Assert.Equal("Green", membersProperty[1].Name);
            Assert.Equal(2, membersProperty[1].Value);

            Assert.Equal("Blue", membersProperty[2].Name);
            Assert.Equal(3, membersProperty[2].Value);
        }

        [Fact]
        public void Clr_types_of_member_values_match_the_UnderlyingType()
        {
            var clrTypeForUndelryingType = new Dictionary<string, Type>() {
                { "Byte", typeof(byte) },
                { "SByte", typeof(sbyte) },
                { "Int16", typeof(short) },
                { "Int32", typeof(int) },
                { "Int64", typeof(long) },
                { "Edm.Byte", typeof(byte) },
                { "Edm.SByte", typeof(sbyte) },
                { "Edm.Int16", typeof(short) },
                { "Edm.Int32", typeof(int) },
                { "Edm.Int64", typeof(long) },
            };

            var enumCSDL = XDocument.Parse(enumCsdl);
            var enumTypeElement = enumCSDL.Descendants(XName.Get("EnumType", edmNamespace)).Single();

            foreach (var kvp in clrTypeForUndelryingType)
            {
                enumTypeElement.SetAttributeValue("UnderlyingType", kvp.Key);
                var edmItemCollection = new EdmItemCollection(new XmlReader[] { enumCSDL.CreateReader() });
                var enumType = edmItemCollection.GetItems<EnumType>().Single();

                Assert.False(enumType.Members.Any(m => m.Value.GetType() != kvp.Value));
            }
        }

        [Fact]
        public void Custom_MetadataProperties_are_populated_for_members_of_enum_type()
        {
            var enumCsdl =
@"<Schema xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"" Namespace=""EnumTestModel"">
  <EnumType Name=""Color"" IsFlags=""false"">
    <Member Name=""Yellow"" Value=""1"" p3:abc=""xyz"" xmlns:p3=""http://tempuri.org"" />
    <Member Name=""Green"" Value=""2"" />
    <Member Name=""Blue"" Value=""3"" />
  </EnumType>
</Schema>";

            var edmItemCollection = new EdmItemCollection(new[] { XmlReader.Create(new StringReader(enumCsdl)) });
            var enumMember = edmItemCollection.GetItems<EnumType>().Single().Members.First();

            var customProperty = enumMember.MetadataProperties["http://tempuri.org:abc"];

            Assert.Equal("xyz", customProperty.Value);
            Assert.Equal(PropertyKind.Extended, customProperty.PropertyKind);
        }

        [Fact]
        public void Nullable_facet_on_EnumProperty_is_set_correctly()
        {
            foreach (var nullableFacetValue in new bool?[] { null, false, true })
            {
                var csdl = XDocument.Parse(enumCsdl);
                csdl.Descendants(XName.Get("Property", edmNamespace))
                    .Where(p => (string)p.Attribute("Name") == "Color")
                    .Single()
                    .SetAttributeValue("Nullable", nullableFacetValue == null ? null : nullableFacetValue.ToString().ToLower());

                var edmItemCollection = new EdmItemCollection(new XmlReader[] { csdl.CreateReader() });

                Assert.Equal(
                    nullableFacetValue ?? true,
                    edmItemCollection.GetItems<EntityType>().Single().Properties.Single(p => p.Name == "Color").Nullable);
            }
        }

        [Fact]
        public void Enum_values_with_facets_can_be_used_on_Parameter_element()
        {
            foreach (var nullableFacetValue in new bool?[] { null, false, true })
            {
                VerifyFunctionDefinitionWithEnumsValid(XElement.Parse(
@"<Function Name=""TestFunc"" xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"">
    <Parameter Name=""Input"" Type=""EnumTestModel.Color"" " + GetNullableFacetAttributeString(nullableFacetValue) + @" />
    <ReturnType Type=""Int32"" />
</Function>"),
                  (edmFunction) =>
                  {
                      Assert.Equal(BuiltInTypeKind.EnumType, edmFunction.Parameters.Single().TypeUsage.EdmType.BuiltInTypeKind);
                      Assert.Equal(
                          nullableFacetValue ?? true,
                          (bool)edmFunction.Parameters.Single().TypeUsage.Facets.Single(f => f.Name == "Nullable").Value);
                  });
            }
        }

        [Fact]
        public void Enum_ReturnTypes_with_facets_can_be_ised_on_Function_element()
        {
            foreach (var nullableFacetValue in new bool?[] { null, false, true })
            {
                VerifyFunctionDefinitionWithEnumsValid(XElement.Parse(
@"<Function Name=""TestFunc"" xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"" ReturnType=""EnumTestModel.Color"" " + GetNullableFacetAttributeString(nullableFacetValue) + " />"),
                    (edmFunction) =>
                    {
                        Assert.Equal(BuiltInTypeKind.EnumType, edmFunction.ReturnParameter.TypeUsage.EdmType.BuiltInTypeKind);
                        Assert.Equal(
                            nullableFacetValue ?? true,
                                (bool)edmFunction.ReturnParameter.TypeUsage.Facets.Single(f => f.Name == "Nullable").Value);
                    });
            }
        }

        [Fact]
        public void Enum_values_with_facets_can_be_used_on_ReturnType_element()
        {
            foreach (var nullableFacetValue in new bool?[] { null, false, true })
            {
                VerifyFunctionDefinitionWithEnumsValid(XElement.Parse(
@"<Function Name=""TestFunc"" xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"">
    <ReturnType Type=""EnumTestModel.Color"" " + GetNullableFacetAttributeString(nullableFacetValue) + @" />
</Function>"),
                  (edmFunction) =>
                  {
                      Assert.Equal(BuiltInTypeKind.EnumType, edmFunction.ReturnParameter.TypeUsage.EdmType.BuiltInTypeKind);
                      Assert.Equal(
                          nullableFacetValue ?? true,
                            (bool)edmFunction.ReturnParameter.TypeUsage.Facets.Single(f => f.Name == "Nullable").Value);
                  });
            }
        }

        private static void VerifyFunctionDefinitionWithEnumsValid(XElement funcDefinition, Action<EdmFunction> verifyFunctionAction)
        {
            Assert.True(funcDefinition.Name.LocalName == "Function");
            Assert.True(funcDefinition.DescendantsAndSelf().Any(e => e.Attributes().Any(a => ((string)a).Contains("EnumTestModel.Color"))));

            var enumCSDL = XDocument.Parse(enumCsdl);
            enumCSDL.Root.Add(funcDefinition);

            var edmItemCollection = new EdmItemCollection(new XmlReader[] { enumCSDL.CreateReader() });
            var function = edmItemCollection.GetItems<EdmFunction>().Where(f => f.Name == (string)funcDefinition.Attribute("Name")).Single();

            verifyFunctionAction(function);
        }

        private static string GetNullableFacetAttributeString(bool? nullableFacetValue)
        {
            return
                nullableFacetValue != null ?
                    string.Format(@"Nullable=""{0}""", nullableFacetValue.ToString().ToLower()) :
                    string.Empty;
        }

        [Fact]
        public void Using_invalid_facets_on__EnumProperty_throws()
        {
            RunInvalidEnumTypeFacetTests((csdl, facetName, facetValue) =>
            {
                csdl.Descendants(XName.Get("Property", edmNamespace))
                    .Where(p => (string)p.Attribute("Name") == "Color")
                    .Single().SetAttributeValue(facetName, facetValue);
            });
        }

        [Fact]
        public void Using_invalid_facets_on_EnumFunction_param_throws()
        {
            RunInvalidEnumTypeFacetTests((csdl, facetName, facetValue) =>
            {
                csdl.Root.Add(XElement.Parse(string.Format(
@"<Function Name=""TestFunc"" xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"" ReturnType=""String"">
    <Parameter Name=""Input"" Type=""EnumTestModel.Color"" {0}=""{1}"" />
</Function>", facetName, facetValue)));
            });
        }

        [Fact]
        public void Using_invalid_facets_on_inline_EnumFunction_ReturnType_throws()
        {
            RunInvalidEnumTypeFacetTests((csdl, facetName, facetValue) =>
            {
                csdl.Root.Add(XElement.Parse(string.Format(
                    @"<Function Name=""TestFunc"" xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"" ReturnType=""EnumTestModel.Color"" {0}=""{1}"" />",
                    facetName, facetValue)));
            });
        }

        [Fact]
        public void Using_invalidFacetsOnEnumFunctionReturnTypeThrows()
        {
            RunInvalidEnumTypeFacetTests((csdl, facetName, facetValue) =>
            {
                csdl.Root.Add(XElement.Parse(string.Format(
@"<Function Name=""TestFunc"" xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"">
    <ReturnType Type=""EnumTestModel.Color"" {0}=""{1}""  />
</Function>", facetName, facetValue)));
            });
        }

        [Fact]
        public void Using_invalid_facets_on_Collection_of_Enum__for_function_ReturnType_throws()
        {
            RunInvalidEnumTypeFacetTests((csdl, facetName, facetValue) =>
            {
                csdl.Root.Add(XElement.Parse(string.Format(
@"<Function Name=""TestFunc"" xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"">
    <ReturnType>
        <CollectionType ElementType=""EnumTestModel.Color"" {0}=""{1}"" />
    </ReturnType>
</Function>", facetName, facetValue)));
            });
        }

        [Fact]
        public void Using_invalid_facets_on_collection_of_Ref_Enum_for_function_ReturnType_throws()
        {
            RunInvalidEnumTypeFacetTests((csdl, facetName, facetValue) =>
            {
                csdl.Root.Add(XElement.Parse(string.Format(
@"<Function Name=""TestFunc"" xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"">
    <ReturnType>
        <CollectionType>
            <TypeRef Type=""EnumTestModel.Color"" {0}=""{1}"" />
        </CollectionType>
    </ReturnType>
</Function>", facetName, facetValue)));
            });
        }

        [Fact]
        public void Using_invalid_facets_on_RowType_of_Collection_of_Enum_for_function_ReturnType_throws()
        {
            RunInvalidEnumTypeFacetTests((csdl, facetName, facetValue) =>
            {
                csdl.Root.Add(XElement.Parse(string.Format(
@"<Function Name=""TestFunc"" xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"">
    <ReturnType>
        <RowType>
            <Property Name=""EnumProperty"" Type=""EnumTestModel.Color"" {0}=""{1}"" />
        </RowType>
    </ReturnType>
</Function>", facetName, facetValue)));
            });
        }

        private static string[,] invalidEnumPropertyFacets = 
        { 
            { "Collation", "Int32" },
            { "FixedLength", "false" },
            { "MaxLength", "30" },
            { "Precision", "3" },
            { "Scale", "2" },
            { "Unicode", "true" }
        };

        private static void RunInvalidEnumTypeFacetTests(Action<XDocument, string, string> updateCsdl)
        {
            for (int i = 0; i < invalidEnumPropertyFacets.Length >> 1; i++)
            {
                var enumCSDL = XDocument.Parse(enumCsdl);
                updateCsdl(enumCSDL, invalidEnumPropertyFacets[i, 0], invalidEnumPropertyFacets[i, 1]);

                try
                {
                    new EdmItemCollection(new XmlReader[] { enumCSDL.CreateReader() });

                    throw new AssertException("Expecting exception to be thrown during EdmItemCollection construction.");
                }
                catch (MetadataException ex)
                {
                    Assert.True(ex.Message.Contains(invalidEnumPropertyFacets[i, 0] + " facet isn't allowed for properties of type EnumTestModel.Color"), "Unexpected exception\n: " + ex);
                }
            }
        }

        [Fact]
        public void Using_invalid_facets_on_FunctionImport_enum_parameter_throws()
        {
            var funcImportInvalidEnumParamFacets = new string[8, 2];
            Array.Copy(invalidEnumPropertyFacets, funcImportInvalidEnumParamFacets, invalidEnumPropertyFacets.Length);

            funcImportInvalidEnumParamFacets[6, 0] = "Nullable";
            funcImportInvalidEnumParamFacets[6, 1] = "false";
            funcImportInvalidEnumParamFacets[7, 0] = "DefaultValue";
            funcImportInvalidEnumParamFacets[7, 1] = "34";

            for (int i = 0; i < funcImportInvalidEnumParamFacets.Length >> 1; i++)
            {
                var enumCSDL = XDocument.Parse(enumCsdl);
                enumCSDL.Root
                    .Descendants(XName.Get("EntityContainer", edmNamespace))
                    .Single()
                    .Add(XElement.Parse(string.Format(
@"<FunctionImport Name=""TestFunc"" xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"">
    <Parameter Name=""EnumProperty"" Type=""EnumTestModel.Color"" {0}=""{1}"" />
</FunctionImport>", funcImportInvalidEnumParamFacets[i, 0], funcImportInvalidEnumParamFacets[i, 1])));

                try
                {
                    new EdmItemCollection(new XmlReader[] { enumCSDL.CreateReader() });

                    // Should never get here - exception is expected
                    Assert.True(1 == 2);
                }
                catch (MetadataException ex)
                {
                    if (LocalizationTestHelpers.IsEnglishLocale())
                    {
                        if (new string[] { "MaxLength", "Precision", "Scale" }.Contains(funcImportInvalidEnumParamFacets[i, 0]))
                        {
                            Assert.True(ex.Message.Contains(funcImportInvalidEnumParamFacets[i, 0] + " facet isn't allowed for properties of type EnumTestModel.Color"), "Unexpected exception\n: " + ex);
                        }
                        else if (funcImportInvalidEnumParamFacets[i, 0] == "Nullable")
                        {
                            Assert.True(ex.Message.Contains("non-nullable"), "Unexpected exception\n: " + ex);
                        }
                        else
                        {
                            Assert.True(ex.Message.Contains("The '" + funcImportInvalidEnumParamFacets[i, 0] + "' attribute is not allowed"), "Unexpected exception\n: " + ex);
                        }
                    }
                }
            }
        }

        [Fact]
        public void Collection_of_enum_values_can_be_specified_as_FunctionImport_ReturnType()
        {
            var functionImportDefinitions = new XElement[] { 
                XElement.Parse(@"<FunctionImport Name=""TestFunc"" xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"" ReturnType=""Collection(EnumTestModel.Color)"" />"),
                XElement.Parse(
                    @"<FunctionImport Name=""TestFunc"" xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"">
                        <ReturnType Type=""Collection(EnumTestModel.Color)"" />
                    </FunctionImport>"),
                XElement.Parse(
                    @"<FunctionImport Name=""TestFunc"" xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"">
                        <ReturnType Type=""Collection(EnumTestModel.Color)"" />
                        <ReturnType Type=""Collection(EnumTestModel.Color)"" />
                    </FunctionImport>")
            };

            foreach (var functionImportDefinition in functionImportDefinitions)
            {
                var enumCSDL = XDocument.Parse(enumCsdl);
                enumCSDL.Root
                    .Descendants(XName.Get("EntityContainer", edmNamespace))
                    .Single()
                    .Add(functionImportDefinition);

                var edmItemCollection = new EdmItemCollection(new XmlReader[] { enumCSDL.CreateReader() });
                var functionImport = edmItemCollection.GetItems<EntityContainer>().Single().FunctionImports.Single(f => f.Name == "TestFunc");

                Assert.Equal(functionImportDefinition.Elements("{http://schemas.microsoft.com/ado/2009/11/edm}ReturnType").Count() + functionImportDefinition.Attributes("ReturnType").Count(),
                    functionImport.ReturnParameters.Count);

                foreach (var retParam in (IEnumerable<FunctionParameter>)functionImport.ReturnParameters ?? new FunctionParameter[] { functionImport.ReturnParameter })
                {
                    var retParamEdmType = retParam.TypeUsage.EdmType;

                    Assert.True(retParamEdmType is CollectionType);
                    Assert.Equal(((CollectionType)retParamEdmType).TypeUsage.EdmType.BuiltInTypeKind, BuiltInTypeKind.EnumType);
                }
            }
        }

        [Fact]
        public void Enum_type_with_code_generation_ExternalTypeName_attribute_is_valid()
        {
            var document = XDocument.Parse(enumCsdl);
            var enumType = document.Descendants(XName.Get("EnumType", edmNamespace)).First();
            enumType.SetAttributeValue(XName.Get("ExternalTypeName", "http://schemas.microsoft.com/ado/2006/04/codegeneration"), "abc");

            document.Validate(csdlSchemaSet, (o, e) => { throw e.Exception; });
        }

        [Fact]
        public void Code_generation_ExternamTypeName_attribute_is_added_to_metadata_properties()
        {
            var document = XDocument.Parse(enumCsdl);
            var enumType = document.Descendants(XName.Get("EnumType", edmNamespace)).First();
            enumType.SetAttributeValue(XName.Get("ExternalTypeName", "http://schemas.microsoft.com/ado/2006/04/codegeneration"), "abc");

            var itemCollection = new EdmItemCollection(new[] { document.CreateReader() });

            Assert.Equal(
                "abc",
                itemCollection.GetItems<EnumType>().Single().MetadataProperties.Single(m => m.Name == "http://schemas.microsoft.com/ado/2006/04/codegeneration:ExternalTypeName").Value);
        }

        [Fact]
        public void Validation_fails_for_enums_with_missing_EnumTypeName()
        {
            var csdl = XDocument.Parse(enumCsdl);
            var enumType = csdl.Descendants(XName.Get("EnumType", edmNamespace)).First();
            enumType.Attributes("Name").Remove();

            var exceptionMessage = string.Empty;
            csdl.Validate(csdlSchemaSet, (o, e) => exceptionMessage = e.Message);
            if (LocalizationTestHelpers.IsEnglishLocale())
            {
                Assert.Equal("The required attribute 'Name' is missing.", exceptionMessage);
            }
            else
            {
                Assert.False(string.IsNullOrEmpty(exceptionMessage));
            }
        }

        [Fact]
        public void Validation_fails_for_enums_with_empty_EnumTypeName()
        {
            var csdl = XDocument.Parse(enumCsdl);
            var enumType = csdl.Descendants(XName.Get("EnumType", edmNamespace)).First();
            enumType.SetAttributeValue("Name", string.Empty);

            var exceptionMessage = string.Empty;
            csdl.Validate(csdlSchemaSet, (o, e) => exceptionMessage = e.Message);
            Assert.True(exceptionMessage.Contains("Name"));
            Assert.True(exceptionMessage.Contains("http://schemas.microsoft.com/ado/2009/11/edm:TSimpleIdentifier"));
        }

        [Fact]
        public void Validation_fails_for_enums_with_empty_UnderlyingType_value()
        {
            var csdl = XDocument.Parse(enumCsdl);
            var enumType = csdl.Descendants(XName.Get("EnumType", edmNamespace)).First();
            enumType.SetAttributeValue("UnderlyingType", string.Empty);

            var exceptionMessage = string.Empty;
            csdl.Validate(csdlSchemaSet, (o, e) => exceptionMessage = e.Message);
            Assert.True(exceptionMessage.Contains("UnderlyingType"));
            Assert.True(exceptionMessage.Contains("http://schemas.microsoft.com/ado/2009/11/edm:TPropertyType"));
        }

        [Fact]
        public void Validation_fails_for_enums_with_empty_IsFlags_value()
        {
            var csdl = XDocument.Parse(enumCsdl);
            var enumType = csdl.Descendants(XName.Get("EnumType", edmNamespace)).First();
            enumType.SetAttributeValue("IsFlags", string.Empty);

            var exceptionMessage = string.Empty;
            csdl.Validate(csdlSchemaSet, (o, e) => exceptionMessage = e.Message);
            Assert.True(exceptionMessage.Contains("IsFlags"));
            Assert.True(exceptionMessage.Contains("http://www.w3.org/2001/XMLSchema:boolean"));
        }

        [Fact]
        public void Validation_fails_for_enums_with_invalid_IsFlags_value()
        {
            var csdl = XDocument.Parse(enumCsdl);
            var enumType = csdl.Descendants(XName.Get("EnumType", edmNamespace)).First();
            enumType.SetAttributeValue("IsFlags", "abc");

            var exceptionMessage = string.Empty;
            csdl.Validate(csdlSchemaSet, (o, e) => exceptionMessage = e.Message);
            Assert.True(exceptionMessage.Contains("IsFlags"));
            Assert.True(exceptionMessage.Contains("abc"));
            Assert.True(exceptionMessage.Contains("http://www.w3.org/2001/XMLSchema:boolean"));
        }

        [Fact]
        public void Validation_fails_for_enums_with_missing_enum_member_Name()
        {
            var csdl = XDocument.Parse(enumCsdl);
            var enumMember = csdl.Descendants(XName.Get("Member", edmNamespace)).First();
            enumMember.Attributes("Name").Remove();

            var exceptionMessage = string.Empty;
            csdl.Validate(csdlSchemaSet, (o, e) => exceptionMessage = e.Message);
            if (LocalizationTestHelpers.IsEnglishLocale())
            {
                Assert.Equal("The required attribute 'Name' is missing.", exceptionMessage);
            }
            else
            {
                Assert.False(string.IsNullOrEmpty(exceptionMessage));
            }
        }

        [Fact]
        public void Validation_fails_for_enums_with_empty_enum_member_Name()
        {
            var csdl = XDocument.Parse(enumCsdl);
            var enumMember = csdl.Descendants(XName.Get("Member", edmNamespace)).First();
            enumMember.SetAttributeValue("Name", string.Empty);

            var exceptionMessage = string.Empty;
            csdl.Validate(csdlSchemaSet, (o, e) => exceptionMessage = e.Message);
            Assert.True(exceptionMessage.Contains("Name"));
            Assert.True(exceptionMessage.Contains("http://schemas.microsoft.com/ado/2009/11/edm:TSimpleIdentifier"));
        }

        [Fact]
        public void Validation_fails_for_enums_with_empty_member_Value()
        {
            var csdl = XDocument.Parse(enumCsdl);
            var enumMember = csdl.Descendants(XName.Get("Member", edmNamespace)).First();
            enumMember.SetAttributeValue("Value", string.Empty);

            var exceptionMessage = string.Empty;
            csdl.Validate(csdlSchemaSet, (o, e) => exceptionMessage = e.Message);
            Assert.True(exceptionMessage.Contains("Value"));
            Assert.True(exceptionMessage.Contains("http://www.w3.org/2001/XMLSchema:long"));
        }

        [Fact]
        public void Validation_fails_for_enums_with_invalid_member_Value()
        {
            var csdl = XDocument.Parse(enumCsdl);
            var enumMember = csdl.Descendants(XName.Get("Member", edmNamespace)).First();
            enumMember.SetAttributeValue("Value", "abc");

            var exceptionMessage = string.Empty;
            csdl.Validate(csdlSchemaSet, (o, e) => exceptionMessage = e.Message);
            Assert.True(exceptionMessage.Contains("Value"));
            Assert.True(exceptionMessage.Contains("abc"));
            Assert.True(exceptionMessage.Contains("http://www.w3.org/2001/XMLSchema:long"));
        }

        [Fact]
        public void Validation_fails_for_enums_with_out_of_range_member_Value()
        {
            var csdl = XDocument.Parse(enumCsdl);
            var enumMember = csdl.Descendants(XName.Get("Member", edmNamespace)).First();
            enumMember.SetAttributeValue("Value", new string('9', 30));

            var exceptionMessage = string.Empty;
            csdl.Validate(csdlSchemaSet, (o, e) => exceptionMessage = e.Message);

            Assert.True(exceptionMessage.Contains("Value"));
            Assert.True(exceptionMessage.Contains("999999999999999999999999999999"));
            Assert.True(exceptionMessage.Contains("http://www.w3.org/2001/XMLSchema:long"));
        }

        [Fact]
        public void Validation_fails_for_enum_with_duplicate_member()
        {
            var csdl =
@"<Schema xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"" Namespace=""EnumTestModel"">
  <EnumType Name=""Color"" IsFlags=""false"">
    <Member Name=""Yellow"" Value=""1"" />
    <Member Name=""Yellow"" Value=""1"" />
    <Member Name=""Green"" Value=""2"" />
  </EnumType>
</Schema>";

            var exception = Assert.Throws<MetadataException>(
                () => new EdmItemCollection(new[] { XmlReader.Create(new StringReader(csdl)) }));

            exception.ValidateMessage(typeof(DbContext).Assembly(), "DuplicateEnumMember", null);
        }

        [Fact]
        public void Validation_fails_for_enums_with_incorrect_UnderlyingType_String()
        {
            IncorrectUnderlyingType("String", "InvalidEnumUnderlyingType");
        }

        [Fact]
        public void Validation_fails_for_enums_with_incorrect_UnderlyingType_ModelType()
        {
            IncorrectUnderlyingType("EnumTestModel.EnumTestEntity", "InvalidEnumUnderlyingType");
        }

        [Fact]
        public void Validation_fails_for_enums_with_incorrect_UnderlyingType_SameEnumType()
        {
            IncorrectUnderlyingType("EnumTestModel.Color", "InvalidEnumUnderlyingType");
        }

        [Fact]
        public void Validation_fails_for_enums_with_incorrect_UnderlyingType_NonExistingType()
        {
            IncorrectUnderlyingType("EnumTestModel.NonExistingType", "NotInNamespaceNoAlias", "NonExistingType", "EnumTestModel");
        }

        [Fact]
        public void Validation_fails_for_enums_with_incorrect_UnderlyingType_InvalidEdmType()
        {
            IncorrectUnderlyingType("Edm.MyType", "NotInNamespaceNoAlias", "MyType", "Edm");
        }

        private void IncorrectUnderlyingType(string typeName, string resourceKey, params object[] exceptionParameters)
        {
            var csdlDocument = XDocument.Parse(enumCsdl);
            var enumType = csdlDocument.Descendants(XName.Get("EnumType", edmNamespace)).Single();
            enumType.SetAttributeValue("UnderlyingType", typeName);

            var exception = Assert.Throws<MetadataException>(() => new EdmItemCollection(new[] { csdlDocument.CreateReader() }));
            exception.ValidateMessage(typeof(DbContext).Assembly(), resourceKey, null, /*isExactMatch*/ false, exceptionParameters);
        }

        [Fact]
        public void Validation_fails_for_enums_with_CalculatedValue_out_of_range()
        {
            var csdl =
@"<Schema xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"" Namespace=""EnumTestModel"">
    <EnumType Name=""Color"" IsFlags=""false"">
    <Member Name=""Yellow"" Value=""9223372036854775807"" />
    <Member Name=""Green"" />
    </EnumType>
</Schema>";

            var exception = Assert.Throws<MetadataException>(() => new EdmItemCollection(new[] { XmlReader.Create(new StringReader(csdl)) }));
            exception.ValidateMessage(typeof(DbContext).Assembly(), "CalculatedEnumValueOutOfRange", null);
        }

        [Fact]
        public void Validation_fails_for_enums_with_SpecifiedValue_too_large_to_fit_in_Byte()
        {
            var value = (((long)Byte.MaxValue) + 1).ToString();
            RunUnderOverflowTestForExplicitlySpecifiedValues("Byte", value, "EnumMemberValueOutOfItsUnderylingTypeRange", value, "Yellow", "Byte");
        }

        [Fact]
        public void Validation_fails_for_enums_with_SpecifiedValue_cannot_be_converted_to_Byte()
        {
            var value = (((long)Byte.MinValue) - 1).ToString();
            RunUnderOverflowTestForExplicitlySpecifiedValues("Byte", value, "EnumMemberValueOutOfItsUnderylingTypeRange", value, "Yellow", "Byte");
        }

        [Fact]
        public void Validation_fails_for_enums_with_SpecifiedValue_too_large_to_fit_in_SByte()
        {
            var value = (((long)SByte.MaxValue) + 1).ToString();
            RunUnderOverflowTestForExplicitlySpecifiedValues("SByte", value, "EnumMemberValueOutOfItsUnderylingTypeRange", value, "Yellow", "SByte");
        }

        [Fact]
        public void Validation_fails_for_enums_with_SpecifiedValue_cannot_be_converted_to_SByte()
        {
            var value = (((long)SByte.MinValue) - 1).ToString();
            RunUnderOverflowTestForExplicitlySpecifiedValues("SByte", value, "EnumMemberValueOutOfItsUnderylingTypeRange", value, "Yellow", "SByte");
        }

        [Fact]
        public void Validation_fails_for_enums_with_SpecifiedValue_too_large_to_fit_in_Int16()
        {
            var value = (((long)Int16.MaxValue) + 1).ToString();
            RunUnderOverflowTestForExplicitlySpecifiedValues("Int16", value, "EnumMemberValueOutOfItsUnderylingTypeRange", value, "Yellow", "Int16");
        }

        [Fact]
        public void Validation_fails_for_enums_with_SpecifiedValue_cannot_be_converted_to_Int16()
        {
            var value = (((long)Int16.MinValue) - 1).ToString();
            RunUnderOverflowTestForExplicitlySpecifiedValues("Int16", value, "EnumMemberValueOutOfItsUnderylingTypeRange", value, "Yellow", "Int16");
        }

        [Fact]
        public void Validation_fails_for_enums_with_SpecifiedValue_too_large_to_fit_in_Int32()
        {
            var value = (((long)Int32.MaxValue) + 1).ToString();
            RunUnderOverflowTestForExplicitlySpecifiedValues("Int32", value, "EnumMemberValueOutOfItsUnderylingTypeRange", value, "Yellow", "Int32");
        }

        [Fact]
        public void Validation_fails_for_enums_with_SpecifiedValue_cannot_be_converted_to_Int32()
        {
            var value = (((long)Int32.MinValue) - 1).ToString();
            RunUnderOverflowTestForExplicitlySpecifiedValues("Int32", value, "EnumMemberValueOutOfItsUnderylingTypeRange", value, "Yellow", "Int32");
        }

        private static void RunUnderOverflowTestForExplicitlySpecifiedValues(string underlyingTypeName, string value, string resourceKey, params object[] exceptionParameters)
        {
            var document = XDocument.Parse(enumCsdl);
            var enumType = document.Descendants(XName.Get("EnumType", edmNamespace)).Single();
            enumType.SetAttributeValue("UnderlyingType", underlyingTypeName);
            document.Descendants(XName.Get("Member", edmNamespace))
                    .First()
                    .SetAttributeValue("Value", value);

            var exception = Assert.Throws<MetadataException>(() => new EdmItemCollection(new[] { document.CreateReader() }));
            exception.ValidateMessage(typeof(DbContext).Assembly(), resourceKey, null, /*isExactMatch*/ false, exceptionParameters);
        }

        [Fact]
        public void Validation_fails_for_enums_with_SpecifiedValue_too_large_to_fit_in_Int64()
        {
            var value = ((long)Int64.MaxValue).ToString() + "1";
            RunUnderOverflowTestForExplicitlySpecifiedValues("Int64", value, "http://www.w3.org/2001/XMLSchema:long");
        }

        [Fact]
        public void Validation_fails_for_enums_with_SpecifiedValue_cannot_be_converted_to_Int64()
        {
            var value = "-1" + ((long)Int64.MaxValue).ToString();
            RunUnderOverflowTestForExplicitlySpecifiedValues("Int64", value, "http://www.w3.org/2001/XMLSchema:long");
        }

        private static void RunUnderOverflowTestForExplicitlySpecifiedValues(string underlyingTypeName, string value, string expectedException)
        {
            var document = XDocument.Parse(enumCsdl);
            var enumType = document.Descendants(XName.Get("EnumType", edmNamespace)).Single();
            enumType.SetAttributeValue("UnderlyingType", underlyingTypeName);
            document.Descendants(XName.Get("Member", edmNamespace))
                    .First()
                    .SetAttributeValue("Value", value);

            var exceptionMessage = Assert.Throws<MetadataException>(() => new EdmItemCollection(new[] { document.CreateReader() })).Message;
            Assert.True(exceptionMessage.Contains(expectedException));
        }

        [Fact]
        public void Validation_fails_for_enums_with_CalculatedValue_too_large_to_fit_in_Byte()
        {
            var value = (long)Byte.MaxValue;
            var exceptions = new List<KeyValuePair<string, object[]>>
            {
                new KeyValuePair<string, object[]>("EnumMemberValueOutOfItsUnderylingTypeRange", new object[] { value + 1, "Green", "Byte" }),
                new KeyValuePair<string, object[]>("EnumMemberValueOutOfItsUnderylingTypeRange", new object[] { value + 2, "Blue", "Byte" }),
            };

            RunOverflowTestsForCalculatedValues("Byte", value, exceptions);
        }

        [Fact]
        public void Validation_fails_for_enums_with_CalculatedValue_too_large_to_fit_in_SByte()
        {
            var value = (long)SByte.MaxValue;
            var exceptions = new List<KeyValuePair<string, object[]>>
            {
                new KeyValuePair<string, object[]>("EnumMemberValueOutOfItsUnderylingTypeRange", new object[] { value + 1, "Green", "SByte" }),
                new KeyValuePair<string, object[]>("EnumMemberValueOutOfItsUnderylingTypeRange", new object[] { value + 2, "Blue", "SByte" }),
            };

            RunOverflowTestsForCalculatedValues("SByte", value, exceptions);
        }

        [Fact]
        public void Validation_fails_for_enums_with_CalculatedValue_too_large_to_fit_in_Int16()
        {
            var value = (long)Int16.MaxValue;
            var exceptions = new List<KeyValuePair<string, object[]>>
            {
                new KeyValuePair<string, object[]>("EnumMemberValueOutOfItsUnderylingTypeRange", new object[] { value + 1, "Green", "Int16" }),
                new KeyValuePair<string, object[]>("EnumMemberValueOutOfItsUnderylingTypeRange", new object[] { value + 2, "Blue", "Int16" }),
            };

            RunOverflowTestsForCalculatedValues("Int16", value, exceptions);
        }

        [Fact]
        public void Validation_fails_for_enums_with_CalculatedValue_too_large_to_fit_in_Int32()
        {
            var value = (long)Int32.MaxValue;
            var exceptions = new List<KeyValuePair<string, object[]>>
            {
                new KeyValuePair<string, object[]>("EnumMemberValueOutOfItsUnderylingTypeRange", new object[] { value + 1, "Green", "Int32" }),
                new KeyValuePair<string, object[]>("EnumMemberValueOutOfItsUnderylingTypeRange", new object[] { value + 2, "Blue", "Int32" }),
            };

            RunOverflowTestsForCalculatedValues("Int32", value, exceptions);
        }

        private static void RunOverflowTestsForCalculatedValues(
            string underlyingTypeName, 
            long lastSpecifiedValue, 
            IEnumerable<KeyValuePair<string, object[]>> expectedExceptions)
        {
            var document = XDocument.Parse(enumCsdl);
            var enumType = document.Descendants(XName.Get("EnumType", edmNamespace)).Single();
            enumType.SetAttributeValue("UnderlyingType", underlyingTypeName);
            document.Descendants(XName.Get("Member", edmNamespace))
                    .First()
                    .SetAttributeValue("Value", lastSpecifiedValue);

            document.Descendants(XName.Get("Member", edmNamespace))
                    .Skip(1)
                    .Attributes("Value")
                    .Remove();

            var exception = Assert.Throws<MetadataException>(() => new EdmItemCollection(new[] { document.CreateReader() }));
            foreach (var expectedException in expectedExceptions)
            {
                exception.ValidateMessage(typeof(DbContext).Assembly(), expectedException.Key, null, /*isExactMatch*/ false, expectedException.Value);
            }
        }

        [Fact]
        public void DefaultValue_not_supported_for_enum_properties()
        {
            var csdl = @"<Schema xmlns=""http://schemas.microsoft.com/ado/2009/11/edm"" Namespace=""EnumTestModel"">
  <EntityType Name=""EnumTestEntity"">
    <Key>
      <PropertyRef Name=""Id"" />
    </Key>
    <Property Name=""Id"" Nullable=""false"" Type=""Int32"" />
    <Property Name=""Color"" Type=""EnumTestModel.Color"" Nullable=""false"" DefaultValue=""5""/>
  </EntityType>
  <EnumType Name=""Color"" IsFlags=""false"">
    <Member Name=""Yellow"" Value=""1"" />
  </EnumType>
</Schema>";

            var exception = Assert.Throws<MetadataException>(() => new EdmItemCollection(new[] { XmlReader.Create(new StringReader(csdl)) }));
            exception.ValidateMessage(typeof(DbContext).Assembly(), "DefaultNotAllowed", null);
        }
    }

    public class MetadataEnumFixture
    {
        public XmlSchemaSet CsdlSchemaSet { get; private set; }

        public MetadataEnumFixture()
        {
            CsdlSchemaSet = new XmlSchemaSet();

            foreach (var schemaName in new string[] { 
                    "System.Data.Resources.CSDLSchema_3.xsd",  
                    "System.Data.Resources.CodeGenerationSchema.xsd",
                    "System.Data.Resources.AnnotationSchema.xsd"})
            {
                this.CsdlSchemaSet.Add(XmlSchema.Read(
                    typeof(EntityContainer).Assembly().GetManifestResourceStream(schemaName),
                    (o, e) => { throw new InvalidOperationException("The built-in schema is invalid", e.Exception); }));
            }

            CsdlSchemaSet.Compile();
        }
    }
}
