Skip to content

Commit 2d6b20e

Browse files
myieyeclaude
andauthored
Use the validation wrapper in MiniLcm API tests (#2360)
The shared MiniLcm test bases now wrap their Api in the same validation/normalization stack production uses, so the tests hit the API the way the app actually does instead of more loosely. Reuses the production AddMiniLcmValidators wiring so new validators flow into tests automatically. One test that deliberately stores an empty value now talks to the raw BaseApi (it checks storage round-tripping, not a real user call, and validation correctly rejects empty values). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent f58948e commit 2d6b20e

5 files changed

Lines changed: 50 additions & 12 deletions

File tree

backend/FwLite/FwLiteProjectSync.Tests/EntrySyncTests.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using MiniLcm.Exceptions;
77
using MiniLcm.Models;
88
using MiniLcm.SyncHelpers;
9+
using MiniLcm.Tests;
910
using MiniLcm.Tests.AutoFakerHelpers;
1011
using Soenneker.Utils.AutoBogus;
1112

@@ -53,7 +54,10 @@ public abstract class EntrySyncTestsBase(ExtraWritingSystemsSyncFixture fixture)
5354
{
5455
public Task InitializeAsync()
5556
{
56-
Api = GetApi(_fixture);
57+
BaseApi = GetApi(_fixture);
58+
// Mirror production sync (CrdtFwdataProjectSyncService): validation only, no normalization,
59+
// because the data is already normalized on both sides.
60+
Api = TestMiniLcmWrappers.CreateValidationFactory().Create(BaseApi);
5761
return Task.CompletedTask;
5862
}
5963

@@ -66,6 +70,7 @@ public Task DisposeAsync()
6670

6771
private readonly SyncFixture _fixture = fixture;
6872
protected IMiniLcmApi Api = null!;
73+
protected IMiniLcmApi BaseApi = null!;
6974

7075
private static readonly AutoFaker AutoFaker = new(AutoFakerDefault.MakeConfig(
7176
ExtraWritingSystemsSyncFixture.VernacularWritingSystems));
@@ -99,12 +104,10 @@ public enum ApiType
99104
public async Task CanSyncRandomEntries(ApiType? roundTripApiType)
100105
{
101106
// arrange
102-
var currentApiType = Api switch
107+
var currentApiType = BaseApi switch
103108
{
104109
FwDataMiniLcmApi => ApiType.FwData,
105110
CrdtMiniLcmApi => ApiType.Crdt,
106-
// This works now, because we're not currently wrapping Api,
107-
// but if we ever do, then we want this to throw, so we know we need to detect the api differently.
108111
_ => throw new InvalidOperationException("Unknown API type")
109112
};
110113

backend/FwLite/MiniLcm.Tests/MiniLcm.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1818
</PackageReference>
1919
<PackageReference Include="FluentAssertions" />
20+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
2021
<PackageReference Include="Soenneker.Utils.AutoBogus" />
2122
<PackageReference Include="System.Linq.Async" />
2223
<PackageReference Include="Moq" />

backend/FwLite/MiniLcm.Tests/MiniLcmTestBase.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using MiniLcm.Normalization;
21
using MiniLcm.Tests.AutoFakerHelpers;
3-
using MiniLcm.Wrappers;
42
using Soenneker.Utils.AutoBogus;
53

64
namespace MiniLcm.Tests;
@@ -17,10 +15,7 @@ public virtual async Task InitializeAsync()
1715
{
1816
BaseApi = await NewApi();
1917
BaseApi.Should().NotBeNull();
20-
Api = BaseApi.WrapWith([
21-
new MiniLcmApiQueryNormalizationWrapperFactory(),
22-
new MiniLcmApiWriteNormalizationWrapperFactory(),
23-
], null!);
18+
Api = TestMiniLcmWrappers.CreateUserFacingWrappers().Apply(BaseApi, null!);
2419
Api.Should().NotBeNull();
2520
}
2621

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using MiniLcm.Validators;
3+
using MiniLcm.Wrappers;
4+
5+
namespace MiniLcm.Tests;
6+
7+
/// <summary>
8+
/// Builds the production MiniLcm wrapper stack via <see cref="MiniLcmValidatorsExtensions.AddMiniLcmValidators"/>,
9+
/// so tests exercise the same validators and wrapper composition that real API entry points use
10+
/// (and pick up new registrations automatically). The resolved wrappers hold no provider-scoped state,
11+
/// so the throwaway provider is disposed immediately.
12+
/// </summary>
13+
public static class TestMiniLcmWrappers
14+
{
15+
private static ServiceProvider BuildProvider() =>
16+
new ServiceCollection().AddMiniLcmValidators().BuildServiceProvider();
17+
18+
/// <summary>
19+
/// The full user-facing stack (query normalization, validation, write normalization) that real
20+
/// API entry points apply via <see cref="MiniLcmApiUserFacingWrappers"/>.
21+
/// </summary>
22+
public static MiniLcmApiUserFacingWrappers CreateUserFacingWrappers()
23+
{
24+
using var provider = BuildProvider();
25+
return provider.GetRequiredService<MiniLcmApiUserFacingWrappers>();
26+
}
27+
28+
/// <summary>
29+
/// The validation-only wrapper that the sync path applies (see <c>CrdtFwdataProjectSyncService</c>),
30+
/// which deliberately skips normalization because both sides are already normalized.
31+
/// </summary>
32+
public static MiniLcmApiValidationWrapperFactory CreateValidationFactory()
33+
{
34+
using var provider = BuildProvider();
35+
return provider.GetRequiredService<MiniLcmApiValidationWrapperFactory>();
36+
}
37+
}

backend/FwLite/MiniLcm.Tests/UpdateEntryTestsBase.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,13 @@ public async Task UpdateEntry_SupportsRemovingARichMultiStringWs()
8484
[Fact]
8585
public async Task UpdateEntry_RoundTripsEmptyStrings()
8686
{
87-
var entry = await Api.GetEntry(Entry1Id);
87+
// Empty MultiString values are rejected by validation, so this exercises the raw storage layer:
88+
// empty values can still reach storage (e.g. via sync) and must round-trip.
89+
var entry = await BaseApi.GetEntry(Entry1Id);
8890
ArgumentNullException.ThrowIfNull(entry);
8991
var before = entry.Copy();
9092
entry.CitationForm["en"] = string.Empty;
91-
var updatedEntry = await Api.UpdateEntry(before, entry);
93+
var updatedEntry = await BaseApi.UpdateEntry(before, entry);
9294
updatedEntry.CitationForm["en"].Should().Be(string.Empty);
9395
}
9496

0 commit comments

Comments
 (0)