117 lines
4.4 KiB
C#
117 lines
4.4 KiB
C#
using System.Security.Cryptography;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using ZB.MOM.WW.Secrets.Abstractions;
|
|
using ZB.MOM.WW.Secrets.DependencyInjection;
|
|
using ZB.MOM.WW.Secrets.Sqlite;
|
|
|
|
namespace ZB.MOM.WW.Secrets.Tests.DependencyInjection;
|
|
|
|
/// <summary>
|
|
/// Verifies that <see cref="SecretsServiceCollectionExtensions.AddZbSecrets"/> wires the whole
|
|
/// core: every seam resolves, a real secret round-trips through the composed graph, and a missing
|
|
/// master key fails closed.
|
|
/// </summary>
|
|
public sealed class AddZbSecretsTests
|
|
{
|
|
private static string NewTempDbPath() =>
|
|
Path.Combine(Path.GetTempPath(), $"zb-secrets-di-{Guid.NewGuid():N}.db");
|
|
|
|
private static string Base64Key32() => Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
|
|
|
|
private static IConfiguration BuildConfig(string sqlitePath, string envVarName) =>
|
|
new ConfigurationBuilder()
|
|
.AddInMemoryCollection(new Dictionary<string, string?>
|
|
{
|
|
["Secrets:SqlitePath"] = sqlitePath,
|
|
["Secrets:MasterKey:Source"] = "Environment",
|
|
["Secrets:MasterKey:EnvVarName"] = envVarName,
|
|
})
|
|
.Build();
|
|
|
|
[Fact]
|
|
public void Registers_All_CoreServices()
|
|
{
|
|
string dbPath = NewTempDbPath();
|
|
string envVar = $"ZB_TEST_KEY_{Guid.NewGuid():N}";
|
|
Environment.SetEnvironmentVariable(envVar, Base64Key32());
|
|
try
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddZbSecrets(BuildConfig(dbPath, envVar), "Secrets");
|
|
using ServiceProvider provider = services.BuildServiceProvider();
|
|
|
|
Assert.NotNull(provider.GetService<ISecretResolver>());
|
|
Assert.NotNull(provider.GetService<ISecretStore>());
|
|
Assert.NotNull(provider.GetService<IMasterKeyProvider>());
|
|
Assert.NotNull(provider.GetService<ISecretCipher>());
|
|
|
|
ISecretReplicator? replicator = provider.GetService<ISecretReplicator>();
|
|
Assert.NotNull(replicator);
|
|
Assert.IsType<NoOpSecretReplicator>(replicator);
|
|
}
|
|
finally
|
|
{
|
|
Environment.SetEnvironmentVariable(envVar, null);
|
|
File.Delete(dbPath);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Resolver_Works_EndToEnd_ThroughDi()
|
|
{
|
|
string dbPath = NewTempDbPath();
|
|
string envVar = $"ZB_TEST_KEY_{Guid.NewGuid():N}";
|
|
Environment.SetEnvironmentVariable(envVar, Base64Key32());
|
|
try
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddZbSecrets(BuildConfig(dbPath, envVar), "Secrets");
|
|
using ServiceProvider provider = services.BuildServiceProvider();
|
|
|
|
// Run the migration first so the schema exists (as the hosted service would on startup).
|
|
await provider.GetRequiredService<SqliteSecretsStoreMigrator>().MigrateAsync(CancellationToken.None);
|
|
|
|
var name = new SecretName("db/password");
|
|
const string plaintext = "super-secret-value";
|
|
|
|
ISecretCipher cipher = provider.GetRequiredService<ISecretCipher>();
|
|
ISecretStore store = provider.GetRequiredService<ISecretStore>();
|
|
StoredSecret row = cipher.Encrypt(name, plaintext, SecretContentType.Text);
|
|
await store.UpsertAsync(row, CancellationToken.None);
|
|
|
|
ISecretResolver resolver = provider.GetRequiredService<ISecretResolver>();
|
|
string? resolved = await resolver.GetAsync(name, CancellationToken.None);
|
|
|
|
Assert.Equal(plaintext, resolved);
|
|
}
|
|
finally
|
|
{
|
|
Environment.SetEnvironmentVariable(envVar, null);
|
|
File.Delete(dbPath);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void MasterKey_Unavailable_FailsClosed()
|
|
{
|
|
string dbPath = NewTempDbPath();
|
|
string envVar = $"ZB_TEST_KEY_{Guid.NewGuid():N}";
|
|
// Deliberately do NOT set the env var — the master key cannot be resolved.
|
|
try
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddZbSecrets(BuildConfig(dbPath, envVar), "Secrets");
|
|
using ServiceProvider provider = services.BuildServiceProvider();
|
|
|
|
IMasterKeyProvider keyProvider = provider.GetRequiredService<IMasterKeyProvider>();
|
|
|
|
Assert.Throws<MasterKeyUnavailableException>(() => keyProvider.GetMasterKey());
|
|
}
|
|
finally
|
|
{
|
|
File.Delete(dbPath);
|
|
}
|
|
}
|
|
}
|