201 lines
7.3 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|