Files
Joseph Doherty e5fe2f06e9 feat: add startup config validation and document ConfigManager pipeline editor
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.
2026-01-21 17:47:15 -05:00

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());
}
}