diff --git a/src/Microsoft.AspNet.OData.Shared/Common/ODataPathHelper.cs b/src/Microsoft.AspNet.OData.Shared/Common/ODataPathHelper.cs new file mode 100644 index 0000000000..1e4c27b23a --- /dev/null +++ b/src/Microsoft.AspNet.OData.Shared/Common/ODataPathHelper.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Common +{ + /// + /// Helper methods for . + /// + internal static class ODataPathHelper + { + /// + /// Get the keys from a . + /// + /// The to extract the keys. + /// Dictionary of keys. + public static Dictionary KeySegmentAsDictionary(KeySegment keySegment) + { + if (keySegment == null) + { + throw Error.ArgumentNull(nameof(keySegment)); + } + + return keySegment.Keys.ToDictionary(d => d.Key, d => d.Value); + } + + /// + /// Get the position of the next in a list of . + /// + /// List of . + /// Current position in the list of . + /// Position of the next if it exists, or -1 otherwise. + public static int GetNextKeySegmentPosition(IReadOnlyList pathSegments, int currentPosition) + { + if (pathSegments == null) + { + throw Error.ArgumentNull(nameof(pathSegments)); + } + + if (currentPosition < 0 || currentPosition >= pathSegments.Count) + { + return -1; + } + + if (pathSegments[currentPosition] is KeySegment) + { + currentPosition++; + } + + for (int i = currentPosition; i < pathSegments.Count; i++) + { + ODataPathSegment currentSegment = pathSegments[i]; + + if (currentSegment is KeySegment) + { + return i; + } + } + + return -1; + } + } +} diff --git a/src/Microsoft.AspNet.OData.Shared/Extensions/ODataPathExtensions.cs b/src/Microsoft.AspNet.OData.Shared/Extensions/ODataPathExtensions.cs index 54808127e9..70467cf20d 100644 --- a/src/Microsoft.AspNet.OData.Shared/Extensions/ODataPathExtensions.cs +++ b/src/Microsoft.AspNet.OData.Shared/Extensions/ODataPathExtensions.cs @@ -6,51 +6,84 @@ //------------------------------------------------------------------------------ using System.Collections.Generic; +using System.Linq; using Microsoft.AspNet.OData.Common; using Microsoft.OData.UriParser; namespace Microsoft.AspNet.OData.Extensions { + /// + /// Extensions method for . + /// internal static class ODataPathExtensions { - public static Dictionary GetKeys(this ODataPath path) + /// + /// Get keys from the last . + /// + /// . + /// Dictionary of keys. + internal static Dictionary GetKeys(this ODataPath path) { + Dictionary keys = new Dictionary(); + if (path == null) { throw Error.ArgumentNull(nameof(path)); } - Dictionary keys = new Dictionary(); - - // Books(1)/Authors(1000)/Namespace.SpecialAuthor - if (path.LastSegment is TypeSegment) + if (path.Count == 0) { - ODataPath pathWithoutLastSegmentCastType = path.TrimEndingTypeSegment(); - - if (pathWithoutLastSegmentCastType.LastSegment is KeySegment) - { - keys = GetKeysFromKeySegment(pathWithoutLastSegmentCastType.LastSegment as KeySegment); - } + return keys; } - // Books(1)/Authors/Namespace.SpecialAuthor/(1000) - else if (path.LastSegment is KeySegment) + + List pathSegments = path.AsList(); + + KeySegment keySegment = pathSegments.OfType().LastOrDefault(); + + if (keySegment == null) { - keys = GetKeysFromKeySegment(path.LastSegment as KeySegment); + return keys; } + keys = ODataPathHelper.KeySegmentAsDictionary(keySegment); + return keys; } - private static Dictionary GetKeysFromKeySegment(KeySegment keySegment) + /// + /// Return the last segment in the path, which is not a or . + /// + /// The . + /// An . + public static ODataPathSegment GetLastNonTypeNonKeySegment(this ODataPath path) { - Dictionary keys = new Dictionary(); + if (path == null) + { + throw Error.ArgumentNull(nameof(path)); + } + + // If the path is Employees(2)/NewFriends(2)/Namespace.MyNewFriend where Namespace.MyNewFriend is a type segment, + // This method will return NewFriends NavigationPropertySegment. - foreach (KeyValuePair kvp in keySegment.Keys) + List pathSegments = path.AsList(); + int position = path.Count - 1; + + while (position >= 0 && (pathSegments[position] is TypeSegment || pathSegments[position] is KeySegment)) { - keys.Add(kvp.Key, kvp.Value); + --position; } - return keys; + return position < 0 ? null : pathSegments[position]; + } + + /// + /// Returns a list of in an . + /// + /// The . + /// List of . + public static List GetSegments(this ODataPath path) + { + return path.AsList(); } } } diff --git a/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems b/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems index 5848fb87c9..4b881021d9 100644 --- a/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems +++ b/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems @@ -25,6 +25,7 @@ + diff --git a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore/Microsoft.Test.E2E.AspNetCore.OData.csproj b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore/Microsoft.Test.E2E.AspNetCore.OData.csproj index 4603d42eeb..53b72ef509 100644 --- a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore/Microsoft.Test.E2E.AspNetCore.OData.csproj +++ b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore/Microsoft.Test.E2E.AspNetCore.OData.csproj @@ -1402,30 +1402,21 @@ OpenType\TypedTest.cs - - BulkOperation\BulkInsertDataModel.cs + + BulkOperation\BulkOperationTest.cs - - BulkOperation\BulkInsertEdmModel.cs + + BulkOperation\BulkOperationDataModel.cs - - BulkOperation\BulkInsertController.cs + + BulkOperation\BulkOperationEdmModel.cs + + + BulkOperation\BulkOperationController.cs BulkOperation\BulkOperationPatchHandlers.cs - - BulkOperation\BulkInsertDataModel.cs - - - BulkOperation\BulkInsertEdmModel.cs - - - BulkOperation\BulkInsertController.cs - - - BulkOperation\BulkOperationPatchHandlers.cs - ParameterAlias\ParameterAliasDataSource.cs diff --git a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/BulkOperation/EFTests/BulkOperationControllerEF.cs b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/BulkOperation/EFTests/BulkOperationControllerEF.cs index c18760bd20..aa87466576 100644 --- a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/BulkOperation/EFTests/BulkOperationControllerEF.cs +++ b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/BulkOperation/EFTests/BulkOperationControllerEF.cs @@ -13,25 +13,24 @@ using Microsoft.AspNet.OData.Extensions; using Microsoft.AspNet.OData.Routing; using Microsoft.EntityFrameworkCore; -using Microsoft.Test.E2E.AspNet.OData.BulkOperation; using Microsoft.Test.E2E.AspNet.OData.Common.Controllers; using Xunit; using static Microsoft.Test.E2E.AspNet.OData.BulkOperation.APIHandlerFactoryEF; -namespace Microsoft.Test.E2E.AspNet.OData.BulkInsert +namespace Microsoft.Test.E2E.AspNet.OData.BulkOperation { public class EmployeesControllerEF : TestODataController { public EmployeesControllerEF() { - + } - + public static List employees; public static List friends; internal DbSet GenerateData(EmployeeDBContext context) - { + { if (context.Employees.Any()) { return context.Employees; @@ -59,7 +58,7 @@ internal DbSet GenerateDataOrders(EmployeeDBContext context) } friends = new List(); - friends.Add(new Friend { Id = 1, Age = 10 , Orders = new List() { new Order { Id = 1, Price = 5 }, new Order { Id = 2, Price = 5 } } }); + friends.Add(new Friend { Id = 1, Age = 10, Orders = new List() { new Order { Id = 1, Price = 5 }, new Order { Id = 2, Price = 5 } } }); friends.Add(new Friend { Id = 2, Age = 20, Orders = new List() { new Order { Id = 10, Price = 5 }, new Order { Id = 20, Price = 5 } } }); friends.Add(new Friend { Id = 3, Age = 30, Orders = new List() { new Order { Id = 3, Price = 5 }, new Order { Id = 4, Price = 5 } } }); friends.Add(new Friend { Id = 4, Age = 40, Orders = new List() { new Order { Id = 30, Price = 5 }, new Order { Id = 40, Price = 5 } } }); diff --git a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/BulkOperation/EFTests/BulkOperationPatchHandlersEF.cs b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/BulkOperation/EFTests/BulkOperationPatchHandlersEF.cs index 7f95a5961c..a09f22adeb 100644 --- a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/BulkOperation/EFTests/BulkOperationPatchHandlersEF.cs +++ b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/BulkOperation/EFTests/BulkOperationPatchHandlersEF.cs @@ -11,7 +11,7 @@ using Microsoft.AspNet.OData; using Microsoft.EntityFrameworkCore; using Microsoft.OData.Edm; -using Microsoft.Test.E2E.AspNet.OData.BulkInsert; +using Microsoft.OData.UriParser; namespace Microsoft.Test.E2E.AspNet.OData.BulkOperation { @@ -47,11 +47,9 @@ protected override void OnModelCreating(Microsoft.EntityFrameworkCore.ModelBuild modelBuilder.Entity().Ignore(c => c.Container); modelBuilder.Entity().Ignore(c => c.Container); } - - } - internal class APIHandlerFactoryEF: ODataAPIHandlerFactory + internal class APIHandlerFactoryEF : ODataAPIHandlerFactory { EmployeeDBContext dbContext; @@ -60,285 +58,275 @@ public APIHandlerFactoryEF(IEdmModel model, EmployeeDBContext dbContext) : base( this.dbContext = dbContext; } - public override IODataAPIHandler GetHandler(NavigationPath navigationPath) + public override IODataAPIHandler GetHandler(ODataPath odataPath) { - if (navigationPath != null) + if (odataPath != null) { - var pathItems = navigationPath; + var pathItems = odataPath; } return null; - } - - internal class EmployeeEFPatchHandler : ODataAPIHandler - { - EmployeeDBContext dbContext = null; - - public EmployeeEFPatchHandler(EmployeeDBContext dbContext) - { - this.dbContext = dbContext; } - public override ODataAPIResponseStatus TryCreate(IDictionary keyValues, out Employee createdObject, out string errorMessage) + internal class EmployeeEFPatchHandler : ODataAPIHandler { - createdObject = null; - errorMessage = string.Empty; + EmployeeDBContext dbContext = null; - try + public EmployeeEFPatchHandler(EmployeeDBContext dbContext) { - createdObject = new Employee(); - dbContext.Employees.Add(createdObject); - - return ODataAPIResponseStatus.Success; + this.dbContext = dbContext; } - catch (Exception ex) + + public override ODataAPIResponseStatus TryCreate(IDictionary keyValues, out Employee createdObject, out string errorMessage) { - errorMessage = ex.Message; + createdObject = null; + errorMessage = string.Empty; - return ODataAPIResponseStatus.Failure; - } - } + try + { + createdObject = new Employee(); + dbContext.Employees.Add(createdObject); - public override ODataAPIResponseStatus TryDelete(IDictionary keyValues, out string errorMessage) - { - errorMessage = string.Empty; + return ODataAPIResponseStatus.Success; + } + catch (Exception ex) + { + errorMessage = ex.Message; + + return ODataAPIResponseStatus.Failure; + } + } - try + public override ODataAPIResponseStatus TryDelete(IDictionary keyValues, out string errorMessage) { - var id = keyValues.First().Value.ToString(); - var customer = dbContext.Employees.First(x => x.ID == Int32.Parse(id)); + errorMessage = string.Empty; - dbContext.Employees.Remove(customer); + try + { + var id = keyValues.First().Value.ToString(); + var customer = dbContext.Employees.First(x => x.ID == Int32.Parse(id)); - return ODataAPIResponseStatus.Success; - } - catch (Exception ex) - { - errorMessage = ex.Message; + dbContext.Employees.Remove(customer); - return ODataAPIResponseStatus.Failure; - } - } + return ODataAPIResponseStatus.Success; + } + catch (Exception ex) + { + errorMessage = ex.Message; - public override ODataAPIResponseStatus TryGet(IDictionary keyValues, out Employee originalObject, out string errorMessage) - { - ODataAPIResponseStatus status = ODataAPIResponseStatus.Success; - errorMessage = string.Empty; - originalObject = null; + return ODataAPIResponseStatus.Failure; + } + } - try + public override ODataAPIResponseStatus TryGet(IDictionary keyValues, out Employee originalObject, out string errorMessage) { - var id = keyValues["ID"].ToString(); - originalObject = dbContext.Employees.First(x => x.ID == Int32.Parse(id)); + ODataAPIResponseStatus status = ODataAPIResponseStatus.Success; + errorMessage = string.Empty; + originalObject = null; + try + { + var id = keyValues["ID"].ToString(); + originalObject = dbContext.Employees.First(x => x.ID == Int32.Parse(id)); + + if (originalObject == null) + { + status = ODataAPIResponseStatus.NotFound; + } - if (originalObject == null) + } + catch (Exception ex) { - status = ODataAPIResponseStatus.NotFound; + status = ODataAPIResponseStatus.Failure; + errorMessage = ex.Message; } + return status; } - catch (Exception ex) + + public override IODataAPIHandler GetNestedHandler(Employee parent, string navigationPropertyName) { - status = ODataAPIResponseStatus.Failure; - errorMessage = ex.Message; + switch (navigationPropertyName) + { + case "Friends": + return new FriendEFPatchHandler(parent); + case "NewFriends": + return new NewFriendEFPatchHandler(parent); + default: + return null; + } } - - return status; } - public override IODataAPIHandler GetNestedHandler(Employee parent, string navigationPropertyName) + internal class FriendEFPatchHandler : ODataAPIHandler { - switch (navigationPropertyName) + Employee employee; + public FriendEFPatchHandler(Employee employee) { - case "Friends": - return new FriendEFPatchHandler(parent); - case "NewFriends": - return new NewFriendEFPatchHandler(parent); - default: - return null; + this.employee = employee; } - - } - - } - internal class FriendEFPatchHandler : ODataAPIHandler - { - Employee employee; - public FriendEFPatchHandler(Employee employee) - { - this.employee = employee; - } - - public override ODataAPIResponseStatus TryCreate(IDictionary keyValues, out Friend createdObject, out string errorMessage) - { - createdObject = null; - errorMessage = string.Empty; - - try + public override ODataAPIResponseStatus TryCreate(IDictionary keyValues, out Friend createdObject, out string errorMessage) { - createdObject = new Friend(); - employee.Friends.Add(createdObject); + createdObject = null; + errorMessage = string.Empty; - return ODataAPIResponseStatus.Success; - } - catch (Exception ex) - { - errorMessage = ex.Message; + try + { + createdObject = new Friend(); + employee.Friends.Add(createdObject); - return ODataAPIResponseStatus.Failure; - } - } + return ODataAPIResponseStatus.Success; + } + catch (Exception ex) + { + errorMessage = ex.Message; - public override ODataAPIResponseStatus TryDelete(IDictionary keyValues, out string errorMessage) - { - errorMessage = string.Empty; + return ODataAPIResponseStatus.Failure; + } + } - try + public override ODataAPIResponseStatus TryDelete(IDictionary keyValues, out string errorMessage) { - var id = keyValues.First().Value.ToString(); - var friend = employee.Friends.First(x => x.Id == Int32.Parse(id)); + errorMessage = string.Empty; - employee.Friends.Remove(friend); + try + { + var id = keyValues.First().Value.ToString(); + var friend = employee.Friends.First(x => x.Id == Int32.Parse(id)); - return ODataAPIResponseStatus.Success; - } - catch (Exception ex) - { - errorMessage = ex.Message; + employee.Friends.Remove(friend); - return ODataAPIResponseStatus.Failure; - } - } + return ODataAPIResponseStatus.Success; + } + catch (Exception ex) + { + errorMessage = ex.Message; - public override ODataAPIResponseStatus TryGet(IDictionary keyValues, out Friend originalObject, out string errorMessage) - { - ODataAPIResponseStatus status = ODataAPIResponseStatus.Success; - errorMessage = string.Empty; - originalObject = null; + return ODataAPIResponseStatus.Failure; + } + } - try + public override ODataAPIResponseStatus TryGet(IDictionary keyValues, out Friend originalObject, out string errorMessage) { - var id = keyValues["Id"].ToString(); - if (employee.Friends == null) - { - status = ODataAPIResponseStatus.NotFound; - } - else + ODataAPIResponseStatus status = ODataAPIResponseStatus.Success; + errorMessage = string.Empty; + originalObject = null; + + try { - originalObject = employee.Friends.FirstOrDefault(x => x.Id == Int32.Parse(id)); - } + var id = keyValues["Id"].ToString(); + if (employee.Friends == null) + { + status = ODataAPIResponseStatus.NotFound; + } + else + { + originalObject = employee.Friends.FirstOrDefault(x => x.Id == Int32.Parse(id)); + } + if (originalObject == null) + { + status = ODataAPIResponseStatus.NotFound; + } - if (originalObject == null) + } + catch (Exception ex) { - status = ODataAPIResponseStatus.NotFound; + status = ODataAPIResponseStatus.Failure; + errorMessage = ex.Message; } + return status; } - catch (Exception ex) + + public override IODataAPIHandler GetNestedHandler(Friend parent, string navigationPropertyName) { - status = ODataAPIResponseStatus.Failure; - errorMessage = ex.Message; + return new OrderEFPatchHandler(parent); } - - return status; } - public override IODataAPIHandler GetNestedHandler(Friend parent, string navigationPropertyName) + internal class NewFriendEFPatchHandler : ODataAPIHandler { - return new OrderEFPatchHandler(parent); - } - - } - - - internal class NewFriendEFPatchHandler : ODataAPIHandler - { - Employee employee; - public NewFriendEFPatchHandler(Employee employee) - { - this.employee = employee; - } - - public override ODataAPIResponseStatus TryCreate(IDictionary keyValues, out NewFriend createdObject, out string errorMessage) - { - createdObject = null; - errorMessage = string.Empty; - - try + Employee employee; + public NewFriendEFPatchHandler(Employee employee) { - createdObject = new NewFriend(); - employee.NewFriends.Add(createdObject); - - return ODataAPIResponseStatus.Success; + this.employee = employee; } - catch (Exception ex) + + public override ODataAPIResponseStatus TryCreate(IDictionary keyValues, out NewFriend createdObject, out string errorMessage) { - errorMessage = ex.Message; + createdObject = null; + errorMessage = string.Empty; - return ODataAPIResponseStatus.Failure; - } - } + try + { + createdObject = new NewFriend(); + employee.NewFriends.Add(createdObject); - public override ODataAPIResponseStatus TryDelete(IDictionary keyValues, out string errorMessage) - { - errorMessage = string.Empty; + return ODataAPIResponseStatus.Success; + } + catch (Exception ex) + { + errorMessage = ex.Message; - try + return ODataAPIResponseStatus.Failure; + } + } + + public override ODataAPIResponseStatus TryDelete(IDictionary keyValues, out string errorMessage) { - var id = keyValues.First().Value.ToString(); - var friend = employee.NewFriends.First(x => x.Id == Int32.Parse(id)); + errorMessage = string.Empty; - employee.NewFriends.Remove(friend); + try + { + var id = keyValues.First().Value.ToString(); + var friend = employee.NewFriends.First(x => x.Id == Int32.Parse(id)); - return ODataAPIResponseStatus.Success; - } - catch (Exception ex) - { - errorMessage = ex.Message; + employee.NewFriends.Remove(friend); - return ODataAPIResponseStatus.Failure; - } - } + return ODataAPIResponseStatus.Success; + } + catch (Exception ex) + { + errorMessage = ex.Message; - public override ODataAPIResponseStatus TryGet(IDictionary keyValues, out NewFriend originalObject, out string errorMessage) - { - ODataAPIResponseStatus status = ODataAPIResponseStatus.Success; - errorMessage = string.Empty; - originalObject = null; + return ODataAPIResponseStatus.Failure; + } + } - try + public override ODataAPIResponseStatus TryGet(IDictionary keyValues, out NewFriend originalObject, out string errorMessage) { - var id = keyValues["Id"].ToString(); - originalObject = employee.NewFriends.First(x => x.Id == Int32.Parse(id)); + ODataAPIResponseStatus status = ODataAPIResponseStatus.Success; + errorMessage = string.Empty; + originalObject = null; + try + { + var id = keyValues["Id"].ToString(); + originalObject = employee.NewFriends.First(x => x.Id == Int32.Parse(id)); + + if (originalObject == null) + { + status = ODataAPIResponseStatus.NotFound; + } - if (originalObject == null) + } + catch (Exception ex) { - status = ODataAPIResponseStatus.NotFound; + status = ODataAPIResponseStatus.Failure; + errorMessage = ex.Message; } + return status; } - catch (Exception ex) + + public override IODataAPIHandler GetNestedHandler(NewFriend parent, string navigationPropertyName) { - status = ODataAPIResponseStatus.Failure; - errorMessage = ex.Message; + return null; } - - return status; - } - - public override IODataAPIHandler GetNestedHandler(NewFriend parent, string navigationPropertyName) - { - return null; } - } - - - internal class OrderEFPatchHandler : ODataAPIHandler { Friend friend; @@ -399,12 +387,10 @@ public override ODataAPIResponseStatus TryGet(IDictionary keyVal var id = keyValues["Id"].ToString(); originalObject = friend.Orders.First(x => x.Id == Int32.Parse(id)); - if (originalObject == null) { status = ODataAPIResponseStatus.NotFound; } - } catch (Exception ex) { @@ -420,6 +406,5 @@ public override IODataAPIHandler GetNestedHandler(Order parent, string navigatio throw new NotImplementedException(); } } - } } diff --git a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/BulkOperation/EFTests/BulkInsertTestEF.cs b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/BulkOperation/EFTests/BulkOperationTestEF.cs similarity index 91% rename from test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/BulkOperation/EFTests/BulkInsertTestEF.cs rename to test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/BulkOperation/EFTests/BulkOperationTestEF.cs index dbeed96620..eed66da892 100644 --- a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/BulkOperation/EFTests/BulkInsertTestEF.cs +++ b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/BulkOperation/EFTests/BulkOperationTestEF.cs @@ -1,49 +1,44 @@ //----------------------------------------------------------------------------- -// +// // Copyright (c) .NET Foundation and Contributors. All rights reserved. // See License.txt in the project root for license information. // //------------------------------------------------------------------------------ -using System; using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.OData; using Microsoft.AspNet.OData.Extensions; using Microsoft.AspNet.OData.Routing; using Microsoft.AspNet.OData.Routing.Conventions; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Test.E2E.AspNet.OData.BulkOperation; using Microsoft.Test.E2E.AspNet.OData.Common.Execution; using Microsoft.Test.E2E.AspNet.OData.Common.Extensions; using Newtonsoft.Json.Linq; using Xunit; -namespace Microsoft.Test.E2E.AspNet.OData.BulkInsert +namespace Microsoft.Test.E2E.AspNet.OData.BulkOperation { - public class BulkInsertTestEF : WebHostTestBase + public class BulkOperationTestEF : WebHostTestBase { - public BulkInsertTestEF(WebHostTestFixture fixture) - :base(fixture) - { + public BulkOperationTestEF(WebHostTestFixture fixture) + : base(fixture) + { } protected override void UpdateConfiguration(WebRouteConfiguration configuration) { var controllers = new[] { typeof(EmployeesControllerEF), typeof(MetadataController) }; configuration.AddControllers(controllers); - + configuration.Routes.Clear(); configuration.Count().Filter().OrderBy().Expand().MaxTop(null).Select(); - configuration.MapODataServiceRoute("convention", "convention", BulkInsertEdmModel.GetConventionModel(configuration)); - configuration.MapODataServiceRoute("explicit", "explicit", BulkInsertEdmModel.GetExplicitModel(configuration), new DefaultODataPathHandler(), ODataRoutingConventions.CreateDefault()); + configuration.MapODataServiceRoute("convention", "convention", BulkOperationEdmModel.GetConventionModel(configuration)); + configuration.MapODataServiceRoute("explicit", "explicit", BulkOperationEdmModel.GetExplicitModel(configuration), new DefaultODataPathHandler(), ODataRoutingConventions.CreateDefault()); configuration.EnsureInitialized(); - + } @@ -68,11 +63,10 @@ public async Task PatchEmployee_WithUpdates() //Act & Assert using (HttpResponseMessage response = await this.Client.SendAsync(requestForPost)) { - var json = await response.Content.ReadAsObject(); + var json = await response.Content.ReadAsObject(); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Contains("Sql", json.ToString()); } - } [Fact] @@ -99,22 +93,21 @@ public async Task PatchEmployee_WithUpdates_WithEmployees() Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Contains("SqlFU", json.ToString()); } - } [Fact] public async Task PatchEmployee_WithUpdates_Employees() { //Arrange - + string requestUri = this.BaseAddress + "/convention/Employees"; var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees/$delta', - 'value':[{ '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkInsert.Employee', 'ID':1,'Name':'Employee1', + 'value':[{ '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkOperation.Employee', 'ID':1,'Name':'Employee1', 'Friends@odata.delta':[{'Id':1,'Name':'Friend1', 'Orders@odata.delta' :[{'Id':1,'Price': 10}, {'Id':2,'Price': 20} ] },{'Id':2,'Name':'Friend2'}] }, - { '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkInsert.Employee', 'ID':2,'Name':'Employee2', + { '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkOperation.Employee', 'ID':2,'Name':'Employee2', 'Friends@odata.delta':[{'Id':3,'Name':'Friend3', 'Orders@odata.delta' :[{'Id':3,'Price': 30}, {'Id':4,'Price': 40} ]},{'Id':4,'Name':'Friend4'}] }] @@ -123,7 +116,7 @@ public async Task PatchEmployee_WithUpdates_Employees() var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); requestForPost.Headers.Add("OData-Version", "4.01"); requestForPost.Headers.Add("OData-MaxVersion", "4.01"); - + StringContent stringContent = new StringContent(content: content, encoding: Encoding.UTF8, mediaType: "application/json"); requestForPost.Content = stringContent; @@ -141,7 +134,6 @@ public async Task PatchEmployee_WithUpdates_Employees() Assert.Contains("Employee1", json); Assert.Contains("Employee2", json); } - } [Fact] @@ -151,11 +143,11 @@ public async Task PatchEmployee_WithUpdates_Employees_InV4() string requestUri = this.BaseAddress + "/convention/Employees"; var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees/$delta', - 'value':[{ '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkInsert.Employee', 'ID':1,'Name':'Employee1', + 'value':[{ '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkOperation.Employee', 'ID':1,'Name':'Employee1', 'Friends@odata.delta':[{'Id':1,'Name':'Friend1', 'Orders@odata.delta' :[{'Id':1,'Price': 10}, {'Id':2,'Price': 20} ] },{'Id':2,'Name':'Friend2'}] }, - { '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkInsert.Employee', 'ID':2,'Name':'Employee2', + { '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkOperation.Employee', 'ID':2,'Name':'Employee2', 'Friends@odata.delta':[{'Id':3,'Name':'Friend3', 'Orders@odata.delta' :[{'Id':3,'Price': 30}, {'Id':4,'Price': 40} ]},{'Id':4,'Name':'Friend4'}] }] @@ -182,15 +174,13 @@ public async Task PatchEmployee_WithUpdates_Employees_InV4() Assert.Contains("Employee1", json); Assert.Contains("Employee2", json); } - } - [Fact] public async Task PatchEmployee_WithDelete() { //Arrange - + string requestUri = this.BaseAddress + "/convention/Employees(1)"; var content = @"{ @@ -210,11 +200,8 @@ public async Task PatchEmployee_WithDelete() var json = response.Content.ReadAsStringAsync().Result; Assert.Contains("Sql", json); } - - } - [Fact] public async Task PatchEmployee_WithAddUpdateAndDelete() { @@ -239,15 +226,13 @@ public async Task PatchEmployee_WithAddUpdateAndDelete() var json = response.Content.ReadAsStringAsync().Result; Assert.Contains("SqlUD", json); } - } - [Fact] public async Task PatchEmployee_WithMultipleUpdatesinOrder1() { //Arrange - + string requestUri = this.BaseAddress + "/convention/Employees(1)"; var content = @"{ @@ -264,17 +249,16 @@ public async Task PatchEmployee_WithMultipleUpdatesinOrder1() using (HttpResponseMessage response = await this.Client.SendAsync(requestForPost)) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var json = response.Content.ReadAsStringAsync().Result; + var json = response.Content.ReadAsStringAsync().Result; Assert.Contains("SqlMU", json); } - } [Fact] public async Task PatchEmployee_WithMultipleUpdatesinOrder2() { //Arrange - + string requestUri = this.BaseAddress + "/convention/Employees(1)"; var content = @"{ @@ -294,11 +278,8 @@ public async Task PatchEmployee_WithMultipleUpdatesinOrder2() var json = response.Content.ReadAsStringAsync().Result; Assert.Contains("SqlMU1", json); } - } - #endregion - } } \ No newline at end of file diff --git a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/Microsoft.Test.E2E.AspNetCore3x.OData.csproj b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/Microsoft.Test.E2E.AspNetCore3x.OData.csproj index 227ca4e4ad..5ee1f5d514 100644 --- a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/Microsoft.Test.E2E.AspNetCore3x.OData.csproj +++ b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNetCore3x/Microsoft.Test.E2E.AspNetCore3x.OData.csproj @@ -1665,18 +1665,21 @@ Validation\DeltaOfTValidationTests.cs - - BulkOperation\BulkInsertDataModel.cs - - - BulkOperation\BulkInsertEdmModel.cs - - - BulkOperation\BulkInsertController.cs - - - BulkOperation\BulkOperationPatchHandlers.cs - + + BulkOperation\BulkOperationTest.cs + + + BulkOperation\BulkOperationDataModel.cs + + + BulkOperation\BulkOperationEdmModel.cs + + + BulkOperation\BulkOperationController.cs + + + BulkOperation\BulkOperationPatchHandlers.cs + diff --git a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkInsertController.cs b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationController.cs similarity index 69% rename from test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkInsertController.cs rename to test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationController.cs index b0697cd2e7..bcc043edd6 100644 --- a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkInsertController.cs +++ b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationController.cs @@ -1,25 +1,22 @@ //----------------------------------------------------------------------------- -// +// // Copyright (c) .NET Foundation and Contributors. All rights reserved. // See License.txt in the project root for license information. // //------------------------------------------------------------------------------ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Reflection; using Microsoft.AspNet.OData; using Microsoft.AspNet.OData.Extensions; using Microsoft.AspNet.OData.Routing; using Microsoft.OData.Edm; -using Microsoft.Test.E2E.AspNet.OData.BulkOperation; using Microsoft.Test.E2E.AspNet.OData.Common.Controllers; using Xunit; -namespace Microsoft.Test.E2E.AspNet.OData.BulkInsert -{ +namespace Microsoft.Test.E2E.AspNet.OData.BulkOperation +{ public class EmployeesController : TestODataController { public EmployeesController() @@ -39,10 +36,9 @@ public EmployeesController() private List Friends = null; - private void InitEmployees() { - Friends = new List { new Friend { Id = 1, Name = "Test0", Age =33 }, new Friend { Id = 2, Name = "Test1", Orders = new List() { new Order { Id = 1, Price = 2 } } }, new Friend { Id = 3, Name = "Test3" }, new Friend { Id = 4, Name = "Test4" } }; + Friends = new List { new Friend { Id = 1, Name = "Test0", Age = 33 }, new Friend { Id = 2, Name = "Test1", Orders = new List() { new Order { Id = 1, Price = 2 } } }, new Friend { Id = 3, Name = "Test3" }, new Friend { Id = 4, Name = "Test4" } }; Employees = new List { @@ -144,20 +140,19 @@ public DeltaSet PatchWithUsersMethod(DeltaSet friendColl, } public EdmChangedObjectCollection PatchWithUsersMethodTypeLess(int key, EdmChangedObjectCollection friendColl) { - - var entity = Request.GetModel().FindDeclaredType("Microsoft.Test.E2E.AspNet.OData.BulkInsert.UnTypedEmployee") as IEdmEntityType; + var entity = Request.GetModel().FindDeclaredType("Microsoft.Test.E2E.AspNet.OData.BulkOperation.UnTypedEmployee") as IEdmEntityType; InitTypeLessEmployees(entity); - var entity1 = Request.GetModel().FindDeclaredType("Microsoft.Test.E2E.AspNet.OData.BulkInsert.UnTypedFriend") as IEdmEntityType; + var entity1 = Request.GetModel().FindDeclaredType("Microsoft.Test.E2E.AspNet.OData.BulkOperation.UnTypedFriend") as IEdmEntityType; - var changedObjColl = friendColl.Patch(new FriendTypelessAPIHandler(EmployeesTypeless[key - 1], entity) ,new TypelessAPIHandlerFactory(Request.GetModel(), entity)); + var changedObjColl = friendColl.Patch(new FriendTypelessAPIHandler(EmployeesTypeless[key - 1], entity), new TypelessAPIHandlerFactory(Request.GetModel(), entity)); return changedObjColl; } public EdmChangedObjectCollection EmployeePatchMethodTypeLess(EdmChangedObjectCollection empColl) { - var entity = Request.GetModel().FindDeclaredType("Microsoft.Test.E2E.AspNet.OData.BulkInsert.UnTypedEmployee") as IEdmEntityType; + var entity = Request.GetModel().FindDeclaredType("Microsoft.Test.E2E.AspNet.OData.BulkOperation.UnTypedEmployee") as IEdmEntityType; InitTypeLessEmployees(entity); var changedObjColl = empColl.Patch(new EmployeeEdmAPIHandler(entity), new TypelessAPIHandlerFactory(Request.GetModel(), entity)); @@ -179,10 +174,11 @@ private void ValidateSuccessfulTypeless() friends.First().TryGetPropertyValue("Name", out obj1); object name; + if (EmployeesTypeless.First().TryGetPropertyValue("Name", out name) && name.ToString() == "Employeeabcd") { Assert.Equal("abcd", obj1.ToString()); - + object age; friends.First().TryGetPropertyValue("Age", out age); @@ -218,7 +214,7 @@ public ITestActionResult GetFriends(int key) [ODataRoute("Employees({key})/UnTypedFriends")] public ITestActionResult GetUnTypedFriends(int key) { - var entity = Request.GetModel().FindDeclaredType("Microsoft.Test.E2E.AspNet.OData.BulkInsert.UnTypedEmployee") as IEdmEntityType; + var entity = Request.GetModel().FindDeclaredType("Microsoft.Test.E2E.AspNet.OData.BulkOperation.UnTypedEmployee") as IEdmEntityType; InitTypeLessEmployees(entity); foreach (var emp in EmployeesTypeless) @@ -236,7 +232,6 @@ public ITestActionResult GetUnTypedFriends(int key) return Ok(); } - [ODataRoute("Employees")] [HttpPatch] [EnableQuery] @@ -251,6 +246,18 @@ public ITestActionResult PatchEmployees([FromBody] DeltaSet coll) return Ok(returncoll); } + [ODataRoute("Employees")] + [HttpPost] + public ITestActionResult Post([FromBody] Employee employee) + { + InitEmployees(); + + var handler = new EmployeeAPIHandler(); + + handler.UpdateLinkedObjects(employee, Request.GetModel()); + + return Ok(employee); + } [ODataRoute("Employees({key})/Friends")] [HttpPatch] @@ -266,7 +273,6 @@ public ITestActionResult PatchFriends(int key, [FromBody] DeltaSet frien return Ok(changedObjColl); } - [ODataRoute("Employees({key})/NewFriends")] [HttpPatch] public ITestActionResult PatchNewFriends(int key, [FromBody] DeltaSet friendColl) @@ -289,7 +295,6 @@ public ITestActionResult PatchNewFriends(int key, [FromBody] DeltaSet return Ok(changedObjColl); } - } [ODataRoute("Employees({key})/UnTypedFriends")] @@ -316,7 +321,6 @@ public ITestActionResult PatchUnTypedFriends(int key, [FromBody] EdmChangedObjec (obj1 as EdmStructuredObject).TryGetPropertyValue("Street", out obj2); Assert.Equal("Abc 123", obj2); - } } @@ -324,7 +328,7 @@ public ITestActionResult PatchUnTypedFriends(int key, [FromBody] EdmChangedObjec } else if (key == 2) { - var entitytype = Request.GetModel().FindDeclaredType("Microsoft.Test.E2E.AspNet.OData.BulkInsert.UnTypedEmployee") as IEdmEntityType; + var entitytype = Request.GetModel().FindDeclaredType("Microsoft.Test.E2E.AspNet.OData.BulkOperation.UnTypedEmployee") as IEdmEntityType; var entity = new EdmEntityObject(friendColl[0].GetEdmType().AsEntity()); entity.TrySetPropertyValue("Id", 2); @@ -348,20 +352,15 @@ public ITestActionResult PatchUnTypedFriends(int key, [FromBody] EdmChangedObjec } } - [ODataRoute("UnTypedEmployees")] [HttpPatch] public ITestActionResult PatchUnTypedEmployees([FromBody] EdmChangedObjectCollection empColl) { - var changedObjColl = EmployeePatchMethodTypeLess(empColl); return Ok(changedObjColl); - } - - [ODataRoute("Employees({key})")] [EnableQuery] public ITestActionResult Patch(int key, [FromBody] Delta delta) @@ -396,8 +395,6 @@ public ITestActionResult Patch(int key, [FromBody] Delta delta) return Ok(employee); } - - } public class CompanyController : TestODataController @@ -416,30 +413,28 @@ public CompanyController() private void InitCompanies() { - OverdueOrders = new List() { new NewOrder { Id = 1, Price = 10, Quantity =1 }, new NewOrder { Id = 2, Price = 20, Quantity = 2 }, new NewOrder { Id = 3, Price = 30 }, new NewOrder { Id = 4, Price = 40 } }; + OverdueOrders = new List() { new NewOrder { Id = 1, Price = 10, Quantity = 1 }, new NewOrder { Id = 2, Price = 20, Quantity = 2 }, new NewOrder { Id = 3, Price = 30 }, new NewOrder { Id = 4, Price = 40 } }; MyOverdueOrders = new List() { new MyNewOrder { Id = 1, Price = 10, Quantity = 1 }, new MyNewOrder { Id = 2, Price = 20, Quantity = 2 }, new MyNewOrder { Id = 3, Price = 30 }, new MyNewOrder { Id = 4, Price = 40 } }; Companies = new List() { new Company { Id = 1, Name = "Company1", OverdueOrders = OverdueOrders.Where(x => x.Id == 2).ToList(), MyOverdueOrders = MyOverdueOrders.Where(x => x.Id == 2).ToList() } , new Company { Id = 2, Name = "Company2", OverdueOrders = OverdueOrders.Where(x => x.Id == 3 || x.Id == 4).ToList() } }; } - [ODataRoute("Companies")] [HttpPatch] public ITestActionResult PatchCompanies([FromBody] DeltaSet coll) { - var empCntrl = new EmployeesController(); InitCompanies(); Assert.NotNull(coll); - + var returncoll = coll.Patch(new CompanyAPIHandler(), new APIHandlerFactory(Request.GetModel())); var comp = coll.First() as Delta; object val; - if(comp.TryGetPropertyValue("Name", out val)) + if (comp.TryGetPropertyValue("Name", out val)) { - if(val.ToString() == "Company02") + if (val.ToString() == "Company02") { ValidateOverdueOrders2(1, 2, 9); } @@ -449,7 +444,6 @@ public ITestActionResult PatchCompanies([FromBody] DeltaSet coll) } } - return Ok(returncoll); } @@ -457,17 +451,18 @@ public ITestActionResult PatchCompanies([FromBody] DeltaSet coll) [HttpPost] public ITestActionResult Post([FromBody] Company company) { - + InitCompanies(); InitEmployees(); - if(company.Id == 4) + if (company.Id == 4) { AddNewOrder(company); } - var idResolver = new BulkOpODataIdResolver(Request.GetModel()); - idResolver.ApplyODataId(company ); + var handler = new CompanyAPIHandler(); + + handler.UpdateLinkedObjects(company, Request.GetModel()); Companies.Add(company); @@ -479,7 +474,6 @@ public ITestActionResult Post([FromBody] Company company) { ValidateOverdueOrders1(3, 1); } - return Ok(company); } @@ -491,167 +485,12 @@ private static void AddNewOrder(Company company) company.OverdueOrders[1] = newOrder; } - - private void MapOdataId(Company company) - { - //More generic. - for(int i =0; i< company.OverdueOrders.Count;i++) - { - var order = company.OverdueOrders[i]; - if(order.Container != null) - { - var pathItems = new NavigationPath(order.Container.ODataId, Request.GetModel()); - - int cnt = 0; - if(pathItems[cnt].Name== "Employees") - { - var emp = GetEmployee(pathItems[cnt].KeyProperties); - - if(emp != null) - { - if(pathItems[++cnt].Name == "NewFriends") - { - var frnd = GetNewFriendFromEmployee(emp, pathItems[cnt].KeyProperties); - - if(frnd!= null) - { - if (pathItems[++cnt].Name == "NewOrders") - { - //{ ID= 1, OdataIdContainer {....}} - add comments. - company.OverdueOrders[i] = GetNewOrderFromNewFriend(frnd, pathItems[cnt].KeyProperties); - } - } - } - } - } - - } - } - } - - - - private void CheckAndApplyODataId(object obj) - { - Type type = obj.GetType(); - - PropertyInfo property = type.GetProperties().FirstOrDefault(s => s.PropertyType == typeof(IODataIdContainer)); - - if(property != null && property.GetValue(obj) is IODataIdContainer container && container != null) - { - var res = ApplyODataId(container); - - foreach(var prop in type.GetProperties()) - { - var resVal = prop.GetValue(res); - - - if(resVal != null) - { - prop.SetValue(obj, resVal); - } - } - } - else - { - foreach (var prop in type.GetProperties().Where(p=> !p.PropertyType.IsPrimitive )) - { - var propVal = prop.GetValue(obj); - if(propVal == null) - { - continue; - } - - if (propVal is IEnumerable lst) - { - - foreach (var val in lst) - { - if (val.GetType().IsPrimitive) - { - break; - } - - CheckAndApplyODataId(val); - - } - } - else - { - CheckAndApplyODataId(propVal); - } - - } - } - - } - - private object ApplyODataId(IODataIdContainer container) - { - var pathItems = new NavigationPath (container.ODataId, Request.GetModel()); - if(pathItems != null) - { - int cnt = 0; - object value = null; - - while(cnt< pathItems.Count) - { - value = GetObject(pathItems[cnt].Name, value, pathItems[cnt].KeyProperties); - cnt++; - } - - return value; - } - - return null; - } - - private object GetObject(string name, object parent, Dictionary keyValues) - { - switch (name) - { - case "Employees": - return EmployeesController.Employees.FirstOrDefault(x => x.ID == (int)keyValues["ID"]); - case "NewFriends": - Employee emp = parent as Employee; - - return emp == null? null: emp.NewFriends.FirstOrDefault(x => x.Id == (int)keyValues["Id"]); - case "NewOrders": - NewFriend frnd = parent as NewFriend; - - return frnd == null ? null : frnd.NewOrders.FirstOrDefault(x => x.Id == (int)keyValues["Id"]); - default: - return null; - } - } - - private Employee GetEmployee(Dictionary keyValues) - { - var emp = EmployeesController.Employees.FirstOrDefault(x => x.ID == (int)keyValues["ID"]); - - return emp; - } - - private NewFriend GetNewFriendFromEmployee(Employee emp, Dictionary keyValues) - { - var frnd = emp.NewFriends.FirstOrDefault(x => x.Id == (int)keyValues["Id"]); - - return frnd; - } - - private NewOrder GetNewOrderFromNewFriend(NewFriend frnd, Dictionary keyValues) - { - var order = frnd.NewOrders.FirstOrDefault(x => x.Id == (int)keyValues["Id"]); - - return order; - } - private void InitEmployees() { var cntrl = new EmployeesController(); } - private void ValidateOverdueOrders1(int companyId, int orderId, int quantity = 0, int price=101) + private void ValidateOverdueOrders1(int companyId, int orderId, int quantity = 0, int price = 101) { var comp = Companies.FirstOrDefault(x => x.Id == companyId); Assert.NotNull(comp); @@ -675,33 +514,4 @@ private void ValidateOverdueOrders2(int companyId, int orderId, int quantity = 0 Assert.Equal(quantity, order.Quantity); } } - - internal class BulkOpODataIdResolver: ODataIDResolver - { - - public BulkOpODataIdResolver(IEdmModel model): base(model) - { - - } - - public override object GetObject(string name, object parent, Dictionary keyValues) - { - switch (name) - { - case "Employees": - return EmployeesController.Employees.FirstOrDefault(x => x.ID == (int)keyValues["ID"]); - case "NewFriends": - Employee emp = parent as Employee; - - return emp == null ? null : emp.NewFriends.FirstOrDefault(x => x.Id == (int)keyValues["Id"]); - case "NewOrders": - NewFriend frnd = parent as NewFriend; - - return frnd == null ? null : frnd.NewOrders.FirstOrDefault(x => x.Id == (int)keyValues["Id"]); - default: - return null; - } - } - - } } \ No newline at end of file diff --git a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkInsertDataModel.cs b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationDataModel.cs similarity index 97% rename from test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkInsertDataModel.cs rename to test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationDataModel.cs index fcf3c09214..a2f8f1072d 100644 --- a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkInsertDataModel.cs +++ b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationDataModel.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// +// // Copyright (c) .NET Foundation and Contributors. All rights reserved. // See License.txt in the project root for license information. // @@ -12,7 +12,7 @@ using Microsoft.AspNet.OData; using Microsoft.AspNet.OData.Builder; -namespace Microsoft.Test.E2E.AspNet.OData.BulkInsert +namespace Microsoft.Test.E2E.AspNet.OData.BulkOperation { [AutoExpand] public class Employee diff --git a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkInsertEdmModel.cs b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationEdmModel.cs similarity index 96% rename from test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkInsertEdmModel.cs rename to test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationEdmModel.cs index f69e877c6b..0ccc5aef7e 100644 --- a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkInsertEdmModel.cs +++ b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationEdmModel.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// +// // Copyright (c) .NET Foundation and Contributors. All rights reserved. // See License.txt in the project root for license information. // @@ -9,9 +9,9 @@ using Microsoft.OData.Edm; using Microsoft.Test.E2E.AspNet.OData.Common.Execution; -namespace Microsoft.Test.E2E.AspNet.OData.BulkInsert +namespace Microsoft.Test.E2E.AspNet.OData.BulkOperation { - internal class BulkInsertEdmModel + internal class BulkOperationEdmModel { public static IEdmModel GetExplicitModel(WebRouteConfiguration configuration) { diff --git a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationPatchHandlers.cs b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationPatchHandlers.cs index a4344344dc..ea6924bad7 100644 --- a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationPatchHandlers.cs +++ b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationPatchHandlers.cs @@ -9,107 +9,148 @@ using System.Collections.Generic; using System.Linq; using Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; using Microsoft.OData.Edm; -using Microsoft.Test.E2E.AspNet.OData.BulkInsert; +using Microsoft.OData.UriParser; namespace Microsoft.Test.E2E.AspNet.OData.BulkOperation { internal class APIHandlerFactory : ODataAPIHandlerFactory { - public APIHandlerFactory(IEdmModel model) : base (model) + public APIHandlerFactory(IEdmModel model) : base(model) { } - public override IODataAPIHandler GetHandler(NavigationPath navigationPath) + public override IODataAPIHandler GetHandler(ODataPath odataPath) { - if(navigationPath != null) + if (odataPath != null) { - var pathItems = navigationPath; + int currentPosition = 0; - int cnt = 0; - - switch (pathItems[cnt].Name) - { - case "Employees": - { - Employee employee; - string msg; - if ((new EmployeeAPIHandler().TryGet(pathItems[cnt].KeyProperties, out employee, out msg)) == ODataAPIResponseStatus.Success) - { - return GetNestedHandlerForEmployee(pathItems, cnt, employee); - } - } - return null; - case "Companies": - return new CompanyAPIHandler(); + if (odataPath.Count == 1) + { + GetHandlerInternal(odataPath.FirstSegment.Identifier, currentPosition); + } - default: - return null; + List pathSegments = odataPath.GetSegments(); - } - + ODataPathSegment currentPathSegment = pathSegments[currentPosition]; + + if (currentPathSegment is EntitySetSegment || currentPathSegment is NavigationPropertySegment || currentPathSegment is SingletonSegment) + { + int keySegmentPosition = ODataPathHelper.GetNextKeySegmentPosition(pathSegments, currentPosition); + KeySegment keySegment = (KeySegment)pathSegments[keySegmentPosition]; + + currentPosition = keySegmentPosition; + + return GetHandlerInternal( + currentPathSegment.Identifier, + currentPosition, + ODataPathHelper.KeySegmentAsDictionary(keySegment), + pathSegments); + } } return null; } - private static IODataAPIHandler GetNestedHandlerForEmployee(List pathItems, int cnt, Employee employee) + private IODataAPIHandler GetHandlerInternal( + string pathName, + int currentPosition, + Dictionary keys = null, + List pathSegments = null) { - cnt++; - if(pathItems.Count <= cnt) + switch (pathName) + { + case "Employees": + Employee employee; + string msg; + if ((new EmployeeAPIHandler().TryGet(keys, out employee, out msg)) == ODataAPIResponseStatus.Success) + { + return GetNestedHandlerForEmployee(pathSegments, currentPosition, employee); + } + return null; + case "Companies": + return new CompanyAPIHandler(); + + default: + return null; + } + } + + private static IODataAPIHandler GetNestedHandlerForEmployee(List pathSegments, int currentPosition, Employee employee) + { + ++currentPosition; + + if (pathSegments.Count <= currentPosition) { return null; } - switch (pathItems[cnt].Name) + ODataPathSegment currentPathSegment = pathSegments[currentPosition]; + + if (currentPathSegment is NavigationPropertySegment) { - case "NewFriends": - CastTypePathItem castItem = pathItems[cnt] as CastTypePathItem; + int keySegmentPosition = ODataPathHelper.GetNextKeySegmentPosition(pathSegments, currentPosition); + KeySegment keySegment = (KeySegment)pathSegments[keySegmentPosition]; + Dictionary keys = ODataPathHelper.KeySegmentAsDictionary(keySegment); - if (castItem != null) - { - if (castItem.CastTypeName == "Microsoft.Test.E2E.AspNet.OData.BulkInsert.MyNewFriend") + currentPosition = keySegmentPosition; + + switch (currentPathSegment.Identifier) + { + case "NewFriends": + ODataPathSegment nextPathSegment = pathSegments[++currentPosition]; + + if (nextPathSegment is TypeSegment) { - MyNewFriend friend = employee.NewFriends.FirstOrDefault(x => x.Id == (int)pathItems[cnt].KeyProperties["Id"]) as MyNewFriend; + currentPosition++; + TypeSegment typeSegment = nextPathSegment as TypeSegment; - if (friend != null) + if (typeSegment.Identifier == "Microsoft.Test.E2E.AspNet.OData.BulkOperation.MyNewFriend") { - switch (pathItems[++cnt].Name) + MyNewFriend friend = employee.NewFriends.FirstOrDefault(x => x.Id == (int)keys["Id"]) as MyNewFriend; + + if (friend != null) { - case "MyNewOrders": - return new MyNewOrderAPIHandler(friend); + switch (pathSegments[++currentPosition].Identifier) + { + case "MyNewOrders": + return new MyNewOrderAPIHandler(friend); - default: - return null; + default: + return null; + } } } } - } - else - { - NewFriend friend = employee.NewFriends.FirstOrDefault(x => x.Id == (int)pathItems[cnt].KeyProperties["Id"]); - - if (friend != null) + else { - switch (pathItems[++cnt].Name) + NewFriend friend = employee.NewFriends.FirstOrDefault(x => x.Id == (int)keys["Id"]); + + if (friend != null) { - case "NewOrders": - return new NewOrderAPIHandler(friend); + switch (pathSegments[++currentPosition].Identifier) + { + case "NewOrders": + return new NewOrderAPIHandler(friend); - default: - return null; + default: + return null; + } } } - } - - return null; + return null; - default: - return null; + default: + return null; + } } + return null; } } @@ -122,95 +163,25 @@ public TypelessAPIHandlerFactory(IEdmModel model, IEdmEntityType entityType) : b this.entityType = entityType; } - public override EdmODataAPIHandler GetHandler(NavigationPath navigationPath) + public override EdmODataAPIHandler GetHandler(ODataPath odataPath) { - if (navigationPath != null) + if (odataPath != null) { - var pathItems = navigationPath; - int cnt = 0; + string pathName = odataPath.GetLastNonTypeNonKeySegment().Identifier; - switch (pathItems[cnt].Name) + switch (pathName) { case "UnTypedEmployees": - { - IEdmStructuredObject employee; - string msg; - if ((new EmployeeEdmAPIHandler(entityType).TryGet(pathItems[cnt].KeyProperties, out employee, out msg)) == ODataAPIResponseStatus.Success) - { - cnt++; - - if (cnt x.Id == (int)pathItems[cnt].KeyProperties["Id"]) as MyNewFriend; - - if (friend != null) - { - switch (pathItems[++cnt].Name) - { - case "MyNewOrders": - return new MyNewOrderAPIHandler(friend); - - default: - return null; - - } - } - } - } - else - { - NewFriend friend = employee.NewFriends.FirstOrDefault(x => x.Id == (int)pathItems[cnt].KeyProperties["Id"]); - - if (friend != null) - { - switch (pathItems[++cnt].Name) - { - case "NewOrders": - return new NewOrderAPIHandler(friend); - - default: - return null; - - } - } - } - - return null; - - default: - return null; - - } - } } @@ -268,7 +239,6 @@ public override ODataAPIResponseStatus TryGet(IDictionary keyVal var id = keyValues["Id"].ToString(); originalObject = CompanyController.Companies.First(x => x.Id == Int32.Parse(id)); - if (originalObject == null) { status = ODataAPIResponseStatus.NotFound; @@ -480,11 +450,10 @@ public override IODataAPIHandler GetNestedHandler(MyNewOrder parent, string navi internal class EmployeeAPIHandler : ODataAPIHandler { - public override ODataAPIResponseStatus TryCreate(IDictionary keyValues, out Employee createdObject, out string errorMessage) { createdObject = null; - errorMessage = string.Empty; + errorMessage = null; try { @@ -503,7 +472,7 @@ public override ODataAPIResponseStatus TryCreate(IDictionary key public override ODataAPIResponseStatus TryDelete(IDictionary keyValues, out string errorMessage) { - errorMessage = string.Empty; + errorMessage = null; try { @@ -525,7 +494,7 @@ public override ODataAPIResponseStatus TryDelete(IDictionary key public override ODataAPIResponseStatus TryGet(IDictionary keyValues, out Employee originalObject, out string errorMessage) { ODataAPIResponseStatus status = ODataAPIResponseStatus.Success; - errorMessage = string.Empty; + errorMessage = null; originalObject = null; try @@ -560,7 +529,7 @@ public override IODataAPIHandler GetNestedHandler(Employee parent, string naviga default: return null; } - + } } @@ -853,8 +822,8 @@ public override ODataAPIResponseStatus TryCreate(IDictionary key try { createdObject = new Order(); - - if(friend.Orders == null) + + if (friend.Orders == null) { friend.Orders = new List(); } @@ -947,7 +916,7 @@ public override ODataAPIResponseStatus TryCreate(IDictionary key { createdObject = new NewFriend(); - if(employee.NewFriends == null) + if (employee.NewFriends == null) { employee.NewFriends = new List(); } @@ -995,7 +964,7 @@ public override ODataAPIResponseStatus TryGet(IDictionary keyVal { var id = keyValues["Id"].ToString(); - if(employee.NewFriends == null) + if (employee.NewFriends == null) { return ODataAPIResponseStatus.NotFound; } @@ -1072,7 +1041,7 @@ public override ODataAPIResponseStatus TryDelete(IDictionary key break; } } - + return ODataAPIResponseStatus.Success; } @@ -1093,17 +1062,17 @@ public override ODataAPIResponseStatus TryGet(IDictionary keyVal try { var id = keyValues["ID"].ToString(); - foreach (var emp in EmployeesController.EmployeesTypeless) - { + foreach (var emp in EmployeesController.EmployeesTypeless) + { object id1; emp.TryGetPropertyValue("ID", out id1); - if(id == id1.ToString()) + if (id == id1.ToString()) { originalObject = emp; break; } - } + } if (originalObject == null) @@ -1127,11 +1096,11 @@ public override EdmODataAPIHandler GetNestedHandler(IEdmStructuredObject parent, { case "UnTypedFriends": return new FriendTypelessAPIHandler(parent, entityType.DeclaredNavigationProperties().First().Type.Definition.AsElementType() as IEdmEntityType); - + default: return null; } - + } } @@ -1141,7 +1110,7 @@ internal class FriendTypelessAPIHandler : EdmODataAPIHandler IEdmEntityType entityType; EdmStructuredObject employee; - public FriendTypelessAPIHandler(IEdmStructuredObject employee, IEdmEntityType entityType) + public FriendTypelessAPIHandler(IEdmStructuredObject employee, IEdmEntityType entityType) { this.employee = employee as EdmStructuredObject; this.entityType = entityType; @@ -1155,7 +1124,7 @@ public override ODataAPIResponseStatus TryCreate(IEdmChangedObject changedObject try { object empid; - if(employee.TryGetPropertyValue("ID" , out empid) && empid as int? == 3) + if (employee.TryGetPropertyValue("ID", out empid) && empid as int? == 3) { throw new Exception("Testing Error"); } @@ -1166,7 +1135,7 @@ public override ODataAPIResponseStatus TryCreate(IEdmChangedObject changedObject var friends = obj as ICollection; - if(friends == null) + if (friends == null) { friends = new List(); } @@ -1192,7 +1161,7 @@ public override ODataAPIResponseStatus TryDelete(IDictionary key try { var id = keyValues.First().Value.ToString(); - if(id == "5") + if (id == "5") { throw new Exception("Testing Error"); } @@ -1211,7 +1180,7 @@ public override ODataAPIResponseStatus TryDelete(IDictionary key friends.Remove(emp); employee.TrySetPropertyValue("UnTypedFriends", friends); - + break; } } @@ -1241,7 +1210,7 @@ public override ODataAPIResponseStatus TryGet(IDictionary keyVal var friends = obj as IList; - if(friends == null) + if (friends == null) { return ODataAPIResponseStatus.NotFound; } @@ -1276,7 +1245,7 @@ public override ODataAPIResponseStatus TryGet(IDictionary keyVal public override EdmODataAPIHandler GetNestedHandler(IEdmStructuredObject parent, string navigationPropertyName) { - return null; + return null; } } diff --git a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkInsertTest.cs b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationTest.cs similarity index 94% rename from test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkInsertTest.cs rename to test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationTest.cs index bce4ce02fd..43e5677536 100644 --- a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkInsertTest.cs +++ b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/BulkOperation/BulkOperationTest.cs @@ -1,11 +1,10 @@ //----------------------------------------------------------------------------- -// +// // Copyright (c) .NET Foundation and Contributors. All rights reserved. // See License.txt in the project root for license information. // //------------------------------------------------------------------------------ -using System; using System.Linq; using System.Net; using System.Net.Http; @@ -16,19 +15,16 @@ using Microsoft.AspNet.OData.Extensions; using Microsoft.AspNet.OData.Routing; using Microsoft.AspNet.OData.Routing.Conventions; -using Microsoft.OData; -using Microsoft.OData.Edm; using Microsoft.Test.E2E.AspNet.OData.Common.Execution; using Microsoft.Test.E2E.AspNet.OData.Common.Extensions; -using Microsoft.Test.E2E.AspNet.OData.ModelBuilder; using Newtonsoft.Json.Linq; using Xunit; -namespace Microsoft.Test.E2E.AspNet.OData.BulkInsert +namespace Microsoft.Test.E2E.AspNet.OData.BulkOperation { - public class BulkInsertTest : WebHostTestBase + public class BulkOperationTest : WebHostTestBase { - public BulkInsertTest(WebHostTestFixture fixture) + public BulkOperationTest(WebHostTestFixture fixture) :base(fixture) { } @@ -40,8 +36,8 @@ protected override void UpdateConfiguration(WebRouteConfiguration configuration) configuration.Routes.Clear(); configuration.Count().Filter().OrderBy().Expand().MaxTop(null).Select(); - configuration.MapODataServiceRoute("convention", "convention", BulkInsertEdmModel.GetConventionModel(configuration)); - configuration.MapODataServiceRoute("explicit", "explicit", BulkInsertEdmModel.GetExplicitModel(configuration), new DefaultODataPathHandler(), ODataRoutingConventions.CreateDefault()); + configuration.MapODataServiceRoute("convention", "convention", BulkOperationEdmModel.GetConventionModel(configuration)); + configuration.MapODataServiceRoute("explicit", "explicit", BulkOperationEdmModel.GetExplicitModel(configuration), new DefaultODataPathHandler(), ODataRoutingConventions.CreateDefault()); configuration.EnsureInitialized(); } @@ -60,11 +56,6 @@ public async Task PatchEmployee_WithUpdates() 'Friends@odata.delta':[{'Id':1,'Name':'Test2'},{'Id':2,'Name':'Test3'}] }"; - //content = @"{ - // 'Name':'Sql' , 'FavoriteSports' :{'Sport': 'Cricket'} - - // }"; - var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); StringContent stringContent = new StringContent(content: content, encoding: Encoding.UTF8, mediaType: "application/json"); @@ -89,7 +80,6 @@ public async Task PatchEmployee_WithUpdates() Assert.Equal(2, result.Count); Assert.Contains("Test2", result.ToString()); } - } [Fact] @@ -129,10 +119,8 @@ public async Task PatchEmployee_WithUpdates_WithEmployees() Assert.Contains("400", result.ToString()); Assert.Contains("900", result.ToString()); } - } - [Fact] public async Task PatchEmployee_WithUpdates_Friends() { @@ -140,7 +128,7 @@ public async Task PatchEmployee_WithUpdates_Friends() string requestUri = this.BaseAddress + "/convention/Employees(1)/Friends"; - var content = @"{'@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkInsert.Friend', + var content = @"{'@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkOperation.Friend', '@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees(1)/Friends/$delta', 'value':[{ 'Id':1,'Name':'Friend1'}, { 'Id':2,'Name':'Friend2'}] }"; @@ -216,7 +204,6 @@ public async Task PatchEmployee_WithDeletes_Friends() Assert.Single(result); Assert.Contains("Friend2", result.ToString()); } - } [Fact] @@ -226,11 +213,10 @@ public async Task PatchEmployee_WithDeletes_Friends_WithNestedTypes() string requestUri = this.BaseAddress + "/convention/Employees(1)/Friends"; - var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees(1)/Friends/$delta', '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkInsert.Friend', + var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees(1)/Friends/$delta', '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkOperation.Friend', 'value':[{ '@odata.removed' : {'reason':'changed'}, 'Id':1, 'Orders@odata.delta' :[{'Id':1,'Price': 10}, {'Id':2,'Price': 20} ] },{ 'Id':2,'Name':'Friend2'}] }"; - var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); requestForPost.Headers.Add("OData-Version", "4.01"); @@ -259,7 +245,6 @@ public async Task PatchEmployee_WithDeletes_Friends_WithNestedTypes() Assert.Single(result); Assert.Contains("Friend2", result.ToString()); } - } [Fact] @@ -269,11 +254,10 @@ public async Task PatchEmployee_WithDeletes_Friends_WithNestedDeletes() string requestUri = this.BaseAddress + "/convention/Employees(1)/Friends"; - var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees(1)/Friends/$delta', '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkInsert.Friend', + var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees(1)/Friends/$delta', '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkOperation.Friend', 'value':[{ '@odata.removed' : {'reason':'changed'}, 'Id':1, 'Orders@odata.delta' :[{'@odata.removed' : {'reason':'changed'}, 'Id':1,'Price': 10}, {'Id':2,'Price': 20} ] },{ 'Id':2,'Name':'Friend2'}] }"; - var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); requestForPost.Headers.Add("OData-Version", "4.01"); @@ -290,7 +274,6 @@ public async Task PatchEmployee_WithDeletes_Friends_WithNestedDeletes() Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Contains(expected.ToLower(), json.ToString().ToLower()); } - requestUri = this.BaseAddress + "/convention/Employees(1)/Friends"; using (HttpResponseMessage response = await this.Client.GetAsync(requestUri)) @@ -302,7 +285,6 @@ public async Task PatchEmployee_WithDeletes_Friends_WithNestedDeletes() Assert.Contains("Friend2", result.ToString()); } - } [Fact] @@ -311,19 +293,15 @@ public async Task PatchEmployee_WithAdds_Friends_WithAnnotations() //Arrange string requestUri = this.BaseAddress + "/convention/Employees(1)/NewFriends"; - //{ '@odata.removed' : {'reason':'changed'}, 'Id':1},{ '@odata.removed' : {'reason':'deleted'}, 'Id':2}, var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees(1)/NewFriends/$delta', 'value':[{ 'Id':3, 'Age':35, '@NS.Test':1}] }"; - var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); requestForPost.Headers.Add("OData-Version", "4.01"); requestForPost.Content = new StringContent(content); requestForPost.Content.Headers.ContentType= MediaTypeWithQualityHeaderValue.Parse("application/json"); - // StringContent stringContent = new StringContent(content: content, encoding: Encoding.UTF8, mediaType: "application/json"); - //requestForPost.Content = stringContent; Client.DefaultRequestHeaders.Add("Prefer", @"odata.include-annotations=""*"""); @@ -343,11 +321,9 @@ public async Task PatchEmployee_WithFailedAdds_Friends() //Arrange string requestUri = this.BaseAddress + "/convention/Employees(1)/NewFriends"; - //{ '@odata.removed' : {'reason':'changed'}, 'Id':1},{ '@odata.removed' : {'reason':'deleted'}, 'Id':2}, var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees(1)/NewFriends/$delta', 'value':[{ 'Id':3, 'Age':3, '@NS.Test':1}] }"; - var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); requestForPost.Headers.Add("OData-Version", "4.01"); @@ -371,12 +347,10 @@ public async Task PatchEmployee_WithFailedDeletes_Friends() { //Arrange string requestUri = this.BaseAddress + "/convention/Employees(2)/NewFriends"; - //{ '@odata.removed' : {'reason':'changed'}, 'Id':1},{ '@odata.removed' : {'reason':'deleted'}, 'Id':2}, var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees(1)/NewFriends/$delta', 'value':[{ '@odata.removed' : {'reason':'changed'}, 'Id':2, '@NS.Test':1}] }"; - var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); requestForPost.Headers.Add("OData-Version", "4.01"); @@ -394,21 +368,17 @@ public async Task PatchEmployee_WithFailedDeletes_Friends() Assert.Contains("$delta", json); Assert.Contains(expected, json.ToString()); } - } - [Fact] public async Task PatchEmployee_WithFailedOperation_WithAnnotations() { //Arrange string requestUri = this.BaseAddress + "/convention/Employees(2)/NewFriends"; - //{ '@odata.removed' : {'reason':'changed'}, 'Id':1},{ '@odata.removed' : {'reason':'deleted'}, 'Id':2}, var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees(2)/NewFriends/$delta', 'value':[{ '@odata.removed' : {'reason':'changed'}, 'Id':2, '@Core.ContentID':3, '@NS.Test2':'testing'}] }"; - var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); requestForPost.Headers.Add("OData-Version", "4.01"); @@ -419,7 +389,6 @@ public async Task PatchEmployee_WithFailedOperation_WithAnnotations() var expected = "/convention/$metadata#NewFriends/$delta\",\"value\":[{\"@NS.Test2\":\"testing\",\"@Core.ContentID\":3," + "\"@Core.DataModificationException\":{\"@type\":\"#Org.OData.Core.V1.DataModificationExceptionType\"},\"Id\":2,\"Name\":null,\"Age\":15}]}"; - using (HttpResponseMessage response = await this.Client.SendAsync(requestForPost)) { var json = response.Content.ReadAsStringAsync().Result; @@ -430,17 +399,14 @@ public async Task PatchEmployee_WithFailedOperation_WithAnnotations() Assert.Contains("Core.DataModificationException", str); Assert.Contains(expected, str); } - } - [Fact] public async Task PatchUntypedEmployee_WithAdds_Friends_Untyped() { //Arrange string requestUri = this.BaseAddress + "/convention/UnTypedEmployees"; - //{ '@odata.removed' : {'reason':'changed'}, 'Id':1},{ '@odata.removed' : {'reason':'deleted'}, 'Id':2}, var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees(2)/UnTypedFriends/$delta', 'value':[{ 'Id':3, 'Age':35,}] }"; @@ -454,7 +420,6 @@ public async Task PatchUntypedEmployee_WithAdds_Friends_Untyped() }] }"; - var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); requestForPost.Headers.Add("OData-Version", "4.01"); @@ -466,7 +431,6 @@ public async Task PatchUntypedEmployee_WithAdds_Friends_Untyped() "[{\"Id\":1,\"Name\":\"Friend1\",\"Age\":0},{\"Id\":2,\"Name\":\"Friend2\",\"Age\":0}]},{\"ID\":2,\"Name\":\"Employee2\",\"UnTypedFriends@delta\":" + "[{\"Id\":3,\"Name\":\"Friend3\",\"Age\":0},{\"Id\":4,\"Name\":\"Friend4\",\"Age\":0}]}]}"; - using (HttpResponseMessage response = await this.Client.SendAsync(requestForPost)) { var json = response.Content.ReadAsStringAsync().Result; @@ -475,19 +439,16 @@ public async Task PatchUntypedEmployee_WithAdds_Friends_Untyped() } } - [Fact] public async Task PatchEmployee_WithAdds_Friends_WithNested_Untyped() { //Arrange string requestUri = this.BaseAddress + "/convention/Employees(1)/UnTypedFriends"; - //{ '@odata.removed' : {'reason':'changed'}, 'Id':1},{ '@odata.removed' : {'reason':'deleted'}, 'Id':2}, var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees(1)/UnTypedFriends/$delta', 'value':[{ 'Id':2, 'Name': 'Friend007', 'Age':35,'Address@odata.delta':{'Id':1, 'Street' : 'Abc 123'}, '@NS.Test':1}] }"; - var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); StringContent stringContent = new StringContent(content: content, encoding: Encoding.UTF8, mediaType: "application/json"); @@ -501,22 +462,18 @@ public async Task PatchEmployee_WithAdds_Friends_WithNested_Untyped() json.ToString().Contains("$delta"); json.ToString().Contains("@NS.Test"); } - } - [Fact] public async Task PatchEmployee_WithAdds_Friends_WithAnnotations_Untyped() { //Arrange string requestUri = this.BaseAddress + "/convention/Employees(2)/UnTypedFriends"; - //{ '@odata.removed' : {'reason':'changed'}, 'Id':1},{ '@odata.removed' : {'reason':'deleted'}, 'Id':2}, var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees(2)/UnTypedFriends/$delta', 'value':[{ 'Id':2, 'Age':35, '@NS.Test':1}] }"; - var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); StringContent stringContent = new StringContent(content: content, encoding: Encoding.UTF8, mediaType: "application/json"); @@ -538,12 +495,10 @@ public async Task PatchEmployee_WithFailedAdds_Friends_Untyped() //Arrange string requestUri = this.BaseAddress + "/convention/Employees(3)/UnTypedFriends"; - //{ '@odata.removed' : {'reason':'changed'}, 'Id':1},{ '@odata.removed' : {'reason':'deleted'}, 'Id':2}, var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees(3)/UnTypedFriends/$delta', 'value':[{ 'Id':3, 'Age':3, '@NS.Test':1}] }"; - var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); StringContent stringContent = new StringContent(content: content, encoding: Encoding.UTF8, mediaType: "application/json"); @@ -568,7 +523,6 @@ public async Task PatchEmployee_WithFailedDeletes_Friends_Untyped() 'value':[{ '@odata.removed' : {'reason':'changed'}, 'Id':5, '@NS.Test':1}] }"; - var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); StringContent stringContent = new StringContent(content: content, encoding: Encoding.UTF8, mediaType: "application/json"); @@ -582,21 +536,17 @@ public async Task PatchEmployee_WithFailedDeletes_Friends_Untyped() Assert.Contains("@Core.DataModificationException", json.ToString()); Assert.Contains("@NS.Test", json.ToString()); } - } - [Fact] public async Task PatchEmployee_WithFailedOperation_WithAnnotations_Untyped() { //Arrange string requestUri = this.BaseAddress + "/convention/Employees(3)/UnTypedFriends"; - //{ '@odata.removed' : {'reason':'changed'}, 'Id':1},{ '@odata.removed' : {'reason':'deleted'}, 'Id':2}, var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Employees(3)/UnTypedFriends/$delta', 'value':[{ '@odata.removed' : {'reason':'changed'}, 'Id':5, '@Core.ContentID':3, '@NS.Test2':'testing'}] }"; - var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); StringContent stringContent = new StringContent(content: content, encoding: Encoding.UTF8, mediaType: "application/json"); @@ -613,7 +563,6 @@ public async Task PatchEmployee_WithFailedOperation_WithAnnotations_Untyped() Assert.Contains("Core.DataModificationException", str); Assert.Contains("Core.ContentID", str); } - } [Fact] @@ -624,11 +573,11 @@ public async Task PatchEmployee_WithUpdates_Employees() string requestUri = this.BaseAddress + "/convention/Employees"; var content = @"{'@odata.context':'"+ this.BaseAddress + @"/convention/$metadata#Employees/$delta', - 'value':[{ '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkInsert.Employee', 'ID':1,'Name':'Employee1', + 'value':[{ '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkOperation.Employee', 'ID':1,'Name':'Employee1', 'Friends@odata.delta':[{'Id':1,'Name':'Friend1', 'Orders@odata.delta' :[{'Id':1,'Price': 10}, {'Id':2,'Price': 20} ] },{'Id':2,'Name':'Friend2'}] }, - { '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkInsert.Employee', 'ID':2,'Name':'Employee2', + { '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkOperation.Employee', 'ID':2,'Name':'Employee2', 'Friends@odata.delta':[{'Id':3,'Name':'Friend3', 'Orders@odata.delta' :[{'Id':3,'Price': 30}, {'Id':4,'Price': 40} ]},{'Id':4,'Name':'Friend4'}] }] @@ -676,7 +625,6 @@ public async Task PatchEmployee_WithUpdates_Employees() } } - [Fact] public async Task PatchEmployee_WithDelete() { @@ -749,7 +697,6 @@ public async Task PatchEmployee_WithODataBind() } } - [Fact] public async Task PatchEmployee_WithAddUpdateAndDelete() { @@ -789,7 +736,6 @@ public async Task PatchEmployee_WithAddUpdateAndDelete() } } - [Fact] public async Task PatchEmployee_WithMultipleUpdatesinOrder1() { @@ -870,7 +816,6 @@ public async Task PatchEmployee_WithMultipleUpdatesinOrder2() } } - [Fact] public async Task PatchCompanies_WithUpdates_ODataId() { @@ -879,13 +824,11 @@ public async Task PatchCompanies_WithUpdates_ODataId() string requestUri = this.BaseAddress + "/convention/Companies"; var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Companies/$delta', - 'value':[{ '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkInsert.Company', 'Id':1,'Name':'Company01', + 'value':[{ '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkOperation.Company', 'Id':1,'Name':'Company01', 'OverdueOrders@odata.delta':[{'@odata.id':'Employees(1)/NewFriends(1)/NewOrders(1)', 'Quantity': 9}] - }] }"; - var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); requestForPost.Headers.Add("OData-Version", "4.01"); @@ -902,8 +845,6 @@ public async Task PatchCompanies_WithUpdates_ODataId() Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Contains(expected, json.ToString()); } - - } [Fact] @@ -914,13 +855,11 @@ public async Task PatchCompanies_WithUpdates_ODataId_WithCast() string requestUri = this.BaseAddress + "/convention/Companies"; var content = @"{'@odata.context':'" + this.BaseAddress + @"/convention/$metadata#Companies/$delta', - 'value':[{ '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkInsert.Company', 'Id':1,'Name':'Company02', - 'MyOverdueOrders@odata.delta':[{'@odata.id':'Employees(2)/NewFriends(2)/Microsoft.Test.E2E.AspNet.OData.BulkInsert.MyNewFriend/MyNewOrders(2)', 'Quantity': 9}] - + 'value':[{ '@odata.type': '#Microsoft.Test.E2E.AspNet.OData.BulkOperation.Company', 'Id':1,'Name':'Company02', + 'MyOverdueOrders@odata.delta':[{'@odata.id':'Employees(2)/NewFriends(2)/Microsoft.Test.E2E.AspNet.OData.BulkOperation.MyNewFriend/MyNewOrders(2)', 'Quantity': 9}] }] }"; - var requestForPost = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); requestForPost.Headers.Add("OData-Version", "4.01"); @@ -937,11 +876,8 @@ public async Task PatchCompanies_WithUpdates_ODataId_WithCast() Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Contains(expected, json.ToString()); } - - } - [Fact] public async Task PatchUntypedEmployee_WithOdataId() { @@ -974,6 +910,9 @@ public async Task PatchUntypedEmployee_WithOdataId() } } + #endregion + + #region Post [Fact] public async Task PostCompany_WithODataId() { @@ -983,8 +922,6 @@ public async Task PostCompany_WithODataId() var content = @"{'Id':3,'Name':'Company03', 'OverdueOrders':[{'@odata.id':'Employees(1)/NewFriends(1)/NewOrders(1)'}] - - }"; var requestForPost = new HttpRequestMessage(new HttpMethod("POST"), requestUri); @@ -998,10 +935,8 @@ public async Task PostCompany_WithODataId() var json = response.Content.ReadAsStringAsync().Result; Assert.Equal(HttpStatusCode.OK, response.StatusCode); } - } - [Fact] public async Task PostCompany_WithODataId_AndWithout() { @@ -1011,8 +946,6 @@ public async Task PostCompany_WithODataId_AndWithout() var content = @"{'Id':4,'Name':'Company04', 'OverdueOrders':[{'@odata.id':'Employees(1)/NewFriends(1)/NewOrders(1)'},{Price:30}] - - }"; var requestForPost = new HttpRequestMessage(new HttpMethod("POST"), requestUri); @@ -1026,12 +959,35 @@ public async Task PostCompany_WithODataId_AndWithout() var json = response.Content.ReadAsStringAsync().Result; Assert.Equal(HttpStatusCode.OK, response.StatusCode); } - } + [Fact] + public async Task PostEmployee_WithCreateFriends() + { + //Arrange + string requestUri = this.BaseAddress + "/convention/Employees"; - #endregion + var content = @"{ + 'Name':'SqlUD', + 'Friends':[{ 'Id':1001, 'Name' : 'Friend 1001', 'Age': 31},{ 'Id':1002, 'Name' : 'Friend 1002', 'Age': 32},{ 'Id':1003, 'Name' : 'Friend 1003', 'Age': 33}] + }"; + + var requestForPatch = new HttpRequestMessage(new HttpMethod("POST"), requestUri); + + StringContent stringContent = new StringContent(content: content, encoding: Encoding.UTF8, mediaType: "application/json"); + requestForPatch.Content = stringContent; + //Act & Assert + using (HttpResponseMessage response = await this.Client.SendAsync(requestForPatch)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var json = response.Content.ReadAsStringAsync().Result; + Assert.Contains("SqlUD", json); + //Assert.Contains("Friends", json); // Activate after fixing serialization issue for DeepInsert nested resources + } + } + + #endregion } } \ No newline at end of file diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Microsoft.AspNet.OData.Test.Shared.projitems b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Microsoft.AspNet.OData.Test.Shared.projitems index acd5930170..48f8deb8fb 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Microsoft.AspNet.OData.Test.Shared.projitems +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Microsoft.AspNet.OData.Test.Shared.projitems @@ -228,6 +228,8 @@ + + @@ -334,6 +336,7 @@ + diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/ODataPathExtensionsTest.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/ODataPathExtensionsTest.cs new file mode 100644 index 0000000000..09b722b044 --- /dev/null +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/ODataPathExtensionsTest.cs @@ -0,0 +1,208 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + + +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Test.Common.Models; +using Microsoft.OData.UriParser; +using Xunit; + +namespace Microsoft.AspNet.OData.Test +{ + public class ODataPathExtensionsTest + { + SampleEdmModel model = new SampleEdmModel(); + KeyValuePair[] customerKey = new[] { new KeyValuePair("Id", "1") }; + KeyValuePair[] friendsKey = new[] { new KeyValuePair("Id", "1001") }; + + [Fact] + public void GetKeys_PathWithTypeSegmentReturnsKeysFromLastKeySegment() + { + // From this path Customers(1) + // GetKeys() should return { "Id": "1" } + + // Arrange + ODataPath path = new ODataPath(new ODataPathSegment[] + { + new EntitySetSegment(model.customerSet), + new KeySegment(customerKey, model.customerType, model.customerSet), + new TypeSegment(model.vipCustomerType, model.customerType, null) + }); + + // Act + Dictionary keys = path.GetKeys(); + + // Assert + Assert.Single(keys); + Assert.Equal("Id", keys.First().Key); + Assert.Equal("1", keys.First().Value); + } + + [Fact] + public void GetKeys_PathWithNoSegmentReturnsEmptyCollection() + { + // Arrange + ODataPath path = new ODataPath(new ODataPathSegment[] + { + }); + + // Act + Dictionary keys = path.GetKeys(); + + // Assert + Assert.Empty(keys); + } + + [Fact] + public void GetKeys_PathWithNoKeySegmentReturnsEmptyCollection() + { + // Arrange + ODataPath path = new ODataPath(new ODataPathSegment[] + { + new EntitySetSegment(model.customerSet), + new TypeSegment(model.vipCustomerType, model.customerType, null) + }); + + // Act + Dictionary keys = path.GetKeys(); + + // Assert + Assert.Empty(keys); + } + + [Fact] + public void GetKeys_PathWithNavPropReturnsKeysFromLastKeySegment() + { + // From this path Customers(1)/Friends(1001) + // GetKeys() should return { "Id": "1001" } + + // Arrange + ODataPath path = new ODataPath(new ODataPathSegment[] + { + new EntitySetSegment(model.customerSet), + new KeySegment(customerKey, model.customerType, model.customerSet), + new NavigationPropertySegment(model.friendsProperty, model.customerSet), + new KeySegment(friendsKey, model.personType, null) + }); + + // Act + Dictionary keys = path.GetKeys(); + + // Assert + Assert.Single(keys); + Assert.Equal("Id", keys.First().Key); + Assert.Equal("1001", keys.First().Value); + } + + [Fact] + public void GetLastNonTypeNonKeySegment_TypeSegmentAsLastSegmentReturnsCorrectSegment() + { + // If the path is Customers(1)/Friends(1001)/Ns.UniquePerson where Ns.UniquePerson is a type segment + // and 1001 is a KeySegment, + // GetLastNonTypeNonKeySegment() should return Friends NavigationPropertySegment. + + // Arrange + ODataPath path = new ODataPath(new ODataPathSegment[] + { + new EntitySetSegment(model.customerSet), + new KeySegment(customerKey, model.customerType, model.customerSet), + new NavigationPropertySegment(model.friendsProperty, model.customerSet), + new KeySegment(friendsKey, model.personType, null), + new TypeSegment(model.uniquePersonType, model.personType, null) + }); + + // Act + ODataPathSegment segment = path.GetLastNonTypeNonKeySegment(); + + // Assert + Assert.Equal("Friends", segment.Identifier); + Assert.True(segment is NavigationPropertySegment); + } + + [Fact] + public void GetLastNonTypeNonKeySegment_KeySegmentAsLastSegmentReturnsCorrectSegment() + { + // If the path is Customers(1)/Friends(1001) where1001 is a KeySegment, + // GetLastNonTypeNonKeySegment() should return Friends NavigationPropertySegment. + + // Arrange + ODataPath path = new ODataPath(new ODataPathSegment[] + { + new EntitySetSegment(model.customerSet), + new KeySegment(customerKey, model.customerType, model.customerSet), + new NavigationPropertySegment(model.friendsProperty, model.customerSet), + new KeySegment(friendsKey, model.personType, null) + }); + + // Act + ODataPathSegment segment = path.GetLastNonTypeNonKeySegment(); + + // Assert + Assert.Equal("Friends", segment.Identifier); + Assert.True(segment is NavigationPropertySegment); + } + + [Fact] + public void GetLastNonTypeNonKeySegment_SingleSegmentPathReturnsCorrectSegment() + { + // If the path is /Customers, + // GetLastNonTypeNonKeySegment() should return Customers EntitySetSegment. + + // Arrange + ODataPath path = new ODataPath(new ODataPathSegment[] + { + new EntitySetSegment(model.customerSet) + }); + + // Act + ODataPathSegment segment = path.GetLastNonTypeNonKeySegment(); + + // Assert + Assert.True(segment is EntitySetSegment); + } + + [Fact] + public void GetLastNonTypeNonKeySegment_SingleKeySegmentPathReturnsNull() + { + // If the path is /1, + // GetLastNonTypeNonKeySegment() should return null since this is a KeySegment. + + // Arrange + ODataPath path = new ODataPath(new ODataPathSegment[] + { + new KeySegment(customerKey, model.customerType, model.customerSet) + }); + + // Act + ODataPathSegment segment = path.GetLastNonTypeNonKeySegment(); + + // Assert + Assert.Null(segment); + } + + [Fact] + public void GetLastNonTypeNonKeySegment_SingleTypeSegmentPathReturnsNull() + { + // If the path is /Ns.UniquePerson, + // GetLastNonTypeNonKeySegment() should return null since this is a TypeSegment. + + // Arrange + ODataPath path = new ODataPath(new ODataPathSegment[] + { + new TypeSegment(model.uniquePersonType, model.personType, null) + }); + + // Act + ODataPathSegment segment = path.GetLastNonTypeNonKeySegment(); + + // Assert + Assert.Null(segment); + } + } +} diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/ODataPathHelperTest.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/ODataPathHelperTest.cs new file mode 100644 index 0000000000..d54e622cb4 --- /dev/null +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/ODataPathHelperTest.cs @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Test.Common; +using Microsoft.AspNet.OData.Test.Common.Models; +using Microsoft.OData.UriParser; +using Xunit; + +namespace Microsoft.AspNet.OData.Test +{ + public class ODataPathHelperTest + { + SampleEdmModel model = new SampleEdmModel(); + KeyValuePair[] customerKey = new[] { new KeyValuePair("Id", "1"), new KeyValuePair("AlternateId", "2") }; + KeyValuePair[] friendsKey = new[] { new KeyValuePair("Id", "1001") }; + + [Fact] + public void GetKeysFromKeySegment_ReturnsCorrectKeysDictionary() + { + // Arrange + KeySegment keySegment = new KeySegment(customerKey, model.customerType, model.customerSet); + + // Act + Dictionary keys = ODataPathHelper.KeySegmentAsDictionary(keySegment); + + // Assert + Assert.Equal(2, keys.Count); + Assert.Equal("Id", keys.First().Key); + Assert.Equal("1", keys.First().Value); + Assert.Equal("AlternateId", keys.Last().Key); + Assert.Equal("2", keys.Last().Value); + } + + [Fact] + public void GetKeysFromKeySegment_ThrowsExceptionForNullKeySegment() + { + KeySegment keySegment = null; + + ExceptionAssert.ThrowsArgumentNull( + () => ODataPathHelper.KeySegmentAsDictionary(keySegment), + nameof(keySegment)); + } + + [Fact] + public void GetNextKeySegmentPosition_ReturnsCorrectPosition() + { + // If the path is Customers(1)/Friends(1001)/Ns.UniqueFriend where Ns.UniqueFriend is a type segment + // and 1001 is a KeySegment, and the starting position is index 1, the next keysegment position is index 3. + + // Arrange + ODataPath path = new ODataPath(new ODataPathSegment[] + { + new EntitySetSegment(model.customerSet), + new KeySegment(customerKey, model.customerType, model.customerSet), + new NavigationPropertySegment(model.friendsProperty, model.customerSet), + new KeySegment(friendsKey, model.personType, null), + new TypeSegment(model.uniquePersonType, model.personType, null) + }); + + // Act + int position = ODataPathHelper.GetNextKeySegmentPosition(path.AsList(), 1); + + // Assert + Assert.Equal(3, position); + } + + [Fact] + public void GetNextKeySegmentPosition_ReturnsNegativeOneIfNoKeySegmentIsFound() + { + // If the path is Customers(1)/Friends(1001)/Ns.UniqueFriend where Ns.UniqueFriend is a type segment + // and 1001 is a KeySegment, and the starting position is index 1, the next keysegment position is index 3. + + // Arrange + ODataPath path = new ODataPath(new ODataPathSegment[] + { + new EntitySetSegment(model.customerSet), + new KeySegment(customerKey, model.customerType, model.customerSet), + new NavigationPropertySegment(model.friendsProperty, model.customerSet), + new TypeSegment(model.uniquePersonType, model.personType, null) + }); + + // Act + int position = ODataPathHelper.GetNextKeySegmentPosition(path.AsList(), 1); + + // Assert + Assert.Equal(-1, position); + } + + [Fact] + public void GetNextKeySegmentPosition_ReturnsNegativeOneIfInvalidPositionIsPassed() + { + // If the path is Customers(1)/Friends(1001)/Ns.UniqueFriend where Ns.UniqueFriend is a type segment + // and 1001 is a KeySegment, and the starting position is index 1, the next keysegment position is index 3. + + // Arrange + ODataPath path = new ODataPath(new ODataPathSegment[] + { + new EntitySetSegment(model.customerSet), + new KeySegment(customerKey, model.customerType, model.customerSet), + new NavigationPropertySegment(model.friendsProperty, model.customerSet), + new TypeSegment(model.uniquePersonType, model.personType, null) + }); + + // Act + int position = ODataPathHelper.GetNextKeySegmentPosition(path.AsList(), 10); + + // Assert + Assert.Equal(-1, position); + } + } +} diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/TestCommon/Models/SampleEdmModel.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/TestCommon/Models/SampleEdmModel.cs new file mode 100644 index 0000000000..89e7db509b --- /dev/null +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/TestCommon/Models/SampleEdmModel.cs @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Test.Common.Models +{ + public class SampleEdmModel + { + public EdmNavigationProperty friendsProperty; + public EdmEntityType customerType; + public EdmEntityType personType; + public EdmEntityType uniquePersonType; + public EdmEntityType vipCustomerType; + public IEdmEntitySet customerSet; + public EdmModel model; + + public SampleEdmModel() + { + model = new EdmModel(); + EdmEntityContainer container = new EdmEntityContainer("NS", "Container"); + + personType = new EdmEntityType("NS", "Person"); + personType.AddKeys(personType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32, isNullable: false)); + personType.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String, isNullable: false); + + customerType = new EdmEntityType("NS", "Customer"); + customerType.AddKeys(customerType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32, isNullable: false)); + customerType.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String, isNullable: false); + friendsProperty = customerType.AddUnidirectionalNavigation( + new EdmNavigationPropertyInfo + { + ContainsTarget = true, + Name = "Friends", + Target = personType, + TargetMultiplicity = EdmMultiplicity.Many + }); + + vipCustomerType = new EdmEntityType("NS", "VipCustomer", customerType); + vipCustomerType.AddKeys(vipCustomerType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32, isNullable: false)); + vipCustomerType.AddStructuralProperty("VipName", EdmPrimitiveTypeKind.String, isNullable: false); + + uniquePersonType = new EdmEntityType("NS", "UniquePerson", personType); + uniquePersonType.AddKeys(uniquePersonType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32, isNullable: false)); + uniquePersonType.AddStructuralProperty("UniqueName", EdmPrimitiveTypeKind.String, isNullable: false); + + model.AddElement(customerType); + model.AddElement(personType); + model.AddElement(uniquePersonType); + model.AddElement(vipCustomerType); + model.AddElement(container); + + customerSet = container.AddEntitySet("Customers", customerType); + } + } +} \ No newline at end of file