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,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 }
};
}
}