Files
scadaproj/ZB.MOM.WW.Auth/tests/ZB.MOM.WW.Auth.ApiKeys.Tests/ApiKeyServiceCollectionExtensionsTests.cs
T

201 lines
7.3 KiB
C#

using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.Auth.Abstractions.ApiKeys;
using ZB.MOM.WW.Auth.ApiKeys.DependencyInjection;
namespace ZB.MOM.WW.Auth.ApiKeys.Tests;
public class ApiKeyServiceCollectionExtensionsTests
{
private const string ApiKeySection = "Auth:ApiKeys";
private const string PepperSecretName = "ApiKeyPepper";
private const string PepperValue = "super-secret-pepper-value";
private static IConfiguration BuildConfiguration(string sqlitePath, bool runMigrationsOnStartup = false) =>
new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
[$"{ApiKeySection}:TokenPrefix"] = "mxgw",
[$"{ApiKeySection}:SqlitePath"] = sqlitePath,
[$"{ApiKeySection}:PepperSecretName"] = PepperSecretName,
[$"{ApiKeySection}:RunMigrationsOnStartup"] = runMigrationsOnStartup ? "true" : "false",
// The pepper itself lives at the top level under the configured secret name.
[PepperSecretName] = PepperValue,
})
.Build();
private static string TempSqlitePath() =>
Path.Combine(Path.GetTempPath(), $"zbauth-test-{Guid.NewGuid():N}.db");
[Fact]
public void AddZbApiKeyAuth_ResolvesVerifier()
{
IConfiguration config = BuildConfiguration(TempSqlitePath());
var services = new ServiceCollection();
services.AddZbApiKeyAuth(config, ApiKeySection);
using ServiceProvider provider = services.BuildServiceProvider();
var verifier = provider.GetRequiredService<IApiKeyVerifier>();
Assert.NotNull(verifier);
}
[Fact]
public void AddZbApiKeyAuth_ResolvesAllStores()
{
IConfiguration config = BuildConfiguration(TempSqlitePath());
var services = new ServiceCollection();
services.AddZbApiKeyAuth(config, ApiKeySection);
using ServiceProvider provider = services.BuildServiceProvider();
Assert.NotNull(provider.GetRequiredService<IApiKeyStore>());
Assert.NotNull(provider.GetRequiredService<IApiKeyAdminStore>());
Assert.NotNull(provider.GetRequiredService<IApiKeyAuditStore>());
}
[Fact]
public void AddZbApiKeyAuth_BindsOptionsFromSection()
{
string sqlitePath = TempSqlitePath();
IConfiguration config = BuildConfiguration(sqlitePath);
var services = new ServiceCollection();
services.AddZbApiKeyAuth(config, ApiKeySection);
using ServiceProvider provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<ApiKeyOptions>>();
Assert.Equal("mxgw", options.Value.TokenPrefix);
Assert.Equal(sqlitePath, options.Value.SqlitePath);
Assert.Equal(PepperSecretName, options.Value.PepperSecretName);
}
[Fact]
public void AddZbApiKeyAuth_PepperProviderReturnsConfiguredPepper()
{
IConfiguration config = BuildConfiguration(TempSqlitePath());
var services = new ServiceCollection();
services.AddZbApiKeyAuth(config, ApiKeySection);
using ServiceProvider provider = services.BuildServiceProvider();
var pepperProvider = provider.GetRequiredService<IApiKeyPepperProvider>();
Assert.IsType<ConfigurationApiKeyPepperProvider>(pepperProvider);
Assert.Equal(PepperValue, pepperProvider.GetPepper());
}
[Fact]
public void ConfigurationApiKeyPepperProvider_ReturnsNull_WhenSecretNameUnset()
{
IConfiguration config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>())
.Build();
var options = Options.Create(new ApiKeyOptions { PepperSecretName = "" });
var provider = new ConfigurationApiKeyPepperProvider(config, options);
Assert.Null(provider.GetPepper());
}
[Fact]
public void ConfigurationApiKeyPepperProvider_ReturnsNull_WhenValueAbsent()
{
IConfiguration config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>())
.Build();
var options = Options.Create(new ApiKeyOptions { PepperSecretName = "Missing" });
var provider = new ConfigurationApiKeyPepperProvider(config, options);
Assert.Null(provider.GetPepper());
}
[Fact]
public async Task AddZbApiKeyAuth_MigrationHostedService_CreatesSchemaOnStartup()
{
string sqlitePath = TempSqlitePath();
try
{
IConfiguration config = BuildConfiguration(sqlitePath, runMigrationsOnStartup: true);
var services = new ServiceCollection();
services.AddZbApiKeyAuth(config, ApiKeySection);
await using ServiceProvider provider = services.BuildServiceProvider();
// Find the ApiKeyMigrationHostedService among all registered IHostedService instances.
var hostedServices = provider.GetServices<IHostedService>().ToList();
IHostedService? migrationService = hostedServices
.FirstOrDefault(s => s.GetType().Name == "ApiKeyMigrationHostedService");
Assert.NotNull(migrationService);
await migrationService!.StartAsync(CancellationToken.None);
// Verify the api_keys table was created by the migration.
string connectionString = new SqliteConnectionStringBuilder
{
DataSource = sqlitePath,
Mode = SqliteOpenMode.ReadOnly,
}.ToString();
await using var connection = new SqliteConnection(connectionString);
await connection.OpenAsync();
await using var command = connection.CreateCommand();
command.CommandText = """
SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = 'api_keys';
""";
long tableCount = (long)(await command.ExecuteScalarAsync() ?? 0L);
Assert.Equal(1L, tableCount);
}
finally
{
if (File.Exists(sqlitePath))
File.Delete(sqlitePath);
}
}
[Fact]
public async Task AddZbApiKeyAuth_MigrationHostedService_SkipsMigration_WhenRunMigrationsOnStartupFalse()
{
string sqlitePath = TempSqlitePath();
try
{
IConfiguration config = BuildConfiguration(sqlitePath, runMigrationsOnStartup: false);
var services = new ServiceCollection();
services.AddZbApiKeyAuth(config, ApiKeySection);
await using ServiceProvider provider = services.BuildServiceProvider();
var hostedServices = provider.GetServices<IHostedService>().ToList();
IHostedService? migrationService = hostedServices
.FirstOrDefault(s => s.GetType().Name == "ApiKeyMigrationHostedService");
Assert.NotNull(migrationService);
// StartAsync should complete without creating the database file.
await migrationService!.StartAsync(CancellationToken.None);
Assert.False(File.Exists(sqlitePath),
"Migration should not run when RunMigrationsOnStartup is false.");
}
finally
{
if (File.Exists(sqlitePath))
File.Delete(sqlitePath);
}
}
}