e5fe2f06e9
Add ConfigurationValidationRunner with IConfigurationValidator interface for validating required settings at startup. Includes SecureStore and LDAP validators. Expand ConfigManager with pipeline editing UI, dialogs, and step editors. Update documentation with config validation guidance.
243 lines
6.5 KiB
C#
243 lines
6.5 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
|
|
};
|
|
|
|
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());
|
|
}
|
|
}
|