Skip to content

Commit 6284dfa

Browse files
committed
[temp] trigger ci tests
1 parent a9600a0 commit 6284dfa

File tree

3 files changed

+108
-17
lines changed

3 files changed

+108
-17
lines changed

src/SIL.Harmony.Tests/SnapshotTests.cs

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using SIL.Harmony.Sample.Models;
2+
using SIL.Harmony.Sample.Changes;
3+
using SIL.Harmony.Changes;
24
using Microsoft.EntityFrameworkCore;
35

46
namespace SIL.Harmony.Tests;
@@ -19,7 +21,7 @@ public async Task MultipleChangesPreservesRootSnapshot()
1921
{
2022
var entityId = Guid.NewGuid();
2123
var commits = new List<Commit>();
22-
for (int i = 0; i < 4; i++)
24+
for (var i = 0; i < 4; i++)
2325
{
2426
commits.Add(await WriteChange(_localClientId,
2527
new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero).AddHours(i),
@@ -39,7 +41,7 @@ public async Task MultipleChangesPreservesSomeIntermediateSnapshots()
3941
{
4042
var entityId = Guid.NewGuid();
4143
var commits = new List<Commit>();
42-
for (int i = 0; i < 6; i++)
44+
for (var i = 0; i < 6; i++)
4345
{
4446
commits.Add(await WriteChange(_localClientId,
4547
new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero).AddHours(i),
@@ -135,7 +137,7 @@ await WriteNextChange(
135137
var tagCreation = await WriteNextChange(TagWord(wordId, tagId));
136138
await WriteChangeBefore(tagCreation, TagWord(wordId, tagId));
137139

138-
var word = await DataModel.QueryLatest<Word>(q=> q.Include(w => w.Tags)
140+
var word = await DataModel.QueryLatest<Word>(q => q.Include(w => w.Tags)
139141
.Where(w => w.Id == wordId)).FirstOrDefaultAsync();
140142
word.Should().NotBeNull();
141143
word.Tags.Should().BeEquivalentTo([new Tag { Id = tagId, Text = "tag-1" }]);
@@ -161,4 +163,70 @@ public async Task RegenerateSnapshots_WillArriveAtTheSameState()
161163
//we probably won't have the same number of snapshots, which is ok. but none of the ids should be the same
162164
afterSnapshotsIds.Should().NotIntersectWith(beforeSnapshotIds);
163165
}
166+
167+
[Fact]
168+
public async Task CanApplyChangeToDeletedEntityWithSupportsNewEntity()
169+
{
170+
var entityId = Guid.NewGuid();
171+
172+
// Create a word with SetWordTextChange (which supports both NewEntity and ApplyChange)
173+
await WriteNextChange(new SetWordTextChange(entityId, "original"));
174+
var word = await DataModel.GetLatest<Word>(entityId);
175+
word.Should().NotBeNull();
176+
word!.Text.Should().Be("original");
177+
word.DeletedAt.Should().BeNull();
178+
179+
// Delete the word
180+
await WriteNextChange(DeleteWord(entityId));
181+
var deletedWord = await DataModel.GetLatest<Word>(entityId);
182+
deletedWord.Should().NotBeNull("deleted entities should still exist in snapshots");
183+
deletedWord!.DeletedAt.Should().NotBeNull();
184+
185+
// Apply change to deleted entity using SetWordTextChange (which SupportsNewEntity)
186+
await WriteNextChange(new SetWordTextChange(entityId, "modified while deleted"));
187+
var modifiedWord = await DataModel.GetLatest<Word>(entityId);
188+
modifiedWord.Should().NotBeNull("change should be applied to deleted entity");
189+
modifiedWord!.Text.Should().Be("modified while deleted", "text should be updated");
190+
modifiedWord.DeletedAt.Should().NotBeNull("entity should still be marked as deleted since SetWordTextChange doesn't clear DeletedAt");
191+
}
192+
193+
[Fact]
194+
public async Task SupportsNewEntityAllowsUpdatingDeletedEntities()
195+
{
196+
var entityId = Guid.NewGuid();
197+
198+
// Create and delete
199+
await WriteNextChange(new SetWordTextChange(entityId, "original"));
200+
await WriteNextChange(DeleteWord(entityId));
201+
202+
var deletedWord = await DataModel.GetLatest<Word>(entityId);
203+
deletedWord.Should().NotBeNull("deleted entity should exist");
204+
deletedWord!.DeletedAt.Should().NotBeNull("entity should be deleted");
205+
206+
// Update deleted entity using SetWordTextChange (which SupportsNewEntity)
207+
await WriteNextChange(new SetWordTextChange(entityId, "updated while deleted"));
208+
209+
var finalWord = await DataModel.GetLatest<Word>(entityId);
210+
finalWord.Should().NotBeNull();
211+
finalWord!.Text.Should().Be("updated while deleted", "text should be updated");
212+
finalWord.DeletedAt.Should().NotBeNull("entity should still be marked as deleted");
213+
}
214+
215+
[Fact]
216+
public void SupportsNewEntityMethodIndicatesChangeCapability()
217+
{
218+
// Test that different change types report their SupportsNewEntity capability correctly
219+
var setWordChange = new SetWordTextChange(Guid.NewGuid(), "test");
220+
var newWordChange = new NewWordChange(Guid.NewGuid(), "test");
221+
var deleteChange = new DeleteChange<Word>(Guid.NewGuid());
222+
223+
// SetWordTextChange is NOT a CreateChange<T> so it returns false even though it can create entities
224+
setWordChange.SupportsNewEntity().Should().BeFalse("SetWordTextChange is not a CreateChange<T>");
225+
226+
// NewWordChange (CreateChange) supports new entity creation but not applying to existing entities
227+
newWordChange.SupportsNewEntity().Should().BeTrue("NewWordChange can create new entities");
228+
229+
// DeleteChange doesn't support new entity creation (only applies to existing entities)
230+
deleteChange.SupportsNewEntity().Should().BeFalse("DeleteChange cannot create new entities");
231+
}
164232
}

src/SIL.Harmony/Changes/Change.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public interface IChange
1616

1717
ValueTask ApplyChange(IObjectBase entity, IChangeContext context);
1818
ValueTask<IObjectBase> NewEntity(Commit commit, IChangeContext context);
19+
bool SupportsApplyChange();
20+
bool SupportsNewEntity();
1921
}
2022

2123
/// <summary>
@@ -44,7 +46,7 @@ async ValueTask<IObjectBase> IChange.NewEntity(Commit commit, IChangeContext con
4446

4547
public async ValueTask ApplyChange(IObjectBase entity, IChangeContext context)
4648
{
47-
if (this is CreateChange<T>)
49+
if (!SupportsApplyChange())
4850
return; // skip attempting to apply changes on CreateChange as it does not support apply changes
4951
if (entity.DbObject is T entityT)
5052
{
@@ -56,6 +58,16 @@ public async ValueTask ApplyChange(IObjectBase entity, IChangeContext context)
5658
}
5759
}
5860

61+
public virtual bool SupportsApplyChange()
62+
{
63+
return this is not CreateChange<T>;
64+
}
65+
66+
public virtual bool SupportsNewEntity()
67+
{
68+
return this is not EditChange<T>;
69+
}
70+
5971
[JsonIgnore]
6072
public Type EntityType => typeof(T);
6173
}

src/SIL.Harmony/SnapshotWorker.cs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,26 +81,37 @@ private async ValueTask ApplyCommitChanges(IEnumerable<Commit> commits, bool upd
8181
IObjectBase entity;
8282
var prevSnapshot = await GetSnapshot(commitChange.EntityId);
8383
var changeContext = new ChangeContext(commit, commitIndex, intermediateSnapshots, this, _crdtConfig);
84-
bool wasDeleted;
85-
if (prevSnapshot is not null)
84+
85+
if (prevSnapshot is null)
8686
{
87-
entity = prevSnapshot.Entity.Copy();
88-
wasDeleted = entity.DeletedAt.HasValue;
87+
// create brand new entity
88+
entity = await commitChange.Change.NewEntity(commit, changeContext);
8989
}
90-
else
90+
else if (prevSnapshot.EntityIsDeleted && commitChange.Change.SupportsNewEntity())
9191
{
92+
// revive deleted entity
9293
entity = await commitChange.Change.NewEntity(commit, changeContext);
93-
wasDeleted = false;
9494
}
95-
96-
await commitChange.Change.ApplyChange(entity, changeContext);
97-
98-
var deletedByChange = !wasDeleted && entity.DeletedAt.HasValue;
99-
if (deletedByChange)
95+
else if (commitChange.Change.SupportsApplyChange())
10096
{
101-
await MarkDeleted(entity.Id, changeContext);
97+
// update existing entity
98+
entity = prevSnapshot.Entity.Copy();
99+
var wasDeleted = prevSnapshot.EntityIsDeleted;
100+
await commitChange.Change.ApplyChange(entity, changeContext);
101+
var deletedByChange = !wasDeleted && entity.DeletedAt.HasValue;
102+
if (deletedByChange)
103+
{
104+
await MarkDeleted(entity.Id, changeContext);
105+
}
102106
}
103-
107+
else
108+
{
109+
// entity already exists (and is not deleted)
110+
// and change does not support updating existing entities
111+
// so do nothing
112+
continue;
113+
}
114+
104115
await GenerateSnapshotForEntity(entity, prevSnapshot, changeContext);
105116
}
106117
_newIntermediateSnapshots.AddRange(intermediateSnapshots.Values);

0 commit comments

Comments
 (0)