604bfe919c
Apply comprehensive fixes from code reviews including: - Extract shared utilities (SqlFormatHelper, CellValueConverter, DbDestinationBase) - Add interface abstractions (IAuthenticationService, IDatabaseMigrator, IMisQueryBuilder) - Implement SecureStore for encrypted secrets storage - Fix error handling with proper HTTP status codes and logging - Optimize double enumeration in DevEtlRegistry - Add DataSync.Dev README for developer onboarding - Extract filter panel base classes to reduce duplication - Update code review docs to mark all issues as fixed
289 lines
8.0 KiB
C#
289 lines
8.0 KiB
C#
using NSubstitute;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using JdeScoping.SecureStoreManager.Services;
|
|
using JdeScoping.SecureStoreManager.ViewModels;
|
|
|
|
namespace JdeScoping.SecureStoreManager.Tests.ViewModels;
|
|
|
|
public class MainWindowViewModelTests
|
|
{
|
|
private readonly ISecureStoreManager _mockStoreManager;
|
|
private readonly MainWindowViewModel _sut;
|
|
|
|
public MainWindowViewModelTests()
|
|
{
|
|
_mockStoreManager = Substitute.For<ISecureStoreManager>();
|
|
_sut = new MainWindowViewModel(_mockStoreManager);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_InitializesEmptySecretsCollection()
|
|
{
|
|
_sut.Secrets.ShouldNotBeNull();
|
|
_sut.Secrets.Count.ShouldBe(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void IsStoreOpen_DelegatesToStoreManager()
|
|
{
|
|
// Arrange
|
|
_mockStoreManager.IsStoreOpen.Returns(true);
|
|
|
|
// Act & Assert
|
|
_sut.IsStoreOpen.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void HasUnsavedChanges_DelegatesToStoreManager()
|
|
{
|
|
// Arrange
|
|
_mockStoreManager.HasUnsavedChanges.Returns(true);
|
|
|
|
// Act & Assert
|
|
_sut.HasUnsavedChanges.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void WindowTitle_WhenNoStoreOpen_ReturnsBasicTitle()
|
|
{
|
|
// Arrange
|
|
_mockStoreManager.IsStoreOpen.Returns(false);
|
|
|
|
// Act & Assert
|
|
_sut.WindowTitle.ShouldBe("SecureStore Manager");
|
|
}
|
|
|
|
[Fact]
|
|
public void WindowTitle_WhenStoreOpen_IncludesPath()
|
|
{
|
|
// Arrange
|
|
_mockStoreManager.IsStoreOpen.Returns(true);
|
|
_mockStoreManager.CurrentStorePath.Returns("/path/to/store.json");
|
|
_mockStoreManager.HasUnsavedChanges.Returns(false);
|
|
|
|
// Act & Assert
|
|
_sut.WindowTitle.ShouldBe("SecureStore Manager - /path/to/store.json");
|
|
}
|
|
|
|
[Fact]
|
|
public void WindowTitle_WhenUnsavedChanges_IncludesAsterisk()
|
|
{
|
|
// Arrange
|
|
_mockStoreManager.IsStoreOpen.Returns(true);
|
|
_mockStoreManager.CurrentStorePath.Returns("/path/to/store.json");
|
|
_mockStoreManager.HasUnsavedChanges.Returns(true);
|
|
|
|
// Act & Assert
|
|
_sut.WindowTitle.ShouldBe("SecureStore Manager - /path/to/store.json *");
|
|
}
|
|
|
|
[Fact]
|
|
public void StatusMessage_DefaultsToReady()
|
|
{
|
|
_sut.StatusMessage.ShouldBe("Ready");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateNewStoreAsync_WithKeyFile_CallsStoreManagerCreateStore()
|
|
{
|
|
// Arrange
|
|
var storePath = "/path/to/store.json";
|
|
var keyPath = "/path/to/key.key";
|
|
_mockStoreManager.GetKeys().Returns(new List<string>().AsReadOnly());
|
|
|
|
// Act
|
|
await _sut.CreateNewStoreAsync(storePath, keyPath, null);
|
|
|
|
// Assert
|
|
_mockStoreManager.Received(1).CreateStore(storePath, keyPath);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateNewStoreAsync_WithPassword_CallsStoreManagerCreateStoreWithPassword()
|
|
{
|
|
// Arrange
|
|
var storePath = "/path/to/store.json";
|
|
var password = "password123";
|
|
_mockStoreManager.GetKeys().Returns(new List<string>().AsReadOnly());
|
|
|
|
// Act
|
|
await _sut.CreateNewStoreAsync(storePath, null, password);
|
|
|
|
// Assert
|
|
_mockStoreManager.Received(1).CreateStoreWithPassword(storePath, password);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task OpenExistingStoreAsync_WithKeyFile_CallsStoreManagerOpenStore()
|
|
{
|
|
// Arrange
|
|
var storePath = "/path/to/store.json";
|
|
var keyPath = "/path/to/key.key";
|
|
_mockStoreManager.GetKeys().Returns(new List<string>().AsReadOnly());
|
|
|
|
// Act
|
|
await _sut.OpenExistingStoreAsync(storePath, keyPath, null);
|
|
|
|
// Assert
|
|
_mockStoreManager.Received(1).OpenStore(storePath, keyPath);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task OpenExistingStoreAsync_WithPassword_CallsStoreManagerOpenStoreWithPassword()
|
|
{
|
|
// Arrange
|
|
var storePath = "/path/to/store.json";
|
|
var password = "password123";
|
|
_mockStoreManager.GetKeys().Returns(new List<string>().AsReadOnly());
|
|
|
|
// Act
|
|
await _sut.OpenExistingStoreAsync(storePath, null, password);
|
|
|
|
// Assert
|
|
_mockStoreManager.Received(1).OpenStoreWithPassword(storePath, password);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SaveSecretAsync_CallsStoreManagerSetSecret()
|
|
{
|
|
// Arrange
|
|
_mockStoreManager.IsStoreOpen.Returns(true);
|
|
_mockStoreManager.GetKeys().Returns(new List<string> { "testKey" }.AsReadOnly());
|
|
_mockStoreManager.GetSecret("testKey").Returns("testValue");
|
|
|
|
// Act
|
|
await _sut.SaveSecretAsync("testKey", "testValue", isNew: true);
|
|
|
|
// Assert
|
|
_mockStoreManager.Received(1).SetSecret("testKey", "testValue");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SaveSecretAsync_RefreshesSecretsCollection()
|
|
{
|
|
// Arrange
|
|
_mockStoreManager.IsStoreOpen.Returns(true);
|
|
_mockStoreManager.GetKeys().Returns(new List<string> { "key1", "key2" }.AsReadOnly());
|
|
_mockStoreManager.GetSecret("key1").Returns("value1");
|
|
_mockStoreManager.GetSecret("key2").Returns("value2");
|
|
|
|
// Act
|
|
await _sut.SaveSecretAsync("key1", "value1", isNew: true);
|
|
|
|
// Assert
|
|
_sut.Secrets.Count.ShouldBe(2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PromptForUnsavedChangesAsync_WhenNoChanges_ReturnsTrue()
|
|
{
|
|
// Arrange
|
|
_mockStoreManager.HasUnsavedChanges.Returns(false);
|
|
|
|
// Act
|
|
var result = await _sut.PromptForUnsavedChangesAsync();
|
|
|
|
// Assert
|
|
result.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void SaveCommand_CanExecute_WhenStoreOpenWithChanges()
|
|
{
|
|
// Arrange
|
|
_mockStoreManager.IsStoreOpen.Returns(true);
|
|
_mockStoreManager.HasUnsavedChanges.Returns(true);
|
|
|
|
// Act
|
|
var canExecute = _sut.SaveCommand.CanExecute(null);
|
|
|
|
// Assert
|
|
canExecute.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void SaveCommand_CannotExecute_WhenNoChanges()
|
|
{
|
|
// Arrange
|
|
_mockStoreManager.IsStoreOpen.Returns(true);
|
|
_mockStoreManager.HasUnsavedChanges.Returns(false);
|
|
|
|
// Act
|
|
var canExecute = _sut.SaveCommand.CanExecute(null);
|
|
|
|
// Assert
|
|
canExecute.ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void AddSecretCommand_CanExecute_WhenStoreOpen()
|
|
{
|
|
// Arrange
|
|
_mockStoreManager.IsStoreOpen.Returns(true);
|
|
|
|
// Act
|
|
var canExecute = _sut.AddSecretCommand.CanExecute(null);
|
|
|
|
// Assert
|
|
canExecute.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void AddSecretCommand_CannotExecute_WhenStoreNotOpen()
|
|
{
|
|
// Arrange
|
|
_mockStoreManager.IsStoreOpen.Returns(false);
|
|
|
|
// Act
|
|
var canExecute = _sut.AddSecretCommand.CanExecute(null);
|
|
|
|
// Assert
|
|
canExecute.ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void CloseStoreCommand_CanExecute_WhenStoreOpen()
|
|
{
|
|
// Arrange
|
|
_mockStoreManager.IsStoreOpen.Returns(true);
|
|
|
|
// Act
|
|
var canExecute = _sut.CloseStoreCommand.CanExecute(null);
|
|
|
|
// Assert
|
|
canExecute.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void CloseStoreCommand_CannotExecute_WhenStoreNotOpen()
|
|
{
|
|
// Arrange
|
|
_mockStoreManager.IsStoreOpen.Returns(false);
|
|
|
|
// Act
|
|
var canExecute = _sut.CloseStoreCommand.CanExecute(null);
|
|
|
|
// Assert
|
|
canExecute.ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void SelectedSecret_SetRaisesPropertyChanged()
|
|
{
|
|
// Arrange
|
|
var propertyChangedRaised = false;
|
|
_sut.PropertyChanged += (s, e) =>
|
|
{
|
|
if (e.PropertyName == nameof(_sut.SelectedSecret))
|
|
propertyChangedRaised = true;
|
|
};
|
|
|
|
// Act
|
|
_sut.SelectedSecret = new SecretItemViewModel("key", "value");
|
|
|
|
// Assert
|
|
propertyChangedRaised.ShouldBeTrue();
|
|
}
|
|
}
|