using System.IO; using JdeScoping.ConfigManager.Services.SecureStore; namespace JdeScoping.ConfigManager.Tests.Services.SecureStore; public class SecureStoreManagerTests : IDisposable { private readonly string _testDirectory; private readonly SecureStoreManager _sut; public SecureStoreManagerTests() { _testDirectory = Path.Combine(Path.GetTempPath(), $"SecureStoreTests_{Guid.NewGuid():N}"); Directory.CreateDirectory(_testDirectory); _sut = new 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(() => _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(() => _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(() => _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(() => _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(() => _sut.GetKeys()); } [Fact] public void SetSecret_WhenNoStoreOpen_ThrowsInvalidOperationException() { // Act & Assert Should.Throw(() => _sut.SetSecret("key", "value")); } [Fact] public void GetSecret_WhenNoStoreOpen_ThrowsInvalidOperationException() { // Act & Assert Should.Throw(() => _sut.GetSecret("key")); } [Fact] public void EnsureAllRequiredEntries_AddsMissingKeys() { // Arrange var storePath = Path.Combine(_testDirectory, "test.json"); var keyPath = Path.Combine(_testDirectory, "test.key"); _sut.CreateStore(storePath, keyPath); _sut.SetSecret("existingKey", "existingValue"); _sut.Save(); // Act var addedKeys = _sut.EnsureAllRequiredEntries( new[] { "existingKey", "newKey1" }, new[] { "LotFinder", "JDE" }); // Assert addedKeys.Count.ShouldBe(3); addedKeys.ShouldContain("newKey1"); addedKeys.ShouldContain("LotFinder"); addedKeys.ShouldContain("JDE"); addedKeys.ShouldNotContain("existingKey"); // Verify all keys exist _sut.GetKeys().ShouldContain("existingKey"); _sut.GetKeys().ShouldContain("newKey1"); _sut.GetKeys().ShouldContain("LotFinder"); _sut.GetKeys().ShouldContain("JDE"); } [Fact] public void EnsureAllRequiredEntries_CreatesEmptyValues() { // Arrange var storePath = Path.Combine(_testDirectory, "test.json"); var keyPath = Path.Combine(_testDirectory, "test.key"); _sut.CreateStore(storePath, keyPath); // Act _sut.EnsureAllRequiredEntries( new[] { "newKey" }, Array.Empty()); // Assert _sut.GetSecret("newKey").ShouldBe(string.Empty); } [Fact] public void EnsureAllRequiredEntries_DoesNotOverwriteExistingValues() { // Arrange var storePath = Path.Combine(_testDirectory, "test.json"); var keyPath = Path.Combine(_testDirectory, "test.key"); _sut.CreateStore(storePath, keyPath); _sut.SetSecret("existingKey", "originalValue"); _sut.Save(); // Act var addedKeys = _sut.EnsureAllRequiredEntries( new[] { "existingKey" }, Array.Empty()); // Assert addedKeys.ShouldBeEmpty(); // Key already existed _sut.GetSecret("existingKey").ShouldBe("originalValue"); // Value preserved } [Fact] public void EnsureAllRequiredEntries_AutoSavesWhenKeysAdded() { // Arrange var storePath = Path.Combine(_testDirectory, "test.json"); var keyPath = Path.Combine(_testDirectory, "test.key"); _sut.CreateStore(storePath, keyPath); // Act _sut.EnsureAllRequiredEntries( new[] { "newKey" }, Array.Empty()); // Assert - should auto-save (HasUnsavedChanges should be false after calling EnsureAllRequiredEntries) _sut.HasUnsavedChanges.ShouldBeFalse(); // Verify persistence _sut.CloseStore(); _sut.OpenStore(storePath, keyPath); _sut.GetKeys().ShouldContain("newKey"); } [Fact] public void EnsureAllRequiredEntries_ReturnsEmptyList_WhenNoMissingKeys() { // 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.Save(); // Act var addedKeys = _sut.EnsureAllRequiredEntries( new[] { "key1" }, new[] { "key2" }); // Assert addedKeys.ShouldBeEmpty(); } [Fact] public void EnsureAllRequiredEntries_WhenNoStoreOpen_ThrowsInvalidOperationException() { // Act & Assert Should.Throw(() => _sut.EnsureAllRequiredEntries( new[] { "key" }, Array.Empty())); } [Fact] public void EnsureAllRequiredEntries_HandlesDuplicatesAcrossLists() { // Arrange var storePath = Path.Combine(_testDirectory, "test.json"); var keyPath = Path.Combine(_testDirectory, "test.key"); _sut.CreateStore(storePath, keyPath); // Act - same key in both lists should not cause issues var addedKeys = _sut.EnsureAllRequiredEntries( new[] { "sharedKey" }, new[] { "sharedKey" }); // Assert - should only add once addedKeys.Count.ShouldBe(1); addedKeys.ShouldContain("sharedKey"); } }