test: add FluentValidation unit tests for all command/query validators

ValidatorTests.cs covers 21 scenarios across 4 validators:

MergePdfsCommandValidator (3):
- TwoOrMoreStreams passes, SingleStream fails, NullStreams fails

AddAnnotationValidator (7):
- Valid circle passes, FreeText without content fails,
  TextMarkup without style fails, invalid/valid color hex,
  zero page number fails, invalid rectangle (X2<X1) fails

AddStampValidator (6):
- Valid text stamp passes, missing Text/ImageBytes/PredefinedType each fail,
  opacity > 1.0 fails, invalid color hex fails

ConvertToPdfACommandValidator (4):
- All valid levels pass, invalid level fails, empty level fails, null stream fails
This commit is contained in:
2026-08-28 11:46:28 +02:00
parent fbaefe1fd2
commit 7559028164

View File

@@ -0,0 +1,270 @@
using DocumentService.Application.AddAnnotation;
using DocumentService.Application.AddStamp;
using DocumentService.Application.ConvertToPdfA;
using DocumentService.Application.MergePdfs;
using DocumentService.Domain.Models.ValueObjects;
using FluentAssertions;
using FluentValidation;
using FluentValidation.TestHelper;
using Xunit;
namespace DocumentService.Tests.Unit.Application.Validators;
// ─── MergePdfsCommandValidator ────────────────────────────────────────────────
public class MergePdfsCommandValidatorTests
{
private readonly MergePdfsCommandValidator _validator = new();
[Fact]
public void Validate_TwoOrMoreStreams_PassesValidation()
{
var cmd = new MergePdfsCommand
{
PdfStreams = [new MemoryStream(), new MemoryStream()]
};
_validator.TestValidate(cmd).ShouldNotHaveAnyValidationErrors();
}
[Fact]
public void Validate_SingleStream_FailsValidation()
{
var cmd = new MergePdfsCommand { PdfStreams = [new MemoryStream()] };
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PdfStreams);
}
[Fact]
public void Validate_NullStreams_FailsValidation()
{
var cmd = new MergePdfsCommand { PdfStreams = null! };
var result = _validator.TestValidate(cmd);
result.Errors.Should().NotBeEmpty();
}
}
// ─── AddAnnotationValidator ───────────────────────────────────────────────────
public class AddAnnotationValidatorTests
{
private readonly AddAnnotationValidator _validator = new();
[Fact]
public void Validate_ValidCircleAnnotation_PassesValidation()
{
var cmd = new AddAnnotationCommand
{
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
AnnotationType = AnnotationType.Circle,
PageNumber = 1,
Rectangle = (10, 20, 200, 100)
};
_validator.TestValidate(cmd).ShouldNotHaveAnyValidationErrors();
}
[Fact]
public void Validate_FreeTextWithoutContent_FailsValidation()
{
var cmd = new AddAnnotationCommand
{
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
AnnotationType = AnnotationType.FreeText,
PageNumber = 1,
Rectangle = (0, 0, 100, 50),
Content = null
};
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Content);
}
[Fact]
public void Validate_TextMarkupWithoutStyle_FailsValidation()
{
var cmd = new AddAnnotationCommand
{
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
AnnotationType = AnnotationType.TextMarkup,
PageNumber = 1,
Rectangle = (0, 0, 100, 50),
TextMarkupStyle = null
};
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.TextMarkupStyle);
}
[Fact]
public void Validate_InvalidColorFormat_FailsValidation()
{
var cmd = new AddAnnotationCommand
{
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
AnnotationType = AnnotationType.Circle,
PageNumber = 1,
Rectangle = (0, 0, 100, 50),
Color = "GGGGGG" // invalid hex
};
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Color);
}
[Fact]
public void Validate_ValidColorFormat_PassesValidation()
{
var cmd = new AddAnnotationCommand
{
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
AnnotationType = AnnotationType.Circle,
PageNumber = 1,
Rectangle = (0, 0, 100, 50),
Color = "FF0000"
};
_validator.TestValidate(cmd).ShouldNotHaveAnyValidationErrors();
}
[Fact]
public void Validate_ZeroPageNumber_FailsValidation()
{
var cmd = new AddAnnotationCommand
{
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
AnnotationType = AnnotationType.Circle,
PageNumber = 0,
Rectangle = (0, 0, 100, 50)
};
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PageNumber);
}
[Fact]
public void Validate_InvalidRectangle_FailsValidation()
{
var cmd = new AddAnnotationCommand
{
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
AnnotationType = AnnotationType.Circle,
PageNumber = 1,
Rectangle = (100, 100, 50, 50) // X2 < X1
};
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Rectangle);
}
}
// ─── AddStampValidator ────────────────────────────────────────────────────────
public class AddStampValidatorTests
{
private readonly AddStampValidator _validator = new();
[Fact]
public void Validate_ValidTextStamp_PassesValidation()
{
var cmd = new AddStampCommand
{
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
StampType = StampType.Text,
Position = (100, 200),
Text = "DRAFT"
};
_validator.TestValidate(cmd).ShouldNotHaveAnyValidationErrors();
}
[Fact]
public void Validate_TextStampWithoutText_FailsValidation()
{
var cmd = new AddStampCommand
{
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
StampType = StampType.Text,
Position = (100, 200),
Text = null
};
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Text);
}
[Fact]
public void Validate_ImageStampWithoutBytes_FailsValidation()
{
var cmd = new AddStampCommand
{
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
StampType = StampType.Image,
Position = (0, 0),
ImageBytes = null
};
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.ImageBytes);
}
[Fact]
public void Validate_PredefinedStampWithoutType_FailsValidation()
{
var cmd = new AddStampCommand
{
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
StampType = StampType.Predefined,
Position = (0, 0),
PredefinedType = null
};
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PredefinedType);
}
[Fact]
public void Validate_OpacityAboveOne_FailsValidation()
{
var cmd = new AddStampCommand
{
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
StampType = StampType.Text,
Position = (0, 0),
Text = "TEST",
Opacity = 1.5
};
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Opacity!.Value);
}
[Fact]
public void Validate_InvalidColorHex_FailsValidation()
{
var cmd = new AddStampCommand
{
PdfStream = new MemoryStream("%PDF"u8.ToArray()),
StampType = StampType.Text,
Position = (0, 0),
Text = "TEST",
Color = "XYZ"
};
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Color!);
}
}
// ─── ConvertToPdfACommandValidator ───────────────────────────────────────────
public class ConvertToPdfAValidatorTests
{
private readonly ConvertToPdfACommandValidator _validator = new();
[Fact]
public void Validate_ValidPdfALevel_PassesValidation()
{
foreach (var level in new[] { "PDF/A-1b", "PDF/A-2b", "PDF/A-3b", "PDF/A-3u" })
{
var cmd = new ConvertToPdfACommand { PdfStream = new MemoryStream(), PdfALevel = level };
_validator.TestValidate(cmd).ShouldNotHaveAnyValidationErrors();
}
}
[Fact]
public void Validate_InvalidPdfALevel_FailsValidation()
{
var cmd = new ConvertToPdfACommand { PdfStream = new MemoryStream(), PdfALevel = "PDF/A-99z" };
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PdfALevel);
}
[Fact]
public void Validate_EmptyPdfALevel_FailsValidation()
{
var cmd = new ConvertToPdfACommand { PdfStream = new MemoryStream(), PdfALevel = "" };
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PdfALevel);
}
[Fact]
public void Validate_NullStream_FailsValidation()
{
var cmd = new ConvertToPdfACommand { PdfStream = null!, PdfALevel = "PDF/A-1b" };
_validator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PdfStream);
}
}