test(configmanager): expand unit test coverage to 451 tests
Add comprehensive tests for services (ConnectionTestService, RuntimeConfigValidation), ViewModels (PipelineEditor, dialogs, transformers), and Avalonia headless UI tests for views and forms.
This commit is contained in:
+241
@@ -0,0 +1,241 @@
|
||||
using JdeScoping.ConfigManager.Services;
|
||||
using JdeScoping.ConfigManager.ViewModels.Dialogs;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Tests.ViewModels.Dialogs;
|
||||
|
||||
public class DiffPreviewDialogViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WithEmptyDiff_InitializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var diff = new DiffResult
|
||||
{
|
||||
HasChanges = false,
|
||||
Lines = [],
|
||||
Insertions = 0,
|
||||
Deletions = 0
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new DiffPreviewDialogViewModel(diff);
|
||||
|
||||
// Assert
|
||||
sut.Lines.Count.ShouldBe(0);
|
||||
sut.HasChanges.ShouldBeFalse();
|
||||
sut.Insertions.ShouldBe(0);
|
||||
sut.Deletions.ShouldBe(0);
|
||||
sut.Result.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithChanges_PopulatesLines()
|
||||
{
|
||||
// Arrange
|
||||
var diff = new DiffResult
|
||||
{
|
||||
HasChanges = true,
|
||||
Lines =
|
||||
[
|
||||
new DiffLine { OldLineNumber = 1, NewLineNumber = 1, Text = "unchanged line", Type = DiffLineType.Unchanged },
|
||||
new DiffLine { OldLineNumber = 2, NewLineNumber = null, Text = "removed line", Type = DiffLineType.Removed },
|
||||
new DiffLine { OldLineNumber = null, NewLineNumber = 2, Text = "added line", Type = DiffLineType.Added }
|
||||
],
|
||||
Insertions = 1,
|
||||
Deletions = 1
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new DiffPreviewDialogViewModel(diff);
|
||||
|
||||
// Assert
|
||||
sut.Lines.Count.ShouldBe(3);
|
||||
sut.HasChanges.ShouldBeTrue();
|
||||
sut.Insertions.ShouldBe(1);
|
||||
sut.Deletions.ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveCommand_SetsResultTrue_AndInvokesRequestClose()
|
||||
{
|
||||
// Arrange
|
||||
var diff = CreateEmptyDiff();
|
||||
var sut = new DiffPreviewDialogViewModel(diff);
|
||||
var closeInvoked = false;
|
||||
sut.RequestClose = () => closeInvoked = true;
|
||||
|
||||
// Act
|
||||
sut.SaveCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
sut.Result.ShouldBeTrue();
|
||||
closeInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CancelCommand_SetsResultFalse_AndInvokesRequestClose()
|
||||
{
|
||||
// Arrange
|
||||
var diff = CreateEmptyDiff();
|
||||
var sut = new DiffPreviewDialogViewModel(diff);
|
||||
var closeInvoked = false;
|
||||
sut.RequestClose = () => closeInvoked = true;
|
||||
|
||||
// Act
|
||||
sut.CancelCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
sut.Result.ShouldBeFalse();
|
||||
closeInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveCommand_DoesNotThrow_WhenRequestCloseIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var diff = CreateEmptyDiff();
|
||||
var sut = new DiffPreviewDialogViewModel(diff);
|
||||
sut.RequestClose = null;
|
||||
|
||||
// Act & Assert - Should not throw
|
||||
Should.NotThrow(() => sut.SaveCommand.Execute(null));
|
||||
sut.Result.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CancelCommand_DoesNotThrow_WhenRequestCloseIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var diff = CreateEmptyDiff();
|
||||
var sut = new DiffPreviewDialogViewModel(diff);
|
||||
sut.RequestClose = null;
|
||||
|
||||
// Act & Assert - Should not throw
|
||||
Should.NotThrow(() => sut.CancelCommand.Execute(null));
|
||||
sut.Result.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullDiff()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new DiffPreviewDialogViewModel(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Result_InitialValue_IsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var diff = CreateEmptyDiff();
|
||||
|
||||
// Act
|
||||
var sut = new DiffPreviewDialogViewModel(diff);
|
||||
|
||||
// Assert
|
||||
sut.Result.ShouldBeFalse();
|
||||
}
|
||||
|
||||
private static DiffResult CreateEmptyDiff()
|
||||
{
|
||||
return new DiffResult
|
||||
{
|
||||
HasChanges = false,
|
||||
Lines = [],
|
||||
Insertions = 0,
|
||||
Deletions = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class DiffLineViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_UnchangedLine_SetsPropertiesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var line = new DiffLine
|
||||
{
|
||||
OldLineNumber = 5,
|
||||
NewLineNumber = 5,
|
||||
Text = "unchanged content",
|
||||
Type = DiffLineType.Unchanged
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new DiffLineViewModel(line);
|
||||
|
||||
// Assert
|
||||
sut.OldLineNumber.ShouldBe("5");
|
||||
sut.NewLineNumber.ShouldBe("5");
|
||||
sut.Text.ShouldBe("unchanged content");
|
||||
sut.Type.ShouldBe(DiffLineType.Unchanged);
|
||||
sut.Background.ShouldBe("Transparent");
|
||||
sut.BorderColor.ShouldBe("Transparent");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_AddedLine_SetsGreenStyling()
|
||||
{
|
||||
// Arrange
|
||||
var line = new DiffLine
|
||||
{
|
||||
OldLineNumber = null,
|
||||
NewLineNumber = 10,
|
||||
Text = "new line",
|
||||
Type = DiffLineType.Added
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new DiffLineViewModel(line);
|
||||
|
||||
// Assert
|
||||
sut.OldLineNumber.ShouldBe("");
|
||||
sut.NewLineNumber.ShouldBe("10");
|
||||
sut.Type.ShouldBe(DiffLineType.Added);
|
||||
sut.Background.ShouldBe("#1A3DD68C");
|
||||
sut.BorderColor.ShouldBe("#3DD68C");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_RemovedLine_SetsRedStyling()
|
||||
{
|
||||
// Arrange
|
||||
var line = new DiffLine
|
||||
{
|
||||
OldLineNumber = 7,
|
||||
NewLineNumber = null,
|
||||
Text = "deleted line",
|
||||
Type = DiffLineType.Removed
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new DiffLineViewModel(line);
|
||||
|
||||
// Assert
|
||||
sut.OldLineNumber.ShouldBe("7");
|
||||
sut.NewLineNumber.ShouldBe("");
|
||||
sut.Type.ShouldBe(DiffLineType.Removed);
|
||||
sut.Background.ShouldBe("#1AFF6B6B");
|
||||
sut.BorderColor.ShouldBe("#FF6B6B");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NullLineNumbers_ReturnsEmptyStrings()
|
||||
{
|
||||
// Arrange
|
||||
var line = new DiffLine
|
||||
{
|
||||
OldLineNumber = null,
|
||||
NewLineNumber = null,
|
||||
Text = "text",
|
||||
Type = DiffLineType.Unchanged
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new DiffLineViewModel(line);
|
||||
|
||||
// Assert
|
||||
sut.OldLineNumber.ShouldBe("");
|
||||
sut.NewLineNumber.ShouldBe("");
|
||||
}
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
using JdeScoping.ConfigManager.Services;
|
||||
using JdeScoping.ConfigManager.ViewModels.Dialogs;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Tests.ViewModels.Dialogs;
|
||||
|
||||
public class ValidationResultsDialogViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WithEmptyResults_HasNoItems()
|
||||
{
|
||||
// Arrange
|
||||
var appSettingsResult = new ValidationResult();
|
||||
var pipelinesResult = new ValidationResult();
|
||||
|
||||
// Act
|
||||
var sut = new ValidationResultsDialogViewModel(appSettingsResult, pipelinesResult);
|
||||
|
||||
// Assert
|
||||
sut.Items.Count.ShouldBe(0);
|
||||
sut.ErrorCount.ShouldBe(0);
|
||||
sut.WarningCount.ShouldBe(0);
|
||||
sut.IsValid.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithErrors_PopulatesItemsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var appSettingsResult = new ValidationResult();
|
||||
appSettingsResult.AddError("Missing connection string");
|
||||
appSettingsResult.AddError("Invalid timeout value");
|
||||
var pipelinesResult = new ValidationResult();
|
||||
|
||||
// Act
|
||||
var sut = new ValidationResultsDialogViewModel(appSettingsResult, pipelinesResult);
|
||||
|
||||
// Assert
|
||||
sut.Items.Count.ShouldBe(2);
|
||||
sut.ErrorCount.ShouldBe(2);
|
||||
sut.WarningCount.ShouldBe(0);
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithWarnings_PopulatesItemsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var appSettingsResult = new ValidationResult();
|
||||
appSettingsResult.AddWarning("Deprecated setting used");
|
||||
var pipelinesResult = new ValidationResult();
|
||||
pipelinesResult.AddWarning("Pipeline has no transformers");
|
||||
|
||||
// Act
|
||||
var sut = new ValidationResultsDialogViewModel(appSettingsResult, pipelinesResult);
|
||||
|
||||
// Assert
|
||||
sut.Items.Count.ShouldBe(2);
|
||||
sut.ErrorCount.ShouldBe(0);
|
||||
sut.WarningCount.ShouldBe(2);
|
||||
sut.IsValid.ShouldBeFalse(); // Both errors and warnings make it invalid
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithMixedResults_PopulatesAllItems()
|
||||
{
|
||||
// Arrange
|
||||
var appSettingsResult = new ValidationResult();
|
||||
appSettingsResult.AddError("Error in appsettings");
|
||||
appSettingsResult.AddWarning("Warning in appsettings");
|
||||
var pipelinesResult = new ValidationResult();
|
||||
pipelinesResult.AddError("Error in pipelines");
|
||||
pipelinesResult.AddWarning("Warning in pipelines");
|
||||
|
||||
// Act
|
||||
var sut = new ValidationResultsDialogViewModel(appSettingsResult, pipelinesResult);
|
||||
|
||||
// Assert
|
||||
sut.Items.Count.ShouldBe(4);
|
||||
sut.ErrorCount.ShouldBe(2);
|
||||
sut.WarningCount.ShouldBe(2);
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SetsCorrectSourceOnItems()
|
||||
{
|
||||
// Arrange
|
||||
var appSettingsResult = new ValidationResult();
|
||||
appSettingsResult.AddError("App error");
|
||||
var pipelinesResult = new ValidationResult();
|
||||
pipelinesResult.AddError("Pipeline error");
|
||||
|
||||
// Act
|
||||
var sut = new ValidationResultsDialogViewModel(appSettingsResult, pipelinesResult);
|
||||
|
||||
// Assert
|
||||
sut.Items.ShouldContain(i => i.Source == "appsettings.json" && i.Message == "App error");
|
||||
sut.Items.ShouldContain(i => i.Source == "pipelines.json" && i.Message == "Pipeline error");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloseCommand_InvokesRequestClose()
|
||||
{
|
||||
// Arrange
|
||||
var appSettingsResult = new ValidationResult();
|
||||
var pipelinesResult = new ValidationResult();
|
||||
var sut = new ValidationResultsDialogViewModel(appSettingsResult, pipelinesResult);
|
||||
var closeInvoked = false;
|
||||
sut.RequestClose = () => closeInvoked = true;
|
||||
|
||||
// Act
|
||||
sut.CloseCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
closeInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloseCommand_DoesNotThrow_WhenRequestCloseIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var appSettingsResult = new ValidationResult();
|
||||
var pipelinesResult = new ValidationResult();
|
||||
var sut = new ValidationResultsDialogViewModel(appSettingsResult, pipelinesResult);
|
||||
sut.RequestClose = null;
|
||||
|
||||
// Act & Assert - Should not throw
|
||||
Should.NotThrow(() => sut.CloseCommand.Execute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullAppSettingsResult()
|
||||
{
|
||||
// Arrange
|
||||
var pipelinesResult = new ValidationResult();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new ValidationResultsDialogViewModel(null!, pipelinesResult));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullPipelinesResult()
|
||||
{
|
||||
// Arrange
|
||||
var appSettingsResult = new ValidationResult();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new ValidationResultsDialogViewModel(appSettingsResult, null!));
|
||||
}
|
||||
}
|
||||
|
||||
public class ValidationItemViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_SetsPropertiesCorrectly()
|
||||
{
|
||||
// Act
|
||||
var sut = new ValidationItemViewModel("Test message", "test.json", ValidationItemType.Error);
|
||||
|
||||
// Assert
|
||||
sut.Message.ShouldBe("Test message");
|
||||
sut.Source.ShouldBe("test.json");
|
||||
sut.Type.ShouldBe(ValidationItemType.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ErrorType_SetsErrorStyling()
|
||||
{
|
||||
// Act
|
||||
var sut = new ValidationItemViewModel("Error", "test.json", ValidationItemType.Error);
|
||||
|
||||
// Assert
|
||||
sut.Icon.ShouldBe("\u2717"); // X mark
|
||||
sut.IconColor.ShouldBe("#FF6B6B");
|
||||
sut.Background.ShouldBe("#1AFF6B6B");
|
||||
sut.BorderColor.ShouldBe("#FF6B6B");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WarningType_SetsWarningStyling()
|
||||
{
|
||||
// Act
|
||||
var sut = new ValidationItemViewModel("Warning", "test.json", ValidationItemType.Warning);
|
||||
|
||||
// Assert
|
||||
sut.Icon.ShouldBe("\u26A0"); // Warning sign
|
||||
sut.IconColor.ShouldBe("#FFB84D");
|
||||
sut.Background.ShouldBe("#1AFFB84D");
|
||||
sut.BorderColor.ShouldBe("#FFB84D");
|
||||
}
|
||||
}
|
||||
+484
-62
@@ -22,7 +22,7 @@ public class ConnectionStringsFormViewModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_InitializesFromModel()
|
||||
public void Constructor_InitializesFromModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
@@ -54,67 +54,67 @@ public class ConnectionStringsFormViewModelTests
|
||||
sut.Connections[0].Server.ShouldBe("server1");
|
||||
sut.Connections[1].Name.ShouldBe("Connection2");
|
||||
sut.Connections[1].Provider.ShouldBe(ConnectionProvider.Oracle);
|
||||
sut.Connections[1].Host.ShouldBe("oracle-host");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_LoadsAndParsesSqlServerConnectionStringFromSecureStore()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry { Name = "LotFinder" }
|
||||
}
|
||||
};
|
||||
_secureStoreManager.IsStoreOpen.Returns(true);
|
||||
_secureStoreManager.GetSecret("LotFinder")
|
||||
.Returns("Server=localhost,1434;Database=ScopingTool;User Id=scopingapp;Password=pass;TrustServerCertificate=true");
|
||||
|
||||
// Act
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Assert
|
||||
sut.Connections.Count.ShouldBe(1);
|
||||
sut.Connections[0].Provider.ShouldBe(ConnectionProvider.SqlServer);
|
||||
sut.Connections[0].Server.ShouldBe("localhost,1434");
|
||||
sut.Connections[0].Database.ShouldBe("ScopingTool");
|
||||
sut.Connections[0].UserId.ShouldBe("scopingapp");
|
||||
sut.Connections[0].Password.ShouldBe("pass");
|
||||
sut.Connections[0].TrustServerCertificate.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_LoadsAndParsesOracleConnectionStringFromSecureStore()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry { Name = "CMS" }
|
||||
}
|
||||
};
|
||||
_secureStoreManager.IsStoreOpen.Returns(true);
|
||||
_secureStoreManager.GetSecret("CMS")
|
||||
.Returns("HOST=ha-iman;Service Name=imanprd;Fetch Array Size=1280000;Port=1522;User ID=app_teamcenter;Password=pass;");
|
||||
|
||||
// Act
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Assert
|
||||
sut.Connections.Count.ShouldBe(1);
|
||||
sut.Connections[0].Provider.ShouldBe(ConnectionProvider.Oracle);
|
||||
sut.Connections[0].Host.ShouldBe("ha-iman");
|
||||
sut.Connections[0].ServiceName.ShouldBe("imanprd");
|
||||
sut.Connections[0].Port.ShouldBe(1522);
|
||||
sut.Connections[0].UserId.ShouldBe("app_teamcenter");
|
||||
sut.Connections[0].Password.ShouldBe("pass");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullModel()
|
||||
sut.Connections[1].Host.ShouldBe("oracle-host");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_LoadsAndParsesSqlServerConnectionStringFromSecureStore()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry { Name = "LotFinder" }
|
||||
}
|
||||
};
|
||||
_secureStoreManager.IsStoreOpen.Returns(true);
|
||||
_secureStoreManager.GetSecret("LotFinder")
|
||||
.Returns("Server=localhost,1434;Database=ScopingTool;User Id=scopingapp;Password=pass;TrustServerCertificate=true");
|
||||
|
||||
// Act
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Assert
|
||||
sut.Connections.Count.ShouldBe(1);
|
||||
sut.Connections[0].Provider.ShouldBe(ConnectionProvider.SqlServer);
|
||||
sut.Connections[0].Server.ShouldBe("localhost,1434");
|
||||
sut.Connections[0].Database.ShouldBe("ScopingTool");
|
||||
sut.Connections[0].UserId.ShouldBe("scopingapp");
|
||||
sut.Connections[0].Password.ShouldBe("pass");
|
||||
sut.Connections[0].TrustServerCertificate.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_LoadsAndParsesOracleConnectionStringFromSecureStore()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry { Name = "CMS" }
|
||||
}
|
||||
};
|
||||
_secureStoreManager.IsStoreOpen.Returns(true);
|
||||
_secureStoreManager.GetSecret("CMS")
|
||||
.Returns("HOST=ha-iman;Service Name=imanprd;Fetch Array Size=1280000;Port=1522;User ID=app_teamcenter;Password=pass;");
|
||||
|
||||
// Act
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Assert
|
||||
sut.Connections.Count.ShouldBe(1);
|
||||
sut.Connections[0].Provider.ShouldBe(ConnectionProvider.Oracle);
|
||||
sut.Connections[0].Host.ShouldBe("ha-iman");
|
||||
sut.Connections[0].ServiceName.ShouldBe("imanprd");
|
||||
sut.Connections[0].Port.ShouldBe(1522);
|
||||
sut.Connections[0].UserId.ShouldBe("app_teamcenter");
|
||||
sut.Connections[0].Password.ShouldBe("pass");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullModel()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
@@ -237,4 +237,426 @@ public class ConnectionStringsFormViewModelTests
|
||||
// Assert
|
||||
sut.ConnectionCount.ShouldBe(4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_InitializesConnectionsFromModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry
|
||||
{
|
||||
Name = "TestConnection",
|
||||
Provider = ConnectionProvider.SqlServer,
|
||||
Server = "localhost",
|
||||
Database = "TestDb",
|
||||
UserId = "sa",
|
||||
Password = "secret"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Assert
|
||||
sut.Connections.Count.ShouldBe(1);
|
||||
sut.Connections[0].Name.ShouldBe("TestConnection");
|
||||
sut.Connections[0].Provider.ShouldBe(ConnectionProvider.SqlServer);
|
||||
sut.Connections[0].Server.ShouldBe("localhost");
|
||||
sut.Connections[0].Database.ShouldBe("TestDb");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddConnectionCommand_AddsNewConnection()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection();
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
var initialCount = sut.Connections.Count;
|
||||
|
||||
// Act
|
||||
sut.AddConnectionCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
sut.Connections.Count.ShouldBe(initialCount + 1);
|
||||
sut.Connections.Last().Name.ShouldBe("NewConnection");
|
||||
sut.Connections.Last().Provider.ShouldBe(ConnectionProvider.Generic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteConnectionCommand_WhenConfirmed_RemovesConnection()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry { Name = "ToDelete" }
|
||||
}
|
||||
};
|
||||
_dialogService.ShowConfirmationAsync(Arg.Any<string>(), Arg.Any<string>())
|
||||
.Returns(true);
|
||||
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
sut.SelectedConnection = sut.Connections[0];
|
||||
|
||||
// Act
|
||||
sut.DeleteConnectionCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
sut.Connections.Count.ShouldBe(0);
|
||||
model.Entries.Count.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteConnectionCommand_WhenCancelled_KeepsConnection()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry { Name = "ToKeep" }
|
||||
}
|
||||
};
|
||||
_dialogService.ShowConfirmationAsync(Arg.Any<string>(), Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
sut.SelectedConnection = sut.Connections[0];
|
||||
|
||||
// Act
|
||||
sut.DeleteConnectionCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
sut.Connections.Count.ShouldBe(1);
|
||||
sut.Connections[0].Name.ShouldBe("ToKeep");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateConnectionCommand_WithEmptyConnectionString_ShowsError()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry
|
||||
{
|
||||
Name = "EmptyConnection",
|
||||
Provider = ConnectionProvider.Generic,
|
||||
RawConnectionString = ""
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
sut.SelectedConnection = sut.Connections[0];
|
||||
|
||||
// Act
|
||||
sut.ValidateConnectionCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
await _dialogService.Received().ShowMessageAsync(
|
||||
"Validation Failed",
|
||||
Arg.Is<string>(s => s.Contains("empty connection string")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateConnectionCommand_WithValidConnectionString_ShowsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry
|
||||
{
|
||||
Name = "ValidConnection",
|
||||
Provider = ConnectionProvider.SqlServer,
|
||||
Server = "localhost",
|
||||
Database = "TestDb"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
sut.SelectedConnection = sut.Connections[0];
|
||||
|
||||
// Act
|
||||
sut.ValidateConnectionCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
await _dialogService.Received().ShowMessageAsync(
|
||||
"Validation Passed",
|
||||
Arg.Is<string>(s => s.Contains("valid connection string")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestConnectionCommand_WhenSuccessful_ShowsSuccessMessage()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry
|
||||
{
|
||||
Name = "TestConn",
|
||||
Provider = ConnectionProvider.SqlServer,
|
||||
Server = "localhost",
|
||||
Database = "TestDb"
|
||||
}
|
||||
}
|
||||
};
|
||||
_connectionTestService.TestConnectionAsync(Arg.Any<string>(), Arg.Any<ConnectionProvider>(), Arg.Any<CancellationToken>())
|
||||
.Returns(new ConnectionTestResult { Success = true, Duration = TimeSpan.FromMilliseconds(50) });
|
||||
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
sut.SelectedConnection = sut.Connections[0];
|
||||
|
||||
// Act
|
||||
sut.TestConnectionCommand.Execute(null);
|
||||
await Task.Delay(150);
|
||||
|
||||
// Assert
|
||||
await _dialogService.Received().ShowMessageAsync(
|
||||
"Connection Successful",
|
||||
Arg.Is<string>(s => s.Contains("Successfully connected")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestConnectionCommand_WhenFailed_ShowsErrorMessage()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry
|
||||
{
|
||||
Name = "FailConn",
|
||||
Provider = ConnectionProvider.SqlServer,
|
||||
Server = "badserver",
|
||||
Database = "TestDb"
|
||||
}
|
||||
}
|
||||
};
|
||||
_connectionTestService.TestConnectionAsync(Arg.Any<string>(), Arg.Any<ConnectionProvider>(), Arg.Any<CancellationToken>())
|
||||
.Returns(new ConnectionTestResult { Success = false, Message = "Connection refused" });
|
||||
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
sut.SelectedConnection = sut.Connections[0];
|
||||
|
||||
// Act
|
||||
sut.TestConnectionCommand.Execute(null);
|
||||
await Task.Delay(150);
|
||||
|
||||
// Assert
|
||||
await _dialogService.Received().ShowMessageAsync(
|
||||
"Connection Failed",
|
||||
Arg.Is<string>(s => s.Contains("Failed to connect") && s.Contains("Connection refused")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestConnectionCommand_SetsIsTesting_DuringExecution()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry
|
||||
{
|
||||
Name = "TestConn",
|
||||
Provider = ConnectionProvider.SqlServer,
|
||||
Server = "localhost",
|
||||
Database = "TestDb"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var taskCompletionSource = new TaskCompletionSource<ConnectionTestResult>();
|
||||
_connectionTestService.TestConnectionAsync(Arg.Any<string>(), Arg.Any<ConnectionProvider>(), Arg.Any<CancellationToken>())
|
||||
.Returns(taskCompletionSource.Task);
|
||||
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
sut.SelectedConnection = sut.Connections[0];
|
||||
|
||||
// Act - Start the command
|
||||
sut.TestConnectionCommand.Execute(null);
|
||||
await Task.Delay(50);
|
||||
|
||||
// Assert - IsTesting should be true during execution
|
||||
sut.IsTesting.ShouldBeTrue();
|
||||
|
||||
// Complete the task
|
||||
taskCompletionSource.SetResult(new ConnectionTestResult { Success = true });
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert - IsTesting should be false after completion
|
||||
sut.IsTesting.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectedConnection_WhenChanged_RaisesHasSelectionPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry { Name = "Conn1" }
|
||||
}
|
||||
};
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
var hasSelectionChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(ConnectionStringsFormViewModel.HasSelection))
|
||||
hasSelectionChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.SelectedConnection = sut.Connections[0];
|
||||
|
||||
// Assert
|
||||
hasSelectionChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnEntryChanged_SavesValueToSecureStore()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry
|
||||
{
|
||||
Name = "TestConn",
|
||||
Provider = ConnectionProvider.SqlServer,
|
||||
Server = "localhost",
|
||||
Database = "TestDb"
|
||||
}
|
||||
}
|
||||
};
|
||||
_secureStoreManager.IsStoreOpen.Returns(true);
|
||||
|
||||
var onChangedCalled = false;
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => onChangedCalled = true, _dialogService, _connectionTestService);
|
||||
sut.SelectedConnection = sut.Connections[0];
|
||||
|
||||
// Act - Change a property on the selected connection
|
||||
sut.SelectedConnection.Database = "NewDatabase";
|
||||
|
||||
// Assert
|
||||
onChangedCalled.ShouldBeTrue();
|
||||
_secureStoreManager.Received().SetSecret("TestConn", Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteConnectionCommand_RemovesFromSecureStore()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry { Name = "ToDelete" }
|
||||
}
|
||||
};
|
||||
_secureStoreManager.IsStoreOpen.Returns(true);
|
||||
_dialogService.ShowConfirmationAsync(Arg.Any<string>(), Arg.Any<string>())
|
||||
.Returns(true);
|
||||
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
sut.SelectedConnection = sut.Connections[0];
|
||||
|
||||
// Act
|
||||
sut.DeleteConnectionCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
_secureStoreManager.Received().RemoveSecret("ToDelete");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddConnectionCommand_CreatesSecureStoreEntry()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection();
|
||||
_secureStoreManager.IsStoreOpen.Returns(true);
|
||||
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Act
|
||||
sut.AddConnectionCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
_secureStoreManager.Received().SetSecret("NewConnection", string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestConnectionCommand_WithEmptyConnectionString_ShowsMessage()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry
|
||||
{
|
||||
Name = "EmptyConn",
|
||||
Provider = ConnectionProvider.Generic,
|
||||
RawConnectionString = ""
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
sut.SelectedConnection = sut.Connections[0];
|
||||
|
||||
// Act
|
||||
sut.TestConnectionCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
await _dialogService.Received().ShowMessageAsync(
|
||||
"Test Connection",
|
||||
Arg.Is<string>(s => s.Contains("connection string is empty")));
|
||||
await _connectionTestService.DidNotReceive().TestConnectionAsync(
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<ConnectionProvider>(),
|
||||
Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AvailableProviders_ContainsAllProviders()
|
||||
{
|
||||
// Assert
|
||||
ConnectionStringsFormViewModel.AvailableProviders.Count.ShouldBe(
|
||||
Enum.GetValues<ConnectionProvider>().Length);
|
||||
ConnectionStringsFormViewModel.AvailableProviders.ShouldContain(ConnectionProvider.SqlServer);
|
||||
ConnectionStringsFormViewModel.AvailableProviders.ShouldContain(ConnectionProvider.Oracle);
|
||||
ConnectionStringsFormViewModel.AvailableProviders.ShouldContain(ConnectionProvider.Generic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EncryptOptions_ContainsExpectedValues()
|
||||
{
|
||||
// Assert
|
||||
ConnectionStringsFormViewModel.EncryptOptions.Count.ShouldBe(3);
|
||||
ConnectionStringsFormViewModel.EncryptOptions.ShouldContain("True");
|
||||
ConnectionStringsFormViewModel.EncryptOptions.ShouldContain("False");
|
||||
ConnectionStringsFormViewModel.EncryptOptions.ShouldContain("Strict");
|
||||
}
|
||||
}
|
||||
|
||||
+630
@@ -0,0 +1,630 @@
|
||||
using JdeScoping.ConfigManager.Services;
|
||||
using JdeScoping.ConfigManager.ViewModels.Forms;
|
||||
using JdeScoping.ConfigManager.ViewModels.PipelineSteps;
|
||||
using JdeScoping.DataSync.Configuration;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Tests.ViewModels.Forms;
|
||||
|
||||
public class PipelineEditorViewModelTests
|
||||
{
|
||||
private readonly IDialogService _dialogService;
|
||||
|
||||
public PipelineEditorViewModelTests()
|
||||
{
|
||||
_dialogService = Substitute.For<IDialogService>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_InitializesPropertiesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var connections = new List<string> { "jde", "cms", "lotfinder" };
|
||||
|
||||
// Act
|
||||
var sut = new PipelineEditorViewModel("TestPipeline", model, connections, _dialogService, () => { });
|
||||
|
||||
// Assert
|
||||
sut.Name.ShouldBe("TestPipeline");
|
||||
sut.AvailableConnections.ShouldBe(connections);
|
||||
sut.PreScripts.ShouldNotBeNull();
|
||||
sut.Transformers.ShouldNotBeNull();
|
||||
sut.PostScripts.ShouldNotBeNull();
|
||||
sut.Source.ShouldNotBeNull();
|
||||
sut.Destination.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_BuildsSourceAndDestinationFromModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new EtlPipelineConfig
|
||||
{
|
||||
Source = new SourceElement { Connection = "jde", Query = "SELECT * FROM WO" },
|
||||
Destination = new DestinationElement { Table = "WorkOrder_Curr" }
|
||||
};
|
||||
var connections = new List<string> { "jde" };
|
||||
|
||||
// Act
|
||||
var sut = new PipelineEditorViewModel("TestPipeline", model, connections, _dialogService, () => { });
|
||||
|
||||
// Assert
|
||||
sut.Source.Connection.ShouldBe("jde");
|
||||
sut.Source.Query.ShouldBe("SELECT * FROM WO");
|
||||
sut.Destination.Table.ShouldBe("WorkOrder_Curr");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsEnabled_Setter_UpdatesModelAndInvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
model.IsEnabled = false;
|
||||
var changedInvoked = false;
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.IsEnabled = true;
|
||||
|
||||
// Assert
|
||||
sut.IsEnabled.ShouldBeTrue();
|
||||
model.IsEnabled.ShouldBeTrue();
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsManualOnly_Setter_UpdatesModelAndInvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
model.IsManualOnly = false;
|
||||
var changedInvoked = false;
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.IsManualOnly = true;
|
||||
|
||||
// Assert
|
||||
sut.IsManualOnly.ShouldBeTrue();
|
||||
model.IsManualOnly.ShouldBeTrue();
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MassSyncEnabled_ToggleOn_SetsDefaultInterval()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
model.MassSyncIntervalMinutes = null;
|
||||
var changedInvoked = false;
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.MassSyncEnabled = true;
|
||||
|
||||
// Assert
|
||||
sut.MassSyncEnabled.ShouldBeTrue();
|
||||
sut.MassSyncIntervalMinutes.ShouldBe(10080); // 1 week default
|
||||
model.MassSyncIntervalMinutes.ShouldBe(10080);
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MassSyncEnabled_ToggleOff_ClearsInterval()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
model.MassSyncIntervalMinutes = 10080;
|
||||
var changedInvoked = false;
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.MassSyncEnabled = false;
|
||||
|
||||
// Assert
|
||||
sut.MassSyncEnabled.ShouldBeFalse();
|
||||
model.MassSyncIntervalMinutes.ShouldBeNull();
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DailySyncEnabled_ToggleOn_SetsDefaultInterval()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
model.DailySyncIntervalMinutes = null;
|
||||
var changedInvoked = false;
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.DailySyncEnabled = true;
|
||||
|
||||
// Assert
|
||||
sut.DailySyncEnabled.ShouldBeTrue();
|
||||
sut.DailySyncIntervalMinutes.ShouldBe(1440); // 1 day default
|
||||
model.DailySyncIntervalMinutes.ShouldBe(1440);
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HourlySyncEnabled_ToggleOn_SetsDefaultInterval()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
model.HourlySyncIntervalMinutes = null;
|
||||
var changedInvoked = false;
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.HourlySyncEnabled = true;
|
||||
|
||||
// Assert
|
||||
sut.HourlySyncEnabled.ShouldBeTrue();
|
||||
sut.HourlySyncIntervalMinutes.ShouldBe(60); // 1 hour default
|
||||
model.HourlySyncIntervalMinutes.ShouldBe(60);
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectedStep_Setter_DeselectsPreviousAndSelectsNew()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
var source = sut.Source;
|
||||
var destination = sut.Destination;
|
||||
|
||||
// Act - Select source
|
||||
sut.SelectedStep = source;
|
||||
|
||||
// Assert
|
||||
source.IsSelected.ShouldBeTrue();
|
||||
sut.SelectedStep.ShouldBe(source);
|
||||
|
||||
// Act - Select destination (should deselect source)
|
||||
sut.SelectedStep = destination;
|
||||
|
||||
// Assert
|
||||
source.IsSelected.ShouldBeFalse();
|
||||
destination.IsSelected.ShouldBeTrue();
|
||||
sut.SelectedStep.ShouldBe(destination);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDeleteSelectedStep_ReturnsFalse_ForSourceStep()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Act
|
||||
sut.SelectedStep = sut.Source;
|
||||
|
||||
// Assert
|
||||
sut.CanDeleteSelectedStep.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDeleteSelectedStep_ReturnsFalse_ForDestinationStep()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Act
|
||||
sut.SelectedStep = sut.Destination;
|
||||
|
||||
// Assert
|
||||
sut.CanDeleteSelectedStep.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDeleteSelectedStep_ReturnsTrue_ForTransformerStep()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Add a transformer
|
||||
sut.AddTransformerCommand.Execute(null);
|
||||
var transformer = sut.Transformers[0];
|
||||
|
||||
// Act
|
||||
sut.SelectedStep = transformer;
|
||||
|
||||
// Assert
|
||||
sut.CanDeleteSelectedStep.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDeleteSelectedStep_ReturnsTrue_ForPreScriptStep()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Add a pre-script
|
||||
sut.AddPreScriptCommand.Execute(null);
|
||||
var preScript = sut.PreScripts[0];
|
||||
|
||||
// Act
|
||||
sut.SelectedStep = preScript;
|
||||
|
||||
// Assert
|
||||
sut.CanDeleteSelectedStep.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDeleteSelectedStep_ReturnsTrue_ForPostScriptStep()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Add a post-script
|
||||
sut.AddPostScriptCommand.Execute(null);
|
||||
var postScript = sut.PostScripts[0];
|
||||
|
||||
// Act
|
||||
sut.SelectedStep = postScript;
|
||||
|
||||
// Assert
|
||||
sut.CanDeleteSelectedStep.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddPreScriptCommand_AddsPreScriptAndSelectsIt()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var changedInvoked = false;
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.AddPreScriptCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
sut.PreScripts.Count.ShouldBe(1);
|
||||
sut.SelectedStep.ShouldBe(sut.PreScripts[0]);
|
||||
sut.PreScripts[0].IsSelected.ShouldBeTrue();
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTransformerCommand_AddsTransformerAndSelectsIt()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var changedInvoked = false;
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.AddTransformerCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
sut.Transformers.Count.ShouldBe(1);
|
||||
sut.SelectedStep.ShouldBe(sut.Transformers[0]);
|
||||
sut.Transformers[0].IsSelected.ShouldBeTrue();
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddPostScriptCommand_AddsPostScriptAndSelectsIt()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var changedInvoked = false;
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.AddPostScriptCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
sut.PostScripts.Count.ShouldBe(1);
|
||||
sut.SelectedStep.ShouldBe(sut.PostScripts[0]);
|
||||
sut.PostScripts[0].IsSelected.ShouldBeTrue();
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveStepCommand_RemovesSelectedStep()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var changedInvoked = false;
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => changedInvoked = true);
|
||||
|
||||
// Add a transformer first
|
||||
sut.AddTransformerCommand.Execute(null);
|
||||
var transformer = sut.Transformers[0];
|
||||
changedInvoked = false;
|
||||
|
||||
// Act
|
||||
sut.RemoveStepCommand.Execute(transformer);
|
||||
|
||||
// Assert
|
||||
sut.Transformers.Count.ShouldBe(0);
|
||||
sut.SelectedStep.ShouldBeNull();
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveStepUpCommand_MovesStepUpInCollection()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Add two transformers
|
||||
sut.AddTransformerCommand.Execute(null);
|
||||
sut.AddTransformerCommand.Execute(null);
|
||||
var firstTransformer = sut.Transformers[0];
|
||||
var secondTransformer = sut.Transformers[1];
|
||||
|
||||
// Act - Move second up
|
||||
sut.MoveStepUpCommand.Execute(secondTransformer);
|
||||
|
||||
// Assert
|
||||
sut.Transformers[0].ShouldBe(secondTransformer);
|
||||
sut.Transformers[1].ShouldBe(firstTransformer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveStepDownCommand_MovesStepDownInCollection()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Add two transformers
|
||||
sut.AddTransformerCommand.Execute(null);
|
||||
sut.AddTransformerCommand.Execute(null);
|
||||
var firstTransformer = sut.Transformers[0];
|
||||
var secondTransformer = sut.Transformers[1];
|
||||
|
||||
// Act - Move first down
|
||||
sut.MoveStepDownCommand.Execute(firstTransformer);
|
||||
|
||||
// Assert
|
||||
sut.Transformers[0].ShouldBe(secondTransformer);
|
||||
sut.Transformers[1].ShouldBe(firstTransformer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveStepUpCommand_DoesNotMove_WhenAtTop()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Add two transformers
|
||||
sut.AddTransformerCommand.Execute(null);
|
||||
sut.AddTransformerCommand.Execute(null);
|
||||
var firstTransformer = sut.Transformers[0];
|
||||
var secondTransformer = sut.Transformers[1];
|
||||
|
||||
// Act - Try to move first up (should do nothing)
|
||||
sut.MoveStepUpCommand.Execute(firstTransformer);
|
||||
|
||||
// Assert
|
||||
sut.Transformers[0].ShouldBe(firstTransformer);
|
||||
sut.Transformers[1].ShouldBe(secondTransformer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveStepDownCommand_DoesNotMove_WhenAtBottom()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Add two transformers
|
||||
sut.AddTransformerCommand.Execute(null);
|
||||
sut.AddTransformerCommand.Execute(null);
|
||||
var firstTransformer = sut.Transformers[0];
|
||||
var secondTransformer = sut.Transformers[1];
|
||||
|
||||
// Act - Try to move second down (should do nothing)
|
||||
sut.MoveStepDownCommand.Execute(secondTransformer);
|
||||
|
||||
// Assert
|
||||
sut.Transformers[0].ShouldBe(firstTransformer);
|
||||
sut.Transformers[1].ShouldBe(secondTransformer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullName()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new PipelineEditorViewModel(null!, model, [], _dialogService, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullModel()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new PipelineEditorViewModel("Test", null!, [], _dialogService, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullDialogService()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new PipelineEditorViewModel("Test", model, [], null!, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new PipelineEditorViewModel("Test", model, [], _dialogService, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_LoadsExistingTransformers()
|
||||
{
|
||||
// Arrange
|
||||
var model = new EtlPipelineConfig
|
||||
{
|
||||
Source = new SourceElement { Connection = "jde", Query = "SELECT * FROM WO" },
|
||||
Destination = new DestinationElement { Table = "WorkOrder" },
|
||||
Transforms =
|
||||
[
|
||||
new TransformElement { TransformType = "ColumnDrop" },
|
||||
new TransformElement { TransformType = "ColumnRename" }
|
||||
]
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Assert
|
||||
sut.Transformers.Count.ShouldBe(2);
|
||||
sut.Transformers[0].ShouldBeOfType<ColumnDropTransformerViewModel>();
|
||||
sut.Transformers[1].ShouldBeOfType<ColumnRenameTransformerViewModel>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_LoadsExistingPreScripts()
|
||||
{
|
||||
// Arrange
|
||||
var model = new EtlPipelineConfig
|
||||
{
|
||||
Source = new SourceElement { Connection = "jde", Query = "SELECT * FROM WO" },
|
||||
Destination = new DestinationElement { Table = "WorkOrder" },
|
||||
PreScripts = [new ScriptElement { Connection = "lotfinder", Script = "TRUNCATE TABLE Test" }]
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Assert
|
||||
sut.PreScripts.Count.ShouldBe(1);
|
||||
sut.PreScripts[0].Script.ShouldBe("TRUNCATE TABLE Test");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_LoadsExistingPostScripts()
|
||||
{
|
||||
// Arrange
|
||||
var model = new EtlPipelineConfig
|
||||
{
|
||||
Source = new SourceElement { Connection = "jde", Query = "SELECT * FROM WO" },
|
||||
Destination = new DestinationElement { Table = "WorkOrder" },
|
||||
PostScripts = [new ScriptElement { Connection = "lotfinder", Script = "UPDATE Stats SET LastRun = GETDATE()" }]
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Assert
|
||||
sut.PostScripts.Count.ShouldBe(1);
|
||||
sut.PostScripts[0].Script.ShouldBe("UPDATE Stats SET LastRun = GETDATE()");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllSteps_ReturnsAllStepsInOrder()
|
||||
{
|
||||
// Arrange
|
||||
var model = new EtlPipelineConfig
|
||||
{
|
||||
Source = new SourceElement { Connection = "jde", Query = "SELECT * FROM WO" },
|
||||
Destination = new DestinationElement { Table = "WorkOrder" },
|
||||
PreScripts = [new ScriptElement { Script = "pre" }],
|
||||
Transforms = [new TransformElement { TransformType = "ColumnDrop" }],
|
||||
PostScripts = [new ScriptElement { Script = "post" }]
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
var allSteps = sut.AllSteps.ToList();
|
||||
|
||||
// Assert
|
||||
allSteps.Count.ShouldBe(5);
|
||||
allSteps[0].ShouldBeOfType<PreScriptStepViewModel>();
|
||||
allSteps[1].ShouldBeOfType<SourceStepViewModel>();
|
||||
allSteps[2].ShouldBeOfType<ColumnDropTransformerViewModel>();
|
||||
allSteps[3].ShouldBeOfType<DestinationStepViewModel>();
|
||||
allSteps[4].ShouldBeOfType<PostScriptStepViewModel>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectedStepEditor_UpdatesWhenSelectedStepChanges()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Act
|
||||
sut.SelectedStep = sut.Source;
|
||||
|
||||
// Assert
|
||||
sut.SelectedStepEditor.ShouldBe(sut.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTransformerOfType_AddsSpecificTransformerType()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Act
|
||||
sut.AddTransformerOfType("JdeDate");
|
||||
|
||||
// Assert
|
||||
sut.Transformers.Count.ShouldBe(1);
|
||||
sut.Transformers[0].ShouldBeOfType<JdeDateTransformerViewModel>();
|
||||
sut.SelectedStep.ShouldBe(sut.Transformers[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectedTransformerType_SetterAndGetter_Work()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Act
|
||||
sut.SelectedTransformerType = "ColumnRename";
|
||||
|
||||
// Assert
|
||||
sut.SelectedTransformerType.ShouldBe("ColumnRename");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AvailableTransformerTypes_ReturnsExpectedTypes()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateDefaultModel();
|
||||
var sut = new PipelineEditorViewModel("Test", model, [], _dialogService, () => { });
|
||||
|
||||
// Assert
|
||||
sut.AvailableTransformerTypes.ShouldContain("ColumnDrop");
|
||||
sut.AvailableTransformerTypes.ShouldContain("ColumnRename");
|
||||
sut.AvailableTransformerTypes.ShouldContain("JdeDate");
|
||||
sut.AvailableTransformerTypes.ShouldContain("Regex");
|
||||
}
|
||||
|
||||
private static EtlPipelineConfig CreateDefaultModel()
|
||||
{
|
||||
return new EtlPipelineConfig
|
||||
{
|
||||
Source = new SourceElement { Connection = string.Empty, Query = string.Empty },
|
||||
Destination = new DestinationElement { Table = string.Empty }
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using JdeScoping.ConfigManager.Constants;
|
||||
using JdeScoping.ConfigManager.Models;
|
||||
using JdeScoping.ConfigManager.Services;
|
||||
using JdeScoping.ConfigManager.Services.SecureStore;
|
||||
@@ -368,6 +369,401 @@ public class MainWindowViewModelTests
|
||||
sut.ConfigFolderPath.ShouldBe(originalPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveCommand_SavesAppSettings()
|
||||
{
|
||||
// Arrange
|
||||
var testFolderPath = "/test/config";
|
||||
var config = new ConfigModel();
|
||||
|
||||
_autoDiscoveryService.FindConfigFolderAsync().Returns((string?)null);
|
||||
|
||||
var sut = CreateViewModel();
|
||||
await Task.Delay(50);
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
// Simulate setting the config folder path and marking as changed
|
||||
var configFolderProperty = typeof(MainWindowViewModel).GetProperty("ConfigFolderPath");
|
||||
configFolderProperty!.SetValue(sut, testFolderPath);
|
||||
sut.HasUnsavedChanges = true;
|
||||
|
||||
// Act
|
||||
sut.SaveCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert - File.Exists is a static call so backup may not be called, but SaveAppSettings should be
|
||||
await _configFileService.Received().SaveAppSettingsAsync(Arg.Any<string>(), Arg.Any<ConfigModel>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveCommand_ResetsHasUnsavedChanges()
|
||||
{
|
||||
// Arrange
|
||||
var testFolderPath = Path.GetTempPath(); // Use real temp path so Directory.CreateDirectory works
|
||||
var config = new ConfigModel
|
||||
{
|
||||
Pipelines = new PipelinesSection { ConfigDirectory = "Pipelines" }
|
||||
};
|
||||
|
||||
_autoDiscoveryService.FindConfigFolderAsync().Returns((string?)null);
|
||||
|
||||
var sut = CreateViewModel();
|
||||
await Task.Delay(50);
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
var configFolderProperty = typeof(MainWindowViewModel).GetProperty("ConfigFolderPath");
|
||||
configFolderProperty!.SetValue(sut, testFolderPath);
|
||||
sut.HasUnsavedChanges = true;
|
||||
|
||||
// Act
|
||||
sut.SaveCommand.Execute(null);
|
||||
await Task.Delay(150);
|
||||
|
||||
// Assert - After successful save, HasUnsavedChanges should be false
|
||||
sut.HasUnsavedChanges.ShouldBeFalse();
|
||||
}
|
||||
|
||||
// Note: ValidateCommand tests are skipped because the Validate() method creates
|
||||
// Avalonia SolidColorBrush objects which require UI thread access. These tests
|
||||
// would need to use [AvaloniaFact] and run in the UI context, but the command
|
||||
// validation logic is covered by the ValidationServiceTests.
|
||||
|
||||
[Fact]
|
||||
public async Task AddSecretCommand_WhenStoreNotOpen_DoesNotShowDialog()
|
||||
{
|
||||
// Arrange
|
||||
_autoDiscoveryService.FindConfigFolderAsync().Returns((string?)null);
|
||||
_secureStoreManager.IsStoreOpen.Returns(false);
|
||||
|
||||
var sut = CreateViewModel();
|
||||
await Task.Delay(50);
|
||||
|
||||
// Act
|
||||
sut.AddSecretCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert - Command should not execute when store is not open
|
||||
await _dialogService.DidNotReceive().ShowMessageAsync(
|
||||
Arg.Is<string>(s => s == "Add Secret"),
|
||||
Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddSecretCommand_WhenStoreOpen_ShowsDialog()
|
||||
{
|
||||
// Arrange
|
||||
_autoDiscoveryService.FindConfigFolderAsync().Returns((string?)null);
|
||||
_secureStoreManager.IsStoreOpen.Returns(true);
|
||||
|
||||
var sut = CreateViewModel();
|
||||
await Task.Delay(50);
|
||||
|
||||
// Act
|
||||
sut.AddSecretCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
await _dialogService.Received().ShowMessageAsync(
|
||||
Arg.Is<string>(s => s == "Add Secret"),
|
||||
Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteSecretCommand_WhenConfirmed_DeletesSecret()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel
|
||||
{
|
||||
SecureStore = new SecureStoreSection
|
||||
{
|
||||
StorePath = "test.store",
|
||||
KeyFilePath = "test.key"
|
||||
}
|
||||
};
|
||||
|
||||
_autoDiscoveryService.FindConfigFolderAsync().Returns((string?)null);
|
||||
_secureStoreManager.IsStoreOpen.Returns(true);
|
||||
_secureStoreManager.GetKeys().Returns(new List<string> { "TestSecret" });
|
||||
_dialogService.ShowConfirmationAsync(Arg.Any<string>(), Arg.Any<string>())
|
||||
.Returns(true);
|
||||
|
||||
var sut = CreateViewModel();
|
||||
await Task.Delay(50);
|
||||
|
||||
// Set ConfigFolderPath first - required for SecureStore node to be built
|
||||
var configFolderProperty = typeof(MainWindowViewModel).GetProperty("ConfigFolderPath");
|
||||
configFolderProperty!.SetValue(sut, "/test/config");
|
||||
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
// Select the secret node
|
||||
var secureStoreNode = sut.TreeNodes.FirstOrDefault(n => n.NodeType == TreeNodeType.SecureStore);
|
||||
secureStoreNode.ShouldNotBeNull();
|
||||
var secretNode = secureStoreNode.Children.FirstOrDefault();
|
||||
secretNode.ShouldNotBeNull();
|
||||
sut.SelectedNode = secretNode;
|
||||
|
||||
// Act
|
||||
sut.DeleteSecretCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
_secureStoreManager.Received().RemoveSecret("TestSecret");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteSecretCommand_WhenCancelled_DoesNotDelete()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel
|
||||
{
|
||||
SecureStore = new SecureStoreSection
|
||||
{
|
||||
StorePath = "test.store",
|
||||
KeyFilePath = "test.key"
|
||||
}
|
||||
};
|
||||
|
||||
_autoDiscoveryService.FindConfigFolderAsync().Returns((string?)null);
|
||||
_secureStoreManager.IsStoreOpen.Returns(true);
|
||||
_secureStoreManager.GetKeys().Returns(new List<string> { "TestSecret" });
|
||||
_dialogService.ShowConfirmationAsync(Arg.Any<string>(), Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
var sut = CreateViewModel();
|
||||
await Task.Delay(50);
|
||||
|
||||
// Set ConfigFolderPath first - required for SecureStore node to be built
|
||||
var configFolderProperty = typeof(MainWindowViewModel).GetProperty("ConfigFolderPath");
|
||||
configFolderProperty!.SetValue(sut, "/test/config");
|
||||
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
// Select the secret node
|
||||
var secureStoreNode = sut.TreeNodes.FirstOrDefault(n => n.NodeType == TreeNodeType.SecureStore);
|
||||
secureStoreNode.ShouldNotBeNull();
|
||||
var secretNode = secureStoreNode.Children.FirstOrDefault();
|
||||
secretNode.ShouldNotBeNull();
|
||||
sut.SelectedNode = secretNode;
|
||||
|
||||
// Act
|
||||
sut.DeleteSecretCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
_secureStoreManager.DidNotReceive().RemoveSecret(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddPipelineCommand_ShowsDialog_AndAddsPipeline()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel();
|
||||
var pipelines = new Dictionary<string, EtlPipelineConfig>();
|
||||
|
||||
_autoDiscoveryService.FindConfigFolderAsync().Returns((string?)null);
|
||||
_dialogService.ShowInputDialogAsync(Arg.Any<string>(), Arg.Any<string>())
|
||||
.Returns("NewPipeline");
|
||||
|
||||
var sut = CreateViewModel();
|
||||
await Task.Delay(50);
|
||||
sut.LoadConfigForTesting(config, pipelines);
|
||||
|
||||
// Act
|
||||
sut.AddPipelineCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
await _dialogService.Received().ShowInputDialogAsync("New Pipeline", "Enter pipeline name:");
|
||||
var pipelinesFolder = sut.TreeNodes.FirstOrDefault(n => n.Name == "Pipelines");
|
||||
pipelinesFolder.ShouldNotBeNull();
|
||||
pipelinesFolder.Children.Any(c => c.Name == "NewPipeline").ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddPipelineCommand_WithDuplicateName_ShowsError()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel();
|
||||
var pipelines = new Dictionary<string, EtlPipelineConfig>
|
||||
{
|
||||
["ExistingPipeline"] = new EtlPipelineConfig
|
||||
{
|
||||
Name = "ExistingPipeline",
|
||||
Source = new SourceElement { Connection = "jde", Query = "SELECT 1" },
|
||||
Destination = new DestinationElement { Table = "TestTable" }
|
||||
}
|
||||
};
|
||||
|
||||
_autoDiscoveryService.FindConfigFolderAsync().Returns((string?)null);
|
||||
_dialogService.ShowInputDialogAsync(Arg.Any<string>(), Arg.Any<string>())
|
||||
.Returns("ExistingPipeline");
|
||||
|
||||
var sut = CreateViewModel();
|
||||
await Task.Delay(50);
|
||||
sut.LoadConfigForTesting(config, pipelines);
|
||||
|
||||
// Act
|
||||
sut.AddPipelineCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
await _dialogService.Received().ShowMessageAsync(
|
||||
"Error",
|
||||
"Pipeline 'ExistingPipeline' already exists.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeletePipelineCommand_WhenConfirmed_RemovesPipelineFromTree()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel
|
||||
{
|
||||
Pipelines = new PipelinesSection { ConfigDirectory = "Pipelines" }
|
||||
};
|
||||
var pipelines = new Dictionary<string, EtlPipelineConfig>
|
||||
{
|
||||
["TestPipeline"] = new EtlPipelineConfig
|
||||
{
|
||||
Name = "TestPipeline",
|
||||
Source = new SourceElement { Connection = "jde", Query = "SELECT 1" },
|
||||
Destination = new DestinationElement { Table = "TestTable" }
|
||||
}
|
||||
};
|
||||
|
||||
_autoDiscoveryService.FindConfigFolderAsync().Returns((string?)null);
|
||||
_dialogService.ShowConfirmationAsync(Arg.Any<string>(), Arg.Any<string>())
|
||||
.Returns(true);
|
||||
|
||||
var sut = CreateViewModel();
|
||||
await Task.Delay(50);
|
||||
sut.LoadConfigForTesting(config, pipelines);
|
||||
|
||||
var configFolderProperty = typeof(MainWindowViewModel).GetProperty("ConfigFolderPath");
|
||||
configFolderProperty!.SetValue(sut, "/test/config");
|
||||
|
||||
// Select the pipeline node
|
||||
var pipelineNode = sut.TreeNodes
|
||||
.SelectMany(n => n.Children)
|
||||
.First(n => n.SectionKey == "TestPipeline");
|
||||
sut.SelectedNode = pipelineNode;
|
||||
|
||||
// Act
|
||||
sut.DeletePipelineCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
await _dialogService.Received().ShowConfirmationAsync(
|
||||
"Delete Pipeline",
|
||||
"Are you sure you want to delete pipeline 'TestPipeline'?");
|
||||
// Pipeline should be removed from tree
|
||||
var pipelinesFolder = sut.TreeNodes.First(n => n.Name == "Pipelines");
|
||||
pipelinesFolder.Children.Any(c => c.SectionKey == "TestPipeline").ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeletePipelineCommand_WhenCancelled_DoesNotDelete()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel();
|
||||
var pipelines = new Dictionary<string, EtlPipelineConfig>
|
||||
{
|
||||
["TestPipeline"] = new EtlPipelineConfig
|
||||
{
|
||||
Name = "TestPipeline",
|
||||
Source = new SourceElement { Connection = "jde", Query = "SELECT 1" },
|
||||
Destination = new DestinationElement { Table = "TestTable" }
|
||||
}
|
||||
};
|
||||
|
||||
_autoDiscoveryService.FindConfigFolderAsync().Returns((string?)null);
|
||||
_dialogService.ShowConfirmationAsync(Arg.Any<string>(), Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
var sut = CreateViewModel();
|
||||
await Task.Delay(50);
|
||||
sut.LoadConfigForTesting(config, pipelines);
|
||||
|
||||
// Select the pipeline node
|
||||
var pipelineNode = sut.TreeNodes
|
||||
.SelectMany(n => n.Children)
|
||||
.First(n => n.SectionKey == "TestPipeline");
|
||||
sut.SelectedNode = pipelineNode;
|
||||
var originalChildCount = sut.TreeNodes
|
||||
.First(n => n.Name == "Pipelines").Children.Count;
|
||||
|
||||
// Act
|
||||
sut.DeletePipelineCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
await _configFileService.DidNotReceive().DeletePipelineFileAsync(Arg.Any<string>());
|
||||
sut.TreeNodes.First(n => n.Name == "Pipelines").Children.Count.ShouldBe(originalChildCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateRuntimeConfigCommand_CallsRuntimeValidationService()
|
||||
{
|
||||
// Arrange
|
||||
var testFolderPath = "/test/config";
|
||||
var config = new ConfigModel();
|
||||
|
||||
_autoDiscoveryService.FindConfigFolderAsync().Returns((string?)null);
|
||||
_runtimeValidationService.ValidateRuntimeConfig(Arg.Any<string>())
|
||||
.Returns(new List<RuntimeValidationResult>());
|
||||
|
||||
var sut = CreateViewModel();
|
||||
await Task.Delay(50);
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
var configFolderProperty = typeof(MainWindowViewModel).GetProperty("ConfigFolderPath");
|
||||
configFolderProperty!.SetValue(sut, testFolderPath);
|
||||
|
||||
// Act
|
||||
sut.ValidateRuntimeConfigCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
_runtimeValidationService.Received().ValidateRuntimeConfig(testFolderPath);
|
||||
await _dialogService.Received().ShowValidationResultsAsync(
|
||||
Arg.Any<ValidationResult>(),
|
||||
Arg.Any<ValidationResult>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateRuntimeConfigCommand_WithErrors_ShowsErrorsInDialog()
|
||||
{
|
||||
// Arrange
|
||||
var testFolderPath = "/test/config";
|
||||
var config = new ConfigModel();
|
||||
|
||||
var runtimeResult = new RuntimeValidationResult
|
||||
{
|
||||
ValidatorName = "TestValidator"
|
||||
};
|
||||
runtimeResult.Errors.Add("Test runtime error");
|
||||
|
||||
_autoDiscoveryService.FindConfigFolderAsync().Returns((string?)null);
|
||||
_runtimeValidationService.ValidateRuntimeConfig(Arg.Any<string>())
|
||||
.Returns(new List<RuntimeValidationResult> { runtimeResult });
|
||||
|
||||
var sut = CreateViewModel();
|
||||
await Task.Delay(50);
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
var configFolderProperty = typeof(MainWindowViewModel).GetProperty("ConfigFolderPath");
|
||||
configFolderProperty!.SetValue(sut, testFolderPath);
|
||||
|
||||
// Act
|
||||
sut.ValidateRuntimeConfigCommand.Execute(null);
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
await _dialogService.Received().ShowValidationResultsAsync(
|
||||
Arg.Is<ValidationResult>(r => r.Errors.Count > 0),
|
||||
Arg.Any<ValidationResult>());
|
||||
}
|
||||
|
||||
private MainWindowViewModel CreateViewModel()
|
||||
{
|
||||
return new MainWindowViewModel(
|
||||
|
||||
+756
@@ -0,0 +1,756 @@
|
||||
using System.Text.Json;
|
||||
using JdeScoping.ConfigManager.ViewModels.PipelineSteps;
|
||||
using JdeScoping.DataSync.Configuration;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Tests.ViewModels.PipelineSteps;
|
||||
|
||||
public class ColumnDropTransformerViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WithElement_ParsesColumnsFromConfig()
|
||||
{
|
||||
// Arrange
|
||||
var element = CreateColumnDropElement("Col1", "Col2", "Col3");
|
||||
|
||||
// Act
|
||||
var sut = new ColumnDropTransformerViewModel(element, () => { });
|
||||
|
||||
// Assert
|
||||
var columns = sut.GetColumns();
|
||||
columns.ShouldContain("Col1");
|
||||
columns.ShouldContain("Col2");
|
||||
columns.ShouldContain("Col3");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithEmptyElement_InitializesEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var element = new TransformElement { TransformType = "ColumnDrop" };
|
||||
|
||||
// Act
|
||||
var sut = new ColumnDropTransformerViewModel(element, () => { });
|
||||
|
||||
// Assert
|
||||
sut.GetColumns().ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_Default_InitializesEmpty()
|
||||
{
|
||||
// Act
|
||||
var sut = new ColumnDropTransformerViewModel(() => { });
|
||||
|
||||
// Assert
|
||||
sut.GetColumns().ShouldBeEmpty();
|
||||
sut.ColumnsText.ShouldBe(string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ColumnsText_Setter_InvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var onChangedCalled = false;
|
||||
var sut = new ColumnDropTransformerViewModel(() => onChangedCalled = true);
|
||||
|
||||
// Act
|
||||
sut.ColumnsText = "Column1\nColumn2";
|
||||
|
||||
// Assert
|
||||
onChangedCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetColumns_ReturnsNewlineSeparatedColumns()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnDropTransformerViewModel(() => { });
|
||||
sut.ColumnsText = "Col1\nCol2\nCol3";
|
||||
|
||||
// Act
|
||||
var columns = sut.GetColumns();
|
||||
|
||||
// Assert
|
||||
columns.Count.ShouldBe(3);
|
||||
columns[0].ShouldBe("Col1");
|
||||
columns[1].ShouldBe("Col2");
|
||||
columns[2].ShouldBe("Col3");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetColumns_TrimsWhitespace()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnDropTransformerViewModel(() => { });
|
||||
sut.ColumnsText = " Col1 \n Col2 ";
|
||||
|
||||
// Act
|
||||
var columns = sut.GetColumns();
|
||||
|
||||
// Assert
|
||||
columns[0].ShouldBe("Col1");
|
||||
columns[1].ShouldBe("Col2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetColumns_IgnoresEmptyLines()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnDropTransformerViewModel(() => { });
|
||||
sut.ColumnsText = "Col1\n\n\nCol2";
|
||||
|
||||
// Act
|
||||
var columns = sut.GetColumns();
|
||||
|
||||
// Assert
|
||||
columns.Count.ShouldBe(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToModel_ReturnsCorrectTransformElement()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnDropTransformerViewModel(() => { });
|
||||
sut.ColumnsText = "DropMe\nAlsoDropMe";
|
||||
|
||||
// Act
|
||||
var model = sut.ToModel();
|
||||
|
||||
// Assert
|
||||
model.TransformType.ShouldBe("ColumnDrop");
|
||||
model.Config.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TransformerType_ReturnsColumnDrop()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnDropTransformerViewModel(() => { });
|
||||
|
||||
// Assert
|
||||
sut.TransformerType.ShouldBe("ColumnDrop");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayName_ReturnsColumnDrop()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnDropTransformerViewModel(() => { });
|
||||
|
||||
// Assert
|
||||
sut.DisplayName.ShouldBe("Column Drop");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Summary_WithColumns_ShowsCount()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnDropTransformerViewModel(() => { });
|
||||
sut.ColumnsText = "Col1\nCol2\nCol3";
|
||||
|
||||
// Assert
|
||||
sut.Summary.ShouldBe("Drop 3 columns");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Summary_WithNoColumns_ShowsNoColumns()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnDropTransformerViewModel(() => { });
|
||||
|
||||
// Assert
|
||||
sut.Summary.ShouldBe("No columns");
|
||||
}
|
||||
|
||||
private static TransformElement CreateColumnDropElement(params string[] columns)
|
||||
{
|
||||
var config = new { columns };
|
||||
var json = JsonSerializer.Serialize(config, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
return new TransformElement
|
||||
{
|
||||
TransformType = "ColumnDrop",
|
||||
Config = doc.RootElement.Clone()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class ColumnRenameTransformerViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WithElement_ParsesMappingsFromConfig()
|
||||
{
|
||||
// Arrange
|
||||
var element = CreateColumnRenameElement(("OldName", "NewName"), ("Source", "Target"));
|
||||
|
||||
// Act
|
||||
var sut = new ColumnRenameTransformerViewModel(element, () => { });
|
||||
|
||||
// Assert
|
||||
sut.Mappings.Count.ShouldBe(2);
|
||||
sut.Mappings.ShouldContain(m => m.OldName == "OldName" && m.NewName == "NewName");
|
||||
sut.Mappings.ShouldContain(m => m.OldName == "Source" && m.NewName == "Target");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithEmptyElement_InitializesEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var element = new TransformElement { TransformType = "ColumnRename" };
|
||||
|
||||
// Act
|
||||
var sut = new ColumnRenameTransformerViewModel(element, () => { });
|
||||
|
||||
// Assert
|
||||
sut.Mappings.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_Default_InitializesEmpty()
|
||||
{
|
||||
// Act
|
||||
var sut = new ColumnRenameTransformerViewModel(() => { });
|
||||
|
||||
// Assert
|
||||
sut.Mappings.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMapping_AddsNewMapping()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnRenameTransformerViewModel(() => { });
|
||||
|
||||
// Act
|
||||
sut.AddMapping();
|
||||
|
||||
// Assert
|
||||
sut.Mappings.Count.ShouldBe(1);
|
||||
sut.Mappings[0].OldName.ShouldBe("");
|
||||
sut.Mappings[0].NewName.ShouldBe("");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMapping_InvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var onChangedCalled = false;
|
||||
var sut = new ColumnRenameTransformerViewModel(() => onChangedCalled = true);
|
||||
|
||||
// Act
|
||||
sut.AddMapping();
|
||||
|
||||
// Assert
|
||||
onChangedCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveMapping_RemovesMapping()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnRenameTransformerViewModel(() => { });
|
||||
sut.AddMapping();
|
||||
var mapping = sut.Mappings[0];
|
||||
|
||||
// Act
|
||||
sut.RemoveMapping(mapping);
|
||||
|
||||
// Assert
|
||||
sut.Mappings.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveMapping_InvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var onChangedCount = 0;
|
||||
var sut = new ColumnRenameTransformerViewModel(() => onChangedCount++);
|
||||
sut.AddMapping();
|
||||
var mapping = sut.Mappings[0];
|
||||
onChangedCount = 0;
|
||||
|
||||
// Act
|
||||
sut.RemoveMapping(mapping);
|
||||
|
||||
// Assert
|
||||
onChangedCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToModel_ReturnsCorrectTransformElement()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnRenameTransformerViewModel(() => { });
|
||||
sut.AddMapping();
|
||||
sut.Mappings[0].OldName = "OldCol";
|
||||
sut.Mappings[0].NewName = "NewCol";
|
||||
|
||||
// Act
|
||||
var model = sut.ToModel();
|
||||
|
||||
// Assert
|
||||
model.TransformType.ShouldBe("ColumnRename");
|
||||
model.Config.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TransformerType_ReturnsColumnRename()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnRenameTransformerViewModel(() => { });
|
||||
|
||||
// Assert
|
||||
sut.TransformerType.ShouldBe("ColumnRename");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayName_ReturnsColumnRename()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnRenameTransformerViewModel(() => { });
|
||||
|
||||
// Assert
|
||||
sut.DisplayName.ShouldBe("Column Rename");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Summary_WithMappings_ShowsCount()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnRenameTransformerViewModel(() => { });
|
||||
sut.AddMapping();
|
||||
sut.AddMapping();
|
||||
|
||||
// Assert
|
||||
sut.Summary.ShouldBe("Rename 2 columns");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Summary_WithNoMappings_ShowsNoMappings()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnRenameTransformerViewModel(() => { });
|
||||
|
||||
// Assert
|
||||
sut.Summary.ShouldBe("No mappings");
|
||||
}
|
||||
|
||||
private static TransformElement CreateColumnRenameElement(params (string oldName, string newName)[] mappings)
|
||||
{
|
||||
var dict = mappings.ToDictionary(m => m.oldName, m => m.newName);
|
||||
var config = new { mappings = dict };
|
||||
var json = JsonSerializer.Serialize(config, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
return new TransformElement
|
||||
{
|
||||
TransformType = "ColumnRename",
|
||||
Config = doc.RootElement.Clone()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class ColumnMappingViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_SetsProperties()
|
||||
{
|
||||
// Act
|
||||
var sut = new ColumnMappingViewModel("OldName", "NewName", () => { });
|
||||
|
||||
// Assert
|
||||
sut.OldName.ShouldBe("OldName");
|
||||
sut.NewName.ShouldBe("NewName");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OldName_Setter_InvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var onChangedCalled = false;
|
||||
var sut = new ColumnMappingViewModel("Old", "New", () => onChangedCalled = true);
|
||||
|
||||
// Act
|
||||
sut.OldName = "Updated";
|
||||
|
||||
// Assert
|
||||
onChangedCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NewName_Setter_InvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var onChangedCalled = false;
|
||||
var sut = new ColumnMappingViewModel("Old", "New", () => onChangedCalled = true);
|
||||
|
||||
// Act
|
||||
sut.NewName = "Updated";
|
||||
|
||||
// Assert
|
||||
onChangedCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OldName_Setter_HandlesNull()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnMappingViewModel("Old", "New", () => { });
|
||||
|
||||
// Act
|
||||
sut.OldName = null!;
|
||||
|
||||
// Assert
|
||||
sut.OldName.ShouldBe(string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NewName_Setter_HandlesNull()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new ColumnMappingViewModel("Old", "New", () => { });
|
||||
|
||||
// Act
|
||||
sut.NewName = null!;
|
||||
|
||||
// Assert
|
||||
sut.NewName.ShouldBe(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public class JdeDateTransformerViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WithElement_ParsesPropertiesFromConfig()
|
||||
{
|
||||
// Arrange
|
||||
var element = CreateJdeDateElement("WADDJ", "WADTM", "CompletionDate");
|
||||
|
||||
// Act
|
||||
var sut = new JdeDateTransformerViewModel(element, () => { });
|
||||
|
||||
// Assert
|
||||
sut.DateColumn.ShouldBe("WADDJ");
|
||||
sut.TimeColumn.ShouldBe("WADTM");
|
||||
sut.OutputColumn.ShouldBe("CompletionDate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithEmptyElement_InitializesNull()
|
||||
{
|
||||
// Arrange
|
||||
var element = new TransformElement { TransformType = "JdeDate" };
|
||||
|
||||
// Act
|
||||
var sut = new JdeDateTransformerViewModel(element, () => { });
|
||||
|
||||
// Assert
|
||||
sut.DateColumn.ShouldBeNull();
|
||||
sut.TimeColumn.ShouldBeNull();
|
||||
sut.OutputColumn.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_Default_InitializesNull()
|
||||
{
|
||||
// Act
|
||||
var sut = new JdeDateTransformerViewModel(() => { });
|
||||
|
||||
// Assert
|
||||
sut.DateColumn.ShouldBeNull();
|
||||
sut.TimeColumn.ShouldBeNull();
|
||||
sut.OutputColumn.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DateColumn_Setter_InvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var onChangedCalled = false;
|
||||
var sut = new JdeDateTransformerViewModel(() => onChangedCalled = true);
|
||||
|
||||
// Act
|
||||
sut.DateColumn = "WADDJ";
|
||||
|
||||
// Assert
|
||||
onChangedCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TimeColumn_Setter_InvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var onChangedCalled = false;
|
||||
var sut = new JdeDateTransformerViewModel(() => onChangedCalled = true);
|
||||
|
||||
// Act
|
||||
sut.TimeColumn = "WADTM";
|
||||
|
||||
// Assert
|
||||
onChangedCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OutputColumn_Setter_InvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var onChangedCalled = false;
|
||||
var sut = new JdeDateTransformerViewModel(() => onChangedCalled = true);
|
||||
|
||||
// Act
|
||||
sut.OutputColumn = "ResultDate";
|
||||
|
||||
// Assert
|
||||
onChangedCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToModel_ReturnsCorrectTransformElement()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new JdeDateTransformerViewModel(() => { });
|
||||
sut.DateColumn = "DateCol";
|
||||
sut.TimeColumn = "TimeCol";
|
||||
sut.OutputColumn = "Output";
|
||||
|
||||
// Act
|
||||
var model = sut.ToModel();
|
||||
|
||||
// Assert
|
||||
model.TransformType.ShouldBe("JdeDate");
|
||||
model.Config.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TransformerType_ReturnsJdeDate()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new JdeDateTransformerViewModel(() => { });
|
||||
|
||||
// Assert
|
||||
sut.TransformerType.ShouldBe("JdeDate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayName_ReturnsJdeDateConvert()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new JdeDateTransformerViewModel(() => { });
|
||||
|
||||
// Assert
|
||||
sut.DisplayName.ShouldBe("JDE Date Convert");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Summary_WithOutputColumn_ShowsOutputColumn()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new JdeDateTransformerViewModel(() => { });
|
||||
sut.OutputColumn = "CompletionDate";
|
||||
|
||||
// Assert
|
||||
sut.Summary.ShouldContain("CompletionDate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Summary_WithNoOutputColumn_ShowsConfigure()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new JdeDateTransformerViewModel(() => { });
|
||||
|
||||
// Assert
|
||||
sut.Summary.ShouldBe("Configure...");
|
||||
}
|
||||
|
||||
private static TransformElement CreateJdeDateElement(string? dateColumn, string? timeColumn, string? outputColumn)
|
||||
{
|
||||
var config = new { dateColumn, timeColumn, outputColumn };
|
||||
var json = JsonSerializer.Serialize(config, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
return new TransformElement
|
||||
{
|
||||
TransformType = "JdeDate",
|
||||
Config = doc.RootElement.Clone()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class TransformerFactoryTests
|
||||
{
|
||||
[Fact]
|
||||
public void Create_ColumnDrop_ReturnsColumnDropViewModel()
|
||||
{
|
||||
// Arrange
|
||||
var element = new TransformElement { TransformType = "ColumnDrop" };
|
||||
|
||||
// Act
|
||||
var result = TransformerFactory.Create(element, () => { });
|
||||
|
||||
// Assert
|
||||
result.ShouldBeOfType<ColumnDropTransformerViewModel>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_ColumnRename_ReturnsColumnRenameViewModel()
|
||||
{
|
||||
// Arrange
|
||||
var element = new TransformElement { TransformType = "ColumnRename" };
|
||||
|
||||
// Act
|
||||
var result = TransformerFactory.Create(element, () => { });
|
||||
|
||||
// Assert
|
||||
result.ShouldBeOfType<ColumnRenameTransformerViewModel>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_JdeDate_ReturnsJdeDateViewModel()
|
||||
{
|
||||
// Arrange
|
||||
var element = new TransformElement { TransformType = "JdeDate" };
|
||||
|
||||
// Act
|
||||
var result = TransformerFactory.Create(element, () => { });
|
||||
|
||||
// Assert
|
||||
result.ShouldBeOfType<JdeDateTransformerViewModel>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_Regex_ReturnsRegexViewModel()
|
||||
{
|
||||
// Arrange
|
||||
var element = new TransformElement { TransformType = "Regex" };
|
||||
|
||||
// Act
|
||||
var result = TransformerFactory.Create(element, () => { });
|
||||
|
||||
// Assert
|
||||
result.ShouldBeOfType<RegexTransformerViewModel>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_CaseInsensitive_WorksCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var element = new TransformElement { TransformType = "COLUMNDROP" };
|
||||
|
||||
// Act
|
||||
var result = TransformerFactory.Create(element, () => { });
|
||||
|
||||
// Assert
|
||||
result.ShouldBeOfType<ColumnDropTransformerViewModel>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_UnknownType_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var element = new TransformElement { TransformType = "UnknownType" };
|
||||
|
||||
// Act
|
||||
var result = TransformerFactory.Create(element, () => { });
|
||||
|
||||
// Assert
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_NullType_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var element = new TransformElement { TransformType = null };
|
||||
|
||||
// Act
|
||||
var result = TransformerFactory.Create(element, () => { });
|
||||
|
||||
// Assert
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateNew_ColumnDrop_ReturnsNewColumnDropViewModel()
|
||||
{
|
||||
// Act
|
||||
var result = TransformerFactory.CreateNew("ColumnDrop", () => { });
|
||||
|
||||
// Assert
|
||||
result.ShouldBeOfType<ColumnDropTransformerViewModel>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateNew_ColumnRename_ReturnsNewColumnRenameViewModel()
|
||||
{
|
||||
// Act
|
||||
var result = TransformerFactory.CreateNew("ColumnRename", () => { });
|
||||
|
||||
// Assert
|
||||
result.ShouldBeOfType<ColumnRenameTransformerViewModel>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateNew_JdeDate_ReturnsNewJdeDateViewModel()
|
||||
{
|
||||
// Act
|
||||
var result = TransformerFactory.CreateNew("JdeDate", () => { });
|
||||
|
||||
// Assert
|
||||
result.ShouldBeOfType<JdeDateTransformerViewModel>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateNew_Regex_ReturnsNewRegexViewModel()
|
||||
{
|
||||
// Act
|
||||
var result = TransformerFactory.CreateNew("Regex", () => { });
|
||||
|
||||
// Assert
|
||||
result.ShouldBeOfType<RegexTransformerViewModel>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateNew_CaseInsensitive_WorksCorrectly()
|
||||
{
|
||||
// Act
|
||||
var result = TransformerFactory.CreateNew("columndrop", () => { });
|
||||
|
||||
// Assert
|
||||
result.ShouldBeOfType<ColumnDropTransformerViewModel>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateNew_UnknownType_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
var result = TransformerFactory.CreateNew("UnknownType", () => { });
|
||||
|
||||
// Assert
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateNew_NullType_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
var result = TransformerFactory.CreateNew(null!, () => { });
|
||||
|
||||
// Assert
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AvailableTypes_ContainsExpectedTypes()
|
||||
{
|
||||
// Assert
|
||||
TransformerFactory.AvailableTypes.ShouldContain("ColumnDrop");
|
||||
TransformerFactory.AvailableTypes.ShouldContain("ColumnRename");
|
||||
TransformerFactory.AvailableTypes.ShouldContain("JdeDate");
|
||||
TransformerFactory.AvailableTypes.ShouldContain("Regex");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AvailableTypes_HasFourTypes()
|
||||
{
|
||||
// Assert
|
||||
TransformerFactory.AvailableTypes.Count.ShouldBe(4);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user