Implement new Remove op & fix value.GetTypê issue in Add op

This commit is contained in:
KevinDockx 2015-08-12 21:39:37 +02:00 committed by Ryan Nowak
parent bf7e0f141e
commit 054b46c1cc
14 changed files with 1683 additions and 105 deletions

View File

@ -161,7 +161,7 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
{
// get the actual type
var propertyValue = container.GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent);
var typeOfPathProperty = value.GetType();
var typeOfPathProperty = propertyValue.GetType();
if (!IsNonStringArray(typeOfPathProperty))
{
@ -447,101 +447,240 @@ namespace Microsoft.AspNet.JsonPatch.Adapters
Remove(operation.path, objectToApplyTo, operation);
}
/// <summary>
/// Remove is used by various operations (eg: remove, move, ...), yet through different operations;
/// This method allows code reuse yet reporting the correct operation on error
/// This method allows code reuse yet reporting the correct operation on error. The return value
/// contains the type of the item that has been removed (and a bool possibly signifying an error)
/// This can be used by other methods, like replace, to ensure that we can pass in the correctly
/// typed value to whatever method follows.
/// </summary>
private void Remove(
[NotNull] string path,
[NotNull] object objectToApplyTo,
[NotNull] Operation operationToReport)
private RemovedPropertyTypeResult Remove(string path, object objectToApplyTo, Operation operationToReport)
{
var removeFromList = false;
var positionAsInteger = -1;
var actualPathToProperty = path;
// get path result
var pathResult = GetActualPropertyPath(
path,
objectToApplyTo,
operationToReport);
if (path.EndsWith("/-"))
if (pathResult == null)
{
removeFromList = true;
actualPathToProperty = path.Substring(0, path.Length - 2);
return new RemovedPropertyTypeResult(null, true);
}
else
{
positionAsInteger = GetNumericEnd(path);
if (positionAsInteger > -1)
var removeFromList = pathResult.ExecuteAtEnd;
var positionAsInteger = pathResult.NumericEnd;
var actualPathToProperty = pathResult.PathToProperty;
var treeAnalysisResult = new ObjectTreeAnalysisResult(
objectToApplyTo,
actualPathToProperty,
ContractResolver);
if (!treeAnalysisResult.IsValidPathForRemove)
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatPropertyCannotBeRemoved(path)));
return new RemovedPropertyTypeResult(null, true);
}
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 (removeFromList || positionAsInteger > -1)
{
actualPathToProperty = path.Substring(0,
path.IndexOf('/' + positionAsInteger.ToString()));
}
}
var propertyValue = treeAnalysisResult.Container
.GetValueForCaseInsensitiveKey(treeAnalysisResult.PropertyPathInParent);
var patchProperty = FindPropertyAndParent(objectToApplyTo, actualPathToProperty);
// 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,
operationToReport,
Resources.FormatCannotDeterminePropertyType(path)));
return new RemovedPropertyTypeResult(null, true);
}
// does the target location exist?
if (!CheckIfPropertyExists(patchProperty, objectToApplyTo, operationToReport, path))
{
return;
}
var typeOfPathProperty = propertyValue.GetType();
// get the property, and remove it - in this case, for DTO's, that means setting
// it to null or its default value; in case of an array, remove at provided index
// or at the end.
if (removeFromList || positionAsInteger > -1)
{
// what if it's an array but there's no position??
if (IsNonStringArray(patchProperty.Property.PropertyType))
{
// now, get the generic type of the IList<> from Property type.
var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType);
if (!IsNonStringArray(typeOfPathProperty))
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
return new RemovedPropertyTypeResult(null, true);
}
// get value (it can be cast, we just checked that)
var array = (IList)patchProperty.Property.ValueProvider.GetValue(patchProperty.Parent);
// now, get the generic type of the enumerable (we'll return this type)
var genericTypeOfArray = GetIListType(typeOfPathProperty);
// get the array
var array = (IList)treeAnalysisResult.Container.GetValueForCaseInsensitiveKey(
treeAnalysisResult.PropertyPathInParent);
if (array.Count == 0)
{
// if the array is empty, we should throw an error
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidIndexForArrayProperty(
operationToReport.op,
path)));
return new RemovedPropertyTypeResult(null, true);
}
if (removeFromList)
{
array.RemoveAt(array.Count - 1);
treeAnalysisResult.Container.SetValueForCaseInsensitiveKey(
treeAnalysisResult.PropertyPathInParent, array);
// return the type of the value that has been removed.
return new RemovedPropertyTypeResult(genericTypeOfArray, false);
}
else
{
if (positionAsInteger < array.Count)
{
array.RemoveAt(positionAsInteger);
}
else
if (positionAsInteger >= array.Count)
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
return;
Resources.FormatInvalidIndexForArrayProperty(
operationToReport.op,
path)));
return new RemovedPropertyTypeResult(null, true);
}
array.RemoveAt(positionAsInteger);
treeAnalysisResult.Container.SetValueForCaseInsensitiveKey(
treeAnalysisResult.PropertyPathInParent, array);
// return the type of the value that has been removed.
return new RemovedPropertyTypeResult(genericTypeOfArray, false);
}
}
else
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidPathForArrayProperty(operationToReport.op, path)));
// get the property
var getResult = treeAnalysisResult.Container.GetValueForCaseInsensitiveKey(
treeAnalysisResult.PropertyPathInParent);
return;
// remove the property
treeAnalysisResult.Container.RemoveValueForCaseInsensitiveKey(
treeAnalysisResult.PropertyPathInParent);
// value is not null, we can determine the type
if (getResult != null)
{
var actualType = getResult.GetType();
return new RemovedPropertyTypeResult(actualType, false);
}
else
{
return new RemovedPropertyTypeResult(null, false);
}
}
}
else
{
// setting the value to "null" will use the default value in case of value types, and
// null in case of reference types
object value = null;
// not dynamic
var patchProperty = treeAnalysisResult.JsonPatchProperty;
if (patchProperty.Property.PropertyType.GetTypeInfo().IsValueType
&& Nullable.GetUnderlyingType(patchProperty.Property.PropertyType) == null)
if (removeFromList || positionAsInteger > -1)
{
value = Activator.CreateInstance(patchProperty.Property.PropertyType);
}
if (!IsNonStringArray(patchProperty.Property.PropertyType))
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidIndexForArrayProperty(operationToReport.op, path)));
return new RemovedPropertyTypeResult(null, true);
}
patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent, value);
// now, get the generic type of the IList<> from Property type.
var genericTypeOfArray = GetIListType(patchProperty.Property.PropertyType);
if (!patchProperty.Property.Readable)
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatCannotReadProperty(path)));
return new RemovedPropertyTypeResult(null, true);
}
var array = (IList)patchProperty.Property.ValueProvider
.GetValue(patchProperty.Parent);
if (array.Count == 0)
{
// if the array is empty, we should throw an error
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidIndexForArrayProperty(
operationToReport.op,
path)));
return new RemovedPropertyTypeResult(null, true);
}
if (removeFromList)
{
array.RemoveAt(array.Count - 1);
// return the type of the value that has been removed
return new RemovedPropertyTypeResult(genericTypeOfArray, false);
}
else
{
if (positionAsInteger >= array.Count)
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatInvalidIndexForArrayProperty(
operationToReport.op,
path)));
return null;
}
array.RemoveAt(positionAsInteger);
// return the type of the value that has been removed
return new RemovedPropertyTypeResult(genericTypeOfArray, false);
}
}
else
{
if (!patchProperty.Property.Writable)
{
LogError(new JsonPatchError(
objectToApplyTo,
operationToReport,
Resources.FormatCannotUpdateProperty(path)));
return new RemovedPropertyTypeResult(null, true);
}
// setting the value to "null" will use the default value in case of value types, and
// null in case of reference types
object value = null;
if (patchProperty.Property.PropertyType.GetTypeInfo().IsValueType
&& Nullable.GetUnderlyingType(patchProperty.Property.PropertyType) == null)
{
value = Activator.CreateInstance(patchProperty.Property.PropertyType);
}
patchProperty.Property.ValueProvider.SetValue(patchProperty.Parent, value);
return new RemovedPropertyTypeResult(patchProperty.Property.PropertyType, false);
}
}
}

