Skip to content

Commit

Permalink
Chore/fix serilization error add entry component change (#1310)
Browse files Browse the repository at this point in the history
* remove headword parameters which are no longer used

* write tests to ensure we can round trip serialize all crdt changes

* fix serialization bugs

* use faker to generate changes
  • Loading branch information
hahn-kev authored Dec 11, 2024
1 parent ce28810 commit 165a44a
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 10 deletions.
82 changes: 82 additions & 0 deletions backend/FwLite/LcmCrdt.Tests/Changes/ChangeSerializationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System.Reflection;
using System.Text.Json;
using FluentAssertions.Execution;
using LcmCrdt.Changes;
using LcmCrdt.Changes.Entries;
using MiniLcm.Tests.AutoFakerHelpers;
using SIL.Harmony.Changes;
using Soenneker.Utils.AutoBogus;
using SystemTextJsonPatch;

namespace LcmCrdt.Tests.Changes;

public class ChangeSerializationTests
{
private static readonly AutoFaker Faker = new()
{
Config =
{
Overrides = [new WritingSystemIdOverride()]
}
};

public static IEnumerable<object[]> Changes()
{
foreach (var type in LcmCrdtKernel.AllChangeTypes())
{
//can't generate this type because there's no public constructor, so its specified below
if (type == typeof(SetComplexFormComponentChange)) continue;

object change;
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(JsonPatchChange<>))
{
change = PatchMethod.MakeGenericMethod(type.GenericTypeArguments[0]).Invoke(null, null)!;
}
else
{
change = Faker.Generate(type);
}
change.Should().NotBeNull($"change type {type.Name} should have been generated");
yield return [change];
}
yield return [SetComplexFormComponentChange.NewComplexForm(Guid.NewGuid(), Guid.NewGuid())];
yield return [SetComplexFormComponentChange.NewComponent(Guid.NewGuid(), Guid.NewGuid())];
yield return [SetComplexFormComponentChange.NewComponentSense(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid())];
}

private static readonly MethodInfo PatchMethod = new Func<IChange>(Patch<Entry>).Method.GetGenericMethodDefinition();

private static IChange Patch<T>() where T : class
{
return new JsonPatchChange<T>(Guid.NewGuid(), new JsonPatchDocument<T>());
}

[Theory]
[MemberData(nameof(Changes))]
public void CanRoundTripChanges(IChange change)
{
var config = new CrdtConfig();
LcmCrdtKernel.ConfigureCrdt(config);
//commit id is not serialized
change.CommitId = Guid.Empty;
var type = change.GetType();
var json = JsonSerializer.Serialize(change, config.JsonSerializerOptions);
var newChange = JsonSerializer.Deserialize(json, type, config.JsonSerializerOptions);
newChange.Should().BeEquivalentTo(change);
}

[Fact]
public void ChangesIncludesAllValidChangeTypes()
{
var allChangeTypes = LcmCrdtKernel.AllChangeTypes();
allChangeTypes.Should().NotBeEmpty();
var testedTypes = Changes().Select(c => c[0].GetType()).ToArray();
using (new AssertionScope())
{
foreach (var allChangeType in allChangeTypes)
{
testedTypes.Should().Contain(allChangeType);
}
}
}
}
2 changes: 2 additions & 0 deletions backend/FwLite/LcmCrdt.Tests/MiniLcmApiFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using MiniLcm;
using MiniLcm.Models;
using Xunit.Abstractions;
Expand All @@ -17,6 +18,7 @@ public class MiniLcmApiFixture : IAsyncLifetime
private LcmCrdtDbContext? _crdtDbContext;
public CrdtMiniLcmApi Api => (CrdtMiniLcmApi)_services.ServiceProvider.GetRequiredService<IMiniLcmApi>();
public DataModel DataModel => _services.ServiceProvider.GetRequiredService<DataModel>();
public CrdtConfig CrdtConfig => _services.ServiceProvider.GetRequiredService<IOptions<CrdtConfig>>().Value;

