Initial commit: scadaproj umbrella — sister-project index, auth component normalization (design + GAPS), and the built ZB.MOM.WW.Auth shared library (0.1.0, flattened in).
This commit is contained in:
+200
@@ -0,0 +1,200 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user