Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
/KustoSchemaTools.Plugins/bin
/.vs
/KustoSchemaTools.Cli/KustoSchemaTools.Cli.csproj.user

201 changes: 201 additions & 0 deletions KustoSchemaTools.Tests/Changes/DatabaseChangesColumnValidationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
using KustoSchemaTools.Changes;
using KustoSchemaTools.Configuration;
using KustoSchemaTools.Model;
using Microsoft.Extensions.Logging;
using Moq;

namespace KustoSchemaTools.Tests.Changes
{
public class DatabaseChangesColumnValidationTests
{
private readonly Mock<ILogger> _loggerMock;

public DatabaseChangesColumnValidationTests()
{
_loggerMock = new Mock<ILogger>();
}

[Fact]
public void GenerateChanges_ValidColumnOrder_NoCommentAttached()
{
// Arrange
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int"), ("NewCol", "bool") }));
var settings = ValidationSettings.WithColumnOrderValidation();

// Act
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);

// Assert
var tableChanges = changes.Where(c => c.EntityType == "Table").ToList();
Assert.All(tableChanges, change => Assert.Null(change.Comment));
}

[Fact]
public void GenerateChanges_InvalidColumnOrder_CautionCommentWithFailsRollout()
{
// Arrange
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol", "bool"), ("Col2", "int") }));
var settings = ValidationSettings.WithColumnOrderValidation();

// Act
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);

// Assert
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
Assert.NotNull(tableChange);
Assert.NotNull(tableChange.Comment);
Assert.True(tableChange.Comment.FailsRollout);
Assert.Equal(CommentKind.Caution, tableChange.Comment.Kind);
Assert.Contains("Column order violation", tableChange.Comment.Text);
Assert.Contains("Col2", tableChange.Comment.Text);
Assert.Contains("NewCol", tableChange.Comment.Text);
}

[Fact]
public void GenerateChanges_NewTable_NoValidationPerformed()
{
// Arrange
var oldDb = new Database { Tables = new Dictionary<string, Table>() };
var newDb = CreateDatabase(("Table1", new[] { ("NewCol", "bool"), ("Col1", "string") }));
var settings = ValidationSettings.WithColumnOrderValidation();

// Act
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);

// Assert
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
Assert.NotNull(tableChange);
Assert.Null(tableChange.Comment);
}

[Fact]
public void GenerateChanges_MultipleTables_OnlyInvalidOnesGetComments()
{
// Arrange
var oldDb = CreateDatabase(
("Table1", new[] { ("Col1", "string") }),
("Table2", new[] { ("Col1", "string") }),
("Table3", new[] { ("Col1", "string") }));

var newDb = CreateDatabase(
("Table1", new[] { ("Col1", "string"), ("NewCol", "int") }), // Valid
("Table2", new[] { ("NewCol", "int"), ("Col1", "string") }), // Invalid
("Table3", new[] { ("Col1", "string"), ("AnotherCol", "bool") })); // Valid

// Act
var settings = ValidationSettings.WithColumnOrderValidation();
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);

// Assert
var table1Change = changes.FirstOrDefault(c => c.Entity == "Table1");
var table2Change = changes.FirstOrDefault(c => c.Entity == "Table2");
var table3Change = changes.FirstOrDefault(c => c.Entity == "Table3");

Assert.Null(table1Change?.Comment);
Assert.NotNull(table2Change?.Comment);
Assert.True(table2Change.Comment.FailsRollout);
Assert.Null(table3Change?.Comment);
}

[Fact]
public void GenerateChanges_InvalidColumnOrder_ErrorMessageIncludesColumnNames()
{
// Arrange
var oldDb = CreateDatabase(("EventsTable", new[] { ("EventId", "string"), ("Timestamp", "datetime") }));
var newDb = CreateDatabase(("EventsTable", new[] { ("EventId", "string"), ("NewMetric", "int"), ("Timestamp", "datetime") }));

// Act
var settings = ValidationSettings.WithColumnOrderValidation();
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);

// Assert
var tableChange = changes.FirstOrDefault(c => c.Entity == "EventsTable");
Assert.NotNull(tableChange?.Comment);
Assert.Contains("NewMetric", tableChange.Comment.Text);
Assert.Contains("Timestamp", tableChange.Comment.Text);
Assert.Contains("EventsTable", tableChange.Comment.Text);
}

[Fact]
public void GenerateChanges_TableWithNoColumnsChanged_NoComment()
{
// Arrange
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));

// Act
var settings = ValidationSettings.WithColumnOrderValidation();
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);

// Assert
var tableChanges = changes.Where(c => c.Entity == "Table1").ToList();
// May have no changes at all if columns are identical
Assert.All(tableChanges, change => Assert.Null(change.Comment));
}

[Fact]
public void GenerateChanges_MultipleNewColumnsInMiddle_FailsValidation()
{
// Arrange
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int"), ("Col3", "bool") }));
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol1", "datetime"), ("NewCol2", "long"), ("Col2", "int"), ("Col3", "bool") }));

// Act
var settings = ValidationSettings.WithColumnOrderValidation();
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);

// Assert
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
Assert.NotNull(tableChange?.Comment);
Assert.True(tableChange.Comment.FailsRollout);
Assert.Contains("Col2", tableChange.Comment.Text);
Assert.Contains("Col3", tableChange.Comment.Text);
}

