Skip to content

Commit 2d218ae

Browse files
authored
Reproduce ArgumentOutOfRangeException when entity doesn't match patches (#87)
1 parent a406ed1 commit 2d218ae

File tree

1 file changed

+130
-0
lines changed

1 file changed

+130
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System.Text.Json.Serialization;
2+
using ChronoJsonDiffPatch;
3+
using FluentAssertions;
4+
5+
namespace ChronoJsonDiffPatchTests;
6+
7+
public class ListPatchingTests
8+
{
9+
internal record ListItem
10+
{
11+
[JsonPropertyName("value")] public string Value { get; set; }
12+
}
13+
14+
internal record EntityWithList
15+
{
16+
[JsonPropertyName("myList")] public List<ListItem> MyList { get; set; }
17+
}
18+
19+
20+
[Fact]
21+
public void Test_List_Patching_Generally_Works_With_Add_And_Reverse()
22+
{
23+
var chain = new TimeRangePatchChain<EntityWithList>();
24+
var initialEntity = new EntityWithList
25+
{
26+
MyList = new List<ListItem>
27+
{
28+
new() { Value = "Foo" },
29+
new() { Value = "Bar" }
30+
}
31+
};
32+
{
33+
var updatedEntity1 = new EntityWithList
34+
{
35+
MyList = new List<ListItem>
36+
{
37+
new() { Value = "fOO" },
38+
new() { Value = "bAR" }
39+
}
40+
};
41+
var keyDate1 = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero);
42+
chain.Add(initialEntity, updatedEntity1, keyDate1);
43+
44+
chain.Count.Should().Be(2); // [-infinity, keyDate1); [keyDate1, +infinity)
45+
ReverseAndRevert(chain, initialEntity);
46+
}
47+
48+
{
49+
var updatedEntity2 = new EntityWithList
50+
{
51+
MyList = new List<ListItem>
52+
{
53+
new() { Value = "fOO" },
54+
new() { Value = "bAR" },
55+
new() { Value = "bAZ" }
56+
}
57+
};
58+
var keyDate2 = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
59+
chain.Add(initialEntity, updatedEntity2, keyDate2);
60+
61+
chain.Count.Should().Be(3); // [-infinity, keyDate1); [keyDate1, keyDate2); [keyDate2, +infinity)
62+
ReverseAndRevert(chain, initialEntity);
63+
}
64+
65+
{
66+
var updatedEntity3 = new EntityWithList
67+
{
68+
MyList = new List<ListItem>
69+
{
70+
new() { Value = "Not so foo anymore" },
71+
new() { Value = "bAR" },
72+
new() { Value = "bAZ" }
73+
}
74+
};
75+
var keyDate3 = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
76+
chain.Add(initialEntity, updatedEntity3, keyDate3);
77+
78+
ReverseAndRevert(chain, initialEntity);
79+
}
80+
}
81+
82+
/// <summary>
83+
/// 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.
84+
/// 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).
85+
/// </summary>
86+
[Fact]
87+
public void Reproduce_ArgumentOutOfRangeException()
88+
{
89+
var chain = new TimeRangePatchChain<EntityWithList>();
90+
var initialEntity = new EntityWithList
91+
{
92+
MyList = new List<ListItem>
93+
{
94+
new() { Value = "My First Value" },
95+
}
96+
};
97+
var keyDate1 = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero);
98+
{
99+
var updatedEntity1 = new EntityWithList
100+
{
101+
MyList = new List<ListItem>
102+
{
103+
new() { Value = "My First Value" },
104+
new() { Value = "My Second Value" }
105+
}
106+
};
107+
108+
chain.Add(initialEntity, updatedEntity1, keyDate1);
109+
chain.Count.Should().Be(2); // [-infinity, keyDate1); [keyDate1, +infinity)
110+
ReverseAndRevert(chain, initialEntity);
111+
}
112+
(var antiparallelInitialEntity, var antiparallelChain) = chain.Reverse(initialEntity);
113+
antiparallelInitialEntity.Should().Match<EntityWithList>(x => x.MyList.Count == 2, because: "Initially the list had 2 items");
114+
var patchingACorrectInitialEntity = () => antiparallelChain.PatchToDate(antiparallelInitialEntity, keyDate1 - TimeSpan.FromDays(10));
115+
patchingACorrectInitialEntity.Should().NotThrow();
116+
117+
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.
118+
corruptedInitialEntity.MyList.RemoveAt(1);
119+
var applyingPatchesToACorruptedInitialEntity = () => antiparallelChain.PatchToDate(corruptedInitialEntity, keyDate1 - TimeSpan.FromDays(10));
120+
applyingPatchesToACorruptedInitialEntity.Should().ThrowExactly<ArgumentOutOfRangeException>();
121+
}
122+
123+
private static void ReverseAndRevert(TimeRangePatchChain<EntityWithList> chain, EntityWithList initialEntity)
124+
{
125+
var (reverseEntity, reverseChain) = chain.Reverse(initialEntity);
126+
var (rereverseEntity, rereverseChain) = reverseChain.Reverse(reverseEntity);
127+
rereverseChain.Should().BeEquivalentTo(chain);
128+
initialEntity.Should().BeEquivalentTo(rereverseEntity);
129+
}
130+
}

0 commit comments

Comments
 (0)