Skip to content

Commit 256b8c6

Browse files
myieyeclaude
andcommitted
Add wrapper tests for validation wiring through IMiniLcmApi
The validator classes have unit tests, but nothing tested the seam where they meet the API - which is exactly where #2362 hid (a dead CreateEntry override). These pin the two things that matter at that seam: methods we expect to validate actually do, and the ones that deliberately don't (CreateEntry, CreatePublication, CreateMorphType) keep passing through, so a future "fix" that breaks sync of empty FLEx values goes red. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9f5e433 commit 256b8c6

1 file changed

Lines changed: 88 additions & 0 deletions

File tree

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using FluentValidation;
2+
using MiniLcm.SyncHelpers;
3+
using Moq;
4+
5+
namespace MiniLcm.Tests.Validators;
6+
7+
/// <summary>
8+
/// Tests the validation wrapper's wiring through the IMiniLcmApi interface - the seam that broke in
9+
/// #2362 - not the validator rules (those have their own unit tests). Two things are worth pinning:
10+
/// that methods we expect to validate actually do (so a silently-dead override like #2362's is caught),
11+
/// and that methods which deliberately DON'T validate despite having a validator (CreateEntry,
12+
/// CreatePublication, CreateMorphType) keep passing through - so a well-meaning "fix" that turns
13+
/// validation on, and breaks sync of empty FLEx values, trips a red test instead.
14+
/// </summary>
15+
public class MiniLcmApiValidationWrapperTests
16+
{
17+
private readonly Mock<IMiniLcmApi> _inner = new();
18+
private readonly IMiniLcmApi _api;
19+
20+
public MiniLcmApiValidationWrapperTests()
21+
{
22+
_api = TestMiniLcmWrappers.CreateValidationFactory().Create(_inner.Object);
23+
}
24+
25+
private static Entry ValidEntry() => new() { Id = Guid.NewGuid(), LexemeForm = new() { { "en", "lexeme" } } };
26+
27+
// An entry the EntryValidator rejects: a MultiString that carries an empty value (NoEmptyValues).
28+
// This is the shape that routinely arrives from FLEx during sync/import.
29+
private static Entry EntryWithEmptyValue() => new()
30+
{
31+
Id = Guid.NewGuid(),
32+
LexemeForm = new() { { "en", "lexeme" } },
33+
CitationForm = new() { { "en", "" } },
34+
};
35+
36+
[Fact]
37+
public async Task UpdateEntry_BeforeAfter_ValidatesTheUpdatedEntry()
38+
{
39+
var act = () => _api.UpdateEntry(ValidEntry(), EntryWithEmptyValue());
40+
41+
await act.Should().ThrowAsync<ValidationException>();
42+
_inner.Verify(a => a.UpdateEntry(It.IsAny<Entry>(), It.IsAny<Entry>(), It.IsAny<IMiniLcmApi?>()), Times.Never);
43+
}
44+
45+
[Fact]
46+
public async Task CreateSense_ValidatesTheSense()
47+
{
48+
var sense = new Sense { Id = Guid.NewGuid(), Gloss = new() { { "en", "" } } };
49+
50+
var act = () => _api.CreateSense(Guid.NewGuid(), sense);
51+
52+
await act.Should().ThrowAsync<ValidationException>();
53+
_inner.Verify(a => a.CreateSense(It.IsAny<Guid>(), It.IsAny<Sense>(), It.IsAny<BetweenPosition?>()), Times.Never);
54+
}
55+
56+
[Fact]
57+
public async Task CreateEntry_DoesNotValidate_SoEmptyFlexValuesReachStorage()
58+
{
59+
var entry = EntryWithEmptyValue();
60+
61+
var act = () => _api.CreateEntry(entry);
62+
63+
await act.Should().NotThrowAsync();
64+
_inner.Verify(a => a.CreateEntry(entry, It.IsAny<CreateEntryOptions?>()), Times.Once);
65+
}
66+
67+
[Fact]
68+
public async Task CreatePublication_DoesNotValidate()
69+
{
70+
var publication = new Publication { Id = Guid.NewGuid(), Name = new() { { "en", "" } } };
71+
72+
var act = () => _api.CreatePublication(publication);
73+
74+
await act.Should().NotThrowAsync();
75+
_inner.Verify(a => a.CreatePublication(publication), Times.Once);
76+
}
77+
78+
[Fact]
79+
public async Task CreateMorphType_DoesNotValidate()
80+
{
81+
var morphType = new MorphType { Id = Guid.NewGuid(), Kind = MorphTypeKind.Unknown, Name = new() { { "en", "" } } };
82+
83+
var act = () => _api.CreateMorphType(morphType);
84+
85+
await act.Should().NotThrowAsync();
86+
_inner.Verify(a => a.CreateMorphType(morphType), Times.Once);
87+
}
88+
}

0 commit comments

Comments
 (0)