[Fact]
public void GenerateChanges_ValidColumnOrderMultipleNewColumns_NoComment()
{
// Arrange
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int"), ("NewCol1", "bool"), ("NewCol2", "datetime"), ("NewCol3", "long") }));

// Act
var settings = ValidationSettings.WithColumnOrderValidation();
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);

// Assert
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
if (tableChange != null)
{
Assert.Null(tableChange.Comment);
}
}

private static Database CreateDatabase(params (string TableName, (string Name, string Type)[] Columns)[] tables)
{
var database = new Database
{
Tables = new Dictionary<string, Table>()
};

foreach (var table in tables)
{
var tableObj = new Table
{
Columns = new Dictionary<string, string>()
};

foreach (var column in table.Columns)
{
tableObj.Columns[column.Name] = column.Type;
}

database.Tables[table.TableName] = tableObj;
}

return database;
}
}
}
188 changes: 188 additions & 0 deletions KustoSchemaTools.Tests/Changes/DatabaseChangesValidationFlagTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
using KustoSchemaTools.Changes;
using KustoSchemaTools.Configuration;
using KustoSchemaTools.Model;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace KustoSchemaTools.Tests.Changes
{
public class DatabaseChangesValidationFlagTests
{
private readonly Mock<ILogger> _loggerMock;

public DatabaseChangesValidationFlagTests()
{
_loggerMock = new Mock<ILogger>();
}

[Fact]
public void GenerateChanges_WithoutValidationSettings_DoesNotApplyValidation()
{
// Arrange
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol", "bool"), ("Col2", "int") }));

// Act - No validation settings provided (null)
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object);

// Assert
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
Assert.NotNull(tableChange);
Assert.Null(tableChange.Comment); // No validation comment should be attached
}

[Fact]
public void GenerateChanges_WithValidationDisabled_DoesNotApplyValidation()
{
// Arrange
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol", "bool"), ("Col2", "int") }));
var settings = new ValidationSettings { EnableColumnOrderValidation = false };

// Act
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);

// Assert
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
Assert.NotNull(tableChange);
Assert.Null(tableChange.Comment); // No validation comment should be attached
}

[Fact]
public void GenerateChanges_WithValidationEnabled_AppliesValidation()
{
// Arrange
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol", "bool"), ("Col2", "int") }));
var settings = new ValidationSettings { EnableColumnOrderValidation = true };

// Act
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);

// Assert
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
Assert.NotNull(tableChange);
Assert.NotNull(tableChange.Comment); // Validation comment should be attached
Assert.True(tableChange.Comment.FailsRollout);
Assert.Contains("Column order violation", tableChange.Comment.Text);
}

[Fact]
public void GenerateChanges_WithValidationEnabledButValidColumnOrder_NoComment()
{
// Arrange
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int"), ("NewCol", "bool") }));
var settings = new ValidationSettings { EnableColumnOrderValidation = true };

// Act
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);

// Assert
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
if (tableChange != null)
{
Assert.Null(tableChange.Comment); // No validation comment should be attached for valid order
}
}

[Fact]
public void GenerateChanges_WithValidationFromEnvironmentVariable_AppliesValidation()
{
// Arrange
Environment.SetEnvironmentVariable("KUSTO_ENABLE_COLUMN_VALIDATION", "true");
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol", "bool"), ("Col2", "int") }));
var settings = ValidationSettings.FromEnvironment();

try
{
// Act
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);

// Assert
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
Assert.NotNull(tableChange);
Assert.NotNull(tableChange.Comment); // Validation comment should be attached
Assert.True(tableChange.Comment.FailsRollout);
Assert.Contains("Column order violation", tableChange.Comment.Text);
}
finally
{
// Cleanup
Environment.SetEnvironmentVariable("KUSTO_ENABLE_COLUMN_VALIDATION", null);
}
}

[Fact]
public void GenerateChanges_WithValidationFromEnvironmentVariableDisabled_DoesNotApplyValidation()
{
// Arrange
Environment.SetEnvironmentVariable("KUSTO_ENABLE_COLUMN_VALIDATION", "false");
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol", "bool"), ("Col2", "int") }));
var settings = ValidationSettings.FromEnvironment();

try
{
// Act
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);

// Assert
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
Assert.NotNull(tableChange);
Assert.Null(tableChange.Comment); // No validation comment should be attached
}
finally
{
// Cleanup
Environment.SetEnvironmentVariable("KUSTO_ENABLE_COLUMN_VALIDATION", null);
}
}

[Fact]
public void GenerateChanges_WithConvenienceMethod_EnablesValidation()
{
// Arrange
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol", "bool"), ("Col2", "int") }));
var settings = ValidationSettings.WithColumnOrderValidation();

// Act
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);

// Assert
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
Assert.NotNull(tableChange);
Assert.NotNull(tableChange.Comment); // Validation comment should be attached
Assert.True(tableChange.Comment.FailsRollout);
Assert.Contains("Column order violation", tableChange.Comment.Text);
}

private static Database CreateDatabase(params (string TableName, (string Name, string Type)[] Columns)[] tables)
{
var database = new Database
{
Tables = new Dictionary<string, Table>()
};

foreach (var table in tables)
{
var tableObj = new Table
{
Columns = new Dictionary<string, string>()
};

foreach (var column in table.Columns)
{
tableObj.Columns[column.Name] = column.Type;
}

database.Tables[table.TableName] = tableObj;
}

return database;
}
}
}
Loading
Loading