Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 11 additions & 21 deletions backend/FwLite/LcmCrdt.Tests/Changes/UseChangesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,19 @@ public async Task CanSyncAllChangesWithDuplicates()
change.GetType()) as IChange;
duplicateChange.Should().NotBeNull();
duplicateChange.GetType().Should().Be(change.GetType());
// we can't create duplicate entities with the same ID
if (IsCreateChange(change)) duplicateChange.EntityId = Guid.NewGuid();
else if (duplicateChange is AddTranslationChange addTranslationChange)
await fixture.DataModel.AddChange(Guid.NewGuid(), duplicateChange);

if (change.SupportsNewEntity())
{
// translations aren't "primary entities" created with a CreateChange<>
// but they are still (correctly) enforced to have unique IDs (at least within a single ExampleSentence)
addTranslationChange.Translation.Id = Guid.NewGuid();
// The previous duplicate change is presumably a no-op, so we'll make a duplicate entity with a different ID as well.
var duplicateCreateChange = JsonSerializer.Deserialize(
JsonSerializer.Serialize(change, Options),
change.GetType()) as IChange;
duplicateCreateChange.Should().NotBeNull();
duplicateCreateChange.EntityId = Guid.NewGuid();
duplicateCreateChange.GetType().Should().Be(change.GetType());
await fixture.DataModel.AddChange(Guid.NewGuid(), duplicateCreateChange);
}
await fixture.DataModel.AddChange(Guid.NewGuid(), duplicateChange);

var allEntries = await fixture.Api.GetEntries().ToArrayAsync();
var result = await EntrySync.SyncFull(allEntries, allEntries, fixture.Api);
Expand Down Expand Up @@ -256,18 +260,4 @@ private static IEnumerable<ChangeWithDependencies> GetAllChanges()
new RemoteResourceUploadedChange(createRemoteResourcePendingUploadChange.EntityId, "test-remote-id"),
[createRemoteResourcePendingUploadChange]);
}

private static bool IsCreateChange(IChange obj)
{
var type = obj.GetType();
while (type != null && type != typeof(object))
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(CreateChange<>))
{
return true;
}
type = type.BaseType;
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Diagnostics;
using SIL.Extensions;
using SIL.Harmony.Changes;
using SIL.Harmony.Core;
Expand All @@ -12,10 +11,8 @@ public class AddTranslationChange(Guid entityId, Translation translation) : Edit

public override ValueTask ApplyChange(ExampleSentence entity, IChangeContext context)
{
var existingTranslation = entity.Translations.FirstOrDefault(t => t.Id == Translation.Id);
Debug.Assert(existingTranslation == null, $"Translation with ID {Translation.Id} already exists in the ExampleSentence with ID ({entity.Id})");
if (existingTranslation != null) entity.Translations.RemoveAll(t => t.Id == Translation.Id);

// could happen if Chorus recreates a translation due to a merge conflict.
entity.Translations.RemoveAll(t => t.Id == Translation.Id);
entity.Translations.Add(Translation);
return ValueTask.CompletedTask;
}
Expand Down
9 changes: 7 additions & 2 deletions backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,8 @@ public async Task<ComplexFormComponent> CreateComplexFormComponent(ComplexFormCo
var existing = await repo.FindComplexFormComponent(complexFormComponent);
if (existing is null)
{
var betweenIds = between is null ? null : new BetweenPosition(between.Previous?.Id, between.Next?.Id);
// todo test between items missing IDs (i.e. from LibLCM)
var betweenIds = between is null ? null : await between.MapAsync(async c => (await repo.FindComplexFormComponent(c))?.Id);
var addEntryComponentChange = await repo.CreateComplexFormComponentChange(complexFormComponent, betweenIds);
await AddChange(addEntryComponentChange);
return await repo.FindComplexFormComponent(addEntryComponentChange.EntityId);
Expand All @@ -336,7 +337,11 @@ public async Task MoveComplexFormComponent(ComplexFormComponent component, Betwe

public async Task DeleteComplexFormComponent(ComplexFormComponent complexFormComponent)
{
await AddChange(new DeleteChange<ComplexFormComponent>(complexFormComponent.Id));
// todo test missing ID (i.e. from LibLCM)
await using var repo = await repoFactory.CreateRepoAsync();
var existing = await repo.FindComplexFormComponent(complexFormComponent);
if (existing is null) return;
await AddChange(new DeleteChange<ComplexFormComponent>(existing.Id));
}

public async Task AddComplexFormType(Guid entryId, Guid complexFormTypeId)
Expand Down
19 changes: 19 additions & 0 deletions backend/FwLite/MiniLcm.Tests/ExampleSentenceTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,23 @@ public async Task RemoveTranslation_RemovesExistingTranslation()
reloaded.Should().NotBeNull();
reloaded.Translations.Should().BeEmpty();
}

/// <summary>
/// Tests that a deleted example-sentence can be recreated.
/// This is necessary if Chorus recreates an example-sentence due to a merge conflict.
/// </summary>
[Fact]
public async Task RecreateDeletedExampleSentence()
{
var initial = await Api.GetExampleSentence(_entryId, _senseId, _exampleSentenceId);
initial.Should().NotBeNull();

await Api.DeleteExampleSentence(_entryId, _senseId, _exampleSentenceId);
var deleted = await Api.GetExampleSentence(_entryId, _senseId, _exampleSentenceId);
deleted.Should().BeNull();

var recreated = await Api.CreateExampleSentence(_entryId, _senseId, initial);
recreated.Should().NotBeNull();
recreated.Should().BeEquivalentTo(initial);
}
}
19 changes: 19 additions & 0 deletions backend/FwLite/MiniLcm.Tests/SenseTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,23 @@ public async Task Get_ExistingSense_ReturnsSense()
sense.Should().NotBeNull();
sense.Gloss["en"].Should().Be("new-sense-gloss");
}

/// <summary>
/// Tests that a deleted sense can be recreated.
/// This is necessary if Chorus recreates a sense due to a merge conflict.
/// </summary>
[Fact]
public async Task RecreateDeletedSense()
{
var initial = await Api.GetSense(_entryId, _senseId);
initial.Should().NotBeNull();

await Api.DeleteSense(_entryId, _senseId);
var deleted = await Api.GetSense(_entryId, _senseId);
deleted.Should().BeNull();

var recreated = await Api.CreateSense(_entryId, initial);
recreated.Should().NotBeNull();
recreated.Should().BeEquivalentTo(initial);
}
}
19 changes: 19 additions & 0 deletions backend/FwLite/MiniLcm.Tests/UpdateEntryTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,4 +333,23 @@ public async Task UpdateEntry_CanReorderComponents(string before, string after,
actualOrderValues.Should().Be(expectedOrderValues);
}
}

/// <summary>
/// Tests that a deleted entry can be recreated.
/// This is necessary if Chorus recreates an entry due to a merge conflict.
/// </summary>
[Fact]
public async Task RecreateDeletedEntry()
{
var initial = await Api.GetEntry(Entry1Id);
initial.Should().NotBeNull();

await Api.DeleteEntry(Entry1Id);
var deleted = await Api.GetEntry(Entry1Id);
deleted.Should().BeNull();

var recreated = await Api.CreateEntry(initial);
recreated.Should().NotBeNull();
recreated.Should().BeEquivalentTo(initial);
}
}
Loading