c1e34c307e
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
146 lines
5.3 KiB
C#
146 lines
5.3 KiB
C#
using System.Globalization;
|
|
using ZB.MOM.WW.Auth.ApiKeys.Sqlite;
|
|
|
|
namespace ZB.MOM.WW.Auth.ApiKeys.SqlServer.Tests.Live;
|
|
|
|
/// <summary>
|
|
/// Live provisioning tests for <see cref="SqlServerApiKeyStoreMigrator"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Every test provisions its own throwaway schema — the concurrency case in particular needs a
|
|
/// virgin one, because the race the retry loop exists for only happens while the schema and tables
|
|
/// do not yet exist. All schemas created by the class are dropped in <see cref="DisposeAsync"/>.
|
|
/// </remarks>
|
|
[Collection("live-sqlserver")]
|
|
public sealed class SqlServerApiKeyStoreMigratorLiveTests : IAsyncLifetime
|
|
{
|
|
private readonly List<string> _schemas = [];
|
|
|
|
/// <inheritdoc />
|
|
public Task InitializeAsync() => Task.CompletedTask;
|
|
|
|
/// <inheritdoc />
|
|
public async Task DisposeAsync()
|
|
{
|
|
foreach (string schema in _schemas)
|
|
{
|
|
await LiveSqlServer.DropSchemaAsync(schema);
|
|
}
|
|
}
|
|
|
|
private string NewSchema()
|
|
{
|
|
string schema = LiveSqlServer.NewSchemaName();
|
|
_schemas.Add(schema);
|
|
return schema;
|
|
}
|
|
|
|
private static SqlServerApiKeyStoreMigrator MigratorFor(string schema) =>
|
|
new(new AuthSqlServerConnectionFactory(LiveSqlServer.OptionsFor(schema)));
|
|
|
|
[SkippableFact]
|
|
public async Task Migrate_OnEmptyDatabase_CreatesSchemaTablesAndVersionRow()
|
|
{
|
|
Skip.IfNot(LiveSqlServer.IsEnabled, LiveSqlServer.SkipReason);
|
|
|
|
string schema = NewSchema();
|
|
|
|
await MigratorFor(schema).MigrateAsync(CancellationToken.None);
|
|
|
|
Assert.Equal(
|
|
1,
|
|
Convert.ToInt32(
|
|
await LiveSqlServer.ScalarAsync(
|
|
$"SELECT COUNT(*) FROM sys.schemas WHERE name = N'{schema}'"),
|
|
CultureInfo.InvariantCulture));
|
|
|
|
foreach (string table in new[]
|
|
{
|
|
SqlServerAuthSchema.SchemaVersionTable,
|
|
SqlServerAuthSchema.ApiKeysTable,
|
|
SqlServerAuthSchema.ApiKeyAuditTable,
|
|
})
|
|
{
|
|
Assert.NotNull(
|
|
await LiveSqlServer.ScalarAsync($"SELECT OBJECT_ID(N'[{schema}].[{table}]', N'U')"));
|
|
}
|
|
|
|
Assert.Equal(
|
|
SqlServerAuthSchema.CurrentVersion,
|
|
Convert.ToInt32(
|
|
await LiveSqlServer.ScalarAsync(
|
|
$"SELECT version FROM [{schema}].[{SqlServerAuthSchema.SchemaVersionTable}] WHERE id = 1"),
|
|
CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
[SkippableFact]
|
|
public async Task Migrate_Twice_IsIdempotent()
|
|
{
|
|
Skip.IfNot(LiveSqlServer.IsEnabled, LiveSqlServer.SkipReason);
|
|
|
|
string schema = NewSchema();
|
|
SqlServerApiKeyStoreMigrator migrator = MigratorFor(schema);
|
|
|
|
await migrator.MigrateAsync(CancellationToken.None);
|
|
await migrator.MigrateAsync(CancellationToken.None);
|
|
|
|
Assert.Equal(
|
|
1,
|
|
Convert.ToInt32(
|
|
await LiveSqlServer.ScalarAsync(
|
|
$"SELECT COUNT(*) FROM [{schema}].[{SqlServerAuthSchema.SchemaVersionTable}]"),
|
|
CultureInfo.InvariantCulture));
|
|
Assert.Equal(
|
|
SqlServerAuthSchema.CurrentVersion,
|
|
Convert.ToInt32(
|
|
await LiveSqlServer.ScalarAsync(
|
|
$"SELECT version FROM [{schema}].[{SqlServerAuthSchema.SchemaVersionTable}] WHERE id = 1"),
|
|
CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
[SkippableFact]
|
|
public async Task Migrate_Concurrently_FromEightTasks_AllSucceed()
|
|
{
|
|
Skip.IfNot(LiveSqlServer.IsEnabled, LiveSqlServer.SkipReason);
|
|
|
|
// A virgin schema provisioned by eight nodes at once — the central-pair first boot, scaled
|
|
// up. Each task gets its own factory (and therefore its own connection) so the DDL really
|
|
// does run on eight sessions.
|
|
string schema = NewSchema();
|
|
|
|
await Task.WhenAll(Enumerable.Range(0, 8).Select(_ =>
|
|
Task.Run(() => MigratorFor(schema).MigrateAsync(CancellationToken.None))));
|
|
|
|
Assert.Equal(
|
|
1,
|
|
Convert.ToInt32(
|
|
await LiveSqlServer.ScalarAsync(
|
|
$"SELECT COUNT(*) FROM [{schema}].[{SqlServerAuthSchema.SchemaVersionTable}]"),
|
|
CultureInfo.InvariantCulture));
|
|
Assert.Equal(
|
|
SqlServerAuthSchema.CurrentVersion,
|
|
Convert.ToInt32(
|
|
await LiveSqlServer.ScalarAsync(
|
|
$"SELECT version FROM [{schema}].[{SqlServerAuthSchema.SchemaVersionTable}] WHERE id = 1"),
|
|
CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
[SkippableFact]
|
|
public async Task Migrate_WhenStoredVersionIsNewer_Throws()
|
|
{
|
|
Skip.IfNot(LiveSqlServer.IsEnabled, LiveSqlServer.SkipReason);
|
|
|
|
string schema = NewSchema();
|
|
SqlServerApiKeyStoreMigrator migrator = MigratorFor(schema);
|
|
|
|
await migrator.MigrateAsync(CancellationToken.None);
|
|
await LiveSqlServer.ExecuteAsync(
|
|
$"UPDATE [{schema}].[{SqlServerAuthSchema.SchemaVersionTable}] SET version = 99 WHERE id = 1");
|
|
|
|
AuthStoreMigrationException exception = await Assert.ThrowsAsync<AuthStoreMigrationException>(
|
|
() => migrator.MigrateAsync(CancellationToken.None));
|
|
|
|
Assert.Contains("99", exception.Message, StringComparison.Ordinal);
|
|
}
|
|
}
|