diff --git a/src/Microsoft.Restier.Providers.EntityFramework/Submit/ChangeSetInitializer.cs b/src/Microsoft.Restier.Providers.EntityFramework/Submit/ChangeSetInitializer.cs index ad8f2985..cf8eab86 100644 --- a/src/Microsoft.Restier.Providers.EntityFramework/Submit/ChangeSetInitializer.cs +++ b/src/Microsoft.Restier.Providers.EntityFramework/Submit/ChangeSetInitializer.cs @@ -220,7 +220,7 @@ private void SetValues(DbEntityEntry dbEntry, DataModificationItem item, Type re propertyPair.Key)); } - value = Activator.CreateInstance(type); + value = propertyEntry.CurrentValue; SetValues(value, type, dic); } diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/E2ETestBase.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/E2ETestBase.cs index 2f212378..e2855c5f 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/E2ETestBase.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/E2ETestBase.cs @@ -155,6 +155,21 @@ protected void TestPostStatusCodeIs(string uriStringAfterServiceRoot, int status Assert.Equal(statusCode, response.StatusCode); } } + + protected async void TestPatchStatusCodeIs(string uriStringAfterServiceRoot, string patchContent, HttpStatusCode statusCode) + { + var requestUri = string.Format("{0}/{1}", this.ServiceBaseUri, uriStringAfterServiceRoot); + var request = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri); + + request.Content = new StringContent(patchContent); + request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); + + HttpClient client = new HttpClient(); + HttpResponseMessage response = await client.SendAsync(request); + + Assert.Equal(statusCode, response.StatusCode); + } + #endregion protected void ResetDataSource() diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/Microsoft.OData.Service.Sample.Tests.csproj b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/Microsoft.OData.Service.Sample.Tests.csproj index 1938e67e..d845fe7a 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/Microsoft.OData.Service.Sample.Tests.csproj +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/Microsoft.OData.Service.Sample.Tests.csproj @@ -80,6 +80,7 @@ ..\..\..\packages\FluentAssertions.4.13.0\lib\net45\FluentAssertions.Core.dll True + ..\..\..\packages\Microsoft.OData.Client.6.15.0\lib\net40\Microsoft.OData.Client.dll True @@ -96,6 +97,10 @@ ..\..\..\packages\Microsoft.Spatial.6.15.0\lib\portable-net45+win+wpa81\Microsoft.Spatial.dll True + + ..\..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/TrippinE2ETestCases.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/TrippinE2ETestCases.cs index 3f96a6cc..2b617389 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/TrippinE2ETestCases.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/TrippinE2ETestCases.cs @@ -5,9 +5,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net; using Microsoft.OData.Client; using Microsoft.OData.Core; using Microsoft.OData.Service.Sample.Trippin.Models; +using Newtonsoft.Json; using Xunit; namespace Microsoft.OData.Service.Sample.Tests @@ -295,11 +297,11 @@ public void CURDComputedImmutableProperty() // Query the updated entity this.TestClientContext.Detach(employee); employee = this.TestClientContext.Orders.Where(e => e.PersonId == personId && e.OrderId == orderId).First(); - + // both computed property and immutable property should not have new value Assert.Equal(400, employee.Price); Assert.NotEqual("ShouldBeIgnored2", employee.ComputedProperty); - + // Immutable property has value set during insert. Assert.NotEqual("ShouldBeIgnored2", employee.ImmutableProperty); Assert.Equal("ShouldNotBeIgnored", employee.ImmutableProperty); @@ -409,9 +411,9 @@ public void UQProperty() Assert.Equal("Cooper", lastName); // Update a property - Dictionary headers = new Dictionary() + Dictionary headers = new Dictionary() { - { "Content-Type", "application/json" } + { "Content-Type", "application/json" } }; HttpWebRequestMessage request = new HttpWebRequestMessage( @@ -566,12 +568,12 @@ public void QueryOptions() // skip people2 = this.TestClientContext.People.Skip((int)(personId - 1)).ToList(); Assert.Equal(personId, people2.First().PersonId); - + // count var countQuery = this.TestClientContext.People.IncludeTotalCount().Skip(1).Take(2) as DataServiceQuery; var response = countQuery.Execute() as QueryOperationResponse; Assert.Equal(response.TotalCount, 14); - + // count with expand countQuery = this.TestClientContext.People.IncludeTotalCount().Expand("Friends").Skip(1).Take(2) as DataServiceQuery; response = countQuery.Execute() as QueryOperationResponse; @@ -638,7 +640,7 @@ public void FilterBuiltInDateFunctions() Assert.True(flight1.All(f => f.StartsAt.Second == startDate.Second)); // Following built-in functions are not supported now. - // fractionalseconds + // fractionalseconds // date // time // totaloffsetminutes @@ -1119,5 +1121,32 @@ public void ConventionBasedChangeSetAuthorizerTest() "The current user does not have permission to delete entities from the EntitySet 'Trips'.", clientException.Message); } + + [Fact] + public void TestPatchSuccessfully() + { + // Get origin content. + var uriStringAfterServiceRoot = "Orders(PersonId=1, OrderId=1)"; + var originContent = default(string); + Action getContent = p => originContent = p; + TestGetPayload(uriStringAfterServiceRoot, getContent); + + // Patch it. + var changedDescription = "TestDescription"; + var changedNormalProperty = "TestNormalProperty"; + string patchContent = + string.Format( + "{{\n \"Description\": \"{0}\",\n \"NormalOrderDetail\": {{\n \"NormalProperty\": \"{1}\"\n }}\n}}", + changedDescription, + changedNormalProperty); + TestPatchStatusCodeIs(uriStringAfterServiceRoot, patchContent, HttpStatusCode.NoContent); + + // Test patch results. + dynamic content = JsonConvert.DeserializeObject(originContent); + content.Description = changedDescription; + content.NormalOrderDetail.NormalProperty = changedNormalProperty; + string changedContent = JsonConvert.SerializeObject(content); + TestGetPayloadContains(uriStringAfterServiceRoot, changedContent); + } } } diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/TrippinInMemoryE2ETest.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/TrippinInMemoryE2ETest.cs index bcc6d1e0..ddc1b1f1 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/TrippinInMemoryE2ETest.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/TrippinInMemoryE2ETest.cs @@ -1,6 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; +using System.Net; +using System.Text.RegularExpressions; +using Newtonsoft.Json; using Xunit; namespace Microsoft.OData.Service.Sample.Tests @@ -44,8 +48,8 @@ public class TrippinInMemoryE2ETest : TrippinInMemoryE2ETestBase // TODO since webapi doesnot handle query with null, the trips here in the datasource are actually not null. [InlineData("/People('clydeguess')/Trips", 200)] // collection of navigation property's property and navigation property has null value - // TODO should be bad request 400 as this is not allowed, 404 is returned by WebApi Route Match method. 500 is returned actually. - [InlineData("/People('willieashmore')/Friends/MiddleName", 500)] + // TODO should be bad request 400 as this is not allowed, 404 is returned by WebApi Route Match method. (404 is returned when key-as-segment, otherwise, 500 will be returned.) + [InlineData("/People('willieashmore')/Friends/MiddleName", 404)] public void QueryPropertyWithNullValueStatusCode(string url, int expectedCode) { TestGetStatusCodeIs(url, expectedCode); @@ -77,8 +81,8 @@ public void QueryPropertyWithNullValueStatusCode(string url, int expectedCode) // collection of navigation property [InlineData("/People('NoneExist')/Friends", 404)] // collection of navigation property's property - // TODO should be bad request 400 as this is not allowed, 404 is returned by WebApi Route Match method. 500 is returned actually. - [InlineData("/People('NoneExist')/Friends/MiddleName", 500)] + // TODO should be bad request 400 as this is not allowed, 404 is returned by WebApi Route Match method. (404 is returned when key-as-segment, otherwise, 500 will be returned.) + [InlineData("/People('NoneExist')/Friends/MiddleName", 404)] public void QueryPropertyWithNonExistEntity(string url, int expectedCode) { TestGetStatusCodeIs(url, expectedCode); @@ -158,5 +162,46 @@ public void TestRawValuedEnumPropertyAccess() { TestGetPayloadIs("People('russellwhyte')/FavoriteFeature/$value", "Feature1"); } + + [Fact] + public void TestPatchSuccessfully() + { + // Get origin content and sessionId. + var uriStringAfterServiceRoot = "Airports('KLAX')"; + var originContent = default(string); + Action getContent = p => originContent = p; + TestGetPayload(uriStringAfterServiceRoot, getContent); + var sessionId = GetSessionIdFromResponse(originContent); + Assert.NotNull(sessionId); + + // Patch it. + uriStringAfterServiceRoot = string.Format(@"(S({0}))/{1}", sessionId, uriStringAfterServiceRoot); + var changedRegion = "TestRegion"; + var changedAddress = "1 World Way, Los Angeles, CA, 90045"; + string patchContent = + string.Format( + "{{\r\n \"Location\":{{\r\n \"Address\":\"{0}\",\r\n \"City\":{{\r\n \"Region\":\"{1}\"\r\n }}\r\n }}\r\n}}", + changedAddress, + changedRegion); + TestPatchStatusCodeIs(uriStringAfterServiceRoot, patchContent, HttpStatusCode.NoContent); + + // Test patch results. + dynamic content = JsonConvert.DeserializeObject(originContent); + content.Location.Address = changedAddress; + content.Location.City.Region = changedRegion; + string changedContent = JsonConvert.SerializeObject(content); + TestGetPayloadContains(uriStringAfterServiceRoot, changedContent); + } + + private static string GetSessionIdFromResponse(string response) + { + var match = Regex.Match(response, @"/\(S\((\w+)\)\)"); + if (match.Success) + { + return match.Groups[1].Value; + } + + return default(string); + } } } \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/packages.config b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/packages.config index 740423ac..ad199180 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/packages.config +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Tests/packages.config @@ -5,6 +5,7 @@ + diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/OrderDetail.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/OrderDetail.cs index cbd6b680..507d40cd 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/OrderDetail.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/OrderDetail.cs @@ -16,6 +16,8 @@ public OrderDetail() public string NormalProperty { get; set; } + public string AnotherNormalProperty { get; set; } + public string ComputedProperty { get; set; } public string ImmutableProperty { get; set; } diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/TrippinModel.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/TrippinModel.cs index 3128f7c7..0b563aaf 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/TrippinModel.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/TrippinModel.cs @@ -563,7 +563,11 @@ public static void ResetDataSource() OrderId = 1, Description = "Person 1 Order 1", Price = 200, - NormalOrderDetail = new OrderDetail(), + NormalOrderDetail = new OrderDetail() + { + NormalProperty = "NormalProperty", + AnotherNormalProperty = "AnotherNormalProperty" + }, ComputedOrderDetail = new OrderDetail(), ImmutableOrderDetail = new OrderDetail() }, diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/App_Start/WebApiConfig.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/App_Start/WebApiConfig.cs index ace3158f..d5908dfc 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/App_Start/WebApiConfig.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/App_Start/WebApiConfig.cs @@ -3,6 +3,7 @@ using System.Web.Http; using System.Web.OData; +using System.Web.OData.Extensions; using Microsoft.OData.Service.Sample.TrippinInMemory.Api; using Microsoft.Restier.Publishers.OData; using Microsoft.Restier.Publishers.OData.Batch; @@ -13,9 +14,10 @@ public static class WebApiConfig { public static void Register(HttpConfiguration config) { - RegisterTrippin(config, GlobalConfiguration.DefaultServer); config.SetUseVerboseErrors(true); config.MessageHandlers.Add(new ETagMessageHandler()); + config.SetUrlKeyDelimiter(ODataUrlKeyDelimiter.Slash); + RegisterTrippin(config, GlobalConfiguration.DefaultServer); } public static async void RegisterTrippin( diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Web.config b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Web.config index d008b610..b337d7ed 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Web.config +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.TrippinInMemory/Web.config @@ -28,6 +28,12 @@ + + + + + + diff --git a/test/ODataEndToEnd/Microsoft.Restier.Providers.InMemory/Submit/ChangeSetInitializer.cs b/test/ODataEndToEnd/Microsoft.Restier.Providers.InMemory/Submit/ChangeSetInitializer.cs index de00f286..ec7bc2a9 100644 --- a/test/ODataEndToEnd/Microsoft.Restier.Providers.InMemory/Submit/ChangeSetInitializer.cs +++ b/test/ODataEndToEnd/Microsoft.Restier.Providers.InMemory/Submit/ChangeSetInitializer.cs @@ -202,7 +202,7 @@ private static void SetValues(object instance, Type type, IReadOnlyDictionary