View File

@ -0,0 +1,38 @@
// 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;
namespace Microsoft.AspNet.JsonPatch.Helpers
{
/// <summary>
/// Return value for Remove operation. The combination tells us what to do next (if this operation
/// is called from inside another operation, eg: Replace, Copy.
///
/// Possible combo:
/// - ActualType contains type: operation succesfully completed, can continue when called from inside
/// another operation
/// - ActualType null and HasError true: operation not completed succesfully, should not be allowed to continue
/// - ActualType null and HasError false: operation completed succesfully, but we should not be allowed to
/// continue when called from inside another method as we could not verify the type of the removed property.
/// This happens when the value of an item in an ExpandoObject dictionary is null.
/// </summary>
internal class RemovedPropertyTypeResult
{
/// <summary>
/// The type of the removed property (value)
/// </summary>
public Type ActualType { get; private set; }
/// <summary>
/// HasError: true when an error occurred, the operation didn't complete succesfully
/// </summary>
public bool HasError { get; set; }
public RemovedPropertyTypeResult(Type actualType, bool hasError)
{
ActualType = actualType;
HasError = hasError;
}
}
}

View File

@ -10,6 +10,22 @@ namespace Microsoft.AspNet.JsonPatch
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNet.JsonPatch.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The type of the property at path '{0}' could not be determined.
/// </summary>
internal static string CannotDeterminePropertyType
{
get { return GetString("CannotDeterminePropertyType"); }
}
/// <summary>
/// The type of the property at path '{0}' could not be determined.
/// </summary>
internal static string FormatCannotDeterminePropertyType(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("CannotDeterminePropertyType"), p0);
}
/// <summary>
/// The property at '{0}' could not be read.
/// </summary>
@ -42,6 +58,22 @@ namespace Microsoft.AspNet.JsonPatch
return string.Format(CultureInfo.CurrentCulture, GetString("CannotUpdateProperty"), p0);
}
/// <summary>
/// The key '{0}' was not found.
/// </summary>
internal static string DictionaryKeyNotFound
{
get { return GetString("DictionaryKeyNotFound"); }
}
/// <summary>
/// The key '{0}' was not found.
/// </summary>
internal static string FormatDictionaryKeyNotFound(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("DictionaryKeyNotFound"), p0);
}
/// <summary>
/// For operation '{0}' on array property at path '{1}', the index is larger than the array size.
/// </summary>
@ -58,23 +90,6 @@ namespace Microsoft.AspNet.JsonPatch
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidIndexForArrayProperty"), p0, p1);
}
/// <summary>
/// For operation '{0}' on array property at path '{1}', the index is negative.
/// </summary>
internal static string NegativeIndexForArrayProperty
{
get { return GetString("NegativeIndexForArrayProperty"); }
}
/// <summary>
/// For operation '{0}' on array property at path '{1}', the index is negative.
/// </summary>
internal static string FormatNegativeIndexForArrayProperty(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("NegativeIndexForArrayProperty"), p0, p1);
}
/// <summary>
/// The type '{0}' was malformed and could not be parsed.
/// </summary>
@ -139,6 +154,22 @@ namespace Microsoft.AspNet.JsonPatch
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidValueForProperty"), p0, p1);
}
/// <summary>
/// For operation '{0}' on array property at path '{1}', the index is negative.
/// </summary>
internal static string NegativeIndexForArrayProperty
{
get { return GetString("NegativeIndexForArrayProperty"); }
}
/// <summary>
/// For operation '{0}' on array property at path '{1}', the index is negative.
/// </summary>
internal static string FormatNegativeIndexForArrayProperty(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("NegativeIndexForArrayProperty"), p0, p1);
}
/// <summary>
/// '{0}' must be of type '{1}'.
/// </summary>
@ -203,23 +234,6 @@ namespace Microsoft.AspNet.JsonPatch
return string.Format(CultureInfo.CurrentCulture, GetString("PropertyDoesNotExist"), p0);
}
/// <summary>
/// The key '{0}' was not found.
/// </summary>
internal static string DictionaryKeyNotFound
{
get { return GetString("DictionaryKeyNotFound"); }
}
/// <summary>
/// The key '{0}' was not found.
/// </summary>
internal static string FormatDictionaryKeyNotFound(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("DictionaryKeyNotFound"), p0);
}
/// <summary>
/// The test operation is not supported.
/// </summary>

