Skip to content

Commit

Permalink
Validate WritingSystemIds on creation (#1230)
Browse files Browse the repository at this point in the history
* Validate WritingSystemIds on creation

Special ID "default" is allowed, all other writing system IDs must be
considered valid by the SIL.WritingSystems validation logic.

* Teach AutoFaker.Generate about valid writing systems

Now when AutoFaker.Generate creates a random writing system ID, it will
be chosen from the list of all valid 2-letter writing system IDs.

* Add some writing system ID validation tests
* move faker overrides into miniLcm tests
* make test WritingSystemCodes ValidTwoLetterCodes readonly
* move MultiStringOverride.cs into auto faker helpers

---------

Co-authored-by: Kevin Hahn <[email protected]>
  • Loading branch information
rmunn and hahn-kev authored Nov 15, 2024
1 parent b562820 commit a5309ad
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 30 deletions.
2 changes: 1 addition & 1 deletion backend/FwLite/FwLiteProjectSync.Tests/UpdateDiffTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class UpdateDiffTests
{
private readonly AutoFaker _autoFaker = new(new AutoFakerConfig()
{
Overrides = [new MultiStringOverride()]
Overrides = [new MultiStringOverride(), new WritingSystemIdOverride()]
});

[Fact]
Expand Down
10 changes: 8 additions & 2 deletions backend/FwLite/LcmCrdt.Tests/DataModelSnapshotTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MiniLcm.Tests.AutoFakerHelpers;
using SIL.Harmony.Changes;
using SIL.Harmony.Entities;
using Soenneker.Utils.AutoBogus;
using Soenneker.Utils.AutoBogus.Config;

namespace LcmCrdt.Tests;

public class DataModelSnapshotTests : IAsyncLifetime
{
private static AutoFaker _faker = new AutoFaker(new AutoFakerConfig()
{
Overrides = [new MultiStringOverride(), new WritingSystemIdOverride()]
});

protected readonly AsyncServiceScope _services;
private readonly LcmCrdtDbContext _crdtDbContext;
private CrdtConfig _crdtConfig;
Expand Down Expand Up @@ -76,15 +83,14 @@ public async Task VerifyIObjectWithIdModels()
[Fact]
public void VerifyIObjectWithIdsMatchAdapterGetObjectTypeName()
{
var faker = new AutoFaker();
var jsonSerializerOptions = _crdtConfig.JsonSerializerOptions;
var types = jsonSerializerOptions.GetTypeInfo(typeof(IObjectWithId)).PolymorphismOptions?.DerivedTypes ?? [];
using (new AssertionScope())
{
foreach (var jsonDerivedType in types)
{
var typeDiscriminator = jsonDerivedType.TypeDiscriminator.Should().BeOfType<string>().Subject;
var obj = faker.Generate(jsonDerivedType.DerivedType);
var obj = _faker.Generate(jsonDerivedType.DerivedType);
new MiniLcmCrdtAdapter((IObjectWithId)obj).GetObjectTypeName().Should().Be(typeDiscriminator);
}
}
Expand Down
7 changes: 6 additions & 1 deletion backend/FwLite/LcmCrdt.Tests/EntityCopyMethodTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using SIL.Harmony.Entities;
using LcmCrdt.Objects;
using MiniLcm.Tests.AutoFakerHelpers;
using SIL.Harmony;
using Soenneker.Utils.AutoBogus;
using Soenneker.Utils.AutoBogus.Config;
Expand All @@ -8,7 +9,11 @@ namespace LcmCrdt.Tests;

public class EntityCopyMethodTests
{
private readonly AutoFaker _autoFaker = new(new AutoFakerConfig());
private readonly AutoFaker _autoFaker = new(new AutoFakerConfig()
{
Overrides = [new MultiStringOverride(), new WritingSystemIdOverride()]
});

public static IEnumerable<object[]> GetEntityTypes()
{
var crdtConfig = new CrdtConfig();
Expand Down
22 changes: 0 additions & 22 deletions backend/FwLite/LcmCrdt.Tests/MultiStringOverride.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ public override void Generate(AutoFakerOverrideContext context)
var wordsArray = context.Faker.Random.WordsArray(1, 4);
foreach (var word in wordsArray)
{
var writingSystemId = validWs is not null
? context.Faker.Random.ArrayElement(validWs)
: context.Faker.Random.String(2, 'a', 'z');
var writingSystemId = context.Faker.Random.ArrayElement(validWs ?? WritingSystemCodes.ValidTwoLetterCodes);
target[writingSystemId] = word;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using MiniLcm.Models;
using Soenneker.Utils.AutoBogus.Context;
using Soenneker.Utils.AutoBogus.Override;

namespace MiniLcm.Tests.AutoFakerHelpers;

public class WritingSystemIdOverride: AutoFakerOverride<WritingSystemId>
{
public override bool Preinitialize => false;

public override void Generate(AutoFakerOverrideContext context)
{
var ws = context.Faker.Random.ArrayElement(WritingSystemCodes.ValidTwoLetterCodes);
context.Instance = new WritingSystemId(ws);
}
}
8 changes: 8 additions & 0 deletions backend/FwLite/MiniLcm.Tests/WritingSystemCodes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using SIL.WritingSystems;

namespace MiniLcm.Tests;

public static class WritingSystemCodes
{
public static readonly string[] ValidTwoLetterCodes = StandardSubtags.RegisteredLanguages.Select(lang => lang.Code).ToArray();
}
34 changes: 34 additions & 0 deletions backend/FwLite/MiniLcm.Tests/WritingSystemIdTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using MiniLcm.Models;

namespace MiniLcm.Tests;

public class WritingSystemIdTests
{
[Theory]
[InlineData("en")]
[InlineData("th")]
[InlineData("en-Zxxx-x-audio")]
public void ValidWritingSystemId_ShouldNotThrow(string code)
{
var ws = new WritingSystemId(code);
ws.Should().NotBeNull();
}

[Theory]
[InlineData("gx")]
[InlineData("oo")]
[InlineData("eng")] // Three-letter codes not allowed when there's a valid two-letter code
[InlineData("eng-Zxxx-x-audio")]
[InlineData("nonsense")]
public void InvalidWritingSystemId_ShouldThrow(string code)
{
Assert.Throws<ArgumentException>(() => new WritingSystemId(code));
}

[Fact]
public void DefaultWritingSystemId_IsValid()
{
var ws = new WritingSystemId("default");
ws.Should().NotBeNull();
}
}
1 change: 1 addition & 0 deletions backend/FwLite/MiniLcm/MiniLcm.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="SIL.WritingSystems" Version="14.2.0-beta*" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="SystemTextJsonPatch" Version="3.2.1" />
</ItemGroup>
Expand Down
18 changes: 17 additions & 1 deletion backend/FwLite/MiniLcm/Models/WritingSystemId.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using SIL.WritingSystems;

namespace MiniLcm.Models;

Expand All @@ -26,11 +27,26 @@ public override void WriteAsPropertyName(Utf8JsonWriter writer, WritingSystemId
}

[JsonConverter(typeof(WritingSystemIdJsonConverter))]
public readonly record struct WritingSystemId(string Code): ISpanFormattable, ISpanParsable<WritingSystemId>
public readonly record struct WritingSystemId: ISpanFormattable, ISpanParsable<WritingSystemId>
{
public string Code { get; init; }

public WritingSystemId(string code)
{
if (code == "default" || IetfLanguageTag.IsValid(code))
{
Code = code;
}
else
{
throw new ArgumentException($"Invalid writing system ID '{code}'", nameof(code));
}
}

public static implicit operator string(WritingSystemId ws) => ws.Code;
public static implicit operator WritingSystemId(string code) => new(code);
public static implicit operator WritingSystemId(ReadOnlySpan<char> code) => new(new string(code));

public override string ToString()
{
return Code;
Expand Down

0 comments on commit a5309ad

Please sign in to comment.