File: JsonPatchDocumentTest.cs
Web Access
Project: src\src\Features\JsonPatch.SystemTextJson\test\Microsoft.AspNetCore.JsonPatch.SystemTextJson.Tests.csproj (Microsoft.AspNetCore.JsonPatch.SystemTextJson.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Text.Json;
using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Converters;
using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Exceptions;
using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations;
using Xunit;
 
namespace Microsoft.AspNetCore.JsonPatch.SystemTextJson;
 
public class JsonPatchDocumentTest
{
    [Fact]
    public void InvalidPathAtBeginningShouldThrowException()
    {
        // Arrange
        var patchDocument = new JsonPatchDocument();
 
        // Act
        var exception = Assert.Throws<JsonPatchException>(() =>
        {
            patchDocument.Add("//NewInt", 1);
        });
 
        // Assert
        Assert.Equal(
           "The provided string '//NewInt' is an invalid path.",
            exception.Message);
    }
 
    [Fact]
    public void InvalidPathAtEndShouldThrowException()
    {
        // Arrange
        var patchDocument = new JsonPatchDocument();
 
        // Act
        var exception = Assert.Throws<JsonPatchException>(() =>
        {
            patchDocument.Add("NewInt//", 1);
        });
 
        // Assert
        Assert.Equal(
           "The provided string 'NewInt//' is an invalid path.",
            exception.Message);
    }
 
    [Fact]
    public void NonGenericPatchDocToGenericMustSerialize()
    {
        // Arrange
        var targetObject = new SimpleObject()
        {
            StringProperty = "A",
            AnotherStringProperty = "B"
        };
 
        var patchDocument = new JsonPatchDocument();
        patchDocument.Copy("StringProperty", "AnotherStringProperty");
 
        var serialized = JsonSerializer.Serialize(patchDocument);
        var deserialized = JsonSerializer.Deserialize<JsonPatchDocument<SimpleObject>>(serialized);
 
        // Act
        deserialized.ApplyTo(targetObject);
 
        // Assert
        Assert.Equal("A", targetObject.AnotherStringProperty);
    }
 
    public class Employee
    {
        public int EmployeeId { get; set; }
        public string Name { get; set; }
    }
 
    public class SalariedEmployee : Employee
    {
        public decimal AnnualSalary { get; set; }
    }
 
    public class Organization
    {
        public List<Employee> Employees { get; } = new();
    }
 
    [Fact]
    public void ListWithGenericTypeWorkForSpecificChildren()
    {
        //Arrange
        var org = new Organization();
        // Populate Employees with two employees
        org.Employees.Add(new SalariedEmployee { EmployeeId = 2, Name = "Jane", AnnualSalary = 50000 });
        org.Employees.Add(new Employee { EmployeeId = 1, Name = "John" });
 
        var doc = new JsonPatchDocument<Organization>();
        doc.Operations.Add(new Operations.Operation<Organization>("add", "/Employees/0/AnnualSalary", "", 100));
 
        // Act
        doc.ApplyTo(org);
 
        // Assert
        Assert.Equal(100, (org.Employees[0] as SalariedEmployee).AnnualSalary);
    }
 
    [Fact]
    public void GenericPatchDocToNonGenericMustSerialize()
    {
        // Arrange
        var targetObject = new SimpleObject()
        {
            StringProperty = "A",
            AnotherStringProperty = "B"
        };
 
        var patchDocTyped = new JsonPatchDocument<SimpleObject>();
        patchDocTyped.Copy(o => o.StringProperty, o => o.AnotherStringProperty);
 
        var patchDocUntyped = new JsonPatchDocument();
        patchDocUntyped.Copy("StringProperty", "AnotherStringProperty");
 
        var serializedTyped = JsonSerializer.Serialize(patchDocTyped);
        var serializedUntyped = JsonSerializer.Serialize(patchDocUntyped);
        var deserialized = JsonSerializer.Deserialize<JsonPatchDocument>(serializedTyped);
 
        // Act
        deserialized.ApplyTo(targetObject);
 
        // Assert
        Assert.Equal("A", targetObject.AnotherStringProperty);
    }
 
    [Fact]
    public void Deserialization_Successful_ForValidJsonPatchDocument()
    {
        // Arrange
        var doc = new SimpleObject()
        {
            StringProperty = "A",
            DecimalValue = 10,
            DoubleValue = 10,
            FloatValue = 10,
            IntegerValue = 10
        };
 
        var patchDocument = new JsonPatchDocument<SimpleObject>();
        patchDocument.Replace(o => o.StringProperty, "B");
        patchDocument.Replace(o => o.DecimalValue, 12);
        patchDocument.Replace(o => o.DoubleValue, 12);
        patchDocument.Replace(o => o.FloatValue, 12);
        patchDocument.Replace(o => o.IntegerValue, 12);
 
        // default: no envelope
        var serialized = JsonSerializer.Serialize(patchDocument);
 
        // Act
        var deserialized = JsonSerializer.Deserialize<JsonPatchDocument<SimpleObject>>(serialized);
 
        // Assert
        Assert.IsType<JsonPatchDocument<SimpleObject>>(deserialized);
    }
 
    [Fact]
    public void Deserialization_Fails_ForInvalidJsonPatchDocument()
    {
        // Arrange
        var serialized = "{\"Operations\": [{ \"op\": \"replace\", \"path\": \"/title\", \"value\": \"New Title\"}]}"{\"Operations\": [{ \"op\": \"replace\", \"path\": \"/title\", \"value\": \"New Title\"}]}";
 
        // Act
        var exception = Assert.Throws<JsonException>(() =>
        {
            var deserialized
                = JsonSerializer.Deserialize<JsonPatchDocument>(serialized);
        });
 
        // Assert
        Assert.Equal("The JSON patch document was malformed and could not be parsed.", exception.Message);
    }
 
    [Fact]
    public void Deserialization_Fails_ForInvalidTypedJsonPatchDocument()
    {
        // Arrange
        var serialized = "{\"Operations\": [{ \"op\": \"replace\", \"path\": \"/title\", \"value\": \"New Title\"}]}"{\"Operations\": [{ \"op\": \"replace\", \"path\": \"/title\", \"value\": \"New Title\"}]}";
 
        // Act
        var exception = Assert.Throws<JsonException>(() =>
        {
            var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
            options.Converters.Add(new JsonConverterForJsonPatchDocumentOfT<SimpleObject>());
            var deserialized
                = JsonSerializer.Deserialize<JsonPatchDocument<SimpleObject>>(serialized, options);
        });
 
        // Assert
        Assert.Equal("The JSON patch document was malformed and could not be parsed.", exception.Message);
    }
 
    [Fact]
    public void Deserialization_RespectsNamingPolicy()
    {
        // Arrange
        var childToAdd = new SimpleObject
        {
            GuidValue = Guid.NewGuid(),
            StringProperty = "some test data"
        };
 
        var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
        options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
        options.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault;
 
        var json = GeneratePatchDocumentJson(childToAdd, options);
 
        var getTestObject = () => new SimpleObject() { SimpleObjectList = new() };
 
        //Act
        var docSuccess = DeserializePatchDocumentWithNamingPolicy(json, JsonNamingPolicy.CamelCase);
        var docFail = DeserializePatchDocumentWithNamingPolicy(json, JsonNamingPolicy.KebabCaseLower);
 
        // Assert
 
        // The following call should succeed
        docSuccess.ApplyTo(getTestObject());
 
        // The following call should fail
        Assert.Throws<JsonPatchException>(() =>
        {
            docFail.ApplyTo(getTestObject());
        });
    }
 
    private static JsonPatchDocument<SimpleObject> DeserializePatchDocumentWithNamingPolicy(string json, JsonNamingPolicy policy)
    {
        var compatibleSerializerOption = new JsonSerializerOptions(JsonSerializerDefaults.Web);
        compatibleSerializerOption.PropertyNamingPolicy = policy;
        var docSuccess = JsonSerializer.Deserialize<JsonPatchDocument<SimpleObject>>(json, compatibleSerializerOption);
        return docSuccess;
    }
 
    private string GeneratePatchDocumentJson(SimpleObject toAdd, JsonSerializerOptions jsonSerializerOptions)
    {
        var document = new JsonPatchDocument<SimpleObject>();
        var operation = new Operation<SimpleObject>
        {
            op = "add",
            path = "/simpleObjectList/-",
            value = toAdd
        };
        document.Operations.Add(operation);
 
        return JsonSerializer.Serialize<JsonPatchDocument<SimpleObject>>(document, jsonSerializerOptions);
    }
}