From 894574d04ea13f9096d67fbd430b136819a17565 Mon Sep 17 00:00:00 2001 From: KevinDockx Date: Thu, 3 Sep 2015 09:56:01 +0200 Subject: [PATCH] Implement Replace Move and Copy for dynamic objects --- .../Adapters/ObjectAdapter.cs | 422 +++++++++--------- .../Helpers/GetValueResult.cs | 30 ++ .../Dynamic/CopyOperationTests.cs | 245 ++++++++++ .../Dynamic/CopyTypedOperationTests.cs | 268 +++++++++++ .../Dynamic/MoveOperationTests.cs | 338 ++++++++++++++ .../Dynamic/MoveTypedOperationTests.cs | 138 ++++++ .../Dynamic/ReplaceOperationTests.cs | 242 ++++++++++ .../Dynamic/ReplaceTypedOperationTests.cs | 191 ++++++++ 8 files changed, 1657 insertions(+), 217 deletions(-) create mode 100644 src/Microsoft.AspNet.JsonPatch/Helpers/GetValueResult.cs create mode 100644 test/Microsoft.AspNet.JsonPatch.Test/Dynamic/CopyOperationTests.cs create mode 100644 test/Microsoft.AspNet.JsonPatch.Test/Dynamic/CopyTypedOperationTests.cs create mode 100644 test/Microsoft.AspNet.JsonPatch.Test/Dynamic/MoveOperationTests.cs create mode 100644 test/Microsoft.AspNet.JsonPatch.Test/Dynamic/MoveTypedOperationTests.cs create mode 100644 test/Microsoft.AspNet.JsonPatch.Test/Dynamic/ReplaceOperationTests.cs create mode 100644 test/Microsoft.AspNet.JsonPatch.Test/Dynamic/ReplaceTypedOperationTests.cs diff --git a/src/Microsoft.AspNet.JsonPatch/Adapters/ObjectAdapter.cs b/src/Microsoft.AspNet.JsonPatch/Adapters/ObjectAdapter.cs index cfaaebb96c..d9523d3a0b 100644 --- a/src/Microsoft.AspNet.JsonPatch/Adapters/ObjectAdapter.cs +++ b/src/Microsoft.AspNet.JsonPatch/Adapters/ObjectAdapter.cs @@ -359,73 +359,30 @@ namespace Microsoft.AspNet.JsonPatch.Adapters /// Object to apply the operation to. public void Move([NotNull] Operation operation, [NotNull] object objectToApplyTo) { - // get value at from location - object valueAtFromLocation = null; - var positionAsInteger = -1; - var actualFromProperty = operation.from; + var valueAtFromLocationResult = GetValueAtLocation(operation.from, objectToApplyTo, operation); - positionAsInteger = GetNumericEnd(operation.from); - - if (positionAsInteger > -1) - { - actualFromProperty = operation.from.Substring(0, - operation.from.IndexOf('/' + positionAsInteger.ToString())); - } - - var patchProperty = FindPropertyAndParent(objectToApplyTo, actualFromProperty); - - // does property at from exist? - if (!CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.from)) + if (valueAtFromLocationResult.HasError) { + // Error has already been logged in GetValueAtLocation. We + // must return, because remove / add should not be allowed to continue return; } - // is the path an array (but not a string (= char[]))? In this case, - // the path must end with "/position" or "/-", which we already determined before. - if (positionAsInteger > -1) - { - if (IsNonStringArray(patchProperty.Property.PropertyType)) - { - // now, get the generic type of the IList<> from Property type. - var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType); - - // get value (it can be cast, we just checked that) - var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent); - - if (array.Count <= positionAsInteger) - { - LogError(new JsonPatchError( - objectToApplyTo, - operation, - Resources.FormatInvalidIndexForArrayProperty(operation.op, operation.from))); - - return; - } - - valueAtFromLocation = array[positionAsInteger]; - } - else - { - LogError(new JsonPatchError( - objectToApplyTo, - operation, - Resources.FormatInvalidPathForArrayProperty(operation.op, operation.from))); - - return; - } - } - else - { - // no list, just get the value - // set the new value - valueAtFromLocation = patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent); - } - // remove that value - Remove(operation.from, objectToApplyTo, operation); + var removeResult = Remove(operation.from, objectToApplyTo, operation); + + if (removeResult.HasError) + { + // Return => error has already been logged in remove method. We must + // return, because add should not be allowed to continue + return; + } // add that value to the path location - Add(operation.path, valueAtFromLocation, objectToApplyTo, operation); + Add(operation.path, + valueAtFromLocationResult.PropertyValue, + objectToApplyTo, + operation); } /// @@ -648,7 +605,7 @@ namespace Microsoft.AspNet.JsonPatch.Adapters Resources.FormatInvalidIndexForArrayProperty( operationToReport.op, path))); - return null; + return new RemovedPropertyTypeResult(null, true); } array.RemoveAt(positionAsInteger); @@ -706,8 +663,37 @@ namespace Microsoft.AspNet.JsonPatch.Adapters /// Object to apply the operation to. public void Replace([NotNull] Operation operation, [NotNull] object objectToApplyTo) { - Remove(operation.path, objectToApplyTo, operation); - Add(operation.path, operation.value, objectToApplyTo, operation); + var removeResult = Remove(operation.path, objectToApplyTo, operation); + + if (removeResult.HasError) + { + // return => error has already been logged in remove method + return; + } + + if (!removeResult.HasError && removeResult.ActualType == null) + { + // the remove operation completed succesfully, but we could not determine the type. + LogError(new JsonPatchError( + objectToApplyTo, + operation, + Resources.FormatCannotDeterminePropertyType(operation.from))); + return; + } + + var conversionResult = ConvertToActualType(removeResult.ActualType, operation.value); + + if (!conversionResult.CanBeConverted) + { + // invalid value for path + LogError(new JsonPatchError( + objectToApplyTo, + operation, + Resources.FormatInvalidValueForProperty(operation.value, operation.path))); + return; + } + + Add(operation.path, conversionResult.ConvertedInstance, objectToApplyTo, operation); } /// @@ -734,100 +720,183 @@ namespace Microsoft.AspNet.JsonPatch.Adapters /// Object to apply the operation to. public void Copy([NotNull] Operation operation, [NotNull] object objectToApplyTo) { - // get value at from location - object valueAtFromLocation = null; - var positionAsInteger = -1; - var actualFromProperty = operation.from; + // get value at from location and add that value to the path location + var valueAtFromLocationResult = GetValueAtLocation(operation.from, objectToApplyTo, operation); - positionAsInteger = GetNumericEnd(operation.from); - - if (positionAsInteger > -1) - { - actualFromProperty = operation.from.Substring(0, - operation.from.IndexOf('/' + positionAsInteger.ToString())); - } - - var patchProperty = FindPropertyAndParent(objectToApplyTo, actualFromProperty); - - // does property at from exist? - if (!CheckIfPropertyExists(patchProperty, objectToApplyTo, operation, operation.from)) + if (valueAtFromLocationResult.HasError) { + // Return, error has already been logged in GetValueAtLocation return; } - // get the property path - // is the path an array (but not a string (= char[]))? In this case, - // the path must end with "/position" or "/-", which we already determined before. - if (positionAsInteger > -1) + Add(operation.path, + valueAtFromLocationResult.PropertyValue, + objectToApplyTo, + operation); + } + + /// + /// Method is used by Copy and Move to avoid duplicate code + /// + /// Location where value should be + /// Object to inspect for the desired value + /// Operation to report in case of an error + /// GetValueResult containing value and a bool signifying a possible error + private GetValueResult GetValueAtLocation( + [NotNull] string location, + [NotNull] object objectToGetValueFrom, + [NotNull] Operation operationToReport) + { + // get path result + var pathResult = GetActualPropertyPath( + location, + objectToGetValueFrom, + operationToReport); + + if (pathResult == null) { - if (IsNonStringArray(patchProperty.Property.PropertyType)) + return new GetValueResult(null, true); + } + + var getAtEndOfList = pathResult.ExecuteAtEnd; + var positionAsInteger = pathResult.NumericEnd; + var actualPathToProperty = pathResult.PathToProperty; + + var treeAnalysisResult = new ObjectTreeAnalysisResult( + objectToGetValueFrom, + actualPathToProperty, + ContractResolver); + + if (treeAnalysisResult.UseDynamicLogic) + { + // if it's not an array, we can remove the property from + // the dictionary. If it's an array, we need to check the position first. + if (getAtEndOfList || positionAsInteger > -1) { - // now, get the generic type of the IList<> from Property type. - var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType); + var propertyValue = treeAnalysisResult.Container + .GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent); - // get value (it can be cast, we just checked that) - var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent); - - if (array.Count <= positionAsInteger) + // we cannot continue when the value is null, because to be able to + // continue we need to be able to check if the array is a non-string array + if (propertyValue == null) { LogError(new JsonPatchError( - objectToApplyTo, - operation, - Resources.FormatInvalidIndexForArrayProperty(operation.op, operation.from))); - - return; + objectToGetValueFrom, + operationToReport, + Resources.FormatCannotDeterminePropertyType(location))); + return new GetValueResult(null, true); } - valueAtFromLocation = array[positionAsInteger]; + var typeOfPathProperty = propertyValue.GetType(); + + if (!IsNonStringArray(typeOfPathProperty)) + { + LogError(new JsonPatchError( + objectToGetValueFrom, + operationToReport, + Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, location))); + return new GetValueResult(null, true); + } + + // get the array + var array = (IList)treeAnalysisResult.Container.GetValueForCaseInsensitiveKey( + treeAnalysisResult.PropertyPathInParent); + + if (positionAsInteger >= array.Count) + { + LogError(new JsonPatchError( + objectToGetValueFrom, + operationToReport, + Resources.FormatInvalidIndexForArrayProperty( + operationToReport.op, + location))); + return new GetValueResult(null, true); + } + + if (getAtEndOfList) + { + return new GetValueResult(array[array.Count-1], false); + } + else + { + return new GetValueResult(array[positionAsInteger], false); + } } else { - LogError(new JsonPatchError( - objectToApplyTo, - operation, - Resources.FormatInvalidPathForArrayProperty(operation.op, operation.from))); + // get the property + var propertyValueAtLocation = treeAnalysisResult.Container.GetValueForCaseInsensitiveKey( + treeAnalysisResult.PropertyPathInParent); - return; - } + return new GetValueResult(propertyValueAtLocation, false); + } } else { - // no list, just get the value - // set the new value - valueAtFromLocation = patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent); + // not dynamic + var patchProperty = treeAnalysisResult.JsonPatchProperty; + + if (getAtEndOfList || positionAsInteger > -1) + { + if (!IsNonStringArray(patchProperty.Property.PropertyType)) + { + LogError(new JsonPatchError( + objectToGetValueFrom, + operationToReport, + Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, location))); + return new GetValueResult(null, true); + } + + if (!patchProperty.Property.Readable) + { + LogError(new JsonPatchError( + objectToGetValueFrom, + operationToReport, + Resources.FormatCannotReadProperty(location))); + return new GetValueResult(null, true); + } + + var array = (IList)patchProperty.Property.ValueProvider + .GetValue(patchProperty.Parent); + + if (positionAsInteger >= array.Count) + { + LogError(new JsonPatchError( + objectToGetValueFrom, + operationToReport, + Resources.FormatInvalidIndexForArrayProperty( + operationToReport.op, + location))); + return new GetValueResult(null, true); + } + + if (getAtEndOfList) + { + return new GetValueResult(array[array.Count - 1], false); + } + else + { + return new GetValueResult(array[positionAsInteger], false); + } + } + else + { + if (!patchProperty.Property.Readable) + { + LogError(new JsonPatchError( + objectToGetValueFrom, + operationToReport, + Resources.FormatCannotReadProperty( + location))); + return new GetValueResult(null, true); + } + + var propertyValueAtLocation = patchProperty.Property.ValueProvider + .GetValue(patchProperty.Parent); + + return new GetValueResult(propertyValueAtLocation, false); + } } - - // add operation to target location with that value. - Add(operation.path, valueAtFromLocation, objectToApplyTo, operation); - } - - private bool CheckIfPropertyExists( - JsonPatchProperty patchProperty, - object objectToApplyTo, - Operation operation, - string propertyPath) - { - if (patchProperty == null) - { - LogError(new JsonPatchError( - objectToApplyTo, - operation, - Resources.FormatPropertyDoesNotExist(propertyPath))); - - return false; - } - - if (patchProperty.Property.Ignored) - { - LogError(new JsonPatchError( - objectToApplyTo, - operation, - Resources.FormatCannotUpdateProperty(propertyPath))); - - return false; - } - - return true; } private bool IsNonStringArray(Type type) @@ -840,25 +909,6 @@ namespace Microsoft.AspNet.JsonPatch.Adapters return (!(type == typeof(string)) && typeof(IList).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())); } - private bool CheckIfPropertyCanBeSet( - ConversionResult result, - object objectToApplyTo, - Operation operation, - string path) - { - if (!result.CanBeConverted) - { - LogError(new JsonPatchError( - objectToApplyTo, - operation, - Resources.FormatInvalidValueForProperty(result.ConvertedInstance, path))); - - return false; - } - - return true; - } - private void LogError(JsonPatchError jsonPatchError) { if (LogErrorAction != null) @@ -871,54 +921,6 @@ namespace Microsoft.AspNet.JsonPatch.Adapters } } - private JsonPatchProperty FindPropertyAndParent(object targetObject, string propertyPath) - { - try - { - var splitPath = propertyPath.Split('/'); - - // skip the first one if it's empty - var startIndex = (string.IsNullOrWhiteSpace(splitPath[0]) ? 1 : 0); - - for (int i = startIndex; i < splitPath.Length; i++) - { - var jsonContract = (JsonObjectContract)ContractResolver.ResolveContract(targetObject.GetType()); - - foreach (var property in jsonContract.Properties) - { - if (string.Equals(property.PropertyName, splitPath[i], StringComparison.OrdinalIgnoreCase)) - { - if (i == (splitPath.Length - 1)) - { - return new JsonPatchProperty(property, targetObject); - } - else - { - targetObject = property.ValueProvider.GetValue(targetObject); - - // if property is of IList type then get the array index from splitPath and get the - // object at the indexed position from the list. - if (GetIListType(property.PropertyType) != null) - { - var index = int.Parse(splitPath[++i]); - targetObject = ((IList)targetObject)[index]; - } - } - - break; - } - } - } - - return null; - } - catch (Exception) - { - // will result in JsonPatchException in calling class, as expected - return null; - } - } - private ConversionResult ConvertToActualType(Type propertyType, object value) { try @@ -962,20 +964,6 @@ namespace Microsoft.AspNet.JsonPatch.Adapters return false; } - private int GetNumericEnd(string path) - { - var possibleIndex = path.Substring(path.LastIndexOf("/") + 1); - var castedIndex = -1; - - if (int.TryParse(possibleIndex, out castedIndex)) - { - return castedIndex; - } - - return -1; - } - - private ActualPropertyPathResult GetActualPropertyPath( [NotNull] string propertyPath, [NotNull] object objectToApplyTo, diff --git a/src/Microsoft.AspNet.JsonPatch/Helpers/GetValueResult.cs b/src/Microsoft.AspNet.JsonPatch/Helpers/GetValueResult.cs new file mode 100644 index 0000000000..89b7c93520 --- /dev/null +++ b/src/Microsoft.AspNet.JsonPatch/Helpers/GetValueResult.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.JsonPatch.Helpers +{ + /// + /// Return value for the helper method used by Copy/Move. Needed to ensure we can make a different + /// decision in the calling method when the value is null because it cannot be fetched (HasError = true) + /// versus when it actually is null (much like why RemovedPropertyTypeResult is used for returning + /// type in the Remove operation). + /// + public class GetValueResult + { + public GetValueResult(object propertyValue, bool hasError) + { + PropertyValue = propertyValue; + HasError = hasError; + } + + /// + /// The value of the property we're trying to get + /// + public object PropertyValue { get; private set; } + + /// + /// HasError: true when an error occurred, the operation didn't complete succesfully + /// + public bool HasError { get; private set; } + } +} diff --git a/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/CopyOperationTests.cs b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/CopyOperationTests.cs new file mode 100644 index 0000000000..3fa546faac --- /dev/null +++ b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/CopyOperationTests.cs @@ -0,0 +1,245 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Dynamic; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNet.JsonPatch.Test.Dynamic +{ + public class CopyOperationTests + { + [Fact] + public void Copy() + { + dynamic doc = new ExpandoObject(); + + doc.StringProperty = "A"; + doc.AnotherStringProperty = "B"; + + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("StringProperty", "AnotherStringProperty"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal("A", doc.AnotherStringProperty); + } + + [Fact] + public void CopyInList() + { + dynamic doc = new ExpandoObject(); + doc.IntegerList = new List() { 1, 2, 3 }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("IntegerList/0", "IntegerList/1"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 1, 1, 2, 3 }, doc.IntegerList); + } + + [Fact] + public void CopyFromListToEndOfList() + { + dynamic doc = new ExpandoObject(); + doc.IntegerList = new List() { 1, 2, 3 }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("IntegerList/0", "IntegerList/-"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 1, 2, 3, 1 }, doc.IntegerList); + } + + [Fact] + public void CopyFromListToNonList() + { + dynamic doc = new ExpandoObject(); + doc.IntegerList = new List() { 1, 2, 3 }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("IntegerList/0", "IntegerValue"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(1, doc.IntegerValue); + } + + [Fact] + public void CopyFromNonListToList() + { + dynamic doc = new ExpandoObject(); + doc.IntegerValue = 5; + doc.IntegerList = new List() { 1, 2, 3 }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("IntegerValue", "IntegerList/0"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 5, 1, 2, 3 }, doc.IntegerList); + } + + [Fact] + public void CopyToEndOfList() + { + dynamic doc = new ExpandoObject(); + doc.IntegerValue = 5; + doc.IntegerList = new List() { 1, 2, 3 }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("IntegerValue", "IntegerList/-"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 1, 2, 3, 5 }, doc.IntegerList); + } + + [Fact] + public void NestedCopy() + { + dynamic doc = new ExpandoObject(); + doc.SimpleDTO = new SimpleDTO() + { + StringProperty = "A", + AnotherStringProperty = "B" + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("SimpleDTO/StringProperty", "SimpleDTO/AnotherStringProperty"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal("A", doc.SimpleDTO.AnotherStringProperty); + } + + [Fact] + public void NestedCopyInList() + { + dynamic doc = new ExpandoObject(); + doc.SimpleDTO = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/1"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 1, 1, 2, 3 }, doc.SimpleDTO.IntegerList); + } + + [Fact] + public void NestedCopyFromListToEndOfList() + { + dynamic doc = new ExpandoObject(); + doc.SimpleDTO = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/-"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 1, 2, 3, 1 }, doc.SimpleDTO.IntegerList); + } + + [Fact] + public void NestedCopyFromListToNonList() + { + dynamic doc = new ExpandoObject(); + doc.SimpleDTO = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerValue"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(1, doc.SimpleDTO.IntegerValue); + } + + [Fact] + public void NestedCopyFromNonListToList() + { + dynamic doc = new ExpandoObject(); + doc.SimpleDTO = new SimpleDTO() + { + IntegerValue = 5, + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/0"); + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 5, 1, 2, 3 }, doc.SimpleDTO.IntegerList); + } + + [Fact] + public void NestedCopyToEndOfList() + { + dynamic doc = new ExpandoObject(); + doc.SimpleDTO = new SimpleDTO() + { + IntegerValue = 5, + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/-"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 1, 2, 3, 5 }, doc.SimpleDTO.IntegerList); + } + } +} diff --git a/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/CopyTypedOperationTests.cs b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/CopyTypedOperationTests.cs new file mode 100644 index 0000000000..5b015416ac --- /dev/null +++ b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/CopyTypedOperationTests.cs @@ -0,0 +1,268 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNet.JsonPatch.Test.Dynamic +{ + public class CopyTypedOperationTests + { + [Fact] + public void Copy() + { + var doc = new SimpleDTO() + { + StringProperty = "A", + AnotherStringProperty = "B" + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("StringProperty", "AnotherStringProperty"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal("A", doc.AnotherStringProperty); + } + + [Fact] + public void CopyInList() + { + var doc = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("IntegerList/0", "IntegerList/1"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 1, 1, 2, 3 }, doc.IntegerList); + } + + [Fact] + public void CopyFromListToEndOfList() + { + var doc = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("IntegerList/0", "IntegerList/-"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 1, 2, 3, 1 }, doc.IntegerList); + } + + [Fact] + public void CopyFromListToNonList() + { + var doc = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("IntegerList/0", "IntegerValue"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(1, doc.IntegerValue); + } + + [Fact] + public void CopyFromNonListToList() + { + var doc = new SimpleDTO() + { + IntegerValue = 5, + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("IntegerValue", "IntegerList/0"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 5, 1, 2, 3 }, doc.IntegerList); + } + + [Fact] + public void CopyToEndOfList() + { + var doc = new SimpleDTO() + { + IntegerValue = 5, + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("IntegerValue", "IntegerList/-"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 1, 2, 3, 5 }, doc.IntegerList); + } + + [Fact] + public void NestedCopy() + { + var doc = new SimpleDTOWithNestedDTO() + { + SimpleDTO = new SimpleDTO() + { + StringProperty = "A", + AnotherStringProperty = "B" + } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("SimpleDTO/StringProperty", "SimpleDTO/AnotherStringProperty"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal("A", doc.SimpleDTO.AnotherStringProperty); + } + + [Fact] + public void NestedCopyInList() + { + var doc = new SimpleDTOWithNestedDTO() + { + SimpleDTO = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/1"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 1, 1, 2, 3 }, doc.SimpleDTO.IntegerList); + } + + [Fact] + public void NestedCopyFromListToEndOfList() + { + var doc = new SimpleDTOWithNestedDTO() + { + SimpleDTO = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerList/-"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 1, 2, 3, 1 }, doc.SimpleDTO.IntegerList); + } + + [Fact] + public void NestedCopyFromListToNonList() + { + var doc = new SimpleDTOWithNestedDTO() + { + SimpleDTO = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("SimpleDTO/IntegerList/0", "SimpleDTO/IntegerValue"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(1, doc.SimpleDTO.IntegerValue); + } + + [Fact] + public void NestedCopyFromNonListToList() + { + var doc = new SimpleDTOWithNestedDTO() + { + SimpleDTO = new SimpleDTO() + { + IntegerValue = 5, + IntegerList = new List() { 1, 2, 3 } + } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/0"); + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 5, 1, 2, 3 }, doc.SimpleDTO.IntegerList); + } + + [Fact] + public void NestedCopyToEndOfList() + { + var doc = new SimpleDTOWithNestedDTO() + { + SimpleDTO = new SimpleDTO() + { + IntegerValue = 5, + IntegerList = new List() { 1, 2, 3 } + } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Copy("SimpleDTO/IntegerValue", "SimpleDTO/IntegerList/-"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 1, 2, 3, 5 }, doc.SimpleDTO.IntegerList); + } + } +} diff --git a/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/MoveOperationTests.cs b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/MoveOperationTests.cs new file mode 100644 index 0000000000..dea2b6ee32 --- /dev/null +++ b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/MoveOperationTests.cs @@ -0,0 +1,338 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Dynamic; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNet.JsonPatch.Test.Dynamic +{ + public class MoveOperationTests + { + [Fact] + public void Move() + { + dynamic doc = new ExpandoObject(); + doc.StringProperty = "A"; + doc.AnotherStringProperty = "B"; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("StringProperty", "AnotherStringProperty"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal("A", doc.AnotherStringProperty); + + var cont = doc as IDictionary; + object valueFromDictionary; + cont.TryGetValue("StringProperty", out valueFromDictionary); + Assert.Null(valueFromDictionary); + } + + [Fact] + public void MoveToNonExisting() + { + dynamic doc = new ExpandoObject(); + doc.StringProperty = "A"; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("StringProperty", "AnotherStringProperty"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal("A", doc.AnotherStringProperty); + + var cont = doc as IDictionary; + object valueFromDictionary; + cont.TryGetValue("StringProperty", out valueFromDictionary); + Assert.Null(valueFromDictionary); + } + + [Fact] + public void MoveDynamicToTyped() + { + dynamic doc = new ExpandoObject(); + doc.StringProperty = "A"; + doc.SimpleDTO = new SimpleDTO() { AnotherStringProperty = "B" }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("StringProperty", "SimpleDTO/AnotherStringProperty"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal("A", doc.SimpleDTO.AnotherStringProperty); + + var cont = doc as IDictionary; + object valueFromDictionary; + cont.TryGetValue("StringProperty", out valueFromDictionary); + Assert.Null(valueFromDictionary); + } + + [Fact] + public void MoveTypedToDynamic() + { + dynamic doc = new ExpandoObject(); + doc.StringProperty = "A"; + doc.SimpleDTO = new SimpleDTO() { AnotherStringProperty = "B" }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("SimpleDTO/AnotherStringProperty", "StringProperty"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal("B", doc.StringProperty); + Assert.Equal(null, doc.SimpleDTO.AnotherStringProperty); + } + + [Fact] + public void NestedMove() + { + dynamic doc = new ExpandoObject(); + doc.Nested = new SimpleDTO() + { + StringProperty = "A", + AnotherStringProperty = "B" + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("Nested/StringProperty", "Nested/AnotherStringProperty"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal("A", doc.Nested.AnotherStringProperty); + Assert.Equal(null, doc.Nested.StringProperty); + } + + [Fact] + public void MoveInList() + { + dynamic doc = new ExpandoObject(); + doc.IntegerList = new List() { 1, 2, 3 }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("IntegerList/0", "IntegerList/1"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 2, 1, 3 }, doc.IntegerList); + } + + [Fact] + public void NestedMoveInList() + { + dynamic doc = new ExpandoObject(); + doc.Nested = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("Nested/IntegerList/0", "Nested/IntegerList/1"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 2, 1, 3 }, doc.Nested.IntegerList); + } + + [Fact] + public void MoveFromListToEndOfList() + { + dynamic doc = new ExpandoObject(); + doc.IntegerList = new List() { 1, 2, 3 }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("IntegerList/0", "IntegerList/-"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 2, 3, 1 }, doc.IntegerList); + } + + [Fact] + public void NestedMoveFromListToEndOfList() + { + dynamic doc = new ExpandoObject(); + doc.Nested = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("Nested/IntegerList/0", "Nested/IntegerList/-"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 2, 3, 1 }, doc.Nested.IntegerList); + } + + [Fact] + public void MoveFomListToNonList() + { + dynamic doc = new ExpandoObject(); + doc.IntegerList = new List() { 1, 2, 3 }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("IntegerList/0", "IntegerValue"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 2, 3 }, doc.IntegerList); + Assert.Equal(1, doc.IntegerValue); + } + + [Fact] + public void NestedMoveFomListToNonList() + { + dynamic doc = new ExpandoObject(); + doc.Nested = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("Nested/IntegerList/0", "Nested/IntegerValue"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 2, 3 }, doc.Nested.IntegerList); + Assert.Equal(1, doc.Nested.IntegerValue); + } + + [Fact] + public void MoveFromNonListToList() + { + dynamic doc = new ExpandoObject(); + doc.IntegerValue = 5; + doc.IntegerList = new List() { 1, 2, 3 }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("IntegerValue", "IntegerList/0"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + var cont = doc as IDictionary; + object valueFromDictionary; + cont.TryGetValue("IntegerValue", out valueFromDictionary); + Assert.Null(valueFromDictionary); + + Assert.Equal(new List() { 5, 1, 2, 3 }, doc.IntegerList); + } + + [Fact] + public void NestedMoveFromNonListToList() + { + dynamic doc = new ExpandoObject(); + doc.Nested = new SimpleDTO() + { + IntegerValue = 5, + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("Nested/IntegerValue", "Nested/IntegerList/0"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(0, doc.Nested.IntegerValue); + Assert.Equal(new List() { 5, 1, 2, 3 }, doc.Nested.IntegerList); + } + + [Fact] + public void MoveToEndOfList() + { + dynamic doc = new ExpandoObject(); + doc.IntegerValue = 5; + doc.IntegerList = new List() { 1, 2, 3 }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("IntegerValue", "IntegerList/-"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + var cont = doc as IDictionary; + object valueFromDictionary; + cont.TryGetValue("IntegerValue", out valueFromDictionary); + Assert.Null(valueFromDictionary); + + Assert.Equal(new List() { 1, 2, 3, 5 }, doc.IntegerList); + } + + [Fact] + public void NestedMoveToEndOfList() + { + dynamic doc = new ExpandoObject(); + doc.Nested = new SimpleDTO() + { + IntegerValue = 5, + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("Nested/IntegerValue", "Nested/IntegerList/-"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(0, doc.Nested.IntegerValue); + Assert.Equal(new List() { 1, 2, 3, 5 }, doc.Nested.IntegerList); + } + } +} diff --git a/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/MoveTypedOperationTests.cs b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/MoveTypedOperationTests.cs new file mode 100644 index 0000000000..3d356a3401 --- /dev/null +++ b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/MoveTypedOperationTests.cs @@ -0,0 +1,138 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNet.JsonPatch.Test.Dynamic +{ + public class MoveTypedOperationTests + { + [Fact] + public void Move() + { + var doc = new SimpleDTO() + { + StringProperty = "A", + AnotherStringProperty = "B" + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("StringProperty", "AnotherStringProperty"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal("A", doc.AnotherStringProperty); + Assert.Equal(null, doc.StringProperty); + } + + [Fact] + public void MoveInList() + { + var doc = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("IntegerList/0", "IntegerList/1"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 2, 1, 3 }, doc.IntegerList); + } + + [Fact] + public void MoveFromListToEndOfList() + { + var doc = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("IntegerList/0", "IntegerList/-"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 2, 3, 1 }, doc.IntegerList); + } + + [Fact] + public void MoveFomListToNonList() + { + var doc = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("IntegerList/0", "IntegerValue"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 2, 3 }, doc.IntegerList); + Assert.Equal(1, doc.IntegerValue); + } + + [Fact] + public void MoveFromNonListToList() + { + var doc = new SimpleDTO() + { + IntegerValue = 5, + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("IntegerValue", "IntegerList/0"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(0, doc.IntegerValue); + Assert.Equal(new List() { 5, 1, 2, 3 }, doc.IntegerList); + } + + [Fact] + public void MoveToEndOfList() + { + var doc = new SimpleDTO() + { + IntegerValue = 5, + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Move("IntegerValue", "IntegerList/-"); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(0, doc.IntegerValue); + Assert.Equal(new List() { 1, 2, 3, 5 }, doc.IntegerList); + } + } +} diff --git a/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/ReplaceOperationTests.cs b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/ReplaceOperationTests.cs new file mode 100644 index 0000000000..c7573c0286 --- /dev/null +++ b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/ReplaceOperationTests.cs @@ -0,0 +1,242 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Dynamic; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNet.JsonPatch.Test.Dynamic +{ + public class ReplaceOperationTests + { + [Fact] + public void ReplaceGuidTest() + { + dynamic doc = new SimpleDTO() + { + GuidValue = Guid.NewGuid() + }; + + var newGuid = Guid.NewGuid(); + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("GuidValue", newGuid); + + // serialize & deserialize + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserizalized = JsonConvert.DeserializeObject(serialized); + + deserizalized.ApplyTo(doc); + + Assert.Equal(newGuid, doc.GuidValue); + } + + [Fact] + public void ReplaceGuidTestExpandoObject() + { + dynamic doc = new ExpandoObject(); + doc.GuidValue = Guid.NewGuid(); + + var newGuid = Guid.NewGuid(); + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("GuidValue", newGuid); + + // serialize & deserialize + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserizalized = JsonConvert.DeserializeObject(serialized); + + deserizalized.ApplyTo(doc); + + Assert.Equal(newGuid, doc.GuidValue); + } + + [Fact] + public void ReplaceGuidTestExpandoObjectInAnonymous() + { + dynamic nestedObject = new ExpandoObject(); + nestedObject.GuidValue = Guid.NewGuid(); + + dynamic doc = new + { + NestedObject = nestedObject + }; + + var newGuid = Guid.NewGuid(); + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("nestedobject/GuidValue", newGuid); + + // serialize & deserialize + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserizalized = JsonConvert.DeserializeObject(serialized); + + deserizalized.ApplyTo(doc); + + Assert.Equal(newGuid, doc.NestedObject.GuidValue); + } + + [Fact] + public void ReplaceNestedObjectTest() + { + dynamic doc = new ExpandoObject(); + doc.SimpleDTO = new SimpleDTO() + { + IntegerValue = 5, + IntegerList = new List() { 1, 2, 3 } + }; + + var newDTO = new SimpleDTO() + { + DoubleValue = 1 + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("SimpleDTO", newDTO); + + // serialize & deserialize + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(1, doc.SimpleDTO.DoubleValue); + Assert.Equal(0, doc.SimpleDTO.IntegerValue); + Assert.Equal(null, doc.SimpleDTO.IntegerList); + } + + [Fact] + public void ReplaceInList() + { + dynamic doc = new ExpandoObject(); + doc.IntegerList = new List() { 1, 2, 3 }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("IntegerList/0", 5); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 5, 2, 3 }, doc.IntegerList); + } + + [Fact] + public void ReplaceFullList() + { + dynamic doc = new ExpandoObject(); + doc.IntegerList = new List() { 1, 2, 3 }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("IntegerList", new List() { 4, 5, 6 }); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList); + } + + [Fact] + public void ReplaceInListInList() + { + dynamic doc = new ExpandoObject(); + doc.SimpleDTOList = new List() { + new SimpleDTO() { + IntegerList = new List(){1,2,3} + }}; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("SimpleDTOList/0/IntegerList/0", 4); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(4, doc.SimpleDTOList[0].IntegerList[0]); + } + + [Fact] + public void ReplaceInListInListAtEnd() + { + dynamic doc = new ExpandoObject(); + doc.SimpleDTOList = new List() { + new SimpleDTO() { + IntegerList = new List(){1,2,3} + }}; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("SimpleDTOList/0/IntegerList/-", 4); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(4, doc.SimpleDTOList[0].IntegerList[2]); + } + + [Fact] + public void ReplaceFullListFromEnumerable() + { + dynamic doc = new ExpandoObject(); + doc.IntegerList = new List() { 1, 2, 3 }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("IntegerList", new List() { 4, 5, 6 }); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList); + } + + [Fact] + public void ReplaceFullListWithCollection() + { + dynamic doc = new ExpandoObject(); + doc.IntegerList = new List() { 1, 2, 3 }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("IntegerList", new Collection() { 4, 5, 6 }); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList); + } + + [Fact] + public void ReplaceAtEndOfList() + { + dynamic doc = new ExpandoObject(); + doc.IntegerList = new List() { 1, 2, 3 }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("IntegerList/-", 5); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 1, 2, 5 }, doc.IntegerList); + } + } +} diff --git a/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/ReplaceTypedOperationTests.cs b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/ReplaceTypedOperationTests.cs new file mode 100644 index 0000000000..ccdd86f1a7 --- /dev/null +++ b/test/Microsoft.AspNet.JsonPatch.Test/Dynamic/ReplaceTypedOperationTests.cs @@ -0,0 +1,191 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNet.JsonPatch.Test.Dynamic +{ + public class ReplaceTypedOperationTests + { + [Fact] + public void ReplaceGuidTest() + { + var doc = new SimpleDTO() + { + GuidValue = Guid.NewGuid() + }; + + var newGuid = Guid.NewGuid(); + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("GuidValue", newGuid); + + // serialize & deserialize + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserizalized = JsonConvert.DeserializeObject(serialized); + + deserizalized.ApplyTo(doc); + + Assert.Equal(newGuid, doc.GuidValue); + } + + [Fact] + public void SerializeAndReplaceNestedObjectTest() + { + var doc = new SimpleDTOWithNestedDTO() + { + SimpleDTO = new SimpleDTO() + { + IntegerValue = 5, + IntegerList = new List() { 1, 2, 3 } + } + }; + + var newDTO = new SimpleDTO() + { + DoubleValue = 1 + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("SimpleDTO", newDTO); + + // serialize & deserialize + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(1, doc.SimpleDTO.DoubleValue); + Assert.Equal(0, doc.SimpleDTO.IntegerValue); + Assert.Equal(null, doc.SimpleDTO.IntegerList); + } + + [Fact] + public void ReplaceInList() + { + var doc = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("IntegerList/0", 5); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 5, 2, 3 }, doc.IntegerList); + } + + [Fact] + public void ReplaceFullList() + { + var doc = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("IntegerList", new List() { 4, 5, 6 }); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList); + } + + [Fact] + public void ReplaceInListInList() + { + var doc = new SimpleDTO() + { + SimpleDTOList = new List() { + new SimpleDTO() { + IntegerList = new List(){1,2,3} + }} + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("SimpleDTOList/0/IntegerList/0", 4); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(4, doc.SimpleDTOList.First().IntegerList.First()); + } + + [Fact] + public void ReplaceFullListFromEnumerable() + { + var doc = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("IntegerList", new List() { 4, 5, 6 }); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList); + } + + [Fact] + public void ReplaceFullListWithCollection() + { + var doc = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("IntegerList", new Collection() { 4, 5, 6 }); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 4, 5, 6 }, doc.IntegerList); + } + + [Fact] + public void ReplaceAtEndOfList() + { + var doc = new SimpleDTO() + { + IntegerList = new List() { 1, 2, 3 } + }; + + // create patch + JsonPatchDocument patchDoc = new JsonPatchDocument(); + patchDoc.Replace("IntegerList/-", 5); + + var serialized = JsonConvert.SerializeObject(patchDoc); + var deserialized = JsonConvert.DeserializeObject(serialized); + deserialized.ApplyTo(doc); + + Assert.Equal(new List() { 1, 2, 5 }, doc.IntegerList); + } + } +}