|
| 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