Skip to content

Commit 5f04d14

Browse files
committed
column order validation feature
1 parent 793d8bd commit 5f04d14

File tree

11 files changed

+1155
-45
lines changed

11 files changed

+1155
-45
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010
/KustoSchemaTools.Plugins/bin
1111
/.vs
1212
/KustoSchemaTools.Cli/KustoSchemaTools.Cli.csproj.user
13+
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
using KustoSchemaTools.Changes;
2+
using KustoSchemaTools.Configuration;
3+
using KustoSchemaTools.Model;
4+
using Microsoft.Extensions.Logging;
5+
using Moq;
6+
7+
namespace KustoSchemaTools.Tests.Changes
8+
{
9+
public class DatabaseChangesColumnValidationTests
10+
{
11+
private readonly Mock<ILogger> _loggerMock;
12+
13+
public DatabaseChangesColumnValidationTests()
14+
{
15+
_loggerMock = new Mock<ILogger>();
16+
}
17+
18+
[Fact]
19+
public void GenerateChanges_ValidColumnOrder_NoCommentAttached()
20+
{
21+
// Arrange
22+
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
23+
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int"), ("NewCol", "bool") }));
24+
var settings = ValidationSettings.WithColumnOrderValidation();
25+
26+
// Act
27+
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);
28+
29+
// Assert
30+
var tableChanges = changes.Where(c => c.EntityType == "Table").ToList();
31+
Assert.All(tableChanges, change => Assert.Null(change.Comment));
32+
}
33+
34+
[Fact]
35+
public void GenerateChanges_InvalidColumnOrder_CautionCommentWithFailsRollout()
36+
{
37+
// Arrange
38+
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
39+
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol", "bool"), ("Col2", "int") }));
40+
var settings = ValidationSettings.WithColumnOrderValidation();
41+
42+
// Act
43+
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);
44+
45+
// Assert
46+
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
47+
Assert.NotNull(tableChange);
48+
Assert.NotNull(tableChange.Comment);
49+
Assert.True(tableChange.Comment.FailsRollout);
50+
Assert.Equal(CommentKind.Caution, tableChange.Comment.Kind);
51+
Assert.Contains("Column order violation", tableChange.Comment.Text);
52+
Assert.Contains("Col2", tableChange.Comment.Text);
53+
Assert.Contains("NewCol", tableChange.Comment.Text);
54+
}
55+
56+
[Fact]
57+
public void GenerateChanges_NewTable_NoValidationPerformed()
58+
{
59+
// Arrange
60+
var oldDb = new Database { Tables = new Dictionary<string, Table>() };
61+
var newDb = CreateDatabase(("Table1", new[] { ("NewCol", "bool"), ("Col1", "string") }));
62+
var settings = ValidationSettings.WithColumnOrderValidation();
63+
64+
// Act
65+
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);
66+
67+
// Assert
68+
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
69+
Assert.NotNull(tableChange);
70+
Assert.Null(tableChange.Comment);
71+
}
72+
73+
[Fact]
74+
public void GenerateChanges_MultipleTables_OnlyInvalidOnesGetComments()
75+
{
76+
// Arrange
77+
var oldDb = CreateDatabase(
78+
("Table1", new[] { ("Col1", "string") }),
79+
("Table2", new[] { ("Col1", "string") }),
80+
("Table3", new[] { ("Col1", "string") }));
81+
82+
var newDb = CreateDatabase(
83+
("Table1", new[] { ("Col1", "string"), ("NewCol", "int") }), // Valid
84+
("Table2", new[] { ("NewCol", "int"), ("Col1", "string") }), // Invalid
85+
("Table3", new[] { ("Col1", "string"), ("AnotherCol", "bool") })); // Valid
86+
87+
// Act
88+
var settings = ValidationSettings.WithColumnOrderValidation();
89+
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);
90+
91+
// Assert
92+
var table1Change = changes.FirstOrDefault(c => c.Entity == "Table1");
93+
var table2Change = changes.FirstOrDefault(c => c.Entity == "Table2");
94+
var table3Change = changes.FirstOrDefault(c => c.Entity == "Table3");
95+
96+
Assert.Null(table1Change?.Comment);
97+
Assert.NotNull(table2Change?.Comment);
98+
Assert.True(table2Change.Comment.FailsRollout);
99+
Assert.Null(table3Change?.Comment);
100+
}
101+
102+
[Fact]
103+
public void GenerateChanges_InvalidColumnOrder_ErrorMessageIncludesColumnNames()
104+
{
105+
// Arrange
106+
var oldDb = CreateDatabase(("EventsTable", new[] { ("EventId", "string"), ("Timestamp", "datetime") }));
107+
var newDb = CreateDatabase(("EventsTable", new[] { ("EventId", "string"), ("NewMetric", "int"), ("Timestamp", "datetime") }));
108+
109+
// Act
110+
var settings = ValidationSettings.WithColumnOrderValidation();
111+
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);
112+
113+
// Assert
114+
var tableChange = changes.FirstOrDefault(c => c.Entity == "EventsTable");
115+
Assert.NotNull(tableChange?.Comment);
116+
Assert.Contains("NewMetric", tableChange.Comment.Text);
117+
Assert.Contains("Timestamp", tableChange.Comment.Text);
118+
Assert.Contains("EventsTable", tableChange.Comment.Text);
119+
}
120+
121+
[Fact]
122+
public void GenerateChanges_TableWithNoColumnsChanged_NoComment()
123+
{
124+
// Arrange
125+
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
126+
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
127+
128+
// Act
129+
var settings = ValidationSettings.WithColumnOrderValidation();
130+
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);
131+
132+
// Assert
133+
var tableChanges = changes.Where(c => c.Entity == "Table1").ToList();
134+
// May have no changes at all if columns are identical
135+
Assert.All(tableChanges, change => Assert.Null(change.Comment));
136+
}
137+
138+
[Fact]
139+
public void GenerateChanges_MultipleNewColumnsInMiddle_FailsValidation()
140+
{
141+
// Arrange
142+
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int"), ("Col3", "bool") }));
143+
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol1", "datetime"), ("NewCol2", "long"), ("Col2", "int"), ("Col3", "bool") }));
144+
145+
// Act
146+
var settings = ValidationSettings.WithColumnOrderValidation();
147+
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);
148+
149+
// Assert
150+
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
151+
Assert.NotNull(tableChange?.Comment);
152+
Assert.True(tableChange.Comment.FailsRollout);
153+
Assert.Contains("Col2", tableChange.Comment.Text);
154+
Assert.Contains("Col3", tableChange.Comment.Text);
155+
}
156+
157+
[Fact]
158+
public void GenerateChanges_ValidColumnOrderMultipleNewColumns_NoComment()
159+
{
160+
// Arrange
161+
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
162+
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int"), ("NewCol1", "bool"), ("NewCol2", "datetime"), ("NewCol3", "long") }));
163+
164+
// Act
165+
var settings = ValidationSettings.WithColumnOrderValidation();
166+
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);
167+
168+
// Assert
169+
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
170+
if (tableChange != null)
171+
{
172+
Assert.Null(tableChange.Comment);
173+
}
174+
}
175+
176+
private static Database CreateDatabase(params (string TableName, (string Name, string Type)[] Columns)[] tables)
177+
{
178+
var database = new Database
179+
{
180+
Tables = new Dictionary<string, Table>()
181+
};
182+
183+
foreach (var table in tables)
184+
{
185+
var tableObj = new Table
186+
{
187+
Columns = new Dictionary<string, string>()
188+
};
189+
190+
foreach (var column in table.Columns)
191+
{
192+
tableObj.Columns[column.Name] = column.Type;
193+
}
194+
195+
database.Tables[table.TableName] = tableObj;
196+
}
197+
198+
return database;
199+
}
200+
}
201+
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
using KustoSchemaTools.Changes;
2+
using KustoSchemaTools.Configuration;
3+
using KustoSchemaTools.Model;
4+
using Microsoft.Extensions.Logging;
5+
using Moq;
6+
using Xunit;
7+
8+
namespace KustoSchemaTools.Tests.Changes
9+
{
10+
public class DatabaseChangesValidationFlagTests
11+
{
12+
private readonly Mock<ILogger> _loggerMock;
13+
14+
public DatabaseChangesValidationFlagTests()
15+
{
16+
_loggerMock = new Mock<ILogger>();
17+
}
18+
19+
[Fact]
20+
public void GenerateChanges_WithoutValidationSettings_DoesNotApplyValidation()
21+
{
22+
// Arrange
23+
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
24+
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol", "bool"), ("Col2", "int") }));
25+
26+
// Act - No validation settings provided (null)
27+
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object);
28+
29+
// Assert
30+
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
31+
Assert.NotNull(tableChange);
32+
Assert.Null(tableChange.Comment); // No validation comment should be attached
33+
}
34+
35+
[Fact]
36+
public void GenerateChanges_WithValidationDisabled_DoesNotApplyValidation()
37+
{
38+
// Arrange
39+
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
40+
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol", "bool"), ("Col2", "int") }));
41+
var settings = new ValidationSettings { EnableColumnOrderValidation = false };
42+
43+
// Act
44+
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);
45+
46+
// Assert
47+
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
48+
Assert.NotNull(tableChange);
49+
Assert.Null(tableChange.Comment); // No validation comment should be attached
50+
}
51+
52+
[Fact]
53+
public void GenerateChanges_WithValidationEnabled_AppliesValidation()
54+
{
55+
// Arrange
56+
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
57+
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol", "bool"), ("Col2", "int") }));
58+
var settings = new ValidationSettings { EnableColumnOrderValidation = true };
59+
60+
// Act
61+
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);
62+
63+
// Assert
64+
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
65+
Assert.NotNull(tableChange);
66+
Assert.NotNull(tableChange.Comment); // Validation comment should be attached
67+
Assert.True(tableChange.Comment.FailsRollout);
68+
Assert.Contains("Column order violation", tableChange.Comment.Text);
69+
}
70+
71+
[Fact]
72+
public void GenerateChanges_WithValidationEnabledButValidColumnOrder_NoComment()
73+
{
74+
// Arrange
75+
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
76+
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int"), ("NewCol", "bool") }));
77+
var settings = new ValidationSettings { EnableColumnOrderValidation = true };
78+
79+
// Act
80+
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);
81+
82+
// Assert
83+
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
84+
if (tableChange != null)
85+
{
86+
Assert.Null(tableChange.Comment); // No validation comment should be attached for valid order
87+
}
88+
}
89+
90+
[Fact]
91+
public void GenerateChanges_WithValidationFromEnvironmentVariable_AppliesValidation()
92+
{
93+
// Arrange
94+
Environment.SetEnvironmentVariable("KUSTO_ENABLE_COLUMN_VALIDATION", "true");
95+
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
96+
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol", "bool"), ("Col2", "int") }));
97+
var settings = ValidationSettings.FromEnvironment();
98+
99+
try
100+
{
101+
// Act
102+
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);
103+
104+
// Assert
105+
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
106+
Assert.NotNull(tableChange);
107+
Assert.NotNull(tableChange.Comment); // Validation comment should be attached
108+
Assert.True(tableChange.Comment.FailsRollout);
109+
Assert.Contains("Column order violation", tableChange.Comment.Text);
110+
}
111+
finally
112+
{
113+
// Cleanup
114+
Environment.SetEnvironmentVariable("KUSTO_ENABLE_COLUMN_VALIDATION", null);
115+
}
116+
}
117+
118+
[Fact]
119+
public void GenerateChanges_WithValidationFromEnvironmentVariableDisabled_DoesNotApplyValidation()
120+
{
121+
// Arrange
122+
Environment.SetEnvironmentVariable("KUSTO_ENABLE_COLUMN_VALIDATION", "false");
123+
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
124+
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol", "bool"), ("Col2", "int") }));
125+
var settings = ValidationSettings.FromEnvironment();
126+
127+
try
128+
{
129+
// Act
130+
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);
131+
132+
// Assert
133+
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
134+
Assert.NotNull(tableChange);
135+
Assert.Null(tableChange.Comment); // No validation comment should be attached
136+
}
137+
finally
138+
{
139+
// Cleanup
140+
Environment.SetEnvironmentVariable("KUSTO_ENABLE_COLUMN_VALIDATION", null);
141+
}
142+
}
143+
144+
[Fact]
145+
public void GenerateChanges_WithConvenienceMethod_EnablesValidation()
146+
{
147+
// Arrange
148+
var oldDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("Col2", "int") }));
149+
var newDb = CreateDatabase(("Table1", new[] { ("Col1", "string"), ("NewCol", "bool"), ("Col2", "int") }));
150+
var settings = ValidationSettings.WithColumnOrderValidation();
151+
152+
// Act
153+
var changes = DatabaseChanges.GenerateChanges(oldDb, newDb, "TestDB", _loggerMock.Object, settings);
154+
155+
// Assert
156+
var tableChange = changes.FirstOrDefault(c => c.Entity == "Table1");
157+
Assert.NotNull(tableChange);
158+
Assert.NotNull(tableChange.Comment); // Validation comment should be attached
159+
Assert.True(tableChange.Comment.FailsRollout);
160+
Assert.Contains("Column order violation", tableChange.Comment.Text);
161+
}
162+
163+
private static Database CreateDatabase(params (string TableName, (string Name, string Type)[] Columns)[] tables)
164+
{
165+
var database = new Database
166+
{
167+
Tables = new Dictionary<string, Table>()
168+
};
169+
170+
foreach (var table in tables)
171+
{
172+
var tableObj = new Table
173+
{
174+
Columns = new Dictionary<string, string>()
175+
};
176+
177+
foreach (var column in table.Columns)
178+
{
179+
tableObj.Columns[column.Name] = column.Type;
180+
}
181+
182+
database.Tables[table.TableName] = tableObj;
183+
}
184+
185+
return database;
186+
}
187+
}
188+
}

0 commit comments

Comments
 (0)