View File

@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CannotDeterminePropertyType" xml:space="preserve">
<value>The type of the property at path '{0}' could not be determined.</value>
</data>
<data name="CannotReadProperty" xml:space="preserve">
<value>The property at '{0}' could not be read.</value>
</data>

View File

@ -0,0 +1,569 @@
// 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.Dynamic;
using Microsoft.AspNet.JsonPatch.Exceptions;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class AddOperationTests
{
[Fact]
public void AddNewPropertyShouldFailIfRootIsNotAnExpandoObject()
{
dynamic doc = new
{
Test = 1
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("NewInt", 1);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/NewInt' could not be added.",
exception.Message);
}
[Fact]
public void AddNewProperty()
{
dynamic obj = new ExpandoObject();
obj.Test = 1;
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("NewInt", 1);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(obj);
Assert.Equal(1, obj.NewInt);
Assert.Equal(1, obj.Test);
}
[Fact]
public void AddNewPropertyToNestedAnonymousObjectShouldFail()
{
dynamic doc = new
{
Test = 1,
nested = new { }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("Nested/NewInt", 1);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/Nested/NewInt' could not be added.",
exception.Message);
}
[Fact]
public void AddNewPropertyToTypedObjectShouldFail()
{
dynamic doc = new
{
Test = 1,
nested = new NestedDTO()
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("Nested/NewInt", 1);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/Nested/NewInt' could not be added.",
exception.Message);
}
[Fact]
public void AddToExistingPropertyOnNestedObject()
{
dynamic doc = new
{
Test = 1,
nested = new NestedDTO()
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("Nested/StringProperty", "A");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("A", doc.nested.StringProperty);
Assert.Equal(1, doc.Test);
}
[Fact]
public void AddNewPropertyToExpandoOject()
{
dynamic doc = new
{
Test = 1,
nested = new ExpandoObject()
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("Nested/NewInt", 1);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(1, doc.nested.NewInt);
Assert.Equal(1, doc.Test);
}
[Fact]
public void AddNewPropertyToExpandoOjectInTypedObject()
{
var doc = new NestedDTO()
{
DynamicProperty = new ExpandoObject()
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("DynamicProperty/NewInt", 1);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(1, doc.DynamicProperty.NewInt);
}
[Fact]
public void AddNewPropertyToTypedObjectInExpandoObject()
{
dynamic dynamicProperty = new ExpandoObject();
dynamicProperty.StringProperty = "A";
var doc = new NestedDTO()
{
DynamicProperty = dynamicProperty
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("DynamicProperty/StringProperty", "B");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("B", doc.DynamicProperty.StringProperty);
}
[Fact]
public void AddNewPropertyToAnonymousObjectShouldFail()
{
dynamic doc = new
{
Test = 1
};
dynamic valueToAdd = new { IntValue = 1, StringValue = "test", GuidValue = Guid.NewGuid() };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("ComplexProperty", valueToAdd);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/ComplexProperty' could not be added.",
exception.Message);
}
[Fact]
public void AddResultsReplaceShouldFailOnAnonymousDueToNoSetter()
{
var doc = new
{
StringProperty = "A"
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("StringProperty", "B");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/StringProperty' could not be updated.",
exception.Message);
}
[Fact]
public void AddResultsShouldReplace()
{
dynamic doc = new ExpandoObject();
doc.StringProperty = "A";
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("StringProperty", "B");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("B", doc.StringProperty);
}
[Fact]
public void AddResultsShouldReplaceInNested()
{
dynamic doc = new ExpandoObject();
doc.InBetweenFirst = new ExpandoObject();
doc.InBetweenFirst.InBetweenSecond = new ExpandoObject();
doc.InBetweenFirst.InBetweenSecond.StringProperty = "A";
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("/InBetweenFirst/InBetweenSecond/StringProperty", "B");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("B", doc.InBetweenFirst.InBetweenSecond.StringProperty);
}
[Fact]
public void AddResultsShouldReplaceInNestedInDynamic()
{
dynamic doc = new ExpandoObject();
doc.Nested = new NestedDTO();
doc.Nested.DynamicProperty = new ExpandoObject();
doc.Nested.DynamicProperty.InBetweenFirst = new ExpandoObject();
doc.Nested.DynamicProperty.InBetweenFirst.InBetweenSecond = new ExpandoObject();
doc.Nested.DynamicProperty.InBetweenFirst.InBetweenSecond.StringProperty = "A";
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("/Nested/DynamicProperty/InBetweenFirst/InBetweenSecond/StringProperty", "B");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("B", doc.Nested.DynamicProperty.InBetweenFirst.InBetweenSecond.StringProperty);
}
[Fact]
public void ShouldNotBeAbleToAddToNonExistingPropertyThatIsNotTheRoot()
{
//Adding to a Nonexistent Target
//
// An example target JSON document:
// { "foo": "bar" }
// A JSON Patch document:
// [
// { "op": "add", "path": "/baz/bat", "value": "qux" }
// ]
// This JSON Patch document, applied to the target JSON document above,
// would result in an error (therefore, it would not be applied),
// because the "add" operation's target location that references neither
// the root of the document, nor a member of an existing object, nor a
// member of an existing array.
var doc = new NestedDTO()
{
DynamicProperty = new ExpandoObject()
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("DynamicProperty/OtherProperty/IntProperty", 1);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/DynamicProperty/OtherProperty/IntProperty' could not be added.",
exception.Message);
}
[Fact]
public void ShouldNotBeAbleToAddToNonExistingPropertyInNestedPropertyThatIsNotTheRoot()
{
//Adding to a Nonexistent Target
//
// An example target JSON document:
// { "foo": "bar" }
// A JSON Patch document:
// [
// { "op": "add", "path": "/baz/bat", "value": "qux" }
// ]
// This JSON Patch document, applied to the target JSON document above,
// would result in an error (therefore, it would not be applied),
// because the "add" operation's target location that references neither
// the root of the document, nor a member of an existing object, nor a
// member of an existing array.
var doc = new
{
Foo = "bar"
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("baz/bat", "qux");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/baz/bat' could not be added.",
exception.Message);
}
[Fact]
public void ShouldReplacePropertyWithDifferentCase()
{
dynamic doc = new ExpandoObject();
doc.StringProperty = "A";
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("stringproperty", "B");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("B", doc.StringProperty);
}
[Fact]
public void AddToList()
{
var doc = new
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/0", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, doc.IntegerList);
}
[Fact]
public void AddToListNegativePosition()
{
dynamic doc = new ExpandoObject();
doc.IntegerList = new List<int>() { 1, 2, 3 };
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/-1", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'add' on array property at path '/IntegerList/-1', the index is negative.",
exception.Message);
}
[Fact]
public void ShouldAddToListWithDifferentCase()
{
var doc = new
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("integerlist/0", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, doc.IntegerList);
}
[Fact]
public void AddToListInvalidPositionTooLarge()
{
var doc = new
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/4", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'add' on array property at path '/IntegerList/4', the index is larger than the array size.",
exception.Message);
}
[Fact]
public void AddToListAtEndWithSerialization()
{
var doc = new
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/3", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2, 3, 4 }, doc.IntegerList);
}
[Fact]
public void AddToListAtBeginning()
{
var doc = new
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/0", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, doc.IntegerList);
}
[Fact]
public void AddToListInvalidPositionTooSmall()
{
var doc = new
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/-1", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'add' on array property at path '/IntegerList/-1', the index is negative.",
exception.Message);
}
[Fact]
public void AddToListAppend()
{
var doc = new
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/-", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2, 3, 4 }, doc.IntegerList);
}
}
}

