chore: deprecate standalone SecureStoreManager utility
Move SecureStoreManager project and tests to Deprecated folder and remove from solution. SecureStore functionality is now integrated into ConfigManager.
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.Headless.XUnit" Version="11.2.*" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.*" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Utils\JdeScoping.SecureStoreManager\JdeScoping.SecureStoreManager.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,299 @@
|
||||
using System.IO;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using JdeScoping.SecureStoreManager.Services;
|
||||
|
||||
namespace JdeScoping.SecureStoreManager.Tests.Services;
|
||||
|
||||
public class SecureStoreManagerTests : IDisposable
|
||||
{
|
||||
private readonly string _testDirectory;
|
||||
private readonly SecureStoreManager.Services.SecureStoreManager _sut;
|
||||
|
||||
public SecureStoreManagerTests()
|
||||
{
|
||||
_testDirectory = Path.Combine(Path.GetTempPath(), $"SecureStoreTests_{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(_testDirectory);
|
||||
_sut = new SecureStoreManager.Services.SecureStoreManager();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_sut.Dispose();
|
||||
if (Directory.Exists(_testDirectory))
|
||||
{
|
||||
Directory.Delete(_testDirectory, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsStoreOpen_WhenNoStoreOpen_ReturnsFalse()
|
||||
{
|
||||
_sut.IsStoreOpen.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CurrentStorePath_WhenNoStoreOpen_ReturnsNull()
|
||||
{
|
||||
_sut.CurrentStorePath.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasUnsavedChanges_WhenNoStoreOpen_ReturnsFalse()
|
||||
{
|
||||
_sut.HasUnsavedChanges.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateStore_WithKeyFile_CreatesStoreAndKeyFile()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
|
||||
// Act
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
|
||||
// Assert
|
||||
_sut.IsStoreOpen.ShouldBeTrue();
|
||||
_sut.CurrentStorePath.ShouldBe(storePath);
|
||||
File.Exists(storePath).ShouldBeTrue();
|
||||
File.Exists(keyPath).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenStore_WithValidKeyFile_OpensStore()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
_sut.CloseStore();
|
||||
|
||||
// Act
|
||||
_sut.OpenStore(storePath, keyPath);
|
||||
|
||||
// Assert
|
||||
_sut.IsStoreOpen.ShouldBeTrue();
|
||||
_sut.CurrentStorePath.ShouldBe(storePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenStore_WithNonExistentStore_ThrowsFileNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "nonexistent.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<FileNotFoundException>(() => _sut.OpenStore(storePath, keyPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloseStore_ClosesOpenStore()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
|
||||
// Act
|
||||
_sut.CloseStore();
|
||||
|
||||
// Assert
|
||||
_sut.IsStoreOpen.ShouldBeFalse();
|
||||
_sut.CurrentStorePath.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetSecret_AddsSecretAndMarksUnsaved()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
_sut.Save(); // Save to clear unsaved flag
|
||||
|
||||
// Act
|
||||
_sut.SetSecret("testKey", "testValue");
|
||||
|
||||
// Assert
|
||||
_sut.HasUnsavedChanges.ShouldBeTrue();
|
||||
_sut.GetKeys().ShouldContain("testKey");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSecret_ReturnsCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
_sut.SetSecret("testKey", "testValue");
|
||||
|
||||
// Act
|
||||
var value = _sut.GetSecret("testKey");
|
||||
|
||||
// Assert
|
||||
value.ShouldBe("testValue");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSecret_WhenKeyNotFound_ThrowsKeyNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<KeyNotFoundException>(() => _sut.GetSecret("nonexistent"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveSecret_RemovesSecretAndMarksUnsaved()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
_sut.SetSecret("testKey", "testValue");
|
||||
_sut.Save();
|
||||
|
||||
// Act
|
||||
_sut.RemoveSecret("testKey");
|
||||
|
||||
// Assert
|
||||
_sut.HasUnsavedChanges.ShouldBeTrue();
|
||||
_sut.GetKeys().ShouldNotContain("testKey");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveSecret_WhenKeyNotFound_ThrowsKeyNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<KeyNotFoundException>(() => _sut.RemoveSecret("nonexistent"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Save_PersistsSecretsToStore()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
_sut.SetSecret("testKey", "testValue");
|
||||
|
||||
// Act
|
||||
_sut.Save();
|
||||
_sut.CloseStore();
|
||||
_sut.OpenStore(storePath, keyPath);
|
||||
|
||||
// Assert
|
||||
_sut.GetKeys().ShouldContain("testKey");
|
||||
_sut.GetSecret("testKey").ShouldBe("testValue");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Save_ClearsUnsavedChangesFlag()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
_sut.SetSecret("testKey", "testValue");
|
||||
_sut.HasUnsavedChanges.ShouldBeTrue();
|
||||
|
||||
// Act
|
||||
_sut.Save();
|
||||
|
||||
// Assert
|
||||
_sut.HasUnsavedChanges.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetKeys_ReturnsAllSecretKeys()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
_sut.SetSecret("key1", "value1");
|
||||
_sut.SetSecret("key2", "value2");
|
||||
_sut.SetSecret("key3", "value3");
|
||||
|
||||
// Act
|
||||
var keys = _sut.GetKeys();
|
||||
|
||||
// Assert
|
||||
keys.Count.ShouldBe(3);
|
||||
keys.ShouldContain("key1");
|
||||
keys.ShouldContain("key2");
|
||||
keys.ShouldContain("key3");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateKeyFile_CreatesNewKeyFile()
|
||||
{
|
||||
// Arrange
|
||||
var keyPath = Path.Combine(_testDirectory, "generated.key");
|
||||
|
||||
// Act
|
||||
_sut.GenerateKeyFile(keyPath);
|
||||
|
||||
// Assert
|
||||
File.Exists(keyPath).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExportKey_WhenNoStoreOpen_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var keyPath = Path.Combine(_testDirectory, "export.key");
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<InvalidOperationException>(() => _sut.ExportKey(keyPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExportKey_WhenStoreOpen_ExportsKey()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var keyPath = Path.Combine(_testDirectory, "test.key");
|
||||
var exportPath = Path.Combine(_testDirectory, "export.key");
|
||||
_sut.CreateStore(storePath, keyPath);
|
||||
|
||||
// Act
|
||||
_sut.ExportKey(exportPath);
|
||||
|
||||
// Assert
|
||||
File.Exists(exportPath).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetKeys_WhenNoStoreOpen_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<InvalidOperationException>(() => _sut.GetKeys());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetSecret_WhenNoStoreOpen_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<InvalidOperationException>(() => _sut.SetSecret("key", "value"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSecret_WhenNoStoreOpen_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<InvalidOperationException>(() => _sut.GetSecret("key"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Headless;
|
||||
using JdeScoping.SecureStoreManager;
|
||||
|
||||
[assembly: AvaloniaTestApplication(typeof(JdeScoping.SecureStoreManager.Tests.TestAppBuilder))]
|
||||
|
||||
namespace JdeScoping.SecureStoreManager.Tests;
|
||||
|
||||
public class TestAppBuilder
|
||||
{
|
||||
public static AppBuilder BuildAvaloniaApp() =>
|
||||
AppBuilder.Configure<App>()
|
||||
.UseHeadless(new AvaloniaHeadlessPlatformOptions());
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using JdeScoping.SecureStoreManager.ViewModels;
|
||||
|
||||
namespace JdeScoping.SecureStoreManager.Tests.ViewModels;
|
||||
|
||||
public class NewStoreDialogViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void IsValid_WhenStorePathEmpty_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = string.Empty;
|
||||
sut.KeyFilePath = "/path/to/key.key";
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WhenKeyFilePathEmpty_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.KeyFilePath = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WithValidConfiguration_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.KeyFilePath = "/path/to/key.key";
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationError_WhenStorePathEmpty_ReturnsAppropriateMessage()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe("Store path is required.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationError_WhenKeyFilePathEmpty_ReturnsAppropriateMessage()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.KeyFilePath = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe("Key file path is required.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationError_WhenValid_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.KeyFilePath = "/path/to/key.key";
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
public class OpenStoreDialogViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void IsValid_WhenStorePathEmpty_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new OpenStoreDialogViewModel();
|
||||
sut.StorePath = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WhenKeyFilePathEmpty_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new OpenStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.KeyFilePath = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationError_WhenStorePathEmpty_ReturnsAppropriateMessage()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new OpenStoreDialogViewModel();
|
||||
sut.StorePath = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe("Store path is required.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationError_WhenStoreFileDoesNotExist_ReturnsAppropriateMessage()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new OpenStoreDialogViewModel();
|
||||
sut.StorePath = "/nonexistent/path/to/store.json";
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe("Store file does not exist.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationError_WhenKeyFilePathEmpty_ReturnsAppropriateMessage()
|
||||
{
|
||||
// Arrange - Create temp store file so we get past that validation
|
||||
var tempFile = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
var sut = new OpenStoreDialogViewModel();
|
||||
sut.StorePath = tempFile;
|
||||
sut.KeyFilePath = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe("Key file path is required.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationError_WhenKeyFileDoesNotExist_ReturnsAppropriateMessage()
|
||||
{
|
||||
// Arrange - Create temp store file so we get past that validation
|
||||
var tempFile = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
var sut = new OpenStoreDialogViewModel();
|
||||
sut.StorePath = tempFile;
|
||||
sut.KeyFilePath = "/nonexistent/key.key";
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe("Key file does not exist.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SecretEditDialogViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void DefaultConstructor_SetsIsNewSecretToTrue()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
|
||||
// Assert
|
||||
sut.IsNewSecret.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParameterizedConstructor_SetsKey()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretEditDialogViewModel("testKey", "testValue");
|
||||
|
||||
// Assert
|
||||
sut.Key.ShouldBe("testKey");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParameterizedConstructor_SetsValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretEditDialogViewModel("testKey", "testValue");
|
||||
|
||||
// Assert
|
||||
sut.Value.ShouldBe("testValue");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParameterizedConstructor_SetsIsNewSecretToFalse()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretEditDialogViewModel("testKey", "testValue");
|
||||
|
||||
// Assert
|
||||
sut.IsNewSecret.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsKeyEditable_WhenIsNewSecret_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
|
||||
// Act & Assert
|
||||
sut.IsKeyEditable.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsKeyEditable_WhenEditingExisting_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel("testKey", "testValue");
|
||||
|
||||
// Act & Assert
|
||||
sut.IsKeyEditable.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DialogTitle_WhenIsNewSecret_ReturnsAddSecret()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
|
||||
// Act & Assert
|
||||
sut.DialogTitle.ShouldBe("Add Secret");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DialogTitle_WhenEditingExisting_ReturnsEditSecret()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel("testKey", "testValue");
|
||||
|
||||
// Act & Assert
|
||||
sut.DialogTitle.ShouldBe("Edit Secret");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WhenKeyEmpty_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
sut.Key = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WhenKeyWhitespace_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
sut.Key = " ";
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WhenKeyProvided_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
sut.Key = "validKey";
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationError_WhenKeyEmpty_ReturnsAppropriateMessage()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
sut.Key = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe("Key is required.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationError_WhenKeyProvided_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
sut.Key = "validKey";
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Key_SetterRaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(sut.Key))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.Key = "newKey";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Value_SetterRaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretEditDialogViewModel();
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(sut.Value))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.Value = "newValue";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
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 IDialogService _mockDialogService;
|
||||
private readonly IClipboardService _mockClipboardService;
|
||||
private readonly MainWindowViewModel _sut;
|
||||
|
||||
public MainWindowViewModelTests()
|
||||
{
|
||||
_mockStoreManager = Substitute.For<ISecureStoreManager>();
|
||||
_mockDialogService = Substitute.For<IDialogService>();
|
||||
_mockClipboardService = Substitute.For<IClipboardService>();
|
||||
_sut = new MainWindowViewModel(_mockStoreManager, _mockDialogService, _mockClipboardService);
|
||||
}
|
||||
|
||||
[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);
|
||||
|
||||
// Assert
|
||||
_mockStoreManager.Received(1).CreateStore(storePath, keyPath);
|
||||
}
|
||||
|
||||
[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);
|
||||
|
||||
// Assert
|
||||
_mockStoreManager.Received(1).OpenStore(storePath, keyPath);
|
||||
}
|
||||
|
||||
[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", _mockClipboardService);
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using JdeScoping.SecureStoreManager.ViewModels;
|
||||
|
||||
namespace JdeScoping.SecureStoreManager.Tests.ViewModels;
|
||||
|
||||
public class RelayCommandTests
|
||||
{
|
||||
[Fact]
|
||||
public void Execute_CallsAction()
|
||||
{
|
||||
// Arrange
|
||||
var wasCalled = false;
|
||||
var sut = new RelayCommand(() => wasCalled = true);
|
||||
|
||||
// Act
|
||||
sut.Execute(null);
|
||||
|
||||
// Assert
|
||||
wasCalled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_PassesParameterToAction()
|
||||
{
|
||||
// Arrange
|
||||
object? receivedParameter = null;
|
||||
var sut = new RelayCommand(p => receivedParameter = p);
|
||||
|
||||
// Act
|
||||
sut.Execute("testParam");
|
||||
|
||||
// Assert
|
||||
receivedParameter.ShouldBe("testParam");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanExecute_WhenNoPredicate_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new RelayCommand(() => { });
|
||||
|
||||
// Act
|
||||
var result = sut.CanExecute(null);
|
||||
|
||||
// Assert
|
||||
result.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanExecute_WhenPredicateReturnsTrue_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new RelayCommand(() => { }, () => true);
|
||||
|
||||
// Act
|
||||
var result = sut.CanExecute(null);
|
||||
|
||||
// Assert
|
||||
result.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanExecute_WhenPredicateReturnsFalse_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new RelayCommand(() => { }, () => false);
|
||||
|
||||
// Act
|
||||
var result = sut.CanExecute(null);
|
||||
|
||||
// Assert
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanExecute_WithParameterPredicate_EvaluatesParameter()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new RelayCommand(_ => { }, p => p is string s && s == "valid");
|
||||
|
||||
// Act
|
||||
var validResult = sut.CanExecute("valid");
|
||||
var invalidResult = sut.CanExecute("invalid");
|
||||
|
||||
// Assert
|
||||
validResult.ShouldBeTrue();
|
||||
invalidResult.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RaiseCanExecuteChanged_FiresCanExecuteChangedEvent()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new RelayCommand(() => { });
|
||||
var eventFired = false;
|
||||
sut.CanExecuteChanged += (s, e) => eventFired = true;
|
||||
|
||||
// Act
|
||||
sut.RaiseCanExecuteChanged();
|
||||
|
||||
// Assert
|
||||
eventFired.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RaiseCanExecuteChanged_EventSenderIsSut()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new RelayCommand(() => { });
|
||||
object? eventSender = null;
|
||||
sut.CanExecuteChanged += (s, e) => eventSender = s;
|
||||
|
||||
// Act
|
||||
sut.RaiseCanExecuteChanged();
|
||||
|
||||
// Assert
|
||||
eventSender.ShouldBe(sut);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullExecute_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new RelayCommand((Action<object?>)null!));
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
using NSubstitute;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using JdeScoping.SecureStoreManager.Services;
|
||||
using JdeScoping.SecureStoreManager.ViewModels;
|
||||
|
||||
namespace JdeScoping.SecureStoreManager.Tests.ViewModels;
|
||||
|
||||
public class SecretItemViewModelTests
|
||||
{
|
||||
private readonly IClipboardService _mockClipboardService;
|
||||
|
||||
public SecretItemViewModelTests()
|
||||
{
|
||||
_mockClipboardService = Substitute.For<IClipboardService>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_InitializesKey()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
|
||||
// Assert
|
||||
sut.Key.ShouldBe("testKey");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_InitializesActualValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
|
||||
// Assert
|
||||
sut.ActualValue.ShouldBe("testValue");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_InitializesIsValueVisibleToFalse()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
|
||||
// Assert
|
||||
sut.IsValueVisible.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayValue_WhenNotVisible_ReturnsMaskedValue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
|
||||
// Act & Assert
|
||||
sut.DisplayValue.ShouldBe("********");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayValue_WhenVisible_ReturnsActualValue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
|
||||
// Act
|
||||
sut.IsValueVisible = true;
|
||||
|
||||
// Assert
|
||||
sut.DisplayValue.ShouldBe("testValue");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToggleVisibilityCommand_TogglesIsValueVisible()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
sut.IsValueVisible.ShouldBeFalse();
|
||||
|
||||
// Act
|
||||
sut.ToggleVisibilityCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
sut.IsValueVisible.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToggleVisibilityCommand_TogglesBackToHidden()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
sut.IsValueVisible = true;
|
||||
|
||||
// Act
|
||||
sut.ToggleVisibilityCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
sut.IsValueVisible.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CopyToClipboardCommand_CallsClipboardService()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "secretPassword", _mockClipboardService);
|
||||
|
||||
// Act
|
||||
sut.CopyToClipboardCommand.Execute(null);
|
||||
|
||||
// Assert - need to wait for async handler
|
||||
// Give the async void handler time to complete
|
||||
await Task.Delay(100);
|
||||
await _mockClipboardService.Received(1).SetTextAsync("secretPassword");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValueVisible_SetterRaisesPropertyChangedForIsValueVisible()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(sut.IsValueVisible))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.IsValueVisible = true;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValueVisible_SetterRaisesPropertyChangedForDisplayValue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(sut.DisplayValue))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.IsValueVisible = true;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValueVisible_SetToSameValue_DoesNotRaisePropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new SecretItemViewModel("testKey", "testValue", _mockClipboardService);
|
||||
sut.IsValueVisible = false; // Already false, but explicitly set
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.IsValueVisible = false;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using JdeScoping.SecureStoreManager.ViewModels;
|
||||
|
||||
namespace JdeScoping.SecureStoreManager.Tests.ViewModels;
|
||||
|
||||
public class ViewModelBaseTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test implementation of ViewModelBase for testing purposes.
|
||||
/// </summary>
|
||||
private class TestViewModel : ViewModelBase
|
||||
{
|
||||
private string _testProperty = string.Empty;
|
||||
private int _testIntProperty;
|
||||
|
||||
public string TestProperty
|
||||
{
|
||||
get => _testProperty;
|
||||
set => SetProperty(ref _testProperty, value);
|
||||
}
|
||||
|
||||
public int TestIntProperty
|
||||
{
|
||||
get => _testIntProperty;
|
||||
set => SetProperty(ref _testIntProperty, value);
|
||||
}
|
||||
|
||||
public void RaisePropertyChangedForTest(string propertyName)
|
||||
{
|
||||
OnPropertyChanged(propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetProperty_WhenValueUnchanged_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new TestViewModel();
|
||||
sut.TestProperty = "initialValue";
|
||||
|
||||
// Act
|
||||
sut.TestProperty = "initialValue"; // Same value
|
||||
|
||||
// Assert - SetProperty should have returned false, but we can verify by checking no event was raised
|
||||
// We test this indirectly through the property changed event not firing
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetProperty_WhenValueUnchanged_DoesNotRaisePropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new TestViewModel();
|
||||
sut.TestProperty = "initialValue";
|
||||
var eventRaised = false;
|
||||
sut.PropertyChanged += (s, e) => eventRaised = true;
|
||||
|
||||
// Act
|
||||
sut.TestProperty = "initialValue"; // Same value
|
||||
|
||||
// Assert
|
||||
eventRaised.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetProperty_WhenValueChanged_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new TestViewModel();
|
||||
|
||||
// Act
|
||||
sut.TestProperty = "newValue";
|
||||
|
||||
// Assert - SetProperty returned true (verified by property change being made)
|
||||
sut.TestProperty.ShouldBe("newValue");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetProperty_WhenValueChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new TestViewModel();
|
||||
var eventRaised = false;
|
||||
sut.PropertyChanged += (s, e) => eventRaised = true;
|
||||
|
||||
// Act
|
||||
sut.TestProperty = "newValue";
|
||||
|
||||
// Assert
|
||||
eventRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetProperty_RaisesEventWithCorrectPropertyName()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new TestViewModel();
|
||||
string? raisedPropertyName = null;
|
||||
sut.PropertyChanged += (s, e) => raisedPropertyName = e.PropertyName;
|
||||
|
||||
// Act
|
||||
sut.TestProperty = "newValue";
|
||||
|
||||
// Assert
|
||||
raisedPropertyName.ShouldBe(nameof(TestViewModel.TestProperty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetProperty_RaisesEventWithSenderAsThis()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new TestViewModel();
|
||||
object? eventSender = null;
|
||||
sut.PropertyChanged += (s, e) => eventSender = s;
|
||||
|
||||
// Act
|
||||
sut.TestProperty = "newValue";
|
||||
|
||||
// Assert
|
||||
eventSender.ShouldBe(sut);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnPropertyChanged_RaisesPropertyChangedEvent()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new TestViewModel();
|
||||
var eventRaised = false;
|
||||
sut.PropertyChanged += (s, e) => eventRaised = true;
|
||||
|
||||
// Act
|
||||
sut.RaisePropertyChangedForTest("SomeProperty");
|
||||
|
||||
// Assert
|
||||
eventRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnPropertyChanged_RaisesEventWithCorrectPropertyName()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new TestViewModel();
|
||||
string? raisedPropertyName = null;
|
||||
sut.PropertyChanged += (s, e) => raisedPropertyName = e.PropertyName;
|
||||
|
||||
// Act
|
||||
sut.RaisePropertyChangedForTest("CustomProperty");
|
||||
|
||||
// Assert
|
||||
raisedPropertyName.ShouldBe("CustomProperty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetProperty_WithValueType_WorksCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new TestViewModel();
|
||||
var eventRaised = false;
|
||||
sut.PropertyChanged += (s, e) => eventRaised = true;
|
||||
|
||||
// Act
|
||||
sut.TestIntProperty = 42;
|
||||
|
||||
// Assert
|
||||
sut.TestIntProperty.ShouldBe(42);
|
||||
eventRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetProperty_WithSameValueType_DoesNotRaiseEvent()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new TestViewModel();
|
||||
sut.TestIntProperty = 42;
|
||||
var eventRaised = false;
|
||||
sut.PropertyChanged += (s, e) => eventRaised = true;
|
||||
|
||||
// Act
|
||||
sut.TestIntProperty = 42; // Same value
|
||||
|
||||
// Assert
|
||||
eventRaised.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Headless.XUnit;
|
||||
using Avalonia.VisualTree;
|
||||
using NSubstitute;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using JdeScoping.SecureStoreManager.Services;
|
||||
using JdeScoping.SecureStoreManager.ViewModels;
|
||||
using JdeScoping.SecureStoreManager.Views;
|
||||
|
||||
namespace JdeScoping.SecureStoreManager.Tests.Views;
|
||||
|
||||
public class MainWindowTests
|
||||
{
|
||||
private static MainWindowViewModel CreateViewModel(ISecureStoreManager? storeManager = null)
|
||||
{
|
||||
var mockStoreManager = storeManager ?? Substitute.For<ISecureStoreManager>();
|
||||
var mockDialogService = Substitute.For<IDialogService>();
|
||||
var mockClipboardService = Substitute.For<IClipboardService>();
|
||||
return new MainWindowViewModel(mockStoreManager, mockDialogService, mockClipboardService);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void MainWindow_ShowsWithCorrectDefaultTitle()
|
||||
{
|
||||
// Arrange
|
||||
var storeManager = Substitute.For<ISecureStoreManager>();
|
||||
storeManager.IsStoreOpen.Returns(false);
|
||||
var viewModel = CreateViewModel(storeManager);
|
||||
var window = new MainWindow { DataContext = viewModel };
|
||||
|
||||
// Act
|
||||
window.Show();
|
||||
|
||||
// Assert - Title is bound to WindowTitle which is "SecureStore Manager" when no store is open
|
||||
window.Title.ShouldBe("SecureStore Manager");
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void MainWindow_HasExpectedWidth()
|
||||
{
|
||||
// Arrange & Act
|
||||
var window = new MainWindow();
|
||||
window.Show();
|
||||
|
||||
// Assert
|
||||
window.Width.ShouldBe(800);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void MainWindow_HasExpectedHeight()
|
||||
{
|
||||
// Arrange & Act
|
||||
var window = new MainWindow();
|
||||
window.Show();
|
||||
|
||||
// Assert
|
||||
window.Height.ShouldBe(500);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void MainWindow_DataContextIsMainWindowViewModel()
|
||||
{
|
||||
// Arrange
|
||||
var viewModel = CreateViewModel();
|
||||
|
||||
// Act
|
||||
var window = new MainWindow { DataContext = viewModel };
|
||||
window.Show();
|
||||
|
||||
// Assert - DataContext is set via DI in production, but must be set manually in tests
|
||||
window.DataContext.ShouldBeOfType<MainWindowViewModel>();
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void MainWindow_ContainsSecretsDataGrid()
|
||||
{
|
||||
// Arrange & Act
|
||||
var window = new MainWindow();
|
||||
window.Show();
|
||||
|
||||
// Assert
|
||||
var dataGrid = window.FindDescendantOfType<DataGrid>();
|
||||
dataGrid.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void MainWindow_ContainsMenuBar()
|
||||
{
|
||||
// Arrange & Act
|
||||
var window = new MainWindow();
|
||||
window.Show();
|
||||
|
||||
// Assert
|
||||
var menu = window.FindDescendantOfType<Menu>();
|
||||
menu.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void MainWindow_ContainsToolbarButtons()
|
||||
{
|
||||
// Arrange & Act
|
||||
var window = new MainWindow();
|
||||
window.Show();
|
||||
|
||||
// Assert
|
||||
var buttons = window.GetVisualDescendants().OfType<Button>().ToList();
|
||||
buttons.Count.ShouldBeGreaterThan(0);
|
||||
|
||||
// Verify toolbar buttons exist with expected content
|
||||
buttons.Any(b => b.Content?.ToString() == "New").ShouldBeTrue();
|
||||
buttons.Any(b => b.Content?.ToString() == "Open").ShouldBeTrue();
|
||||
buttons.Any(b => b.Content?.ToString() == "Save").ShouldBeTrue();
|
||||
buttons.Any(b => b.Content?.ToString() == "Add").ShouldBeTrue();
|
||||
buttons.Any(b => b.Content?.ToString() == "Edit").ShouldBeTrue();
|
||||
buttons.Any(b => b.Content?.ToString() == "Delete").ShouldBeTrue();
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void MainWindow_NewButtonCommand_IsBoundToViewModel()
|
||||
{
|
||||
// Arrange
|
||||
var storeManager = Substitute.For<ISecureStoreManager>();
|
||||
var viewModel = CreateViewModel(storeManager);
|
||||
var window = new MainWindow { DataContext = viewModel };
|
||||
|
||||
// Act
|
||||
window.Show();
|
||||
|
||||
var buttons = window.GetVisualDescendants().OfType<Button>().ToList();
|
||||
var newButton = buttons.FirstOrDefault(b => b.Content?.ToString() == "New");
|
||||
|
||||
// Assert
|
||||
newButton.ShouldNotBeNull();
|
||||
newButton.Command.ShouldBe(viewModel.NewStoreCommand);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void MainWindow_StatusBar_DisplaysStatusMessage()
|
||||
{
|
||||
// Arrange
|
||||
var storeManager = Substitute.For<ISecureStoreManager>();
|
||||
var viewModel = CreateViewModel(storeManager);
|
||||
var window = new MainWindow { DataContext = viewModel };
|
||||
|
||||
// Act
|
||||
window.Show();
|
||||
|
||||
// Assert - Find status bar text blocks
|
||||
var textBlocks = window.GetVisualDescendants().OfType<TextBlock>().ToList();
|
||||
// Status message should be "Ready" by default
|
||||
textBlocks.Any(tb => tb.Text == viewModel.StatusMessage).ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Headless.XUnit;
|
||||
using Avalonia.VisualTree;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using JdeScoping.SecureStoreManager.ViewModels;
|
||||
using JdeScoping.SecureStoreManager.Views;
|
||||
|
||||
namespace JdeScoping.SecureStoreManager.Tests.Views;
|
||||
|
||||
public class NewStoreDialogTests
|
||||
{
|
||||
[AvaloniaFact]
|
||||
public void NewStoreDialog_ShowsWithCorrectTitle()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new NewStoreDialog();
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
dialog.Title.ShouldBe("Create New Store");
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void NewStoreDialog_DataContextIsNewStoreDialogViewModel()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new NewStoreDialog();
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
dialog.DataContext.ShouldBeOfType<NewStoreDialogViewModel>();
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void NewStoreDialog_HasStorePathTextBox()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new NewStoreDialog();
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
var textBoxes = dialog.GetVisualDescendants().OfType<TextBox>().ToList();
|
||||
textBoxes.Count.ShouldBeGreaterThan(0);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void NewStoreDialog_HasKeyFilePathTextBox()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new NewStoreDialog();
|
||||
dialog.Show();
|
||||
|
||||
// Assert - Should have at least 2 text boxes: store path and key file path
|
||||
var textBoxes = dialog.GetVisualDescendants().OfType<TextBox>().ToList();
|
||||
textBoxes.Count.ShouldBeGreaterThanOrEqualTo(2);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void NewStoreDialog_HasCreateAndCancelButtons()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new NewStoreDialog();
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
var buttons = dialog.GetVisualDescendants().OfType<Button>().ToList();
|
||||
buttons.Any(b => b.Content?.ToString() == "Create").ShouldBeTrue();
|
||||
buttons.Any(b => b.Content?.ToString() == "Cancel").ShouldBeTrue();
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void NewStoreDialog_ViewModelProperty_ReturnsDataContext()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new NewStoreDialog();
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
dialog.ViewModel.ShouldBe(dialog.DataContext);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void NewStoreDialog_CannotResize()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new NewStoreDialog();
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
dialog.CanResize.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Headless.XUnit;
|
||||
using Avalonia.VisualTree;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using JdeScoping.SecureStoreManager.ViewModels;
|
||||
using JdeScoping.SecureStoreManager.Views;
|
||||
|
||||
namespace JdeScoping.SecureStoreManager.Tests.Views;
|
||||
|
||||
public class SecretEditDialogTests
|
||||
{
|
||||
[AvaloniaFact]
|
||||
public void SecretEditDialog_DefaultConstructor_ShowsAddSecretTitle()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new SecretEditDialog();
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
dialog.Title.ShouldBe("Add Secret");
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void SecretEditDialog_WithKeyValueParams_ShowsEditSecretTitle()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new SecretEditDialog("existingKey", "existingValue");
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
dialog.Title.ShouldBe("Edit Secret");
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void SecretEditDialog_DataContextIsSecretEditDialogViewModel()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new SecretEditDialog();
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
dialog.DataContext.ShouldBeOfType<SecretEditDialogViewModel>();
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void SecretEditDialog_HasKeyAndValueTextBoxes()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new SecretEditDialog();
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
var textBoxes = dialog.GetVisualDescendants().OfType<TextBox>().ToList();
|
||||
textBoxes.Count.ShouldBeGreaterThanOrEqualTo(2);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void SecretEditDialog_HasSaveAndCancelButtons()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new SecretEditDialog();
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
var buttons = dialog.GetVisualDescendants().OfType<Button>().ToList();
|
||||
buttons.Any(b => b.Content?.ToString() == "Save").ShouldBeTrue();
|
||||
buttons.Any(b => b.Content?.ToString() == "Cancel").ShouldBeTrue();
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void SecretEditDialog_ViewModelProperty_ReturnsDataContext()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new SecretEditDialog();
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
dialog.ViewModel.ShouldBe(dialog.DataContext);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void SecretEditDialog_ParameterizedConstructor_SetsViewModelWithKey()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new SecretEditDialog("testKey", "testValue");
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
dialog.ViewModel.Key.ShouldBe("testKey");
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void SecretEditDialog_ParameterizedConstructor_SetsViewModelWithValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new SecretEditDialog("testKey", "testValue");
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
dialog.ViewModel.Value.ShouldBe("testValue");
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void SecretEditDialog_CannotResize()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new SecretEditDialog();
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
dialog.CanResize.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user