diff --git a/src/Microsoft.OData.Core/EdmExtensionMethods.cs b/src/Microsoft.OData.Core/EdmExtensionMethods.cs
index 9a99491efb..a34da9b89d 100644
--- a/src/Microsoft.OData.Core/EdmExtensionMethods.cs
+++ b/src/Microsoft.OData.Core/EdmExtensionMethods.cs
@@ -7,6 +7,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq;
+using System.Text;
using Microsoft.OData.Edm;
using Microsoft.OData.UriParser;
@@ -164,5 +166,160 @@ public static bool ContainsMember(this IEdmEnumType enumType, string memberName,
return false;
}
+
+ ///
+ /// Determines whether the specified enum type contains a member with the given name, using the specified string comparison.
+ ///
+ /// The to search for the member.
+ /// The name of the member to locate within the enum type.
+ /// When this method returns, contains the that matches the specified name, if a match is found; otherwise, null.
+ /// The comparison type to use for string comparison. Default is Ordinal.
+ /// True if the member name exists in the enum type; otherwise, false.
+ public static bool ContainsMember(this IEdmEnumType enumType, string memberName, out IEdmEnumMember exactMemberName, StringComparison comparison = StringComparison.Ordinal)
+ {
+ foreach (IEdmEnumMember member in enumType.Members)
+ {
+ if (string.Equals(member.Name, memberName, comparison))
+ {
+ exactMemberName = member;
+ return true;
+ }
+ }
+
+ exactMemberName = null;
+ return false;
+ }
+
+ ///
+ /// Checks if the given member name exists in the enum type.
+ ///
+ /// The enum type to search for the member.
+ /// The name of the member to locate, represented as a of characters.
+ /// The to use when comparing the member names. The default is .
+ /// True if the member name exists in the enum type; otherwise, false.
+ public static bool ContainsMember(this IEdmEnumType enumType, ReadOnlySpan memberName, StringComparison comparison = StringComparison.Ordinal)
+ {
+ foreach (IEdmEnumMember member in enumType.Members)
+ {
+ if (member.Name.AsSpan().Equals(memberName, comparison))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Checks if the given member name exists in the enum type.
+ ///
+ /// The enum type to search for the member.
+ /// The name of the member to locate, represented as a of characters.
+ /// When this method returns, contains the that matches the specified name, if a match is found; otherwise, null.
+ /// The to use when comparing the member names. The default is .
+ /// True if the member name exists in the enum type; otherwise, false.
+ public static bool ContainsMember(this IEdmEnumType enumType, ReadOnlySpan memberName, out IEdmEnumMember exactMemberName, StringComparison comparison = StringComparison.Ordinal)
+ {
+ foreach (IEdmEnumMember member in enumType.Members)
+ {
+ if (member.Name.AsSpan().Equals(memberName, comparison))
+ {
+ exactMemberName = member;
+ return true;
+ }
+ }
+
+ exactMemberName = null;
+ return false;
+ }
+
+ ///
+ /// Parses the specified integral value into a comma-separated string of flag names based on the members of the given EDM enum type.
+ ///
+ /// The EDM enum type containing the members to parse.
+ /// The integral value to parse into flag names.
+ /// A comma-separated string of flag names corresponding to the set bits in the specified value. Returns null otherwise.
+ public static string ParseFlagsFromIntegralValue(this IEdmEnumType enumType, long value)
+ {
+ // Sort members by descending flag value to prioritize composite flags
+ var members = enumType.Members
+ .OrderByDescending(m => m.Value.Value);
+
+ var result = new List();
+ long remaining = value;
+
+ foreach (IEdmEnumMember member in members)
+ {
+ long flagValue = Convert.ToInt64(member.Value.Value);
+ if (flagValue != 0 && (remaining & flagValue) == flagValue)
+ {
+ result.Add(member.Name);
+ remaining &= ~flagValue; // Remove matched bits
+ }
+ }
+
+ return result.Count > 0 && remaining == 0 ? string.Join(", ", result.Reverse()) : null;
+ }
+
+ ///
+ /// Parses a comma-separated string of enum member names into a validated, formatted string containing only valid members of the specified .
+ ///
+ /// The EDM enum type to validate the enum member names against.
+ /// A comma-separated string containing the names of enum members to parse and validate.
+ /// The to use when comparing the provided member names against the enum type's
+ /// defined members.
+ /// A formatted string containing the validated enum member names, separated by commas and trimmed of whitespace. Otherwise, null or empty string.
+ public static string ParseFlagsFromStringValue(this IEdmEnumType enumType, string memberName, StringComparison comparison)
+ {
+ var stringBuilder = new StringBuilder();
+ int start = 0, end = 0;
+ while (end < memberName.Length)
+ {
+ while (end < memberName.Length && memberName[end] != ',')
+ {
+ end++;
+ }
+
+ ReadOnlySpan currentValue = memberName.AsSpan()[start..end].Trim();
+ if (!enumType.ContainsMember(currentValue, out IEdmEnumMember edmEnumMember, comparison))
+ {
+ return null;
+ }
+
+ if (stringBuilder.Length > 0)
+ {
+ stringBuilder.Append(", ");
+ }
+
+ stringBuilder.Append(edmEnumMember.Name);
+ start = end + 1;
+ end = start;
+ }
+
+ return stringBuilder.ToString();
+ }
+
+ ///
+ /// Determines whether the specified integral value is a valid combination of flags for the given enumeration type.
+ ///
+ /// The enumeration type to validate against. Must represent a flags enumeration.
+ /// The integral value to validate as a combination of flags.
+ /// if the specified value is a valid combination of the flags defined in the
+ /// enumeration; otherwise, .
+ public static bool IsValidFlagsEnumValue(this IEdmEnumType enumType, long memberIntegralValue)
+ {
+ if(enumType == null || !enumType.IsFlags)
+ {
+ return false;
+ }
+
+ int allFlagsMask = 0;
+ foreach (IEdmEnumMember member in enumType.Members)
+ {
+ allFlagsMask |= (int)member.Value.Value;
+ }
+
+ return (memberIntegralValue & ~allFlagsMask) == 0;
+ }
}
}
diff --git a/src/Microsoft.OData.Core/UriParser/Binders/InBinder.cs b/src/Microsoft.OData.Core/UriParser/Binders/InBinder.cs
index fbf1c8a075..ce84acdb1d 100644
--- a/src/Microsoft.OData.Core/UriParser/Binders/InBinder.cs
+++ b/src/Microsoft.OData.Core/UriParser/Binders/InBinder.cs
@@ -70,7 +70,7 @@ internal QueryNode BindInOperator(InToken inToken, BindingState state)
// Calls the MetadataBindingUtils.ConvertToTypeIfNeeded() method to convert the left operand to the same enum type as the right operand.
if ((!(right is CollectionConstantNode) && right.ItemType.IsEnum()) && (left.TypeReference != null && (left.TypeReference.IsString() || left.TypeReference.IsIntegral())))
{
- left = MetadataBindingUtils.ConvertToTypeIfNeeded(left, right.ItemType);
+ left = MetadataBindingUtils.ConvertToTypeIfNeeded(left, right.ItemType, this.resolver.EnableCaseInsensitive);
}
MetadataBindingUtils.VerifyCollectionNode(right, this.resolver.EnableCaseInsensitive);
@@ -157,6 +157,10 @@ private CollectionNode GetCollectionOperandFromToken(QueryToken queryToken, IEdm
// ==> ['1970-01-01T00:00:00Z', '1980-01-01T01:01:01+01:00']
bracketLiteralText = NormalizeDateTimeCollectionItems(bracketLiteralText);
}
+ else if (expectedType.Definition.AsElementType().TypeKind == EdmTypeKind.Enum)
+ {
+ bracketLiteralText = NormalizeEnumCollectionItems(bracketLiteralText, expectedTypeFullName);
+ }
}
object collection = ODataUriConversionUtils.ConvertFromCollectionValue(bracketLiteralText, model, expectedType);
@@ -425,6 +429,106 @@ private static string NormalizeDateTimeCollectionItems(string bracketLiteralText
return "[" + String.Join(",", items) + "]";
}
+ private static string NormalizeEnumCollectionItems(string bracketLiteralText, string expectedTypeFullName)
+ {
+ // Remove the '[' and ']' or '(' and ')' and trim the content
+ ReadOnlySpan normalizedText = bracketLiteralText.AsSpan(1, bracketLiteralText.Length - 2);
+
+ // Trim leading/trailing whitespace
+ int left = 0;
+ int right = normalizedText.Length - 1;
+ while (left <= right && char.IsWhiteSpace(normalizedText[left]))
+ {
+ left++;
+ }
+
+ while (right >= left && char.IsWhiteSpace(normalizedText[right]))
+ {
+ right--;
+ }
+
+ if (left > right)
+ {
+ return "[]";
+ }
+
+ int expectedTypeFullNameLen = string.IsNullOrEmpty(expectedTypeFullName) ? 0 : expectedTypeFullName.Length;
+ ReadOnlySpan content = normalizedText.Slice(left, right - left + 1);
+ int startIndex = 0;
+ int length = content.Length;
+
+ StringBuilder result = new StringBuilder(length + 2);
+ result.Append('[');
+
+ while (startIndex < length)
+ {
+ char currentChar = content[startIndex];
+
+ if (currentChar == '\'' || currentChar == '"')
+ {
+ // Handle quoted items
+ int relativeEnd = content.Slice(startIndex + 1).IndexOf(currentChar);
+ if (relativeEnd < 0)
+ {
+ throw new ODataException(Error.Format("Found unbalanced quotes '{0}'", currentChar));
+ }
+
+ // Include closing quote
+ int endIndex = startIndex + 1 + relativeEnd;
+ result.Append(content.Slice(startIndex, endIndex - startIndex + 1));
+ startIndex = endIndex + 1;
+ }
+ else if (currentChar == ',')
+ {
+ // Handle commas
+ result.Append(',');
+ startIndex++;
+ }
+ else if (char.IsWhiteSpace(currentChar))
+ {
+ // Skip whitespace
+ startIndex++;
+ }
+ else
+ {
+ // Handle non-quoted items
+ int end = startIndex;
+ while (end < length && content[end] != ',' && !char.IsWhiteSpace(content[end]))
+ {
+ end++;
+ }
+
+ ReadOnlySpan token = content[startIndex..end];
+ if (expectedTypeFullNameLen > 0 && token.Length > expectedTypeFullNameLen &&
+ token.StartsWith(expectedTypeFullName, StringComparison.Ordinal))
+ {
+ char next = token[expectedTypeFullNameLen];
+ if (next == '\'' || next == '\"')
+ {
+ token = token.Slice(expectedTypeFullNameLen);
+ }
+ }
+
+ // If item is already quoted, keep it; otherwise wrap in single quotes
+ if (token.Length > 0 && (token[0] == '\'' || token[0] == '"'))
+ {
+ result.Append(token);
+ }
+ else
+ {
+ result.Append('\'');
+ result.Append(token);
+ result.Append('\'');
+ }
+
+ startIndex = end;
+ }
+ }
+
+ result.Append(']');
+ return result.ToString();
+ }
+
private static bool IsCollectionEmptyOrWhiteSpace(string bracketLiteralText)
{
string content = bracketLiteralText[1..^1].Trim();
diff --git a/src/Microsoft.OData.Core/UriParser/Binders/MetadataBindingUtils.cs b/src/Microsoft.OData.Core/UriParser/Binders/MetadataBindingUtils.cs
index d817b415cf..0dfbe7ae40 100644
--- a/src/Microsoft.OData.Core/UriParser/Binders/MetadataBindingUtils.cs
+++ b/src/Microsoft.OData.Core/UriParser/Binders/MetadataBindingUtils.cs
@@ -8,11 +8,10 @@ namespace Microsoft.OData.UriParser
{
using System;
using System.Diagnostics;
- using System.Linq;
- using Microsoft.OData.Edm;
using Microsoft.OData;
- using Microsoft.OData.Metadata;
using Microsoft.OData.Core;
+ using Microsoft.OData.Edm;
+ using Microsoft.OData.Metadata;
///
/// Helper methods for metadata binding.
@@ -25,8 +24,9 @@ internal static class MetadataBindingUtils
///
/// The source node to apply the conversion to.
/// The target primitive type. May be null - this method will do nothing in that case.
+ /// Whether to enable case insensitive comparison for enum member names.
/// The converted query node, or the original source node unchanged.
- internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IEdmTypeReference targetTypeReference)
+ internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IEdmTypeReference targetTypeReference, bool enableCaseInsensitive = false)
{
Debug.Assert(source != null, "source != null");
@@ -62,19 +62,49 @@ internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IE
// and the target type is an enum.
if (constantNode != null && constantNode.Value != null && (source.TypeReference.IsString() || source.TypeReference.IsIntegral()) && targetTypeReference.IsEnum())
{
+ // String comparison for enum member names is case-sensitive by default.
+ StringComparison comparison = enableCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
+
string memberName = constantNode.Value.ToString();
IEdmEnumType enumType = targetTypeReference.Definition as IEdmEnumType;
- if (enumType.ContainsMember(memberName, StringComparison.Ordinal))
+
+ // If the member name is an integral value, we should try to convert it to the enum member name and find the enum member with the matching integral value
+ if (long.TryParse(memberName, out long memberIntegralValue))
+ {
+ if(enumType.TryParse(memberIntegralValue, out IEdmEnumMember enumMember))
+ {
+ string literalText = ODataUriUtils.ConvertToUriLiteral(enumMember.Name, default(ODataVersion));
+ return new ConstantNode(new ODataEnumValue(enumMember.Name, enumType.ToString()), literalText, targetTypeReference);
+ }
+
+ if(enumType.IsFlags)
+ {
+ string flagsValue = enumType.ParseFlagsFromIntegralValue(memberIntegralValue);
+ if(!string.IsNullOrEmpty(flagsValue))
+ {
+ string literalText = ODataUriUtils.ConvertToUriLiteral(flagsValue, default(ODataVersion));
+ return new ConstantNode(new ODataEnumValue(flagsValue, enumType.ToString()), literalText, targetTypeReference);
+ }
+ }
+ }
+
+ // Check if the member name is a valid enum member name
+ if (enumType.ContainsMember(memberName, out IEdmEnumMember edmEnumMember, comparison))
{
string literalText = ODataUriUtils.ConvertToUriLiteral(constantNode.Value, default(ODataVersion));
- return new ConstantNode(new ODataEnumValue(memberName, enumType.ToString()), literalText, targetTypeReference);
+ return new ConstantNode(new ODataEnumValue(edmEnumMember.Name, enumType.ToString()), literalText, targetTypeReference);
}
- // If the member name is an integral value, we should try to convert it to the enum member name and find the enum member with the matching integral value
- if (long.TryParse(memberName, out long memberIntegralValue) && enumType.TryParse(memberIntegralValue, out IEdmEnumMember enumMember))
+ // If the member name is a string representation of a flags value,
+ // we should try to convert it to the enum member name and find the enum member with the matching flags value
+ if (enumType.IsFlags)
{
- string literalText = ODataUriUtils.ConvertToUriLiteral(enumMember.Name, default(ODataVersion));
- return new ConstantNode(new ODataEnumValue(enumMember.Name, enumType.ToString()), literalText, targetTypeReference);
+ string flagsValue = enumType.ParseFlagsFromStringValue(memberName, comparison);
+ if (!string.IsNullOrEmpty(flagsValue))
+ {
+ string literalText = ODataUriUtils.ConvertToUriLiteral(flagsValue, default(ODataVersion));
+ return new ConstantNode(new ODataEnumValue(flagsValue, enumType.ToString()), literalText, targetTypeReference);
+ }
}
throw new ODataException(Error.Format(SRResources.Binder_IsNotValidEnumConstant, memberName));
@@ -195,16 +225,39 @@ internal static void VerifyCollectionNode(CollectionNode node, bool enableCaseIn
}
IEdmEnumType enumType = collectionConstantNode.ItemType.Definition as IEdmEnumType;
-
StringComparison comparison = enableCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
+
foreach (ConstantNode item in collectionConstantNode.Collection)
{
if (item != null && item.Value != null && item.Value is ODataEnumValue enumValue)
{
- if (!enumType.ContainsMember(enumValue.Value, comparison))
+ // Check if the enum value is a valid enum member name
+ if (enumType.ContainsMember(enumValue.Value, comparison))
{
- throw new ODataException(Error.Format(SRResources.Binder_IsNotValidEnumConstant, enumValue.Value));
+ continue;
}
+
+ if (long.TryParse(enumValue.Value, out long memberIntegralValue))
+ {
+ // Check if the enum value is a valid integral value
+ if (enumType.TryParse(memberIntegralValue, out IEdmEnumMember _))
+ {
+ continue;
+ }
+
+ // Check if the enum value is a valid flags value
+ if (enumType.IsFlags && enumType.IsValidFlagsEnumValue(memberIntegralValue))
+ {
+ continue;
+ }
+ }
+
+ if (enumType.IsFlags && !string.IsNullOrEmpty(enumType.ParseFlagsFromStringValue(enumValue.Value, comparison)))
+ {
+ continue;
+ }
+
+ throw new ODataException(Error.Format(SRResources.Binder_IsNotValidEnumConstant, enumValue.Value));
}
}
}
diff --git a/test/EndToEndTests/Common/Microsoft.OData.E2E.TestCommon/Common/Server/Asynchronous/AsynchronousDelayQueryTestsController.cs b/test/EndToEndTests/Common/Microsoft.OData.E2E.TestCommon/Common/Server/Asynchronous/AsynchronousDelayQueryTestsController.cs
index 4cc5f2b53f..56a6d3277c 100644
--- a/test/EndToEndTests/Common/Microsoft.OData.E2E.TestCommon/Common/Server/Asynchronous/AsynchronousDelayQueryTestsController.cs
+++ b/test/EndToEndTests/Common/Microsoft.OData.E2E.TestCommon/Common/Server/Asynchronous/AsynchronousDelayQueryTestsController.cs
@@ -119,7 +119,7 @@ public IActionResult GetAllProducts([FromODataUri] int start, [FromODataUri] int
[HttpGet("odata/GetProductsByAccessLevel(accessLevel={accessLevel})")]
public IActionResult GetProductsByAccessLevel([FromODataUri] AccessLevel accessLevel)
{
- var count = _dataSource.Products?.Where(p => (p.UserAccess & accessLevel) == accessLevel).Count();
+ var count = _dataSource.Products?.Where(p => p.UserAccess == accessLevel).Count();
return Ok((double)count);
}
diff --git a/test/EndToEndTests/Common/Microsoft.OData.E2E.TestCommon/Common/Server/Default/DefaultDataSource.cs b/test/EndToEndTests/Common/Microsoft.OData.E2E.TestCommon/Common/Server/Default/DefaultDataSource.cs
index 7736c8ec79..3e677a903a 100644
--- a/test/EndToEndTests/Common/Microsoft.OData.E2E.TestCommon/Common/Server/Default/DefaultDataSource.cs
+++ b/test/EndToEndTests/Common/Microsoft.OData.E2E.TestCommon/Common/Server/Default/DefaultDataSource.cs
@@ -327,7 +327,7 @@ public void Initialize()
Discontinued = false,
SkinColor = Color.Red,
CoverColors = [Color.Red, Color.Red, Color.Blue],
- UserAccess = AccessLevel.Execute,
+ UserAccess = AccessLevel.Write | AccessLevel.Execute | AccessLevel.Read,
},
new Product()
{
diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/AsynchronousTests/AsynchronousDelayQueryTests.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/AsynchronousTests/AsynchronousDelayQueryTests.cs
index f77c177ae6..c01b33ef65 100644
--- a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/AsynchronousTests/AsynchronousDelayQueryTests.cs
+++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/AsynchronousTests/AsynchronousDelayQueryTests.cs
@@ -260,6 +260,11 @@ public async Task DelayQuery_OnFunctionImport_Test()
var productsCount = await productsQuery.GetValueAsync();
Assert.EndsWith("/GetProductsByAccessLevel(accessLevel=Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'Read%2CExecute')", productsQuery.RequestUri.AbsoluteUri);
Assert.Equal(1, productsCount);
+
+ productsQuery = context.GetProductsByAccessLevel(AccessLevel.Execute | AccessLevel.Read);
+ productsCount = await productsQuery.GetValueAsync();
+ Assert.EndsWith("/GetProductsByAccessLevel(accessLevel=Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'Read%2CExecute')", productsQuery.RequestUri.AbsoluteUri);
+ Assert.Equal(1, productsCount);
}
[Fact]
diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/EnumerationTypeTests/EnumerationTypeQueryTests.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/EnumerationTypeTests/EnumerationTypeQueryTests.cs
index 8633b84358..0dff65b214 100644
--- a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/EnumerationTypeTests/EnumerationTypeQueryTests.cs
+++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/EnumerationTypeTests/EnumerationTypeQueryTests.cs
@@ -90,7 +90,8 @@ public void QueryEntityUsingODataClientAndVerifyEnumProperties()
var result = queryable.GetValue();
Assert.NotNull(result);
Assert.Equal(Color.Red, result.SkinColor);
- Assert.Equal(AccessLevel.Execute, result.UserAccess);
+ Assert.Equal((AccessLevel.Write | AccessLevel.Execute | AccessLevel.Read), result.UserAccess);
+ Assert.Equal((AccessLevel.ReadWrite | AccessLevel.Execute), result.UserAccess);
}
[Fact]
@@ -103,7 +104,8 @@ public void QueryEnumPropertyUsingODataClientAndVerifyValue()
var userAccess = _context.Execute(new Uri(_baseUri.AbsoluteUri + "Products(8)/UserAccess"));
List enumResult = userAccess.ToList();
Assert.Single(enumResult);
- Assert.Equal(AccessLevel.Execute, enumResult[0]);
+ Assert.Equal((AccessLevel.Write | AccessLevel.Execute | AccessLevel.Read), enumResult[0]);
+ Assert.Equal((AccessLevel.ReadWrite | AccessLevel.Execute), enumResult[0]);
}
[Fact]
@@ -135,7 +137,8 @@ public void QueryEntitiesWithQueryOptionsUsingODataClientAndVerifyResults()
Assert.True(result.All(s => s.SkinColor == Color.Red));
Assert.NotNull(result.SingleOrDefault(r => r.ProductID == 5 && r.UserAccess == AccessLevel.None));
Assert.NotNull(result.SingleOrDefault(r => r.ProductID == 7 && r.UserAccess == AccessLevel.Read));
- Assert.NotNull(result.SingleOrDefault(r => r.ProductID == 8 && r.UserAccess == AccessLevel.Execute));
+ Assert.NotNull(result.SingleOrDefault(r => r.ProductID == 8 &&
+ (r.UserAccess == (AccessLevel.Write | AccessLevel.Execute | AccessLevel.Read) || r.UserAccess == (AccessLevel.ReadWrite | AccessLevel.Execute))));
// FullMetadata: UseJson() + have $select in request uri
_context.Format.UseJson(_model);
@@ -149,7 +152,8 @@ public void QueryEntitiesWithQueryOptionsUsingODataClientAndVerifyResults()
Assert.True(result.All(s => s.SkinColor == Color.Red));
Assert.NotNull(result.SingleOrDefault(r => r.ProductID == 5 && r.UserAccess == AccessLevel.None));
Assert.NotNull(result.SingleOrDefault(r => r.ProductID == 7 && r.UserAccess == AccessLevel.Read));
- Assert.NotNull(result.SingleOrDefault(r => r.ProductID == 8 && r.UserAccess == AccessLevel.Execute));
+ Assert.NotNull(result.SingleOrDefault(r => r.ProductID == 8 &&
+ (r.UserAccess == (AccessLevel.Write | AccessLevel.Execute | AccessLevel.Read) || r.UserAccess == (AccessLevel.ReadWrite | AccessLevel.Execute))));
// Atom
queryable = _context.CreateQuery("Products")
@@ -159,7 +163,7 @@ public void QueryEntitiesWithQueryOptionsUsingODataClientAndVerifyResults()
result = queryable.ToList();
Assert.Equal(result.Select(s => s.ProductID).Distinct().Count(), result.Count);
Assert.True(result.All(s => s.SkinColor == Color.Red));
- Assert.Contains(result, s => s.UserAccess == AccessLevel.Execute);
+ Assert.Contains(result, s => (s.UserAccess == (AccessLevel.Write | AccessLevel.Execute | AccessLevel.Read) || s.UserAccess == (AccessLevel.ReadWrite | AccessLevel.Execute)));
Assert.Contains(result, s => s.UserAccess == AccessLevel.Read);
}
@@ -345,6 +349,129 @@ public void QueryEntitiesAndSelectMultiplePropertiesUsingODataClientAndVerifyRes
}
}
+ [Fact]
+ public void QueryEntitiesWithCombinedFlagStringFormatUsingODataClient()
+ {
+ // Arrange
+ _context.Format.UseJson(_model);
+
+ // Act
+ var queryable = _context.Products
+ .Where(p => p.UserAccess == AccessLevel.ReadWrite) as DataServiceQuery;
+ Assert.NotNull(queryable);
+ var result = queryable.ToList();
+
+ // Assert
+ Assert.EndsWith("/Products?$filter=UserAccess eq Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'ReadWrite'", queryable.RequestUri.OriginalString);
+ Assert.All(result, p => Assert.Equal(AccessLevel.ReadWrite, p.UserAccess));
+ }
+
+ [Fact]
+ public void QueryEntitiesWithCombinedFlagNumericRepresentationUsingODataClient()
+ {
+ // Arrange
+ _context.Format.UseJson(_model);
+
+ // Act
+ var queryable = _context.Products
+ .Where(p => (int)p.UserAccess == 3) as DataServiceQuery;
+ Assert.NotNull(queryable);
+ var result = queryable.ToList();
+
+ // Assert
+ Assert.EndsWith("/Products?$filter=UserAccess eq Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'ReadWrite'", queryable.RequestUri.OriginalString);
+ Assert.All(result, p => Assert.Equal(AccessLevel.ReadWrite, p.UserAccess));
+ }
+
+ [Fact]
+ public void QueryEntitiesWithHasOperatorForCombinedFlagsUsingODataClient()
+ {
+ // Arrange
+ _context.Format.UseJson(_model);
+
+ // Act
+ var queryable = _context.Products
+ .Where(p => p.UserAccess.Value.HasFlag(AccessLevel.Read) && p.UserAccess.Value.HasFlag(AccessLevel.Write)) as DataServiceQuery;
+ Assert.NotNull(queryable);
+ var result = queryable.ToList();
+
+ // Assert
+ Assert.EndsWith("/Products?$filter=UserAccess has Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'Read' and UserAccess has Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'Write'", queryable.RequestUri.OriginalString);
+ Assert.All(result, p => Assert.True(p.UserAccess.Value.HasFlag(AccessLevel.Read) && p.UserAccess.Value.HasFlag(AccessLevel.Write)));
+ }
+
+ [Fact]
+ public void QueryEntitiesWithInOperatorForFlagsUsingODataClient()
+ {
+ // Arrange
+ _context.Format.UseJson(_model);
+
+ // Act
+ var queryable = _context.Products
+ .Where(p => new[] { AccessLevel.Read, AccessLevel.Write, AccessLevel.ReadWrite }.Contains(p.UserAccess.Value)) as DataServiceQuery;
+ Assert.NotNull(queryable);
+ var result = queryable.ToList();
+
+ // Assert
+ Assert.EndsWith("/Products?$filter=UserAccess in ('Read','Write','ReadWrite')", queryable.RequestUri.OriginalString);
+ Assert.All(result, p => new[] { AccessLevel.Read, AccessLevel.Write, AccessLevel.ReadWrite }.Contains(p.UserAccess.Value));
+ }
+
+ [Fact]
+ public void QueryEntitiesWithCombinedFlagNumericEqualityUsingODataClient()
+ {
+ // Arrange
+ _context.Format.UseJson(_model);
+
+ // Act
+ // ReadWrite = 3
+ var queryable = _context.Products
+ .Where(p => (int)p.UserAccess == 3) as DataServiceQuery;
+ Assert.NotNull(queryable);
+ var result = queryable.ToList();
+
+ // Assert
+ Assert.EndsWith("/Products?$filter=UserAccess eq Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'ReadWrite'", queryable.RequestUri.OriginalString);
+ Assert.All(result, p => Assert.Equal(AccessLevel.ReadWrite, p.UserAccess));
+ }
+
+ [Fact]
+ public void QueryEntitiesWithCombinedFlagNumericGreaterThanUsingODataClient()
+ {
+ // Arrange
+ _context.Format.UseJson(_model);
+
+ // Act
+ // Execute = 4, so this will match Execute and any higher value if present
+ var queryable = _context.Products
+ .Where(p => (int)p.UserAccess > 3) as DataServiceQuery;
+ Assert.NotNull(queryable);
+ var result = queryable.ToList();
+
+ // Assert
+ Assert.EndsWith("/Products?$filter=UserAccess gt Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'ReadWrite'", queryable.RequestUri.OriginalString);
+ Assert.All(result, p => Assert.True((int)p.UserAccess > 3));
+ }
+
+ [Fact]
+ public void QueryEntitiesWithCombinedFlagNumericInOperatorUsingODataClient()
+ {
+ // Arrange
+ _context.Format.UseJson(_model);
+
+ // Act
+ // None = 0, Read = 1, Write = 2, ReadWrite = 3, Execute = 4
+ var validValues = new[] { 0, 3, 4 };
+ var queryable = _context.Products
+ .Where(p => validValues.Contains((int)p.UserAccess)) as DataServiceQuery;
+ Assert.NotNull(queryable);
+ var result = queryable.ToList();
+
+ // Assert
+ Assert.EndsWith("/Products?$filter=UserAccess in (0,3,4)", queryable.RequestUri.OriginalString, StringComparison.Ordinal);
+ Assert.All(result, p => Assert.Contains((int)p.UserAccess, validValues));
+ }
+
#region Private methods
private void ResetDefaultDataSource()
diff --git a/test/EndToEndTests/Tests/Core/Microsoft.OData.Core.E2E.Tests/EnumerationTypeTests/EnumerationTypeQueryTests.cs b/test/EndToEndTests/Tests/Core/Microsoft.OData.Core.E2E.Tests/EnumerationTypeTests/EnumerationTypeQueryTests.cs
index 54a3042237..6d2f98f5ff 100644
--- a/test/EndToEndTests/Tests/Core/Microsoft.OData.Core.E2E.Tests/EnumerationTypeTests/EnumerationTypeQueryTests.cs
+++ b/test/EndToEndTests/Tests/Core/Microsoft.OData.Core.E2E.Tests/EnumerationTypeTests/EnumerationTypeQueryTests.cs
@@ -58,8 +58,6 @@ public EnumerationTypeQueryTests(TestWebApplicationFactory fixture
private static string NameSpacePrefix = typeof(DefaultEdmModel).Namespace ?? "Microsoft.OData.E2E.TestCommon.Common.Server.Default";
// Constants
- private const string MimeTypeODataParameterFullMetadata = MimeTypes.ApplicationJson + MimeTypes.ODataParameterFullMetadata;
- private const string MimeTypeODataParameterMinimalMetadata = MimeTypes.ApplicationJson + MimeTypes.ODataParameterMinimalMetadata;
private const string MimeTypeApplicationAtomXml = MimeTypes.ApplicationAtomXml;
#region Tests querying entity set and verifies the enumeration type properties
@@ -264,7 +262,7 @@ public async Task QueryEntitiesWithEnumFilterAndVerifyResults(string mimeType)
List entries = await TestsHelper.QueryResourceSetsAsync(queryText, mimeType);
// Assert
- Assert.Equal(3, entries.Count);
+ Assert.Equal(4, entries.Count);
var entity0 = entries[0].Properties.ToDictionary(p => p.Name, p => p as ODataProperty);
Assert.Equal(6, entity0["ProductID"]?.Value);
@@ -279,10 +277,16 @@ public async Task QueryEntitiesWithEnumFilterAndVerifyResults(string mimeType)
Assert.Equal("Read", (entity1["UserAccess"]?.Value as ODataEnumValue)?.Value);
var entity2 = entries[2].Properties.ToDictionary(p => p.Name, p => p as ODataProperty);
- Assert.Equal(9, entity2["ProductID"]?.Value);
- Assert.Equal("Computer", entity2["Name"]?.Value);
- Assert.Equal("Green", (entity2["SkinColor"]?.Value as ODataEnumValue)?.Value);
- Assert.Equal("Read", (entity2["UserAccess"]?.Value as ODataEnumValue)?.Value);
+ Assert.Equal(8, entity2["ProductID"]?.Value);
+ Assert.Equal("Car", entity2["Name"]?.Value);
+ Assert.Equal("Red", (entity2["SkinColor"]?.Value as ODataEnumValue)?.Value);
+ Assert.Equal("ReadWrite, Execute", (entity2["UserAccess"]?.Value as ODataEnumValue)?.Value);
+
+ var entity3 = entries[3].Properties.ToDictionary(p => p.Name, p => p as ODataProperty);
+ Assert.Equal(9, entity3["ProductID"]?.Value);
+ Assert.Equal("Computer", entity3["Name"]?.Value);
+ Assert.Equal("Green", (entity3["SkinColor"]?.Value as ODataEnumValue)?.Value);
+ Assert.Equal("Read", (entity3["UserAccess"]?.Value as ODataEnumValue)?.Value);
}
#endregion
@@ -336,6 +340,217 @@ public async Task QueryEntitiesAndSelectEnumProperties(string mimeType)
#endregion
+ #region Tests Flags Enums and verifies the results.
+
+ [Theory]
+ [InlineData("UserAccess has Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'Read'", 4)]
+ [InlineData("UserAccess has Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'Execute'", 1)]
+ [InlineData("UserAccess has Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'ReadWrite'", 2)]
+ [InlineData("UserAccess has Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'Read, Write'", 2)]
+ [InlineData("UserAccess has Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'Read, Write, Execute'", 1)]
+ public async Task QueryFlagsEnumAndVerifyResults_WithHasOperator(string filter, int expectedCount)
+ {
+ // Arrange
+ var queryText = $"Products?$filter={filter}";
+
+ // Act
+ List entries = await TestsHelper.QueryResourceSetsAsync(queryText, MimeTypeODataParameterFullMetadata);
+
+ // Assert
+ Assert.Equal(expectedCount, entries.Count);
+ }
+
+ [Theory]
+ [InlineData("UserAccess in (0,3,4)", new object[] { AccessLevel.None, AccessLevel.ReadWrite })]
+ [InlineData("UserAccess in ('1',3,'4')", new object[] { AccessLevel.Read, AccessLevel.ReadWrite, AccessLevel.Read })]
+ [InlineData("UserAccess in ('Read', 'ReadWrite','Execute')", new object[] { AccessLevel.Read, AccessLevel.ReadWrite, AccessLevel.Read })]
+ [InlineData("UserAccess in (Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'Read', Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'ReadWrite')", new object[] { AccessLevel.Read, AccessLevel.ReadWrite, AccessLevel.Read })]
+ [InlineData("UserAccess in ( Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'Read', Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'ReadWrite')", new object[] { AccessLevel.Read, AccessLevel.ReadWrite, AccessLevel.Read })]
+ [InlineData("UserAccess in ('Read', 'Read, Write, Execute','Execute')", new object[] { AccessLevel.Read, AccessLevel.ReadWrite | AccessLevel.Execute, AccessLevel.Read })]
+ [InlineData("UserAccess in ('Read', 'Read, Write, Execute','Execute')", new object[] { AccessLevel.Read, AccessLevel.ReadWrite | AccessLevel.Execute, AccessLevel.Read })]
+ [InlineData("UserAccess in ('None', 'ReadWrite, Execute','Execute')", new object[] { AccessLevel.None, AccessLevel.ReadWrite | AccessLevel.Execute })]
+ [InlineData("UserAccess in ('None', ' ReadWrite, Execute', 'Execute')", new object[] { AccessLevel.None, AccessLevel.ReadWrite | AccessLevel.Execute })]
+ [InlineData("UserAccess in ('Read', 'ReadWrite, Execute')", new object[] { AccessLevel.Read, AccessLevel.ReadWrite | AccessLevel.Execute, AccessLevel.Read })]
+ public async Task QueryFlagsEnumAndVerifyResults_WithInOperator(string filter, object[] expectedUserAccess)
+ {
+ // Arrange
+ var queryText = $"Products?$filter={filter}";
+
+ // Act
+ List entries = await TestsHelper.QueryResourceSetsAsync(queryText, MimeTypeODataParameterMinimalMetadata);
+
+ // Assert
+ Assert.Equal(expectedUserAccess.Length, entries.Count);
+
+ var userAccessValues = entries.Select(e => e.Properties.Single(p => p.Name == "UserAccess") as ODataProperty).Select(p => (p.Value as ODataEnumValue)?.Value);
+ Assert.All(expectedUserAccess, ua => Assert.Contains(ua.ToString(), userAccessValues));
+ }
+
+ [Fact]
+ public async Task QueryFlagsEnumAndVerifyResults_InOperator_StringNumbers()
+ {
+ var filter = @"UserAccess in (""1"",3,""4"")";
+ var expectedUserAccess = new object[] { AccessLevel.Read, AccessLevel.ReadWrite, AccessLevel.Read };
+
+ var queryText = $"Products?$filter={filter}";
+ List entries = await TestsHelper.QueryResourceSetsAsync(queryText, MimeTypeODataParameterMinimalMetadata);
+
+ Assert.Equal(expectedUserAccess.Length, entries.Count);
+ var userAccessValues = entries.Select(e => e.Properties.Single(p => p.Name == "UserAccess") as ODataProperty)
+ .Select(p => (p.Value as ODataEnumValue)?.Value);
+ Assert.All(expectedUserAccess, ua => Assert.Contains(ua.ToString(), userAccessValues));
+ }
+
+ [Fact]
+ public async Task QueryFlagsEnumAndVerifyResults_InOperator_WorksWithMixedDoubleAndSingleQuotes()
+ {
+ var filter = @"UserAccess in (""Read"", 'ReadWrite',""Execute"")";
+ var expectedUserAccess = new object[] { AccessLevel.Read, AccessLevel.ReadWrite, AccessLevel.Read };
+
+ var queryText = $"Products?$filter={filter}";
+ List entries = await TestsHelper.QueryResourceSetsAsync(queryText, MimeTypeODataParameterMinimalMetadata);
+
+ Assert.Equal(expectedUserAccess.Length, entries.Count);
+ var userAccessValues = entries.Select(e => e.Properties.Single(p => p.Name == "UserAccess") as ODataProperty)
+ .Select(p => (p.Value as ODataEnumValue)?.Value);
+ Assert.All(expectedUserAccess, ua => Assert.Contains(ua.ToString(), userAccessValues));
+ }
+
+ [Fact]
+ public async Task QueryFlagsEnumAndVerifyResults_InOperator_FullQualifiedNameWorksWithDoubleQuotes()
+ {
+ var filter = @"UserAccess in (Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel""Read"", Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel""ReadWrite"")";
+ var expectedUserAccess = new object[] { AccessLevel.Read, AccessLevel.ReadWrite, AccessLevel.Read };
+
+ var queryText = $"Products?$filter={filter}";
+ List entries = await TestsHelper.QueryResourceSetsAsync(queryText, MimeTypeODataParameterMinimalMetadata);
+
+ Assert.Equal(expectedUserAccess.Length, entries.Count);
+ var userAccessValues = entries.Select(e => e.Properties.Single(p => p.Name == "UserAccess") as ODataProperty)
+ .Select(p => (p.Value as ODataEnumValue)?.Value);
+ Assert.All(expectedUserAccess, ua => Assert.Contains(ua.ToString(), userAccessValues));
+ }
+
+ [Fact]
+ public async Task QueryFlagsEnumAndVerifyResults_InOperator_WorksWithDoubleQuotes()
+ {
+ var filter = @"UserAccess in (""None"", "" ReadWrite, Execute"", ""Execute"")";
+ var expectedUserAccess = new object[] { AccessLevel.None, AccessLevel.ReadWrite | AccessLevel.Execute };
+
+ var queryText = $"Products?$filter={filter}";
+ List entries = await TestsHelper.QueryResourceSetsAsync(queryText, MimeTypeODataParameterMinimalMetadata);
+
+ Assert.Equal(expectedUserAccess.Length, entries.Count);
+ var userAccessValues = entries.Select(e => e.Properties.Single(p => p.Name == "UserAccess") as ODataProperty)
+ .Select(p => (p.Value as ODataEnumValue)?.Value);
+ Assert.All(expectedUserAccess, ua => Assert.Contains(ua.ToString(), userAccessValues));
+ }
+
+ [Fact]
+ public async Task QueryFlagsEnumAndVerifyResults_InOperator_MissingDoubleQuote1()
+ {
+ var filter = @"UserAccess in ('Read', ""ReadWrite, Execute)";
+ var queryText = $"Products?$filter={filter}";
+ ODataMessageReaderSettings readerSettings = new() { BaseUri = _baseUri };
+ var requestUrl = new Uri(_baseUri.AbsoluteUri + queryText, UriKind.Absolute);
+
+ var requestMessage = new TestHttpClientRequestMessage(requestUrl, Client);
+ requestMessage.SetHeader("Accept", MimeTypeODataParameterMinimalMetadata);
+
+ var responseMessage = await requestMessage.GetResponseAsync();
+
+ Assert.Equal(400, responseMessage.StatusCode);
+ }
+
+ [Fact]
+ public async Task QueryFlagsEnumAndVerifyResults_InOperator_MissingDoubleQuote2()
+ {
+ var filter = @"UserAccess in (""Read, ""ReadWrite, Execute"")";
+ var queryText = $"Products?$filter={filter}";
+ ODataMessageReaderSettings readerSettings = new() { BaseUri = _baseUri };
+ var requestUrl = new Uri(_baseUri.AbsoluteUri + queryText, UriKind.Absolute);
+
+ var requestMessage = new TestHttpClientRequestMessage(requestUrl, Client);
+ requestMessage.SetHeader("Accept", MimeTypeODataParameterMinimalMetadata);
+
+ var responseMessage = await requestMessage.GetResponseAsync();
+
+ Assert.Equal(400, responseMessage.StatusCode);
+ }
+
+ [Fact]
+ public async Task QueryFlagsEnumAndVerifyResults_InOperator_MissingSingleQuote1()
+ {
+ var filter = @"UserAccess in ('Read, 'ReadWrite, Execute')";
+ var queryText = $"Products?$filter={filter}";
+ ODataMessageReaderSettings readerSettings = new() { BaseUri = _baseUri };
+ var requestUrl = new Uri(_baseUri.AbsoluteUri + queryText, UriKind.Absolute);
+
+ var requestMessage = new TestHttpClientRequestMessage(requestUrl, Client);
+ requestMessage.SetHeader("Accept", MimeTypeODataParameterMinimalMetadata);
+
+ var responseMessage = await requestMessage.GetResponseAsync();
+
+ Assert.Equal(400, responseMessage.StatusCode);
+ }
+
+ [Fact]
+ public async Task QueryFlagsEnumAndVerifyResults_InOperator_MissingSingleQuote2()
+ {
+ var filter = @"UserAccess in ('Read', ReadWrite, Execute')";
+ var queryText = $"Products?$filter={filter}";
+ ODataMessageReaderSettings readerSettings = new() { BaseUri = _baseUri };
+ var requestUrl = new Uri(_baseUri.AbsoluteUri + queryText, UriKind.Absolute);
+
+ var requestMessage = new TestHttpClientRequestMessage(requestUrl, Client);
+ requestMessage.SetHeader("Accept", MimeTypeODataParameterMinimalMetadata);
+
+ var responseMessage = await requestMessage.GetResponseAsync();
+
+ Assert.Equal(400, responseMessage.StatusCode);
+ }
+
+ [Fact]
+ public async Task QueryFlagsEnumAndVerifyResults_InOperator_MissingSingleQuote3()
+ {
+ var filter = @"UserAccess in (Read', ReadWrite, Execute')";
+ var queryText = $"Products?$filter={filter}";
+ ODataMessageReaderSettings readerSettings = new() { BaseUri = _baseUri };
+ var requestUrl = new Uri(_baseUri.AbsoluteUri + queryText, UriKind.Absolute);
+
+ var requestMessage = new TestHttpClientRequestMessage(requestUrl, Client);
+ requestMessage.SetHeader("Accept", MimeTypeODataParameterMinimalMetadata);
+
+ var responseMessage = await requestMessage.GetResponseAsync();
+
+ Assert.Equal(400, responseMessage.StatusCode);
+ }
+
+ [Theory]
+ [InlineData("UserAccess eq 3", 1)]
+ [InlineData("UserAccess eq '3'", 1)]
+ [InlineData("UserAccess eq 'ReadWrite'", 1)]
+ [InlineData("UserAccess eq Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'ReadWrite'", 1)]
+ [InlineData("UserAccess ne 1", 3)]
+ [InlineData("UserAccess ne 'Read'", 3)]
+ [InlineData("UserAccess ne Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'Read'", 3)]
+ [InlineData("UserAccess ne 0", 4)]
+ [InlineData("UserAccess ne 'None'", 4)]
+ [InlineData("UserAccess ne Microsoft.OData.E2E.TestCommon.Common.Server.Default.AccessLevel'None'", 4)]
+ public async Task QueryFlagsEnumAndVerifyResults_WithSeveralOperators(string filter, int expectedCount)
+ {
+ // Arrange
+ var queryText = $"Products?$filter={filter}";
+
+ // Act
+ List entries = await TestsHelper.QueryResourceSetsAsync(queryText, MimeTypeODataParameterMinimalMetadata);
+
+ // Assert
+ Assert.Equal(expectedCount, entries.Count);
+ }
+
+ #endregion
+
#region Private methods
private EnumerationTypeQueryTestsHelper TestsHelper
{
diff --git a/test/UnitTests/Microsoft.OData.Core.Tests/UriParser/Binders/MetadataBindingUtilsTests.cs b/test/UnitTests/Microsoft.OData.Core.Tests/UriParser/Binders/MetadataBindingUtilsTests.cs
index b3c6097082..41d81d4719 100644
--- a/test/UnitTests/Microsoft.OData.Core.Tests/UriParser/Binders/MetadataBindingUtilsTests.cs
+++ b/test/UnitTests/Microsoft.OData.Core.Tests/UriParser/Binders/MetadataBindingUtilsTests.cs
@@ -214,6 +214,85 @@ public void IfTypePromotionNeeded_SourceIsFloatMemberValuesInStringAndTargetIsEn
}
}
+ [Theory]
+ [InlineData("FullTime")]
+ [InlineData("PartTime")]
+ [InlineData("Contractor")]
+ [InlineData("Temporary")]
+ [InlineData("FullTime, PartTime")]
+ [InlineData("FullTime, Temporary")]
+ [InlineData("Permanent, Temporary")]
+ [InlineData("Intern, Temporary")]
+ [InlineData("Permanent")]
+ [InlineData("PartTime, Contractor, Temporary")]
+ public void IfTypePromotionNeeded_SourceIsFlagsCompositeMemberNameOrComposite_ConstantNodeIsCreated(string enumValue)
+ {
+ // Arrange
+ SingleValueNode source = new ConstantNode(enumValue);
+ IEdmTypeReference targetTypeReference = new EdmEnumTypeReference(EmployeeTypeWithFlags, false);
+ // Act
+ ConstantNode result = MetadataBindingUtils.ConvertToTypeIfNeeded(source, targetTypeReference) as ConstantNode;
+
+ // Assert
+ result.ShouldBeEnumNode(EmployeeTypeWithFlags, enumValue);
+ Assert.Equal(enumValue, result.Value.ToString());
+ }
+
+ [Theory]
+ [InlineData("fulltime", "FullTime")]
+ [InlineData("FULLTIME", "FullTime")]
+ [InlineData("parttime", "PartTime")]
+ [InlineData("PARTTIME", "PartTime")]
+ [InlineData("contractor", "Contractor")]
+ [InlineData("temporary", "Temporary")]
+ [InlineData("fulltime, parttime", "FullTime, PartTime")]
+ [InlineData("fulltime, temporary", "FullTime, Temporary")]
+ [InlineData("permanent, temporary", "Permanent, Temporary")]
+ [InlineData("intern, temporary", "Intern, Temporary")]
+ [InlineData("permanent", "Permanent")]
+ [InlineData("parttime, contractor, temporary", "PartTime, Contractor, Temporary")]
+ [InlineData("PARTTIME, contractor, Temporary", "PartTime, Contractor, Temporary")]
+ public void IfTypePromotionNeeded_SourceIsFlagsCompositeMemberNameOrComposite_enableCaseInsensitive_ConstantNodeIsCreated(string enumValue, string expected)
+ {
+ // Arrange
+ SingleValueNode source = new ConstantNode(enumValue);
+ IEdmTypeReference targetTypeReference = new EdmEnumTypeReference(EmployeeTypeWithFlags, false);
+
+ // Act
+ ConstantNode result = MetadataBindingUtils.ConvertToTypeIfNeeded(source, targetTypeReference, true) as ConstantNode;
+
+ // Assert
+ result.ShouldBeEnumNode(EmployeeTypeWithFlags, expected);
+ Assert.Equal(expected, result.Value.ToString());
+ }
+
+ [Theory]
+ [InlineData(2, "FullTime")]
+ [InlineData("2", "FullTime")]
+ [InlineData(6, "Permanent")]
+ [InlineData("6", "Permanent")]
+ [InlineData(8, "Contractor")]
+ [InlineData("8", "Contractor")]
+ [InlineData(28, "Temporary")]
+ [InlineData("28", "Temporary")]
+ [InlineData(26, "FullTime, Contractor, Intern")]
+ [InlineData("26", "FullTime, Contractor, Intern")]
+ [InlineData(30, "FullTime, Temporary")]
+ [InlineData("22", "Permanent, Intern")]
+ [InlineData(22, "Permanent, Intern")]
+ public void IfTypePromotionNeeded_SourceIsFlagsIntegralValues_ConstantNodeIsCreated(object enumValue, string expectedLiteralValue)
+ {
+ // Arrange
+ SingleValueNode source = new ConstantNode(enumValue);
+ IEdmTypeReference targetTypeReference = new EdmEnumTypeReference(EmployeeTypeWithFlags, false);
+ // Act
+ ConstantNode result = MetadataBindingUtils.ConvertToTypeIfNeeded(source, targetTypeReference) as ConstantNode;
+
+ // Assert
+ result.ShouldBeEnumNode(EmployeeTypeWithFlags, expectedLiteralValue);
+ Assert.Equal(expectedLiteralValue, result.Value.ToString());
+ }
+
private static EdmEnumType WeekDayEmumType
{
get
@@ -243,6 +322,23 @@ private static EdmEnumType EmployeeType
return employeeType;
}
}
+
+ private static EdmEnumType EmployeeTypeWithFlags
+ {
+ get
+ {
+ EdmEnumType employeeType = new EdmEnumType("NS", "EmployeeTypeWithFlags", isFlags: true);
+ employeeType.AddMember("FullTime", new EdmEnumMemberValue((long)2));
+ employeeType.AddMember("PartTime", new EdmEnumMemberValue((long)4));
+ employeeType.AddMember("Permanent", new EdmEnumMemberValue((long)(2 | 4))); // FullTime | PartTime = 6
+ employeeType.AddMember("Contractor", new EdmEnumMemberValue((long)8));
+ employeeType.AddMember("Intern", new EdmEnumMemberValue((long)16));
+ employeeType.AddMember("Temporary", new EdmEnumMemberValue((long) (4 | 8 | 16))); // PartTime | Contractor | Intern = 28
+
+ return employeeType;
+ }
+ }
+
#endregion
}
}