View File

@ -0,0 +1,121 @@
// 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 Microsoft.AspNet.JsonPatch.Exceptions;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class AddTypedOperationTests
{
[Fact]
public void AddToListNegativePosition()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("IntegerList/-1", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'add' on array property at path '/IntegerList/-1', the index is negative.",
exception.Message);
}
[Fact]
public void AddToListInList()
{
var doc = new SimpleDTOWithNestedDTO()
{
ListOfSimpleDTO = new List<SimpleDTO>()
{
new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("ListOfSimpleDTO/0/IntegerList/0", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, doc.ListOfSimpleDTO[0].IntegerList);
}
[Fact]
public void AddToListInListInvalidPositionTooSmall()
{
var doc = new SimpleDTOWithNestedDTO()
{
ListOfSimpleDTO = new List<SimpleDTO>()
{
new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("ListOfSimpleDTO/-1/IntegerList/0", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/ListOfSimpleDTO/-1/IntegerList/0' could not be added.",
exception.Message);
}
[Fact]
public void AddToListInListInvalidPositionTooLarge()
{
var doc = new SimpleDTOWithNestedDTO()
{
ListOfSimpleDTO = new List<SimpleDTO>()
{
new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add("ListOfSimpleDTO/20/IntegerList/0", 4);
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/ListOfSimpleDTO/20/IntegerList/0' could not be added.",
exception.Message);
}
}
}

View File

@ -0,0 +1,11 @@
// 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.Test.Dynamic
{
public class NestedDTO
{
public string StringProperty { get; set; }
public dynamic DynamicProperty { get; set; }
}
}

View File

@ -0,0 +1,95 @@
// 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 Microsoft.AspNet.JsonPatch.Exceptions;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class PatchDocumentTests
{
[Fact]
public void InvalidPathAtBeginningShouldThrowException()
{
JsonPatchDocument patchDoc = new JsonPatchDocument();
var exception = Assert.Throws<JsonPatchException>(() =>
{
patchDoc.Add("//NewInt", 1);
});
Assert.Equal(
"The provided string '//NewInt' is an invalid path.",
exception.Message);
}
[Fact]
public void InvalidPathAtEndShouldThrowException()
{
JsonPatchDocument patchDoc = new JsonPatchDocument();
var exception = Assert.Throws<JsonPatchException>(() =>
{
patchDoc.Add("NewInt//", 1);
});
Assert.Equal(
"The provided string 'NewInt//' is an invalid path.",
exception.Message);
}
[Fact]
public void InvalidPathWithDotShouldThrowException()
{
JsonPatchDocument patchDoc = new JsonPatchDocument();
var exception = Assert.Throws<JsonPatchException>(() =>
{
patchDoc.Add("NewInt.Test", 1);
});
Assert.Equal(
"The provided string 'NewInt.Test' is an invalid path.",
exception.Message);
}
[Fact]
public void NonGenericPatchDocToGenericMustSerialize()
{
var doc = new SimpleDTO()
{
StringProperty = "A",
AnotherStringProperty = "B"
};
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Copy("StringProperty", "AnotherStringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<SimpleDTO>>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal("A", doc.AnotherStringProperty);
}
[Fact]
public void GenericPatchDocToNonGenericMustSerialize()
{
var doc = new SimpleDTO()
{
StringProperty = "A",
AnotherStringProperty = "B"
};
JsonPatchDocument<SimpleDTO> patchDocTyped = new JsonPatchDocument<SimpleDTO>();
patchDocTyped.Copy<string>(o => o.StringProperty, o => o.AnotherStringProperty);
JsonPatchDocument patchDocUntyped = new JsonPatchDocument();
patchDocUntyped.Copy("StringProperty", "AnotherStringProperty");
var serializedTyped = JsonConvert.SerializeObject(patchDocTyped);
var serializedUntyped = JsonConvert.SerializeObject(patchDocUntyped);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serializedTyped);
deserialized.ApplyTo(doc);
Assert.Equal("A", doc.AnotherStringProperty);
}
}
}

View File

@ -0,0 +1,302 @@
// 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 Microsoft.AspNet.JsonPatch.Exceptions;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class RemoveOperationTests
{
[Fact]
public void RemovePropertyShouldFailIfRootIsAnonymous()
{
dynamic doc = new
{
Test = 1
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("Test");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/Test' could not be updated.",
exception.Message);
}
[Fact]
public void RemovePropertyShouldFailIfItDoesntExist()
{
dynamic doc = new ExpandoObject();
doc.Test = 1;
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("NonExisting");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"The property at path '/NonExisting' could not be removed.",
exception.Message);
}
[Fact]
public void RemovePropertyFromExpandoObject()
{
dynamic obj = new ExpandoObject();
obj.Test = 1;
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("Test");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(obj);
var cont = obj as IDictionary<string, object>;
object valueFromDictionary;
cont.TryGetValue("Test", out valueFromDictionary);
Assert.Null(valueFromDictionary);
}
[Fact]
public void RemovePropertyFromExpandoObjectMixedCase()
{
dynamic obj = new ExpandoObject();
obj.Test = 1;
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("test");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(obj);
var cont = obj as IDictionary<string, object>;
object valueFromDictionary;
cont.TryGetValue("Test", out valueFromDictionary);
Assert.Null(valueFromDictionary);
}
[Fact]
public void RemoveNestedPropertyFromExpandoObject()
{
dynamic obj = new ExpandoObject();
obj.Test = new ExpandoObject();
obj.Test.AnotherTest = "A";
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("Test");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(obj);
var cont = obj as IDictionary<string, object>;
object valueFromDictionary;
cont.TryGetValue("Test", out valueFromDictionary);
Assert.Null(valueFromDictionary);
}
[Fact]
public void RemoveNestedPropertyFromExpandoObjectMixedCase()
{
dynamic obj = new ExpandoObject();
obj.Test = new ExpandoObject();
obj.Test.AnotherTest = "A";
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("test");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(obj);
var cont = obj as IDictionary<string, object>;
object valueFromDictionary;
cont.TryGetValue("Test", out valueFromDictionary);
Assert.Null(valueFromDictionary);
}
[Fact]
public void NestedRemove()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
StringProperty = "A"
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/StringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(null, doc.SimpleDTO.StringProperty);
}
[Fact]
public void NestedRemoveMixedCase()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
StringProperty = "A"
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("Simpledto/stringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(null, doc.SimpleDTO.StringProperty);
}
[Fact]
public void NestedRemoveFromList()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/2");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2 }, doc.SimpleDTO.IntegerList);
}
[Fact]
public void NestedRemoveFromListMixedCase()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/Integerlist/2");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2 }, doc.SimpleDTO.IntegerList);
}
[Fact]
public void NestedRemoveFromListInvalidPositionTooLarge()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/3");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'remove' on array property at path '/SimpleDTO/IntegerList/3', the index is larger than the array size.",
exception.Message);
}
[Fact]
public void NestedRemoveFromListInvalidPositionTooSmall()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/-1");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'remove' on array property at path '/SimpleDTO/IntegerList/-1', the index is negative.",
exception.Message);
}
[Fact]
public void NestedRemoveFromEndOfList()
{
dynamic doc = new ExpandoObject();
doc.SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2 }, doc.SimpleDTO.IntegerList);
}
}
}

