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:
Joseph Doherty
2026-01-27 07:24:55 -05:00
parent 227a749cdf
commit 937eb66ac8
14 changed files with 4053 additions and 62 deletions
@@ -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("");
}
}
@@ -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");
}
}
@@ -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");
}
}
@@ -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(
@@ -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);
}
}