Skip to content

Commit

Permalink
write deserialization tests to ensure we can always deserialize chang…
Browse files Browse the repository at this point in the history
…es, even after they are modified
  • Loading branch information
hahn-kev committed Jan 20, 2025
1 parent de7e10e commit 0a031e5
Show file tree
Hide file tree
Showing 2 changed files with 348 additions and 11 deletions.
76 changes: 65 additions & 11 deletions backend/FwLite/LcmCrdt.Tests/Changes/ChangeSerializationTests.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
using FluentAssertions.Execution;
using LcmCrdt.Changes;
using LcmCrdt.Changes.Entries;
using MiniLcm.Tests.AutoFakerHelpers;
using SIL.Harmony.Changes;
using SIL.WritingSystems;
using Soenneker.Utils.AutoBogus;
using SystemTextJsonPatch;

namespace LcmCrdt.Tests.Changes;

public class ChangeSerializationTests
{
private static readonly Lazy<JsonSerializerOptions> LazyOptions = new(() =>
{
var config = new CrdtConfig();
LcmCrdtKernel.ConfigureCrdt(config);
return config.JsonSerializerOptions;
});
private static readonly JsonSerializerOptions Options = LazyOptions.Value;
private static readonly AutoFaker Faker = new()
{
Config =
{
Overrides = [new WritingSystemIdOverride()]
Overrides = [new WritingSystemIdOverride(), new MultiStringOverride()]
}
};

public static IEnumerable<object[]> Changes()
private static IEnumerable<IChange> GeneratedChanges()
{
foreach (var type in LcmCrdtKernel.AllChangeTypes())
{
Expand All @@ -34,14 +43,31 @@ public static IEnumerable<object[]> Changes()
}
else
{
change = Faker.Generate(type);
try
{
change = Faker.Generate(type);
}
catch (Exception e)
{
throw new Exception($"Failed to generate change of type {type.Name}", e);
}
}
change.Should().NotBeNull($"change type {type.Name} should have been generated");

change.Should().NotBeNull($"change type {type.Name} should have been generated").And.BeAssignableTo<IChange>();
yield return (IChange) 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());
}

public static IEnumerable<object[]> Changes()
{
foreach (var change in GeneratedChanges())
{
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();
Expand All @@ -55,13 +81,11 @@ private static IChange Patch<T>() where T : class
[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);
var json = JsonSerializer.Serialize(change, Options);
var newChange = JsonSerializer.Deserialize(json, type, Options);
newChange.Should().BeEquivalentTo(change);
}

Expand All @@ -79,4 +103,34 @@ public void ChangesIncludesAllValidChangeTypes()
}
}
}

[Fact]
public void CanDeserializeJson()
{
//this file represents projects which already have changes applied, we want to ensure that we don't break anything.
//nothing should ever be removed from this file
//if a new property is added then a new json object should be added with that property
using var jsonFile = File.OpenRead(GetJsonFilePath("ExistingJson.json"));
var changes = JsonSerializer.Deserialize<List<IChange>>(jsonFile, Options);
changes.Should().NotBeNullOrEmpty().And.NotContainNulls();

//ensure that all change types are represented and none should be removed from AllChangeTypes
changes.Select(c => c.GetType()).Distinct()
.Should().BeEquivalentTo(LcmCrdtKernel.AllChangeTypes());
}

//helper method, can be called manually to regenerate the json file
[Fact(Skip = "Only run manually")]
public static void GenerateNewJsonFile()
{
using var jsonFile = File.Open(GetJsonFilePath("NewJson.json"), FileMode.Create);
JsonSerializer.Serialize(jsonFile, GeneratedChanges(), Options);
}

private static string GetJsonFilePath(string name, [CallerFilePath] string sourceFile = "")
{
return Path.Combine(
Path.GetDirectoryName(sourceFile) ?? throw new InvalidOperationException("Could not get directory of source file"),
name);
}
}
Loading

0 comments on commit 0a031e5

Please sign in to comment.