refactor(configmanager): rename UI project and split test projects
Rename ConfigManager to ConfigManager.Ui to match the Core/CLI/UI project structure, and split the monolithic test project into Core.Tests, Cli.Tests, and Ui.Tests to align with the source project organization.
This commit is contained in:
+241
@@ -0,0 +1,241 @@
|
||||
using JdeScoping.ConfigManager.Core.Services;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Dialogs;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.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("");
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
using JdeScoping.ConfigManager.Core.Constants;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Dialogs;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels.Dialogs;
|
||||
|
||||
public class NewStoreDialogViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void StorePath_WhenEmpty_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
StorePath = "",
|
||||
KeyFilePath = "/path/to/key.key"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StorePath_WhenEmpty_ValidationErrorReturnsStorePathRequired()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
StorePath = "",
|
||||
KeyFilePath = "/path/to/key.key"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.StorePathRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StorePath_WhenWhitespace_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
StorePath = " ",
|
||||
KeyFilePath = "/path/to/key.key"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.StorePathRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenEmpty_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
StorePath = "/path/to/store.secure",
|
||||
KeyFilePath = ""
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenEmpty_ValidationErrorReturnsKeyFilePathRequired()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
StorePath = "/path/to/store.secure",
|
||||
KeyFilePath = ""
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.KeyFilePathRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenProvided_IsValidReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
StorePath = "/path/to/store.secure",
|
||||
KeyFilePath = "/path/to/key.key"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeTrue();
|
||||
sut.ValidationError.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StorePath_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(NewStoreDialogViewModel.StorePath))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.StorePath = "/new/path";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StorePath_WhenChanged_RaisesIsValidPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
var isValidPropertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(NewStoreDialogViewModel.IsValid))
|
||||
isValidPropertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.StorePath = "/new/path";
|
||||
|
||||
// Assert
|
||||
isValidPropertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StorePath_WhenChanged_RaisesValidationErrorPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
var validationErrorPropertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(NewStoreDialogViewModel.ValidationError))
|
||||
validationErrorPropertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.StorePath = "/new/path";
|
||||
|
||||
// Assert
|
||||
validationErrorPropertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(NewStoreDialogViewModel.KeyFilePath))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.KeyFilePath = "/new/key/path";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Commands_AreInitialized()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
|
||||
// Assert
|
||||
sut.BrowseStorePathCommand.ShouldNotBeNull();
|
||||
sut.BrowseKeyFilePathCommand.ShouldNotBeNull();
|
||||
sut.GenerateKeyFileCommand.ShouldNotBeNull();
|
||||
}
|
||||
}
|
||||
+343
@@ -0,0 +1,343 @@
|
||||
using JdeScoping.ConfigManager.Core.Constants;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Dialogs;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels.Dialogs;
|
||||
|
||||
public class SecretEditDialogViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void DefaultConstructor_SetsIsNewSecretToTrue()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
|
||||
// Assert
|
||||
sut.IsNewSecret.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultConstructor_SetsKeyToEmpty()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
|
||||
// Assert
|
||||
sut.Key.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultConstructor_SetsValueToEmpty()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
|
||||
// Assert
|
||||
sut.Value.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EditConstructor_SetsKeyFromParameter()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretEditDialogViewModel("myKey", "myValue");
|
||||
|
||||
// Assert
|
||||
sut.Key.ShouldBe("myKey");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EditConstructor_SetsValueFromParameter()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretEditDialogViewModel("myKey", "myValue");
|
||||
|
||||
// Assert
|
||||
sut.Value.ShouldBe("myValue");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EditConstructor_SetsIsNewSecretToFalse()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretEditDialogViewModel("myKey", "myValue");
|
||||
|
||||
// Assert
|
||||
sut.IsNewSecret.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Key_WhenEmpty_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel
|
||||
{
|
||||
Key = ""
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Key_WhenEmpty_ValidationErrorReturnsKeyRequired()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel
|
||||
{
|
||||
Key = ""
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.KeyRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Key_WhenWhitespace_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel
|
||||
{
|
||||
Key = " "
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.KeyRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Key_WhenProvided_IsValidReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel
|
||||
{
|
||||
Key = "validKey"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeTrue();
|
||||
sut.ValidationError.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsKeyEditable_WhenIsNewSecretIsTrue_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
|
||||
// Act & Assert
|
||||
sut.IsKeyEditable.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsKeyEditable_WhenIsNewSecretIsFalse_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel("existingKey", "existingValue");
|
||||
|
||||
// Act & Assert
|
||||
sut.IsKeyEditable.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DialogTitle_WhenIsNewSecretIsTrue_ReturnsAddSecret()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
|
||||
// Act & Assert
|
||||
sut.DialogTitle.ShouldBe("Add Secret");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DialogTitle_WhenIsNewSecretIsFalse_ReturnsEditSecret()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel("existingKey", "existingValue");
|
||||
|
||||
// Act & Assert
|
||||
sut.DialogTitle.ShouldBe("Edit Secret");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsNewSecret_WhenChanged_RaisesIsKeyEditablePropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
var isKeyEditablePropertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretEditDialogViewModel.IsKeyEditable))
|
||||
isKeyEditablePropertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.IsNewSecret = false;
|
||||
|
||||
// Assert
|
||||
isKeyEditablePropertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsNewSecret_WhenChanged_RaisesDialogTitlePropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
var dialogTitlePropertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretEditDialogViewModel.DialogTitle))
|
||||
dialogTitlePropertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.IsNewSecret = false;
|
||||
|
||||
// Assert
|
||||
dialogTitlePropertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Key_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretEditDialogViewModel.Key))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.Key = "newKey";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Key_WhenChanged_RaisesIsValidPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
var isValidPropertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretEditDialogViewModel.IsValid))
|
||||
isValidPropertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.Key = "newKey";
|
||||
|
||||
// Assert
|
||||
isValidPropertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Key_WhenChanged_RaisesValidationErrorPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
var validationErrorPropertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretEditDialogViewModel.ValidationError))
|
||||
validationErrorPropertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.Key = "newKey";
|
||||
|
||||
// Assert
|
||||
validationErrorPropertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Value_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretEditDialogViewModel.Value))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.Value = "newValue";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Value_CanBeEmpty_IsValidStillReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel
|
||||
{
|
||||
Key = "validKey",
|
||||
Value = ""
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeTrue();
|
||||
sut.ValidationError.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Value_CanBeNull_IsValidStillReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel
|
||||
{
|
||||
Key = "validKey",
|
||||
Value = null!
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeTrue();
|
||||
sut.ValidationError.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsNewSecret_WhenSetToSameValue_DoesNotRaisePropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
var propertyChangedCount = 0;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretEditDialogViewModel.IsNewSecret))
|
||||
propertyChangedCount++;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.IsNewSecret = true; // Same value as default
|
||||
|
||||
// Assert
|
||||
propertyChangedCount.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Key_WhenSetToSameValue_DoesNotRaisePropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel { Key = "testKey" };
|
||||
var propertyChangedCount = 0;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretEditDialogViewModel.Key))
|
||||
propertyChangedCount++;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.Key = "testKey"; // Same value
|
||||
|
||||
// Assert
|
||||
propertyChangedCount.ShouldBe(0);
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
using JdeScoping.ConfigManager.Core.Constants;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Dialogs;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels.Dialogs;
|
||||
|
||||
public class UnlockStoreDialogViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_SetsStorePathFromParameter()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = "/path/to/store.secure";
|
||||
|
||||
// Act
|
||||
var sut = new UnlockStoreDialogViewModel(storePath);
|
||||
|
||||
// Assert
|
||||
sut.StorePath.ShouldBe(storePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullStorePath()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new UnlockStoreDialogViewModel(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StorePath_IsReadOnly()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
|
||||
// Assert - StorePath property has no setter, verified by checking it returns what was passed
|
||||
sut.StorePath.ShouldBe("/path/to/store.secure");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenEmpty_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
KeyFilePath = ""
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenEmpty_ValidationErrorReturnsKeyFilePathRequired()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
KeyFilePath = ""
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.KeyFilePathRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenWhitespace_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
KeyFilePath = " "
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.KeyFilePathRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenFileDoesNotExist_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
KeyFilePath = "/nonexistent/path/to/key.key"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenFileDoesNotExist_ValidationErrorReturnsKeyFileNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
KeyFilePath = "/nonexistent/path/to/key.key"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.KeyFileNotFound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(UnlockStoreDialogViewModel.KeyFilePath))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.KeyFilePath = "/new/key/path";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenChanged_RaisesIsValidPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
var isValidPropertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(UnlockStoreDialogViewModel.IsValid))
|
||||
isValidPropertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.KeyFilePath = "/new/key/path";
|
||||
|
||||
// Assert
|
||||
isValidPropertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenChanged_RaisesValidationErrorPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
var validationErrorPropertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(UnlockStoreDialogViewModel.ValidationError))
|
||||
validationErrorPropertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.KeyFilePath = "/new/key/path";
|
||||
|
||||
// Assert
|
||||
validationErrorPropertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BrowseKeyFilePathCommand_IsInitialized()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
|
||||
// Assert
|
||||
sut.BrowseKeyFilePathCommand.ShouldNotBeNull();
|
||||
}
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
using JdeScoping.ConfigManager.Core.Services;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Dialogs;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.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");
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
using JdeScoping.ConfigManager.Core.Models;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Forms;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels.Forms;
|
||||
|
||||
public class AuthFormViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_InitializesFromModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new AuthSection
|
||||
{
|
||||
CookieName = ".TestApp.Auth",
|
||||
CookieExpirationMinutes = 120
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new AuthFormViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.CookieName.ShouldBe(".TestApp.Auth");
|
||||
sut.CookieExpirationMinutes.ShouldBe(120);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_UpdatesModelAndInvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new AuthSection();
|
||||
var changedInvoked = false;
|
||||
var sut = new AuthFormViewModel(model, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.CookieName = ".Custom.Cookie";
|
||||
|
||||
// Assert
|
||||
model.CookieName.ShouldBe(".Custom.Cookie");
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CookieExpirationMinutes_UpdatesModelAndInvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new AuthSection { CookieExpirationMinutes = 480 };
|
||||
var changedInvoked = false;
|
||||
var sut = new AuthFormViewModel(model, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.CookieExpirationMinutes = 60;
|
||||
|
||||
// Assert
|
||||
model.CookieExpirationMinutes.ShouldBe(60);
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new AuthSection();
|
||||
var sut = new AuthFormViewModel(model, () => { });
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(AuthFormViewModel.CookieName))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.CookieName = ".New.Cookie";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullModel()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new AuthFormViewModel(null!, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullCallback()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new AuthFormViewModel(new AuthSection(), null!));
|
||||
}
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
using JdeScoping.ConfigManager.Core.Models;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Forms;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels.Forms;
|
||||
|
||||
public class ConnectionStringEntryViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_InitializesFromModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry
|
||||
{
|
||||
Name = "TestConnection",
|
||||
Provider = ConnectionProvider.SqlServer,
|
||||
Server = "localhost",
|
||||
SqlServerPort = 1434,
|
||||
Database = "TestDb",
|
||||
UserId = "testuser",
|
||||
Password = "testpass",
|
||||
Encrypt = "Strict",
|
||||
TrustServerCertificate = true,
|
||||
ConnectionTimeout = 45,
|
||||
ApplicationName = "TestApp"
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.Name.ShouldBe("TestConnection");
|
||||
sut.Provider.ShouldBe(ConnectionProvider.SqlServer);
|
||||
sut.Server.ShouldBe("localhost");
|
||||
sut.SqlServerPort.ShouldBe(1434);
|
||||
sut.Database.ShouldBe("TestDb");
|
||||
sut.UserId.ShouldBe("testuser");
|
||||
sut.Password.ShouldBe("testpass");
|
||||
sut.Encrypt.ShouldBe("Strict");
|
||||
sut.TrustServerCertificate.ShouldBeTrue();
|
||||
sut.ConnectionTimeout.ShouldBe(45);
|
||||
sut.ApplicationName.ShouldBe("TestApp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullModel()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new ConnectionStringEntryViewModel(null!, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new ConnectionStringEntryViewModel(model, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_UpdatesModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry();
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => { });
|
||||
|
||||
// Act
|
||||
sut.Name = "UpdatedName";
|
||||
sut.Server = "newserver";
|
||||
sut.Database = "newdb";
|
||||
|
||||
// Assert
|
||||
model.Name.ShouldBe("UpdatedName");
|
||||
model.Server.ShouldBe("newserver");
|
||||
model.Database.ShouldBe("newdb");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_InvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry();
|
||||
var changedInvoked = false;
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.Name = "NewName";
|
||||
|
||||
// Assert
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TogglePasswordVisibility_TogglesIsPasswordVisible()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry();
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => { });
|
||||
|
||||
// Assert initial state
|
||||
sut.IsPasswordVisible.ShouldBeFalse();
|
||||
|
||||
// Act - first toggle
|
||||
sut.TogglePasswordVisibilityCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
sut.IsPasswordVisible.ShouldBeTrue();
|
||||
|
||||
// Act - second toggle
|
||||
sut.TogglePasswordVisibilityCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
sut.IsPasswordVisible.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProviderDisplay_ReturnsCorrectString()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry { Provider = ConnectionProvider.SqlServer };
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.ProviderDisplay.ShouldBe("SqlServer");
|
||||
|
||||
// Act
|
||||
sut.Provider = ConnectionProvider.Oracle;
|
||||
|
||||
// Assert
|
||||
sut.ProviderDisplay.ShouldBe("Oracle");
|
||||
|
||||
// Act
|
||||
sut.Provider = ConnectionProvider.Generic;
|
||||
|
||||
// Assert
|
||||
sut.ProviderDisplay.ShouldBe("Generic");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServerDisplay_ReturnsServerForSqlServer()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry
|
||||
{
|
||||
Provider = ConnectionProvider.SqlServer,
|
||||
Server = "sql-server-host"
|
||||
};
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.ServerDisplay.ShouldBe("sql-server-host");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServerDisplay_ReturnsHostForOracle()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry
|
||||
{
|
||||
Provider = ConnectionProvider.Oracle,
|
||||
Host = "oracle-host"
|
||||
};
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.ServerDisplay.ShouldBe("oracle-host");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServerDisplay_ReturnsDashForGeneric()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry
|
||||
{
|
||||
Provider = ConnectionProvider.Generic,
|
||||
RawConnectionString = "some connection string"
|
||||
};
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.ServerDisplay.ShouldBe("-");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SqlServerPort_PropertyChange_UpdatesModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry();
|
||||
var changedInvoked = false;
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.SqlServerPort = 1434;
|
||||
|
||||
// Assert
|
||||
model.SqlServerPort.ShouldBe(1434);
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SqlServerPort_PropertyChange_UpdatesGeneratedConnectionString()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry
|
||||
{
|
||||
Provider = ConnectionProvider.SqlServer,
|
||||
Server = "localhost",
|
||||
Database = "TestDb"
|
||||
};
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => { });
|
||||
|
||||
// Act
|
||||
sut.SqlServerPort = 1434;
|
||||
|
||||
// Assert
|
||||
sut.GeneratedConnectionString.ShouldContain("Server=localhost,1434");
|
||||
}
|
||||
}
|
||||
+664
@@ -0,0 +1,664 @@
|
||||
using JdeScoping.ConfigManager.Core.Models;
|
||||
using JdeScoping.ConfigManager.Core.Services;
|
||||
using JdeScoping.ConfigManager.Core.Services.SecureStore;
|
||||
using JdeScoping.ConfigManager.Ui.Services;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Forms;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels.Forms;
|
||||
|
||||
public class ConnectionStringsFormViewModelTests
|
||||
{
|
||||
private readonly ISecureStoreManager _secureStoreManager;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IConnectionTestService _connectionTestService;
|
||||
|
||||
public ConnectionStringsFormViewModelTests()
|
||||
{
|
||||
_secureStoreManager = Substitute.For<ISecureStoreManager>();
|
||||
_dialogService = Substitute.For<IDialogService>();
|
||||
_connectionTestService = Substitute.For<IConnectionTestService>();
|
||||
|
||||
// Setup default behavior - SecureStore is not open by default in tests
|
||||
_secureStoreManager.IsStoreOpen.Returns(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_InitializesFromModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry
|
||||
{
|
||||
Name = "Connection1",
|
||||
Provider = ConnectionProvider.SqlServer,
|
||||
Server = "server1"
|
||||
},
|
||||
new ConnectionStringEntry
|
||||
{
|
||||
Name = "Connection2",
|
||||
Provider = ConnectionProvider.Oracle,
|
||||
Host = "oracle-host"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Assert
|
||||
sut.Connections.Count.ShouldBe(2);
|
||||
sut.Connections[0].Name.ShouldBe("Connection1");
|
||||
sut.Connections[0].Provider.ShouldBe(ConnectionProvider.SqlServer);
|
||||
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 - port stays embedded in server name, not parsed into SqlServerPort
|
||||
sut.Connections.Count.ShouldBe(1);
|
||||
sut.Connections[0].Provider.ShouldBe(ConnectionProvider.SqlServer);
|
||||
sut.Connections[0].Server.ShouldBe("localhost,1434");
|
||||
sut.Connections[0].SqlServerPort.ShouldBeNull();
|
||||
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>(() =>
|
||||
new ConnectionStringsFormViewModel(null!, _secureStoreManager, () => { }, _dialogService, _connectionTestService));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullSecureStoreManager()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new ConnectionStringsFormViewModel(model, null!, () => { }, _dialogService, _connectionTestService));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new ConnectionStringsFormViewModel(model, _secureStoreManager, null!, _dialogService, _connectionTestService));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullConnectionTestService()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddConnection_CreatesNewEntryAndSelectsIt()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection();
|
||||
var changedInvoked = false;
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => changedInvoked = true, _dialogService, _connectionTestService);
|
||||
|
||||
// Act
|
||||
sut.AddConnectionCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
sut.Connections.Count.ShouldBe(1);
|
||||
sut.Connections[0].Name.ShouldBe("NewConnection");
|
||||
sut.SelectedConnection.ShouldBe(sut.Connections[0]);
|
||||
model.Entries.Count.ShouldBe(1);
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasSelection_IsFalseWhenNothingSelected()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry { Name = "Conn1" }
|
||||
}
|
||||
};
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Assert - no selection by default
|
||||
sut.SelectedConnection.ShouldBeNull();
|
||||
sut.HasSelection.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasSelection_IsTrueWhenConnectionSelected()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry { Name = "Conn1" }
|
||||
}
|
||||
};
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Act
|
||||
sut.SelectedConnection = sut.Connections[0];
|
||||
|
||||
// Assert
|
||||
sut.HasSelection.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConnectionCount_ReflectsCollectionSize()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry { Name = "Conn1" },
|
||||
new ConnectionStringEntry { Name = "Conn2" },
|
||||
new ConnectionStringEntry { Name = "Conn3" }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Assert
|
||||
sut.ConnectionCount.ShouldBe(3);
|
||||
|
||||
// Act - add another
|
||||
sut.AddConnectionCommand.Execute(null);
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using JdeScoping.ConfigManager.Core.Models;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Forms;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels.Forms;
|
||||
|
||||
public class DataAccessFormViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_InitializesFromModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new DataAccessSection
|
||||
{
|
||||
DefaultTimeoutSeconds = 60,
|
||||
ProductionSchema = "dbo",
|
||||
EnableDetailedLogging = true
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new DataAccessFormViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.DefaultTimeoutSeconds.ShouldBe(60);
|
||||
sut.ProductionSchema.ShouldBe("dbo");
|
||||
sut.EnableDetailedLogging.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_UpdatesModelAndInvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new DataAccessSection();
|
||||
var changedInvoked = false;
|
||||
var sut = new DataAccessFormViewModel(model, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.ArchiveSchema = "hist";
|
||||
|
||||
// Assert
|
||||
model.ArchiveSchema.ShouldBe("hist");
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
using JdeScoping.ConfigManager.Core.Models;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Forms;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels.Forms;
|
||||
|
||||
public class DataSyncFormViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_InitializesFromModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new DataSyncSection
|
||||
{
|
||||
Enabled = true,
|
||||
MaxDegreeOfParallelism = 8,
|
||||
BatchSize = 25000
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new DataSyncFormViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.Enabled.ShouldBeTrue();
|
||||
sut.MaxDegreeOfParallelism.ShouldBe(8);
|
||||
sut.BatchSize.ShouldBe(25000);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_UpdatesModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new DataSyncSection { MaxDegreeOfParallelism = 4 };
|
||||
var sut = new DataSyncFormViewModel(model, () => { });
|
||||
|
||||
// Act
|
||||
sut.MaxDegreeOfParallelism = 16;
|
||||
|
||||
// Assert
|
||||
model.MaxDegreeOfParallelism.ShouldBe(16);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_InvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new DataSyncSection();
|
||||
var changedInvoked = false;
|
||||
var sut = new DataSyncFormViewModel(model, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.BatchSize = 10000;
|
||||
|
||||
// Assert
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new DataSyncSection();
|
||||
var sut = new DataSyncFormViewModel(model, () => { });
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(DataSyncFormViewModel.LookbackMultiplier))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.LookbackMultiplier = 2.5;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
using JdeScoping.ConfigManager.Core.Models;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Forms;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels.Forms;
|
||||
|
||||
public class ExcelExportFormViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_InitializesFromModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ExcelExportSection
|
||||
{
|
||||
MaxRowsPerSheet = 500000,
|
||||
DefaultDateFormat = "MM/dd/yyyy",
|
||||
DebugWriteToFile = true,
|
||||
DebugOutputDirectory = "/tmp/debug",
|
||||
TimezoneId = "America/Los_Angeles"
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new ExcelExportFormViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.MaxRowsPerSheet.ShouldBe(500000);
|
||||
sut.DefaultDateFormat.ShouldBe("MM/dd/yyyy");
|
||||
sut.DebugWriteToFile.ShouldBeTrue();
|
||||
sut.DebugOutputDirectory.ShouldBe("/tmp/debug");
|
||||
sut.SelectedTimezone.ShouldBe("America/Los_Angeles");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_UsesModelTimezone()
|
||||
{
|
||||
// Arrange - model defaults to "America/Chicago"
|
||||
var model = new ExcelExportSection();
|
||||
|
||||
// Act
|
||||
var sut = new ExcelExportFormViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.SelectedTimezone.ShouldBe("America/Chicago");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AvailableTimezones_ContainsSystemTimezones()
|
||||
{
|
||||
// Act & Assert
|
||||
ExcelExportFormViewModel.AvailableTimezones.ShouldNotBeEmpty();
|
||||
// Check for common IANA timezones
|
||||
ExcelExportFormViewModel.AvailableTimezones.ShouldContain("America/Chicago");
|
||||
ExcelExportFormViewModel.AvailableTimezones.ShouldContain("UTC");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_UpdatesModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ExcelExportSection { MaxRowsPerSheet = 1000000 };
|
||||
var sut = new ExcelExportFormViewModel(model, () => { });
|
||||
|
||||
// Act
|
||||
sut.MaxRowsPerSheet = 750000;
|
||||
|
||||
// Assert
|
||||
model.MaxRowsPerSheet.ShouldBe(750000);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectedTimezone_UpdatesModelTimezoneId()
|
||||
{
|
||||
// Arrange - model defaults to "America/Chicago"
|
||||
var model = new ExcelExportSection();
|
||||
var sut = new ExcelExportFormViewModel(model, () => { });
|
||||
|
||||
// Act - change to a different timezone
|
||||
sut.SelectedTimezone = "America/New_York";
|
||||
|
||||
// Assert
|
||||
model.TimezoneId.ShouldBe("America/New_York");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_InvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ExcelExportSection(); // Default TimezoneId is "America/Chicago"
|
||||
var changedInvoked = false;
|
||||
var sut = new ExcelExportFormViewModel(model, () => changedInvoked = true);
|
||||
|
||||
// Act - change to a different timezone than the default
|
||||
sut.SelectedTimezone = "America/Denver";
|
||||
|
||||
// Assert
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ExcelExportSection();
|
||||
var sut = new ExcelExportFormViewModel(model, () => { });
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(ExcelExportFormViewModel.DefaultDateFormat))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.DefaultDateFormat = "dd-MMM-yyyy";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SameValueAssignment_DoesNotInvokeOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ExcelExportSection { DebugWriteToFile = true };
|
||||
var changedInvoked = false;
|
||||
var sut = new ExcelExportFormViewModel(model, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.DebugWriteToFile = true; // Same value
|
||||
|
||||
// Assert
|
||||
changedInvoked.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullModel()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new ExcelExportFormViewModel(null!, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ExcelExportSection();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new ExcelExportFormViewModel(model, null!));
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
using JdeScoping.ConfigManager.Core.Models;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Forms;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels.Forms;
|
||||
|
||||
public class LdapFormViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_InitializesFromModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new LdapSection
|
||||
{
|
||||
ServerUrls = ["ldap://server1.local", "ldap://server2.local"],
|
||||
GroupDn = "CN=Admins,DC=corp",
|
||||
SearchBase = "DC=corp,DC=local",
|
||||
UseFakeAuth = true
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new LdapFormViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.ServerUrlsText.ShouldBe("ldap://server1.local\nldap://server2.local");
|
||||
sut.GroupDn.ShouldBe("CN=Admins,DC=corp");
|
||||
sut.SearchBase.ShouldBe("DC=corp,DC=local");
|
||||
sut.UseFakeAuth.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServerUrlsText_SplitsIntoArray()
|
||||
{
|
||||
// Arrange
|
||||
var model = new LdapSection();
|
||||
var sut = new LdapFormViewModel(model, () => { });
|
||||
|
||||
// Act
|
||||
sut.ServerUrlsText = "ldap://a.local\nldap://b.local\nldap://c.local";
|
||||
|
||||
// Assert
|
||||
model.ServerUrls.Length.ShouldBe(3);
|
||||
model.ServerUrls[0].ShouldBe("ldap://a.local");
|
||||
model.ServerUrls[2].ShouldBe("ldap://c.local");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdminBypassUsersText_SplitsIntoArray()
|
||||
{
|
||||
// Arrange
|
||||
var model = new LdapSection();
|
||||
var sut = new LdapFormViewModel(model, () => { });
|
||||
|
||||
// Act
|
||||
sut.AdminBypassUsersText = "admin\nservice_account";
|
||||
|
||||
// Assert
|
||||
model.AdminBypassUsers.Length.ShouldBe(2);
|
||||
model.AdminBypassUsers[0].ShouldBe("admin");
|
||||
}
|
||||
}
|
||||
+630
@@ -0,0 +1,630 @@
|
||||
using JdeScoping.ConfigManager.Ui.Services;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Forms;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.PipelineSteps;
|
||||
using JdeScoping.DataSync.Configuration;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.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 }
|
||||
};
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
using JdeScoping.ConfigManager.Core.Models;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Forms;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels.Forms;
|
||||
|
||||
public class SearchFormViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_InitializesFromModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new SearchSection
|
||||
{
|
||||
MaxResultRows = 50000,
|
||||
TimeoutSeconds = 600,
|
||||
MaxConcurrentSearches = 10
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new SearchFormViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.MaxResultRows.ShouldBe(50000);
|
||||
sut.TimeoutSeconds.ShouldBe(600);
|
||||
sut.MaxConcurrentSearches.ShouldBe(10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_UpdatesModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new SearchSection { MaxResultRows = 100000 };
|
||||
var sut = new SearchFormViewModel(model, () => { });
|
||||
|
||||
// Act
|
||||
sut.MaxResultRows = 25000;
|
||||
|
||||
// Assert
|
||||
model.MaxResultRows.ShouldBe(25000);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_InvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new SearchSection();
|
||||
var changedInvoked = false;
|
||||
var sut = new SearchFormViewModel(model, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.TimeoutSeconds = 120;
|
||||
|
||||
// Assert
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new SearchSection();
|
||||
var sut = new SearchFormViewModel(model, () => { });
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SearchFormViewModel.MaxConcurrentSearches))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.MaxConcurrentSearches = 8;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullModel()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new SearchFormViewModel(null!, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullCallback()
|
||||
{
|
||||
// Arrange
|
||||
var model = new SearchSection();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new SearchFormViewModel(model, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_DoesNotInvokeOnChangedWhenValueUnchanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new SearchSection { MaxResultRows = 50000 };
|
||||
var changedCount = 0;
|
||||
var sut = new SearchFormViewModel(model, () => changedCount++);
|
||||
|
||||
// Act - set to same value
|
||||
sut.MaxResultRows = 50000;
|
||||
|
||||
// Assert
|
||||
changedCount.ShouldBe(0);
|
||||
}
|
||||
}
|
||||
+412
@@ -0,0 +1,412 @@
|
||||
using JdeScoping.ConfigManager.Ui.Services;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Forms;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels.Forms;
|
||||
|
||||
public class SecretFormViewModelTests
|
||||
{
|
||||
private readonly IClipboardService _clipboardService;
|
||||
|
||||
public SecretFormViewModelTests()
|
||||
{
|
||||
_clipboardService = Substitute.For<IClipboardService>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SetsPropertiesCorrectly()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret-value-123",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Assert
|
||||
sut.Key.ShouldBe("API_KEY");
|
||||
sut.Value.ShouldBe("secret-value-123");
|
||||
sut.IsValueVisible.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullValue_SetsEmptyString()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
null!,
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Assert
|
||||
sut.Value.ShouldBe(string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Value_Setter_InvokesCallback()
|
||||
{
|
||||
// Arrange
|
||||
string? changedValue = null;
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"initial",
|
||||
_clipboardService,
|
||||
v => changedValue = v,
|
||||
() => { });
|
||||
|
||||
// Act
|
||||
sut.Value = "new-value";
|
||||
|
||||
// Assert
|
||||
changedValue.ShouldBe("new-value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Value_Setter_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"initial",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretFormViewModel.Value))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.Value = "new-value";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Value_Setter_RaisesDisplayValuePropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"initial",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
var displayValueChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretFormViewModel.DisplayValue))
|
||||
displayValueChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.Value = "new-value";
|
||||
|
||||
// Assert
|
||||
displayValueChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValueVisible_Toggle_Works()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert - Initial state
|
||||
sut.IsValueVisible.ShouldBeFalse();
|
||||
|
||||
// Act - Toggle on
|
||||
sut.IsValueVisible = true;
|
||||
sut.IsValueVisible.ShouldBeTrue();
|
||||
|
||||
// Act - Toggle off
|
||||
sut.IsValueVisible = false;
|
||||
sut.IsValueVisible.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValueVisible_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretFormViewModel.IsValueVisible))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.IsValueVisible = true;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValueVisible_RaisesDisplayValueAndVisibilityButtonTextPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
var displayValueChangedRaised = false;
|
||||
var visibilityButtonTextChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(SecretFormViewModel.DisplayValue))
|
||||
displayValueChangedRaised = true;
|
||||
if (e.PropertyName == nameof(SecretFormViewModel.VisibilityButtonText))
|
||||
visibilityButtonTextChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.IsValueVisible = true;
|
||||
|
||||
// Assert
|
||||
displayValueChangedRaised.ShouldBeTrue();
|
||||
visibilityButtonTextChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayValue_ShowsMaskedValue_WhenNotVisible()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret123",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValueVisible.ShouldBeFalse();
|
||||
sut.DisplayValue.ShouldBe("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"); // 9 bullet points
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayValue_ShowsActualValue_WhenVisible()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret123",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act
|
||||
sut.IsValueVisible = true;
|
||||
|
||||
// Assert
|
||||
sut.DisplayValue.ShouldBe("secret123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayValue_LimitsMaskedLength_ToTwentyCharacters()
|
||||
{
|
||||
// Arrange
|
||||
var longValue = new string('x', 50);
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
longValue,
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert
|
||||
sut.DisplayValue.Length.ShouldBe(20);
|
||||
sut.DisplayValue.ShouldBe(new string('\u2022', 20));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayValue_HandlesEmptyValue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert
|
||||
sut.DisplayValue.ShouldBe("");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VisibilityButtonText_ShowsShow_WhenHidden()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert
|
||||
sut.VisibilityButtonText.ShouldBe("Show");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VisibilityButtonText_ShowsHide_WhenVisible()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act
|
||||
sut.IsValueVisible = true;
|
||||
|
||||
// Assert
|
||||
sut.VisibilityButtonText.ShouldBe("Hide");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToggleVisibilityCommand_TogglesVisibility()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act & Assert - Toggle on
|
||||
sut.ToggleVisibilityCommand.Execute(null);
|
||||
sut.IsValueVisible.ShouldBeTrue();
|
||||
|
||||
// Act & Assert - Toggle off
|
||||
sut.ToggleVisibilityCommand.Execute(null);
|
||||
sut.IsValueVisible.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CopyToClipboardCommand_CopiesValueToClipboard()
|
||||
{
|
||||
// Arrange
|
||||
_clipboardService.SetTextAsync(Arg.Any<string>()).Returns(Task.CompletedTask);
|
||||
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret-to-copy",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => { });
|
||||
|
||||
// Act
|
||||
sut.CopyToClipboardCommand.Execute(null);
|
||||
|
||||
// Small delay to allow async command to complete
|
||||
await Task.Delay(50);
|
||||
|
||||
// Assert
|
||||
await _clipboardService.Received(1).SetTextAsync("secret-to-copy");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteCommand_InvokesCallback()
|
||||
{
|
||||
// Arrange
|
||||
var deleteCalled = false;
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"secret",
|
||||
_clipboardService,
|
||||
_ => { },
|
||||
() => deleteCalled = true);
|
||||
|
||||
// Act
|
||||
sut.DeleteCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
deleteCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullKey()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new SecretFormViewModel(null!, "value", _clipboardService, _ => { }, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullClipboardService()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new SecretFormViewModel("key", "value", null!, _ => { }, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullValueChangedCallback()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new SecretFormViewModel("key", "value", _clipboardService, null!, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullDeleteCallback()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new SecretFormViewModel("key", "value", _clipboardService, _ => { }, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Key_IsReadOnly()
|
||||
{
|
||||
// Assert - Verify Key property is get-only
|
||||
var keyProperty = typeof(SecretFormViewModel).GetProperty(nameof(SecretFormViewModel.Key));
|
||||
keyProperty!.CanWrite.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Value_DoesNotInvokeCallback_WhenValueUnchanged()
|
||||
{
|
||||
// Arrange
|
||||
var callbackCount = 0;
|
||||
var sut = new SecretFormViewModel(
|
||||
"API_KEY",
|
||||
"same-value",
|
||||
_clipboardService,
|
||||
_ => callbackCount++,
|
||||
() => { });
|
||||
|
||||
// Act
|
||||
sut.Value = "same-value"; // Same as initial value
|
||||
|
||||
// Assert
|
||||
callbackCount.ShouldBe(0);
|
||||
}
|
||||
}
|
||||
+783
@@ -0,0 +1,783 @@
|
||||
using JdeScoping.ConfigManager.Core.Constants;
|
||||
using JdeScoping.ConfigManager.Core.Models;
|
||||
using JdeScoping.ConfigManager.Core.Services;
|
||||
using JdeScoping.ConfigManager.Core.Services.SecureStore;
|
||||
using JdeScoping.ConfigManager.Ui.Services;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.Forms;
|
||||
using JdeScoping.DataSync.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels;
|
||||
|
||||
public class MainWindowViewModelTests
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IConfigFileService _configFileService;
|
||||
private readonly IValidationService _validationService;
|
||||
private readonly IBackupService _backupService;
|
||||
private readonly IAutoDiscoveryService _autoDiscoveryService;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly ISecureStoreManager _secureStoreManager;
|
||||
private readonly IClipboardService _clipboardService;
|
||||
private readonly IRuntimeConfigValidationService _runtimeValidationService;
|
||||
private readonly IConnectionTestService _connectionTestService;
|
||||
private readonly ILogger<MainWindowViewModel> _logger;
|
||||
|
||||
public MainWindowViewModelTests()
|
||||
{
|
||||
_fileSystem = Substitute.For<IFileSystem>();
|
||||
_configFileService = Substitute.For<IConfigFileService>();
|
||||
_validationService = Substitute.For<IValidationService>();
|
||||
_backupService = Substitute.For<IBackupService>();
|
||||
_autoDiscoveryService = Substitute.For<IAutoDiscoveryService>();
|
||||
_dialogService = Substitute.For<IDialogService>();
|
||||
_secureStoreManager = Substitute.For<ISecureStoreManager>();
|
||||
_clipboardService = Substitute.For<IClipboardService>();
|
||||
_runtimeValidationService = Substitute.For<IRuntimeConfigValidationService>();
|
||||
_connectionTestService = Substitute.For<IConnectionTestService>();
|
||||
_logger = Substitute.For<ILogger<MainWindowViewModel>>();
|
||||
|
||||
_validationService.ValidateAppSettings(Arg.Any<ConfigModel>())
|
||||
.Returns(new ValidationResult());
|
||||
_validationService.ValidatePipelines(Arg.Any<Dictionary<string, EtlPipelineConfig>>())
|
||||
.Returns(new ValidationResult());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectingDataSyncNode_LoadsDataSyncFormViewModel()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel { DataSync = new DataSyncSection { MaxDegreeOfParallelism = 8 } };
|
||||
var sut = CreateViewModel();
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
var dataSyncNode = sut.TreeNodes
|
||||
.SelectMany(n => n.Children)
|
||||
.First(n => n.SectionKey == "DataSync");
|
||||
|
||||
// Act
|
||||
sut.SelectedNode = dataSyncNode;
|
||||
|
||||
// Assert
|
||||
sut.SelectedFormViewModel.ShouldBeOfType<DataSyncFormViewModel>();
|
||||
((DataSyncFormViewModel)sut.SelectedFormViewModel!).MaxDegreeOfParallelism.ShouldBe(8);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectingDataAccessNode_LoadsDataAccessFormViewModel()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel { DataAccess = new DataAccessSection { ProductionSchema = "custom" } };
|
||||
var sut = CreateViewModel();
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
var dataAccessNode = sut.TreeNodes
|
||||
.SelectMany(n => n.Children)
|
||||
.First(n => n.SectionKey == "DataAccess");
|
||||
|
||||
// Act
|
||||
sut.SelectedNode = dataAccessNode;
|
||||
|
||||
// Assert
|
||||
sut.SelectedFormViewModel.ShouldBeOfType<DataAccessFormViewModel>();
|
||||
((DataAccessFormViewModel)sut.SelectedFormViewModel!).ProductionSchema.ShouldBe("custom");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectingAuthNode_LoadsAuthFormViewModel()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel { Auth = new AuthSection { CookieName = "TestCookie" } };
|
||||
var sut = CreateViewModel();
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
var authNode = sut.TreeNodes
|
||||
.SelectMany(n => n.Children)
|
||||
.First(n => n.SectionKey == "Auth");
|
||||
|
||||
// Act
|
||||
sut.SelectedNode = authNode;
|
||||
|
||||
// Assert
|
||||
sut.SelectedFormViewModel.ShouldBeOfType<AuthFormViewModel>();
|
||||
((AuthFormViewModel)sut.SelectedFormViewModel!).CookieName.ShouldBe("TestCookie");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectingLdapNode_LoadsLdapFormViewModel()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel { Ldap = new LdapSection { GroupDn = "CN=TestGroup" } };
|
||||
var sut = CreateViewModel();
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
var ldapNode = sut.TreeNodes
|
||||
.SelectMany(n => n.Children)
|
||||
.First(n => n.SectionKey == "Ldap");
|
||||
|
||||
// Act
|
||||
sut.SelectedNode = ldapNode;
|
||||
|
||||
// Assert
|
||||
sut.SelectedFormViewModel.ShouldBeOfType<LdapFormViewModel>();
|
||||
((LdapFormViewModel)sut.SelectedFormViewModel!).GroupDn.ShouldBe("CN=TestGroup");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectingSearchNode_LoadsSearchFormViewModel()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel { Search = new SearchSection { MaxResultRows = 50000 } };
|
||||
var sut = CreateViewModel();
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
var searchNode = sut.TreeNodes
|
||||
.SelectMany(n => n.Children)
|
||||
.First(n => n.SectionKey == "Search");
|
||||
|
||||
// Act
|
||||
sut.SelectedNode = searchNode;
|
||||
|
||||
// Assert
|
||||
sut.SelectedFormViewModel.ShouldBeOfType<SearchFormViewModel>();
|
||||
((SearchFormViewModel)sut.SelectedFormViewModel!).MaxResultRows.ShouldBe(50000);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectingExcelExportNode_LoadsExcelExportFormViewModel()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel { ExcelExport = new ExcelExportSection { TimezoneId = "America/New_York" } };
|
||||
var sut = CreateViewModel();
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
var excelNode = sut.TreeNodes
|
||||
.SelectMany(n => n.Children)
|
||||
.First(n => n.SectionKey == "ExcelExport");
|
||||
|
||||
// Act
|
||||
sut.SelectedNode = excelNode;
|
||||
|
||||
// Assert
|
||||
sut.SelectedFormViewModel.ShouldBeOfType<ExcelExportFormViewModel>();
|
||||
((ExcelExportFormViewModel)sut.SelectedFormViewModel!).SelectedTimezone.ShouldBe("America/New_York");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectingPipelineNode_LoadsPipelineEditorViewModel()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel();
|
||||
var pipelines = new Dictionary<string, EtlPipelineConfig>
|
||||
{
|
||||
["WorkOrders"] = new EtlPipelineConfig
|
||||
{
|
||||
Name = "WorkOrders",
|
||||
Source = new SourceElement { Connection = "jde", Query = "SELECT * FROM WO" },
|
||||
Destination = new DestinationElement { Table = "WorkOrder_Curr" }
|
||||
}
|
||||
};
|
||||
var sut = CreateViewModel();
|
||||
sut.LoadConfigForTesting(config, pipelines);
|
||||
|
||||
var pipelineNode = sut.TreeNodes
|
||||
.SelectMany(n => n.Children)
|
||||
.First(n => n.SectionKey == "WorkOrders");
|
||||
|
||||
// Act
|
||||
sut.SelectedNode = pipelineNode;
|
||||
|
||||
// Assert
|
||||
sut.SelectedFormViewModel.ShouldBeOfType<PipelineEditorViewModel>();
|
||||
var pipelineEditor = (PipelineEditorViewModel)sut.SelectedFormViewModel!;
|
||||
pipelineEditor.Name.ShouldBe("WorkOrders");
|
||||
pipelineEditor.Source.Connection.ShouldBe("jde");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModifyingFormProperty_SetsHasUnsavedChanges()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel();
|
||||
var sut = CreateViewModel();
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
var dataSyncNode = sut.TreeNodes
|
||||
.SelectMany(n => n.Children)
|
||||
.First(n => n.SectionKey == "DataSync");
|
||||
sut.SelectedNode = dataSyncNode;
|
||||
|
||||
// Act
|
||||
((DataSyncFormViewModel)sut.SelectedFormViewModel!).BatchSize = 10000;
|
||||
|
||||
// Assert
|
||||
sut.HasUnsavedChanges.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModifyingFormProperty_MarksNodeAsModified()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel();
|
||||
var sut = CreateViewModel();
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
var dataSyncNode = sut.TreeNodes
|
||||
.SelectMany(n => n.Children)
|
||||
.First(n => n.SectionKey == "DataSync");
|
||||
sut.SelectedNode = dataSyncNode;
|
||||
|
||||
// Act
|
||||
((DataSyncFormViewModel)sut.SelectedFormViewModel!).MaxDegreeOfParallelism = 16;
|
||||
|
||||
// Assert
|
||||
dataSyncNode.IsModified.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectingFolderNode_SetsSelectedFormViewModelToNull()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel();
|
||||
var sut = CreateViewModel();
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
var folderNode = sut.TreeNodes.First(); // Settings folder
|
||||
|
||||
// Act
|
||||
sut.SelectedNode = folderNode;
|
||||
|
||||
// Assert
|
||||
sut.SelectedFormViewModel.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectingNull_SetsSelectedFormViewModelToNull()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel();
|
||||
var sut = CreateViewModel();
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
var dataSyncNode = sut.TreeNodes
|
||||
.SelectMany(n => n.Children)
|
||||
.First(n => n.SectionKey == "DataSync");
|
||||
sut.SelectedNode = dataSyncNode;
|
||||
|
||||
// Act
|
||||
sut.SelectedNode = null;
|
||||
|
||||
// Assert
|
||||
sut.SelectedFormViewModel.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadConfigForTesting_BuildsTreeNodes()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel();
|
||||
var sut = CreateViewModel();
|
||||
|
||||
// Act
|
||||
sut.LoadConfigForTesting(config, null);
|
||||
|
||||
// Assert
|
||||
// Without a configured/open SecureStore, only Settings and Pipelines appear
|
||||
sut.TreeNodes.Count.ShouldBe(2); // Settings, Pipelines (no Secure Store when not configured)
|
||||
sut.TreeNodes[0].Name.ShouldBe("Settings");
|
||||
sut.TreeNodes[0].Children.Count.ShouldBe(7); // ConnectionStrings, DataSync, DataAccess, Auth, Ldap, Search, ExcelExport
|
||||
sut.TreeNodes[1].Name.ShouldBe("Pipelines");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadConfigForTesting_WithPipelines_BuildsPipelineNodes()
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel();
|
||||
var pipelines = new Dictionary<string, EtlPipelineConfig>
|
||||
{
|
||||
["Pipeline1"] = new EtlPipelineConfig { Name = "Pipeline1" },
|
||||
["Pipeline2"] = new EtlPipelineConfig { Name = "Pipeline2" }
|
||||
};
|
||||
var sut = CreateViewModel();
|
||||
|
||||
// Act
|
||||
sut.LoadConfigForTesting(config, pipelines);
|
||||
|
||||
// Assert
|
||||
sut.TreeNodes[1].Name.ShouldBe("Pipelines");
|
||||
sut.TreeNodes[1].Children.Count.ShouldBe(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OpenFolderCommand_UsesFilePicker_AndDerivesFolder()
|
||||
{
|
||||
// Arrange
|
||||
var expectedFilePath = "/path/to/folder/appsettings.json";
|
||||
var expectedFolder = "/path/to/folder";
|
||||
var config = new ConfigModel();
|
||||
|
||||
// Ensure auto-discovery doesn't load config
|
||||
_autoDiscoveryService.FindConfigFolderAsync().Returns((string?)null);
|
||||
_dialogService.ShowFilePickerAsync(Arg.Any<string?>())
|
||||
.Returns(expectedFilePath);
|
||||
_configFileService.LoadAppSettingsAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(config);
|
||||
|
||||
var sut = CreateViewModel();
|
||||
// Wait for constructor async init to complete
|
||||
await Task.Delay(50);
|
||||
_configFileService.ClearReceivedCalls();
|
||||
|
||||
// Act
|
||||
sut.OpenFolderCommand.Execute(null);
|
||||
// Give async command time to complete
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
await _dialogService.Received(1).ShowFilePickerAsync("Select Configuration File");
|
||||
await _configFileService.Received(1).LoadAppSettingsAsync(
|
||||
Arg.Is<string>(s => s.Contains(expectedFolder)),
|
||||
Arg.Any<CancellationToken>());
|
||||
sut.ConfigFolderPath.ShouldBe(expectedFolder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OpenFolderCommand_WhenCancelled_DoesNotLoadConfig()
|
||||
{
|
||||
// Arrange
|
||||
// Ensure auto-discovery doesn't load config
|
||||
_autoDiscoveryService.FindConfigFolderAsync().Returns((string?)null);
|
||||
_dialogService.ShowFilePickerAsync(Arg.Any<string?>())
|
||||
.Returns((string?)null);
|
||||
|
||||
var sut = CreateViewModel();
|
||||
var originalPath = sut.ConfigFolderPath;
|
||||
// Wait for constructor async init to complete
|
||||
await Task.Delay(50);
|
||||
_configFileService.ClearReceivedCalls();
|
||||
|
||||
// Act
|
||||
sut.OpenFolderCommand.Execute(null);
|
||||
// Give async command time to complete
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
await _dialogService.Received(1).ShowFilePickerAsync(Arg.Any<string?>());
|
||||
await _configFileService.DidNotReceive().LoadAppSettingsAsync(Arg.Any<string>(), Arg.Any<CancellationToken>());
|
||||
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(
|
||||
_fileSystem,
|
||||
_configFileService,
|
||||
_validationService,
|
||||
_backupService,
|
||||
_autoDiscoveryService,
|
||||
_dialogService,
|
||||
_secureStoreManager,
|
||||
_clipboardService,
|
||||
_runtimeValidationService,
|
||||
_connectionTestService,
|
||||
_logger);
|
||||
}
|
||||
}
|
||||
+756
@@ -0,0 +1,756 @@
|
||||
using System.Text.Json;
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.PipelineSteps;
|
||||
using JdeScoping.DataSync.Configuration;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.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);
|
||||
}
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels.PipelineSteps;
|
||||
using JdeScoping.DataSync.Configuration;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels;
|
||||
|
||||
public class RegexTransformerViewModelTests
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
private static TransformElement CreateElement(object config)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(config, JsonOptions);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
return new TransformElement
|
||||
{
|
||||
TransformType = "Regex",
|
||||
Config = doc.RootElement.Clone()
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_FromElement_LoadsAllProperties()
|
||||
{
|
||||
// Arrange
|
||||
var element = CreateElement(new
|
||||
{
|
||||
columnName = "BatchID",
|
||||
pattern = "^IIS_",
|
||||
replacement = "",
|
||||
ignoreCase = true,
|
||||
nonMatchBehavior = "ReturnEmpty"
|
||||
});
|
||||
|
||||
// Act
|
||||
var vm = new RegexTransformerViewModel(element, () => { });
|
||||
|
||||
// Assert
|
||||
Assert.Equal("BatchID", vm.ColumnName);
|
||||
Assert.Equal("^IIS_", vm.Pattern);
|
||||
Assert.Equal("", vm.Replacement);
|
||||
Assert.True(vm.IsFindReplaceMode);
|
||||
Assert.True(vm.IgnoreCase);
|
||||
Assert.Equal(NonMatchBehavior.ReturnEmpty, vm.NonMatchBehavior);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_FromElement_MatchExtractMode_WhenReplacementNull()
|
||||
{
|
||||
// Arrange
|
||||
var element = CreateElement(new
|
||||
{
|
||||
columnName = "Code",
|
||||
pattern = @"(\d+)"
|
||||
});
|
||||
|
||||
// Act
|
||||
var vm = new RegexTransformerViewModel(element, () => { });
|
||||
|
||||
// Assert
|
||||
Assert.False(vm.IsFindReplaceMode);
|
||||
Assert.True(vm.IsMatchExtractMode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToModel_SerializesCorrectly_FindReplaceMode()
|
||||
{
|
||||
// Arrange
|
||||
var vm = new RegexTransformerViewModel(() => { })
|
||||
{
|
||||
ColumnName = "BatchID",
|
||||
Pattern = "^IIS_",
|
||||
Replacement = "",
|
||||
IsFindReplaceMode = true,
|
||||
IgnoreCase = true,
|
||||
NonMatchBehavior = NonMatchBehavior.KeepOriginal
|
||||
};
|
||||
|
||||
// Act
|
||||
var element = vm.ToModel();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Regex", element.TransformType);
|
||||
Assert.True(element.Config.HasValue);
|
||||
|
||||
// Parse the config to verify
|
||||
var config = element.Config!.Value;
|
||||
Assert.Equal("BatchID", config.GetProperty("columnName").GetString());
|
||||
Assert.Equal("^IIS_", config.GetProperty("pattern").GetString());
|
||||
Assert.Equal("", config.GetProperty("replacement").GetString());
|
||||
Assert.True(config.GetProperty("ignoreCase").GetBoolean());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToModel_SerializesCorrectly_MatchExtractMode()
|
||||
{
|
||||
// Arrange
|
||||
var vm = new RegexTransformerViewModel(() => { })
|
||||
{
|
||||
ColumnName = "Code",
|
||||
Pattern = @"(\d+)",
|
||||
IsFindReplaceMode = false
|
||||
};
|
||||
|
||||
// Act
|
||||
var element = vm.ToModel();
|
||||
|
||||
// Assert
|
||||
Assert.True(element.Config.HasValue);
|
||||
var config = element.Config!.Value;
|
||||
|
||||
// replacement should be null in Match & Extract mode
|
||||
Assert.True(config.TryGetProperty("replacement", out var replacement));
|
||||
Assert.Equal(JsonValueKind.Null, replacement.ValueKind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestPatternCommand_ValidPattern_ShowsResult()
|
||||
{
|
||||
// Arrange
|
||||
var vm = new RegexTransformerViewModel(() => { })
|
||||
{
|
||||
Pattern = "^IIS_",
|
||||
Replacement = "",
|
||||
IsFindReplaceMode = true,
|
||||
TestInput = "IIS_12345"
|
||||
};
|
||||
|
||||
// Act
|
||||
vm.TestPatternCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
Assert.True(vm.HasTestResult);
|
||||
Assert.False(vm.HasTestError);
|
||||
Assert.Equal("12345", vm.TestResultValue);
|
||||
Assert.Equal("Output", vm.TestResultLabel);
|
||||
Assert.Equal("✓", vm.TestResultIcon);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestPatternCommand_InvalidPattern_ShowsError()
|
||||
{
|
||||
// Arrange
|
||||
var vm = new RegexTransformerViewModel(() => { })
|
||||
{
|
||||
Pattern = "[invalid(regex",
|
||||
Replacement = "",
|
||||
TestInput = "test"
|
||||
};
|
||||
|
||||
// Act
|
||||
vm.TestPatternCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
Assert.False(vm.HasTestResult);
|
||||
Assert.True(vm.HasTestError);
|
||||
Assert.NotEmpty(vm.TestErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestPatternCommand_MatchExtract_NoMatch_ShowsNonMatchBehavior()
|
||||
{
|
||||
// Arrange
|
||||
var vm = new RegexTransformerViewModel(() => { })
|
||||
{
|
||||
Pattern = @"(\d+)",
|
||||
IsFindReplaceMode = false,
|
||||
NonMatchBehavior = NonMatchBehavior.ReturnNull,
|
||||
TestInput = "NoNumbers"
|
||||
};
|
||||
|
||||
// Act
|
||||
vm.TestPatternCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
Assert.True(vm.HasTestResult);
|
||||
Assert.Equal("No Match", vm.TestResultLabel);
|
||||
Assert.Equal("(null)", vm.TestResultValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModeSwitch_UpdatesPatternHelpText()
|
||||
{
|
||||
// Arrange
|
||||
var vm = new RegexTransformerViewModel(() => { })
|
||||
{
|
||||
IsFindReplaceMode = true
|
||||
};
|
||||
var findReplaceHelp = vm.PatternHelpText;
|
||||
|
||||
// Act
|
||||
vm.IsFindReplaceMode = false;
|
||||
var matchExtractHelp = vm.PatternHelpText;
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(findReplaceHelp, matchExtractHelp);
|
||||
Assert.Contains("capture group", matchExtractHelp, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Summary_ShowsColumnAndMode()
|
||||
{
|
||||
// Arrange & Act
|
||||
var vm = new RegexTransformerViewModel(() => { })
|
||||
{
|
||||
ColumnName = "BatchID",
|
||||
IsFindReplaceMode = true
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.Contains("BatchID", vm.Summary);
|
||||
Assert.Contains("Replace", vm.Summary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_NotifiesChanged()
|
||||
{
|
||||
// Arrange
|
||||
var changedCalled = false;
|
||||
var vm = new RegexTransformerViewModel(() => changedCalled = true);
|
||||
|
||||
// Act
|
||||
vm.ColumnName = "NewColumn";
|
||||
|
||||
// Assert
|
||||
Assert.True(changedCalled);
|
||||
}
|
||||
}
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
using JdeScoping.ConfigManager.Ui.ViewModels;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels;
|
||||
|
||||
public class TreeNodeViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_SetsProperties()
|
||||
{
|
||||
// Arrange & Act
|
||||
var node = new TreeNodeViewModel("DataSync", "⟳", TreeNodeType.SettingsSection);
|
||||
|
||||
// Assert
|
||||
node.Name.ShouldBe("DataSync");
|
||||
node.Icon.ShouldBe("⟳");
|
||||
node.NodeType.ShouldBe(TreeNodeType.SettingsSection);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsModified_WhenSet_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
|
||||
var propertyChangedRaised = false;
|
||||
node.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(TreeNodeViewModel.IsModified))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
node.IsModified = true;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsExpanded_WhenSet_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
|
||||
var propertyChangedRaised = false;
|
||||
node.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(TreeNodeViewModel.IsExpanded))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
node.IsExpanded = true;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
node.IsExpanded.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsSelected_WhenSet_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
|
||||
var propertyChangedRaised = false;
|
||||
node.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(TreeNodeViewModel.IsSelected))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
node.IsSelected = true;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
node.IsSelected.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationState_WhenSetToValid_StatusIconIsCheckmark()
|
||||
{
|
||||
// Arrange
|
||||
var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
|
||||
|
||||
// Act
|
||||
node.ValidationState = ValidationState.Valid;
|
||||
|
||||
// Assert
|
||||
node.StatusIcon.ShouldBe("✓");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationState_WhenSetToWarning_StatusIconIsWarning()
|
||||
{
|
||||
// Arrange
|
||||
var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
|
||||
|
||||
// Act
|
||||
node.ValidationState = ValidationState.Warning;
|
||||
|
||||
// Assert
|
||||
node.StatusIcon.ShouldBe("⚠");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationState_WhenSetToError_StatusIconIsX()
|
||||
{
|
||||
// Arrange
|
||||
var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
|
||||
|
||||
// Act
|
||||
node.ValidationState = ValidationState.Error;
|
||||
|
||||
// Assert
|
||||
node.StatusIcon.ShouldBe("✗");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationState_WhenSetToUnknown_StatusIconIsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
|
||||
|
||||
// Act
|
||||
node.ValidationState = ValidationState.Unknown;
|
||||
|
||||
// Assert
|
||||
node.StatusIcon.ShouldBe("");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationState_WhenChanged_RaisesPropertyChangedForStatusIcon()
|
||||
{
|
||||
// Arrange
|
||||
var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
|
||||
var statusIconPropertyChanged = false;
|
||||
node.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(TreeNodeViewModel.StatusIcon))
|
||||
statusIconPropertyChanged = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
node.ValidationState = ValidationState.Valid;
|
||||
|
||||
// Assert
|
||||
statusIconPropertyChanged.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SectionKey_CanBeSetViaInitializer()
|
||||
{
|
||||
// Arrange & Act
|
||||
var node = new TreeNodeViewModel("DataSync", "⟳", TreeNodeType.SettingsSection)
|
||||
{
|
||||
SectionKey = "DataSync"
|
||||
};
|
||||
|
||||
// Assert
|
||||
node.SectionKey.ShouldBe("DataSync");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Children_IsInitialized()
|
||||
{
|
||||
// Arrange & Act
|
||||
var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
|
||||
|
||||
// Assert
|
||||
node.Children.ShouldNotBeNull();
|
||||
node.Children.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Children_CanAddChildNodes()
|
||||
{
|
||||
// Arrange
|
||||
var parent = new TreeNodeViewModel("Settings", "📁", TreeNodeType.Folder);
|
||||
var child = new TreeNodeViewModel("DataSync", "⟳", TreeNodeType.SettingsSection);
|
||||
|
||||
// Act
|
||||
parent.Children.Add(child);
|
||||
|
||||
// Assert
|
||||
parent.Children.Count.ShouldBe(1);
|
||||
parent.Children[0].ShouldBe(child);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(TreeNodeType.Folder)]
|
||||
[InlineData(TreeNodeType.SettingsSection)]
|
||||
[InlineData(TreeNodeType.Pipeline)]
|
||||
public void NodeType_CanBeSetForAllTypes(TreeNodeType nodeType)
|
||||
{
|
||||
// Arrange & Act
|
||||
var node = new TreeNodeViewModel("Test", "📁", nodeType);
|
||||
|
||||
// Assert
|
||||
node.NodeType.ShouldBe(nodeType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsModified_WhenSetToSameValue_DoesNotRaisePropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var node = new TreeNodeViewModel("Test", "📁", TreeNodeType.Folder);
|
||||
node.IsModified = true;
|
||||
|
||||
var propertyChangedRaised = false;
|
||||
node.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(TreeNodeViewModel.IsModified))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
node.IsModified = true; // Same value
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeFalse();
|
||||
}
|
||||
|
||||
#region SecureStore Node Type Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(TreeNodeType.SecureStore)]
|
||||
[InlineData(TreeNodeType.Secret)]
|
||||
public void Constructor_WithSecureStoreNodeTypes_SetsNodeTypeCorrectly(TreeNodeType nodeType)
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new TreeNodeViewModel("Test", "icon", nodeType);
|
||||
|
||||
// Assert
|
||||
sut.NodeType.ShouldBe(nodeType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StorePath_CanBeSetViaInitializer()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new TreeNodeViewModel("Store", "🔒", TreeNodeType.SecureStore)
|
||||
{
|
||||
StorePath = "/path/to/store.secrets"
|
||||
};
|
||||
|
||||
// Assert
|
||||
sut.StorePath.ShouldBe("/path/to/store.secrets");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StorePath_DefaultsToNull()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new TreeNodeViewModel("Store", "🔒", TreeNodeType.SecureStore);
|
||||
|
||||
// Assert
|
||||
sut.StorePath.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SecretKey_CanBeSetViaInitializer()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new TreeNodeViewModel("MySecret", "🔑", TreeNodeType.Secret)
|
||||
{
|
||||
SecretKey = "ConnectionStrings:Database"
|
||||
};
|
||||
|
||||
// Assert
|
||||
sut.SecretKey.ShouldBe("ConnectionStrings:Database");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SecretKey_DefaultsToNull()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new TreeNodeViewModel("MySecret", "🔑", TreeNodeType.Secret);
|
||||
|
||||
// Assert
|
||||
sut.SecretKey.ShouldBeNull();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user