Files
jdescopingtool/NEW/tests/JdeScoping.Infrastructure.Tests/Security/SecureStoreServiceTests.cs
T
Joseph Doherty 604bfe919c refactor: address code review findings across all projects
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
2026-01-19 11:05:36 -05:00

244 lines
6.6 KiB
C#

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,
MigrateExistingSecrets = false
};
return new SecureStoreService(
Microsoft.Extensions.Options.Options.Create(options),
NullLogger<SecureStoreService>.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<InvalidOperationException>(() => 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<KeyNotFoundException>(() => 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<ObjectDisposedException>(() => service.Get("key"));
Should.Throw<ObjectDisposedException>(() => service.Set("key", "value"));
Should.Throw<ObjectDisposedException>(() => service.Contains("key"));
Should.Throw<ObjectDisposedException>(() => service.Remove("key"));
Should.Throw<ObjectDisposedException>(() => service.Save());
}
}