Skip to content

Commit

Permalink
Reproduce ArgumentOutOfRangeException when entity doesn't match pat…
Browse files Browse the repository at this point in the history
…ches (#87)
  • Loading branch information
hf-kklein authored Jul 14, 2024
1 parent a406ed1 commit 2d218ae
Showing 1 changed file with 130 additions and 0 deletions.
130 changes: 130 additions & 0 deletions ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System.Text.Json.Serialization;
using ChronoJsonDiffPatch;
using FluentAssertions;

namespace ChronoJsonDiffPatchTests;

public class ListPatchingTests
{
internal record ListItem
{
[JsonPropertyName("value")] public string Value { get; set; }

Check warning on line 11 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (7.0.100)

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 11 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (8.0.100)

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 11 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (6.0.201)

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 11 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / coverage

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 11 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (7.0.100)

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 11 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (6.0.201)

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 11 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (8.0.100)

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 11 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / coverage

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 11 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / pushrelease

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 11 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / pushrelease

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 11 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (7.0.100)

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 11 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (6.0.201)

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 11 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (8.0.100)

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 11 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / coverage

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 11 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / pushrelease

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
}

internal record EntityWithList
{
[JsonPropertyName("myList")] public List<ListItem> MyList { get; set; }

Check warning on line 16 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (7.0.100)

Non-nullable property 'MyList' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 16 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (8.0.100)

Non-nullable property 'MyList' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 16 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (6.0.201)

Non-nullable property 'MyList' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 16 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / coverage

Non-nullable property 'MyList' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 16 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (7.0.100)

Non-nullable property 'MyList' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 16 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (6.0.201)

Non-nullable property 'MyList' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 16 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (8.0.100)

Non-nullable property 'MyList' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 16 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / coverage

Non-nullable property 'MyList' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 16 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / pushrelease

Non-nullable property 'MyList' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 16 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / pushrelease

Non-nullable property 'MyList' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 16 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (7.0.100)

Non-nullable property 'MyList' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 16 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (6.0.201)

Non-nullable property 'MyList' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 16 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / unittest (8.0.100)

Non-nullable property 'MyList' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 16 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / coverage

Non-nullable property 'MyList' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 16 in ChronoJsonDiffPatch/ChronoJsonDiffPatchTests/ListPatchingTests.cs

View workflow job for this annotation

GitHub Actions / pushrelease

Non-nullable property 'MyList' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
}


[Fact]
public void Test_List_Patching_Generally_Works_With_Add_And_Reverse()
{
var chain = new TimeRangePatchChain<EntityWithList>();
var initialEntity = new EntityWithList
{
MyList = new List<ListItem>
{
new() { Value = "Foo" },
new() { Value = "Bar" }
}
};
{
var updatedEntity1 = new EntityWithList
{
MyList = new List<ListItem>
{
new() { Value = "fOO" },
new() { Value = "bAR" }
}
};
var keyDate1 = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero);
chain.Add(initialEntity, updatedEntity1, keyDate1);

chain.Count.Should().Be(2); // [-infinity, keyDate1); [keyDate1, +infinity)
ReverseAndRevert(chain, initialEntity);
}

{
var updatedEntity2 = new EntityWithList
{
MyList = new List<ListItem>
{
new() { Value = "fOO" },
new() { Value = "bAR" },
new() { Value = "bAZ" }
}
};
var keyDate2 = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
chain.Add(initialEntity, updatedEntity2, keyDate2);

chain.Count.Should().Be(3); // [-infinity, keyDate1); [keyDate1, keyDate2); [keyDate2, +infinity)
ReverseAndRevert(chain, initialEntity);
}

{
var updatedEntity3 = new EntityWithList
{
MyList = new List<ListItem>
{
new() { Value = "Not so foo anymore" },
new() { Value = "bAR" },
new() { Value = "bAZ" }
}
};
var keyDate3 = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
chain.Add(initialEntity, updatedEntity3, keyDate3);

ReverseAndRevert(chain, initialEntity);
}
}

/// <summary>
/// In <see cref="Test_List_Patching_Generally_Works_With_Add_And_Reverse"/> we showed that adding and removing list entries is generally well-supported by this library.
/// In this test, we show, than when users run into an <see cref="ArgumentOutOfRangeException"/>, this is probably due to initial entities not matching the expected state (corrupted).
/// </summary>
[Fact]
public void Reproduce_ArgumentOutOfRangeException()
{
var chain = new TimeRangePatchChain<EntityWithList>();
var initialEntity = new EntityWithList
{
MyList = new List<ListItem>
{
new() { Value = "My First Value" },
}
};
var keyDate1 = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero);
{
var updatedEntity1 = new EntityWithList
{
MyList = new List<ListItem>
{
new() { Value = "My First Value" },
new() { Value = "My Second Value" }
}
};

chain.Add(initialEntity, updatedEntity1, keyDate1);
chain.Count.Should().Be(2); // [-infinity, keyDate1); [keyDate1, +infinity)
ReverseAndRevert(chain, initialEntity);
}
(var antiparallelInitialEntity, var antiparallelChain) = chain.Reverse(initialEntity);
antiparallelInitialEntity.Should().Match<EntityWithList>(x => x.MyList.Count == 2, because: "Initially the list had 2 items");
var patchingACorrectInitialEntity = () => antiparallelChain.PatchToDate(antiparallelInitialEntity, keyDate1 - TimeSpan.FromDays(10));
patchingACorrectInitialEntity.Should().NotThrow();

var corruptedInitialEntity = antiparallelInitialEntity; // we modify the reference here, but that's fine. We improve the readability but don't re-use the antiparallelInitialEntity anywhere downstream.
corruptedInitialEntity.MyList.RemoveAt(1);
var applyingPatchesToACorruptedInitialEntity = () => antiparallelChain.PatchToDate(corruptedInitialEntity, keyDate1 - TimeSpan.FromDays(10));
applyingPatchesToACorruptedInitialEntity.Should().ThrowExactly<ArgumentOutOfRangeException>();
}

private static void ReverseAndRevert(TimeRangePatchChain<EntityWithList> chain, EntityWithList initialEntity)
{
var (reverseEntity, reverseChain) = chain.Reverse(initialEntity);
var (rereverseEntity, rereverseChain) = reverseChain.Reverse(reverseEntity);
rereverseChain.Should().BeEquivalentTo(chain);
initialEntity.Should().BeEquivalentTo(rereverseEntity);
}
}

0 comments on commit 2d218ae

Please sign in to comment.