View File

@ -0,0 +1,244 @@
// 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 Microsoft.AspNet.JsonPatch.Exceptions;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class RemoveTypedOperationTests
{
[Fact]
public void Remove()
{
var doc = new SimpleDTO()
{
StringProperty = "A"
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("StringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(null, doc.StringProperty);
}
[Fact]
public void RemoveFromList()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("IntegerList/2");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2 }, doc.IntegerList);
}
[Fact]
public void RemoveFromListInvalidPositionTooLarge()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("IntegerList/3");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'remove' on array property at path '/IntegerList/3', the index is larger than the array size.",
exception.Message);
}
[Fact]
public void RemoveFromListInvalidPositionTooSmall()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("IntegerList/-1");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'remove' on array property at path '/IntegerList/-1', the index is negative.",
exception.Message);
}
[Fact]
public void RemoveFromEndOfList()
{
var doc = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2 }, doc.IntegerList);
}
[Fact]
public void NestedRemove()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
StringProperty = "A"
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/StringProperty");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(null, doc.SimpleDTO.StringProperty);
}
[Fact]
public void NestedRemoveFromList()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/2");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2 }, doc.SimpleDTO.IntegerList);
}
[Fact]
public void NestedRemoveFromListInvalidPositionTooLarge()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/3");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'remove' on array property at path '/SimpleDTO/IntegerList/3', the index is larger than the array size.",
exception.Message);
}
[Fact]
public void NestedRemoveFromListInvalidPositionTooSmall()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/-1");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
var exception = Assert.Throws<JsonPatchException>(() =>
{
deserialized.ApplyTo(doc);
});
Assert.Equal(
"For operation 'remove' on array property at path '/SimpleDTO/IntegerList/-1', the index is negative.",
exception.Message);
}
[Fact]
public void NestedRemoveFromEndOfList()
{
var doc = new SimpleDTOWithNestedDTO()
{
SimpleDTO = new SimpleDTO()
{
IntegerList = new List<int>() { 1, 2, 3 }
}
};
// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Remove("SimpleDTO/IntegerList/-");
var serialized = JsonConvert.SerializeObject(patchDoc);
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
deserialized.ApplyTo(doc);
Assert.Equal(new List<int>() { 1, 2 }, doc.SimpleDTO.IntegerList);
}
}
}