public MiniLcmApiFixture()
{
Expand Down
4 changes: 2 additions & 2 deletions backend/FwLite/LcmCrdt/Changes/AddSemanticDomainChange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

namespace LcmCrdt.Changes;

public class AddSemanticDomainChange(SemanticDomain semanticDomain, Guid senseId)
: EditChange<Sense>(senseId), ISelfNamedType<AddSemanticDomainChange>
public class AddSemanticDomainChange(SemanticDomain semanticDomain, Guid entityId)
: EditChange<Sense>(entityId), ISelfNamedType<AddSemanticDomainChange>
{
public SemanticDomain SemanticDomain { get; } = semanticDomain;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ public class AddEntryComponentChange : CreateChange<ComplexFormComponent>, ISelf
[JsonConstructor]
public AddEntryComponentChange(Guid entityId,
Guid complexFormEntryId,
string? complexFormHeadword,
Guid componentEntryId,
string? componentHeadword,
Guid? componentSenseId = null) : base(entityId)
{
ComplexFormEntryId = complexFormEntryId;
Expand All @@ -28,9 +26,7 @@ public AddEntryComponentChange(Guid entityId,

public AddEntryComponentChange(ComplexFormComponent component) : this(component.Id == default ? Guid.NewGuid() : component.Id,
component.ComplexFormEntryId,
component.ComplexFormHeadword,
component.ComponentEntryId,
component.ComponentHeadword,
component.ComponentSenseId)
{
}
Expand Down
4 changes: 2 additions & 2 deletions backend/FwLite/LcmCrdt/Changes/RemoveSemanticDomainChange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

namespace LcmCrdt.Changes;

public class RemoveSemanticDomainChange(Guid semanticDomainId, Guid senseId)
: EditChange<Sense>(senseId), ISelfNamedType<RemoveSemanticDomainChange>
public class RemoveSemanticDomainChange(Guid semanticDomainId, Guid entityId)
: EditChange<Sense>(entityId), ISelfNamedType<RemoveSemanticDomainChange>
{
public Guid SemanticDomainId { get; } = semanticDomainId;

Expand Down
4 changes: 2 additions & 2 deletions backend/FwLite/LcmCrdt/Changes/ReplaceSemanticDomainChange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

namespace LcmCrdt.Changes;

public class ReplaceSemanticDomainChange(Guid oldSemanticDomainId, SemanticDomain semanticDomain, Guid senseId)
: EditChange<Sense>(senseId), ISelfNamedType<ReplaceSemanticDomainChange>
public class ReplaceSemanticDomainChange(Guid oldSemanticDomainId, SemanticDomain semanticDomain, Guid entityId)
: EditChange<Sense>(entityId), ISelfNamedType<ReplaceSemanticDomainChange>
{
public Guid OldSemanticDomainId { get; } = oldSemanticDomainId;
public SemanticDomain SemanticDomain { get; } = semanticDomain;
Expand Down
14 changes: 14 additions & 0 deletions backend/FwLite/LcmCrdt/LcmCrdtKernel.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using SIL.Harmony;
using SIL.Harmony.Core;
using SIL.Harmony.Changes;
Expand Down Expand Up @@ -191,6 +193,18 @@ public static void ConfigureCrdt(CrdtConfig config)
.Add<CreateComplexFormType>();
}

public static Type[] AllChangeTypes()
{
var crdtConfig = new CrdtConfig();
ConfigureCrdt(crdtConfig);


var list = typeof(ChangeTypeListBuilder).GetProperty("Types", BindingFlags.Instance | BindingFlags.NonPublic)
?.GetValue(crdtConfig.ChangeTypeListBuilder) as List<JsonDerivedType>;
return list?.Select(t => t.DerivedType).ToArray() ?? [];
}


public static Task<IMiniLcmApi> OpenCrdtProject(this IServiceProvider services, CrdtProject project)
{
//this method must not be async, otherwise Setting the project scope will not work as expected.
Expand Down

0 comments on commit 165a44a

Please sign in to comment.