231 lines
9.4 KiB
C#
231 lines
9.4 KiB
C#
// 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 Newtonsoft.Json.Serialization;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|
{
|
|
public class ObjectVisitorTest
|
|
{
|
|
private class Class1
|
|
{
|
|
public string Name { get; set; }
|
|
public IList<string> States { get; set; } = new List<string>();
|
|
public IDictionary<string, string> Countries = new Dictionary<string, string>();
|
|
public dynamic Items { get; set; } = new ExpandoObject();
|
|
}
|
|
|
|
private class Class1Nested
|
|
{
|
|
public List<Class1> Customers { get; set; } = new List<Class1>();
|
|
}
|
|
|
|
public static IEnumerable<object[]> ReturnsListAdapterData
|
|
{
|
|
get
|
|
{
|
|
var model = new Class1();
|
|
yield return new object[] { model, "/States/-", model.States };
|
|
yield return new object[] { model.States, "/-", model.States };
|
|
|
|
var nestedModel = new Class1Nested();
|
|
nestedModel.Customers.Add(new Class1());
|
|
yield return new object[] { nestedModel, "/Customers/0/States/-", nestedModel.Customers[0].States };
|
|
yield return new object[] { nestedModel, "/Customers/0/States/0", nestedModel.Customers[0].States };
|
|
yield return new object[] { nestedModel.Customers, "/0/States/-", nestedModel.Customers[0].States };
|
|
yield return new object[] { nestedModel.Customers[0], "/States/-", nestedModel.Customers[0].States };
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ReturnsListAdapterData))]
|
|
public void Visit_ValidPathToArray_ReturnsListAdapter(object targetObject, string path, object expectedTargetObject)
|
|
{
|
|
// Arrange
|
|
var visitor = new ObjectVisitor(new ParsedPath(path), new DefaultContractResolver());
|
|
|
|
// Act
|
|
var visitStatus = visitor.TryVisit(ref targetObject, out var adapter, out var message);
|
|
|
|
// Assert
|
|
Assert.True(visitStatus);
|
|
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
|
Assert.Same(expectedTargetObject, targetObject);
|
|
Assert.IsType<ListAdapter>(adapter);
|
|
}
|
|
|
|
public static IEnumerable<object[]> ReturnsDictionaryAdapterData
|
|
{
|
|
get
|
|
{
|
|
var model = new Class1();
|
|
yield return new object[] { model, "/Countries/USA", model.Countries };
|
|
yield return new object[] { model.Countries, "/USA", model.Countries };
|
|
|
|
var nestedModel = new Class1Nested();
|
|
nestedModel.Customers.Add(new Class1());
|
|
yield return new object[] { nestedModel, "/Customers/0/Countries/USA", nestedModel.Customers[0].Countries };
|
|
yield return new object[] { nestedModel.Customers, "/0/Countries/USA", nestedModel.Customers[0].Countries };
|
|
yield return new object[] { nestedModel.Customers[0], "/Countries/USA", nestedModel.Customers[0].Countries };
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ReturnsDictionaryAdapterData))]
|
|
public void Visit_ValidPathToDictionary_ReturnsDictionaryAdapter(object targetObject, string path, object expectedTargetObject)
|
|
{
|
|
// Arrange
|
|
var visitor = new ObjectVisitor(new ParsedPath(path), new DefaultContractResolver());
|
|
|
|
// Act
|
|
var visitStatus = visitor.TryVisit(ref targetObject, out var adapter, out var message);
|
|
|
|
// Assert
|
|
Assert.True(visitStatus);
|
|
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
|
Assert.Same(expectedTargetObject, targetObject);
|
|
Assert.Equal(typeof(DictionaryAdapter<string, string>), adapter.GetType());
|
|
}
|
|
|
|
public static IEnumerable<object[]> ReturnsExpandoAdapterData
|
|
{
|
|
get
|
|
{
|
|
var nestedModel = new Class1Nested();
|
|
nestedModel.Customers.Add(new Class1());
|
|
yield return new object[] { nestedModel, "/Customers/0/Items/Name", nestedModel.Customers[0].Items };
|
|
yield return new object[] { nestedModel.Customers, "/0/Items/Name", nestedModel.Customers[0].Items };
|
|
yield return new object[] { nestedModel.Customers[0], "/Items/Name", nestedModel.Customers[0].Items };
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ReturnsExpandoAdapterData))]
|
|
public void Visit_ValidPathToExpandoObject_ReturnsExpandoAdapter(object targetObject, string path, object expectedTargetObject)
|
|
{
|
|
// Arrange
|
|
var contractResolver = new DefaultContractResolver();
|
|
var visitor = new ObjectVisitor(new ParsedPath(path), contractResolver);
|
|
|
|
// Act
|
|
var visitStatus = visitor.TryVisit(ref targetObject, out var adapter, out var message);
|
|
|
|
// Assert
|
|
Assert.True(visitStatus);
|
|
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
|
Assert.Same(expectedTargetObject, targetObject);
|
|
Assert.Same(typeof(DictionaryAdapter<string, object>), adapter.GetType());
|
|
}
|
|
|
|
public static IEnumerable<object[]> ReturnsPocoAdapterData
|
|
{
|
|
get
|
|
{
|
|
var model = new Class1();
|
|
yield return new object[] { model, "/Name", model };
|
|
|
|
var nestedModel = new Class1Nested();
|
|
nestedModel.Customers.Add(new Class1());
|
|
yield return new object[] { nestedModel, "/Customers/0/Name", nestedModel.Customers[0] };
|
|
yield return new object[] { nestedModel.Customers, "/0/Name", nestedModel.Customers[0] };
|
|
yield return new object[] { nestedModel.Customers[0], "/Name", nestedModel.Customers[0] };
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ReturnsPocoAdapterData))]
|
|
public void Visit_ValidPath_ReturnsExpandoAdapter(object targetObject, string path, object expectedTargetObject)
|
|
{
|
|
// Arrange
|
|
var visitor = new ObjectVisitor(new ParsedPath(path), new DefaultContractResolver());
|
|
|
|
// Act
|
|
var visitStatus = visitor.TryVisit(ref targetObject, out var adapter, out var message);
|
|
|
|
// Assert
|
|
Assert.True(visitStatus);
|
|
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
|
Assert.Same(expectedTargetObject, targetObject);
|
|
Assert.IsType<PocoAdapter>(adapter);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("0")]
|
|
[InlineData("-1")]
|
|
public void Visit_InvalidIndexToArray_Fails(string position)
|
|
{
|
|
// Arrange
|
|
var visitor = new ObjectVisitor(new ParsedPath($"/Customers/{position}/States/-"), new DefaultContractResolver());
|
|
var automobileDepartment = new Class1Nested();
|
|
object targetObject = automobileDepartment;
|
|
|
|
// Act
|
|
var visitStatus = visitor.TryVisit(ref targetObject, out var adapter, out var message);
|
|
|
|
// Assert
|
|
Assert.False(visitStatus);
|
|
Assert.Equal(
|
|
string.Format("The index value provided by path segment '{0}' is out of bounds of the array size.", position),
|
|
message);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("-")]
|
|
[InlineData("foo")]
|
|
public void Visit_InvalidIndexFormatToArray_Fails(string position)
|
|
{
|
|
// Arrange
|
|
var visitor = new ObjectVisitor(new ParsedPath($"/Customers/{position}/States/-"), new DefaultContractResolver());
|
|
var automobileDepartment = new Class1Nested();
|
|
object targetObject = automobileDepartment;
|
|
|
|
// Act
|
|
var visitStatus = visitor.TryVisit(ref targetObject, out var adapter, out var message);
|
|
|
|
// Assert
|
|
Assert.False(visitStatus);
|
|
Assert.Equal(string.Format(
|
|
"The path segment '{0}' is invalid for an array index.", position),
|
|
message);
|
|
}
|
|
|
|
// The adapter takes care of the responsibility of validating the final segment
|
|
[Fact]
|
|
public void Visit_DoesNotValidate_FinalPathSegment()
|
|
{
|
|
// Arrange
|
|
var visitor = new ObjectVisitor(new ParsedPath($"/NonExisting"), new DefaultContractResolver());
|
|
var model = new Class1();
|
|
object targetObject = model;
|
|
|
|
// Act
|
|
var visitStatus = visitor.TryVisit(ref targetObject, out var adapter, out var message);
|
|
|
|
// Assert
|
|
Assert.True(visitStatus);
|
|
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
|
Assert.IsType<PocoAdapter>(adapter);
|
|
}
|
|
|
|
[Fact]
|
|
public void Visit_NullTarget_ReturnsNullAdapter()
|
|
{
|
|
// Arrange
|
|
var visitor = new ObjectVisitor(new ParsedPath("test"), new DefaultContractResolver());
|
|
|
|
// Act
|
|
object target = null;
|
|
var visitStatus = visitor.TryVisit(ref target, out var adapter, out var message);
|
|
|
|
// Assert
|
|
Assert.False(visitStatus);
|
|
Assert.Null(adapter);
|
|
Assert.Null(message);
|
|
}
|
|
}
|
|
}
|