using JdeScoping.DataSync.Models; using JdeScoping.DataSync.Services; namespace JdeScoping.DataSync.Tests.Services; public class SchemaValidatorTests { private readonly SchemaValidator _validator = new(); private class TestEntity { public int Id { get; set; } public string Name { get; set; } = string.Empty; public string? NullableName { get; set; } public decimal Amount { get; set; } public DateTime CreatedDate { get; set; } } #region ValidateBatch Tests [Fact] public void ValidateBatch_EmptyData_ReturnsEmptyList() { // Arrange var data = Array.Empty(); var schema = new List { new("Id", "int", null, 10, 0, false, 1) }; // Act var errors = _validator.ValidateBatch(data, schema); // Assert Assert.Empty(errors); } [Fact] public void ValidateBatch_EmptySchema_ReturnsEmptyList() { // Arrange var data = new List { new() { Id = 1, Name = "Test" } }; var schema = Array.Empty(); // Act var errors = _validator.ValidateBatch(data, schema); // Assert Assert.Empty(errors); } [Fact] public void ValidateBatch_ValidData_ReturnsEmptyList() { // Arrange var data = new List { new() { Id = 1, Name = "Test", Amount = 100.50m } }; var schema = new List { new("Id", "int", null, 10, 0, false, 1), new("Name", "nvarchar", 50, null, null, false, 2), new("Amount", "decimal", null, 10, 2, false, 3) }; // Act var errors = _validator.ValidateBatch(data, schema); // Assert Assert.Empty(errors); } [Fact] public void ValidateBatch_StringTooLong_ReturnsError() { // Arrange var data = new List { new() { Id = 1, Name = "This is a very long string that exceeds the maximum length" } }; var schema = new List { new("Id", "int", null, 10, 0, false, 1), new("Name", "nvarchar", 10, null, null, false, 2) }; // Act var errors = _validator.ValidateBatch(data, schema); // Assert Assert.Single(errors); Assert.Equal("Name", errors[0].ColumnName); Assert.Equal(0, errors[0].RowIndex); Assert.Contains("exceeds maximum length", errors[0].Message); } [Fact] public void ValidateBatch_NullInNonNullableColumn_ReturnsError() { // Arrange var data = new List { new() { Id = 1, Name = "" } // Empty string treated as null for non-nullable }; var schema = new List { new("Id", "int", null, 10, 0, false, 1), new("Name", "nvarchar", 50, null, null, false, 2) }; // Act var errors = _validator.ValidateBatch(data, schema); // Assert Assert.Single(errors); Assert.Equal("Name", errors[0].ColumnName); Assert.Contains("does not allow null", errors[0].Message); } [Fact] public void ValidateBatch_NullInNullableColumn_NoError() { // Arrange var data = new List { new() { Id = 1, Name = "Test", NullableName = null } }; var schema = new List { new("Id", "int", null, 10, 0, false, 1), new("Name", "nvarchar", 50, null, null, false, 2), new("NullableName", "nvarchar", 50, null, null, true, 3) }; // Act var errors = _validator.ValidateBatch(data, schema); // Assert Assert.Empty(errors); } [Fact] public void ValidateBatch_DecimalOverflow_ReturnsError() { // Arrange var data = new List { new() { Id = 1, Name = "Test", Amount = 12345678.90m } // Too many integer digits for decimal(8,2) }; var schema = new List { new("Id", "int", null, 10, 0, false, 1), new("Name", "nvarchar", 50, null, null, false, 2), new("Amount", "decimal", null, 8, 2, false, 3) // Max 6 integer digits }; // Act var errors = _validator.ValidateBatch(data, schema); // Assert Assert.Single(errors); Assert.Equal("Amount", errors[0].ColumnName); Assert.Contains("exceeds maximum integer digits", errors[0].Message); } [Fact] public void ValidateBatch_DecimalWithinRange_NoError() { // Arrange var data = new List { new() { Id = 1, Name = "Test", Amount = 123456.78m } // Within decimal(10,2) - 8 integer digits }; var schema = new List { new("Id", "int", null, 10, 0, false, 1), new("Name", "nvarchar", 50, null, null, false, 2), new("Amount", "decimal", null, 10, 2, false, 3) // Max 8 integer digits }; // Act var errors = _validator.ValidateBatch(data, schema); // Assert Assert.Empty(errors); } [Fact] public void ValidateBatch_MultipleErrors_ReturnsAll() { // Arrange var data = new List { new() { Id = 1, Name = "This is too long" }, new() { Id = 2, Name = "Also too long!" } }; var schema = new List { new("Id", "int", null, 10, 0, false, 1), new("Name", "nvarchar", 5, null, null, false, 2) }; // Act var errors = _validator.ValidateBatch(data, schema); // Assert Assert.Equal(2, errors.Count); Assert.Equal(0, errors[0].RowIndex); Assert.Equal(1, errors[1].RowIndex); } [Fact] public void ValidateBatch_MaxErrors_StopsAtLimit() { // Arrange var data = Enumerable.Range(0, 10) .Select(i => new TestEntity { Id = i, Name = "This is way too long" }) .ToList(); var schema = new List { new("Id", "int", null, 10, 0, false, 1), new("Name", "nvarchar", 5, null, null, false, 2) }; // Act var errors = _validator.ValidateBatch(data, schema, maxErrors: 3); // Assert Assert.Equal(3, errors.Count); } [Fact] public void ValidateBatch_UnmatchedColumn_Ignored() { // Arrange var data = new List { new() { Id = 1, Name = "Test" } }; var schema = new List { new("Id", "int", null, 10, 0, false, 1), new("Name", "nvarchar", 50, null, null, false, 2), new("UnknownColumn", "nvarchar", 50, null, null, false, 3) }; // Act var errors = _validator.ValidateBatch(data, schema); // Assert Assert.Empty(errors); } [Fact] public void ValidateBatch_IdColumn_AllowsNull() { // Arrange - Id columns are treated as identity/auto-generated var data = new List { new() { Id = 0, Name = "Test" } // Id = 0 might be treated as "not set" }; var schema = new List { new("Id", "int", null, 10, 0, false, 1), // Not nullable in schema new("Name", "nvarchar", 50, null, null, false, 2) }; // Act var errors = _validator.ValidateBatch(data, schema); // Assert - No error because Id columns are treated specially Assert.Empty(errors); } #endregion }