View File

@ -0,0 +1,21 @@
// 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;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class SimpleDTO
{
public List<SimpleDTO> SimpleDTOList { get; set; }
public List<int> IntegerList { get; set; }
public int IntegerValue { get; set; }
public string StringProperty { get; set; }
public string AnotherStringProperty { get; set; }
public decimal DecimalValue { get; set; }
public double DoubleValue { get; set; }
public float FloatValue { get; set; }
public Guid GuidValue { get; set; }
}
}

View File

@ -0,0 +1,22 @@
// 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;
namespace Microsoft.AspNet.JsonPatch.Test.Dynamic
{
public class SimpleDTOWithNestedDTO
{
public int IntegerValue { get; set; }
public NestedDTO NestedDTO { get; set; }
public SimpleDTO SimpleDTO { get; set; }
public List<SimpleDTO> ListOfSimpleDTO { get; set; }
public SimpleDTOWithNestedDTO()
{
NestedDTO = new NestedDTO();
SimpleDTO = new SimpleDTO();
ListOfSimpleDTO = new List<SimpleDTO>();
}
}
}

View File

@ -778,7 +778,7 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message);
Assert.Equal("For operation 'remove' on array property at path '/simpledto/integerlist/-1', the index is negative.", exception.Message);
}
[Fact]
@ -805,7 +805,7 @@ namespace Microsoft.AspNet.JsonPatch.Test
{
deserialized.ApplyTo(doc);
});
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", exception.Message);
Assert.Equal("For operation 'remove' on array property at path '/simpledto/integerlist/-1', the index is negative.", exception.Message);
}
[Fact]
@ -830,7 +830,7 @@ namespace Microsoft.AspNet.JsonPatch.Test
patchDoc.ApplyTo(doc, logger.LogErrorMessage);
// Assert
Assert.Equal("Property does not exist at path '/simpledto/integerlist/-1'.", logger.ErrorMessage);
Assert.Equal("For operation 'remove' on array property at path '/simpledto/integerlist/-1', the index is negative.", logger.ErrorMessage);
}
[Fact]
@ -1319,11 +1319,10 @@ namespace Microsoft.AspNet.JsonPatch.Test
// create patch
var patchDoc = new JsonPatchDocument<SimpleDTOWithNestedDTO>();
patchDoc.Replace<int>(o => o.SimpleDTO.IntegerList, 5, -1);
// Act & Assert
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
Assert.Equal(
"Property does not exist at path '/simpledto/integerlist/-1'.",
Assert.Equal("For operation 'replace' on array property at path '/simpledto/integerlist/-1', the index is negative.",
exception.Message);
}
@ -1349,7 +1348,7 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException>(() => { deserialized.ApplyTo(doc); });
Assert.Equal(
"Property does not exist at path '/simpledto/integerlist/-1'.",
"For operation 'replace' on array property at path '/simpledto/integerlist/-1', the index is negative.",
exception.Message);
}

