using JdeScoping.Core.Options; using JdeScoping.Infrastructure.Security; using Microsoft.Extensions.Logging.Abstractions; using Shouldly; namespace JdeScoping.Infrastructure.Tests.Security; public class SecureStoreServiceTests : IDisposable { private readonly string _testDir; private readonly string _storePath; private readonly string _keyPath; public SecureStoreServiceTests() { _testDir = Path.Combine(Path.GetTempPath(), $"securestore-test-{Guid.NewGuid()}"); Directory.CreateDirectory(_testDir); _storePath = Path.Combine(_testDir, "secrets.json"); _keyPath = Path.Combine(_testDir, "secrets.key"); } public void Dispose() { if (Directory.Exists(_testDir)) { try { Directory.Delete(_testDir, recursive: true); } catch { /* Ignore cleanup failures */ } } } private SecureStoreService CreateService(SecureStoreOptions? options = null) { options ??= new SecureStoreOptions { StorePath = _storePath, KeyFilePath = _keyPath, AutoCreateStore = true }; return new SecureStoreService( Microsoft.Extensions.Options.Options.Create(options), NullLogger.Instance); } [Fact] public void Constructor_WhenStoreDoesNotExist_CreatesStoreAndKeyFile() { // Arrange - ensure files don't exist File.Exists(_storePath).ShouldBeFalse(); File.Exists(_keyPath).ShouldBeFalse(); // Act using var service = CreateService(); // Assert File.Exists(_storePath).ShouldBeTrue(); File.Exists(_keyPath).ShouldBeTrue(); } [Fact] public void Constructor_WhenAutoCreateDisabledAndStoreDoesNotExist_Throws() { // Arrange var options = new SecureStoreOptions { StorePath = _storePath, KeyFilePath = _keyPath, AutoCreateStore = false }; // Act & Assert Should.Throw(() => CreateService(options)); } [Fact] public void SetAndGet_RoundTrip_ReturnsStoredValue() { // Arrange using var service = CreateService(); const string key = "TestSecret"; const string value = "MySecretValue123"; // Act service.Set(key, value); var retrieved = service.Get(key); // Assert retrieved.ShouldBe(value); } [Fact] public void Get_WhenKeyDoesNotExist_ReturnsNull() { // Arrange using var service = CreateService(); // Act var result = service.Get("NonExistentKey"); // Assert result.ShouldBeNull(); } [Fact] public void GetRequired_WhenKeyDoesNotExist_ThrowsKeyNotFoundException() { // Arrange using var service = CreateService(); // Act & Assert Should.Throw(() => service.GetRequired("NonExistentKey")); } [Fact] public void Contains_WhenKeyExists_ReturnsTrue() { // Arrange using var service = CreateService(); service.Set("ExistingKey", "value"); // Act var result = service.Contains("ExistingKey"); // Assert result.ShouldBeTrue(); } [Fact] public void Contains_WhenKeyDoesNotExist_ReturnsFalse() { // Arrange using var service = CreateService(); // Act var result = service.Contains("NonExistentKey"); // Assert result.ShouldBeFalse(); } [Fact] public void Remove_WhenKeyExists_ReturnsTrue() { // Arrange using var service = CreateService(); service.Set("KeyToRemove", "value"); // Act var result = service.Remove("KeyToRemove"); // Assert result.ShouldBeTrue(); service.Contains("KeyToRemove").ShouldBeFalse(); } [Fact] public void Remove_WhenKeyDoesNotExist_ReturnsFalse() { // Arrange using var service = CreateService(); // Act var result = service.Remove("NonExistentKey"); // Assert result.ShouldBeFalse(); } [Fact] public void Save_PersistsChanges_LoadableByNewInstance() { // Arrange const string key = "PersistedSecret"; const string value = "PersistedValue"; // Act - save with first instance using (var service1 = CreateService()) { service1.Set(key, value); service1.SaveWithMetadata(); } // Assert - load with second instance using var service2 = CreateService(); var retrieved = service2.Get(key); retrieved.ShouldBe(value); } [Fact] public void Keys_ReturnsAllStoredKeys() { // Arrange using var service = CreateService(); service.Set("Key1", "Value1"); service.Set("Key2", "Value2"); service.Set("Key3", "Value3"); // Act var keys = service.Keys.ToList(); // Assert keys.ShouldContain("Key1"); keys.ShouldContain("Key2"); keys.ShouldContain("Key3"); } [Fact] public void Dispose_AutoSavesDirtyChanges() { // Arrange const string key = "AutoSavedSecret"; const string value = "AutoSavedValue"; // Act - set and dispose (should auto-save) using (var service1 = CreateService()) { service1.Set(key, value); // Note: SaveWithMetadata needed for keys tracking service1.SaveWithMetadata(); } // Assert - load with second instance using var service2 = CreateService(); var retrieved = service2.Get(key); retrieved.ShouldBe(value); } [Fact] public void Operations_AfterDispose_ThrowObjectDisposedException() { // Arrange var service = CreateService(); service.Dispose(); // Act & Assert Should.Throw(() => service.Get("key")); Should.Throw(() => service.Set("key", "value")); Should.Throw(() => service.Contains("key")); Should.Throw(() => service.Remove("key")); Should.Throw(() => service.Save()); } }