View File

@ -577,7 +577,7 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException>(() => { patchDoc.ApplyTo(doc); });
Assert.Equal("Property does not exist at path '/integerlist/-1'.", exception.Message);
Assert.Equal("For operation 'remove' on array property at path '/integerlist/-1', the index is negative.", exception.Message);
}
[Fact]
@ -598,7 +598,7 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Act & Assert
var exception = Assert.Throws<JsonPatchException>(() => { deserialized.ApplyTo(doc); });
Assert.Equal("Property does not exist at path '/integerlist/-1'.", exception.Message);
Assert.Equal("For operation 'remove' on array property at path '/integerlist/-1', the index is negative.", exception.Message);
}
[Fact]
@ -621,7 +621,7 @@ namespace Microsoft.AspNet.JsonPatch.Test
// Assert
Assert.Equal("Property does not exist at path '/integerlist/-1'.", logger.ErrorMessage);
Assert.Equal("For operation 'remove' on array property at path '/integerlist/-1', the index is negative.", logger.ErrorMessage);
}
[Fact]
@ -1172,7 +1172,7 @@ namespace Microsoft.AspNet.JsonPatch.Test
{
patchDoc.ApplyTo(doc);
});
Assert.Equal("Property does not exist at path '/integerlist/-1'.", exception.Message);
Assert.Equal("For operation 'replace' on array property at path '/integerlist/-1', the index is negative.", exception.Message);
}
[Fact]
@ -1196,7 +1196,7 @@ namespace Microsoft.AspNet.JsonPatch.Test
{
deserialized.ApplyTo(doc);
});
Assert.Equal("Property does not exist at path '/integerlist/-1'.", exception.Message);
Assert.Equal("For operation 'replace' on array property at path '/integerlist/-1', the index is negative.", exception.Message);
}
[Fact]