mirror of
https://github.com/bitwarden/server.git
synced 2025-05-29 07:14:50 -05:00
Migrate Infrastructure.IntegrationTest to XUnit v3
This commit is contained in:
parent
f4dd794cba
commit
cbfd4bc3e2
@ -1,31 +0,0 @@
|
||||
using Bit.Core.Enums;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Bit.Infrastructure.IntegrationTest;
|
||||
|
||||
public class Database
|
||||
{
|
||||
public SupportedDatabaseProviders Type { get; set; }
|
||||
public string ConnectionString { get; set; } = default!;
|
||||
public bool UseEf { get; set; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
}
|
||||
|
||||
internal class TypedConfig
|
||||
{
|
||||
public Database[] Databases { get; set; } = default!;
|
||||
}
|
||||
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static Database[] GetDatabases(this IConfiguration config)
|
||||
{
|
||||
var typedConfig = config.Get<TypedConfig>();
|
||||
if (typedConfig?.Databases == null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return typedConfig.Databases.Where(d => d.Enabled).ToArray();
|
||||
}
|
||||
}
|
@ -5,12 +5,15 @@ using Bit.Infrastructure.Dapper;
|
||||
using Bit.Infrastructure.EntityFramework;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Infrastructure.IntegrationTest.Services;
|
||||
using Bit.Infrastructure.IntegrationTest.Utilities;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
using Xunit.v3;
|
||||
|
||||
namespace Bit.Infrastructure.IntegrationTest;
|
||||
|
||||
@ -20,112 +23,25 @@ public class DatabaseDataAttribute : DataAttribute
|
||||
public bool UseFakeTimeProvider { get; set; }
|
||||
public string? MigrationName { get; set; }
|
||||
|
||||
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
|
||||
public override ValueTask<IReadOnlyCollection<ITheoryDataRow>> GetData(MethodInfo testMethod, DisposalTracker disposalTracker)
|
||||
{
|
||||
var parameters = testMethod.GetParameters();
|
||||
var builders = DatabaseStartup.Builders;
|
||||
|
||||
var config = DatabaseTheoryAttribute.GetConfiguration();
|
||||
|
||||
var serviceProviders = GetDatabaseProviders(config);
|
||||
|
||||
foreach (var provider in serviceProviders)
|
||||
if (builders == null)
|
||||
{
|
||||
var objects = new object[parameters.Length];
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
objects[i] = provider.GetRequiredService(parameters[i].ParameterType);
|
||||
}
|
||||
yield return objects;
|
||||
throw new InvalidOperationException("Builders wasn't supplied, this likely means DatabaseStartup didn't run.");
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<IServiceProvider> GetDatabaseProviders(IConfiguration config)
|
||||
{
|
||||
var configureLogging = (ILoggingBuilder builder) =>
|
||||
var theoryData = new ITheoryDataRow[builders.Count];
|
||||
for (var i = 0; i < builders.Count; i++)
|
||||
{
|
||||
if (!config.GetValue<bool>("Quiet"))
|
||||
{
|
||||
builder.AddConfiguration(config);
|
||||
builder.AddConsole();
|
||||
builder.AddDebug();
|
||||
}
|
||||
};
|
||||
|
||||
var databases = config.GetDatabases();
|
||||
|
||||
foreach (var database in databases)
|
||||
{
|
||||
if (database.Type == SupportedDatabaseProviders.SqlServer && !database.UseEf)
|
||||
{
|
||||
var dapperSqlServerCollection = new ServiceCollection();
|
||||
AddCommonServices(dapperSqlServerCollection, configureLogging);
|
||||
dapperSqlServerCollection.AddDapperRepositories(SelfHosted);
|
||||
var globalSettings = new GlobalSettings
|
||||
{
|
||||
DatabaseProvider = "sqlServer",
|
||||
SqlServer = new GlobalSettings.SqlSettings
|
||||
{
|
||||
ConnectionString = database.ConnectionString,
|
||||
},
|
||||
};
|
||||
dapperSqlServerCollection.AddSingleton(globalSettings);
|
||||
dapperSqlServerCollection.AddSingleton<IGlobalSettings>(globalSettings);
|
||||
dapperSqlServerCollection.AddSingleton(database);
|
||||
dapperSqlServerCollection.AddDistributedSqlServerCache((o) =>
|
||||
{
|
||||
o.ConnectionString = database.ConnectionString;
|
||||
o.SchemaName = "dbo";
|
||||
o.TableName = "Cache";
|
||||
});
|
||||
|
||||
if (!string.IsNullOrEmpty(MigrationName))
|
||||
{
|
||||
AddSqlMigrationTester(dapperSqlServerCollection, database.ConnectionString, MigrationName);
|
||||
}
|
||||
|
||||
yield return dapperSqlServerCollection.BuildServiceProvider();
|
||||
}
|
||||
else
|
||||
{
|
||||
var efCollection = new ServiceCollection();
|
||||
AddCommonServices(efCollection, configureLogging);
|
||||
efCollection.SetupEntityFramework(database.ConnectionString, database.Type);
|
||||
efCollection.AddPasswordManagerEFRepositories(SelfHosted);
|
||||
efCollection.AddSingleton(database);
|
||||
efCollection.AddSingleton<IDistributedCache, EntityFrameworkCache>();
|
||||
|
||||
if (!string.IsNullOrEmpty(MigrationName))
|
||||
{
|
||||
AddEfMigrationTester(efCollection, database.Type, MigrationName);
|
||||
}
|
||||
|
||||
yield return efCollection.BuildServiceProvider();
|
||||
}
|
||||
theoryData[i] = builders[i](testMethod, disposalTracker, this);
|
||||
}
|
||||
return new(theoryData);
|
||||
}
|
||||
|
||||
private void AddCommonServices(IServiceCollection services, Action<ILoggingBuilder> configureLogging)
|
||||
public override bool SupportsDiscoveryEnumeration()
|
||||
{
|
||||
services.AddLogging(configureLogging);
|
||||
services.AddDataProtection();
|
||||
|
||||
if (UseFakeTimeProvider)
|
||||
{
|
||||
services.AddSingleton<TimeProvider, FakeTimeProvider>();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSqlMigrationTester(IServiceCollection services, string connectionString, string migrationName)
|
||||
{
|
||||
services.AddSingleton<IMigrationTesterService, SqlMigrationTesterService>(sp => new SqlMigrationTesterService(connectionString, migrationName));
|
||||
}
|
||||
|
||||
private void AddEfMigrationTester(IServiceCollection services, SupportedDatabaseProviders databaseType, string migrationName)
|
||||
{
|
||||
services.AddSingleton<IMigrationTesterService, EfMigrationTesterService>(sp =>
|
||||
{
|
||||
var dbContext = sp.GetRequiredService<DatabaseContext>();
|
||||
return new EfMigrationTesterService(dbContext, databaseType, migrationName);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -3,30 +3,11 @@ using Xunit;
|
||||
|
||||
namespace Bit.Infrastructure.IntegrationTest;
|
||||
|
||||
[Obsolete("[DatabaseTheory] is no longer needed, you can just use [Theory]")]
|
||||
public class DatabaseTheoryAttribute : TheoryAttribute
|
||||
{
|
||||
private static IConfiguration? _cachedConfiguration;
|
||||
|
||||
public DatabaseTheoryAttribute()
|
||||
{
|
||||
if (!HasAnyDatabaseSetup())
|
||||
{
|
||||
Skip = "No databases setup.";
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasAnyDatabaseSetup()
|
||||
{
|
||||
var config = GetConfiguration();
|
||||
return config.GetDatabases().Length > 0;
|
||||
}
|
||||
|
||||
public static IConfiguration GetConfiguration()
|
||||
{
|
||||
return _cachedConfiguration ??= new ConfigurationBuilder()
|
||||
.AddUserSecrets<DatabaseDataAttribute>(optional: true, reloadOnChange: false)
|
||||
.AddEnvironmentVariables("BW_TEST_")
|
||||
.AddCommandLine(Environment.GetCommandLineArgs())
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
|
@ -13,8 +13,8 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" Version="8.10.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNetTestSdkVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XUnitVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XUnitRunnerVisualStudioVersion)">
|
||||
<PackageReference Include="xunit.v3" Version="0.6.0-pre.7" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.0-pre.49">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -0,0 +1,4 @@
|
||||
using Bit.Infrastructure.IntegrationTest.Utilities;
|
||||
using Xunit.v3;
|
||||
|
||||
[assembly: TestPipelineStartup(typeof(DatabaseStartup))]
|
184
test/Infrastructure.IntegrationTest/Utilities/DatabaseStartup.cs
Normal file
184
test/Infrastructure.IntegrationTest/Utilities/DatabaseStartup.cs
Normal file
@ -0,0 +1,184 @@
|
||||
using System.Reflection;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Infrastructure.Dapper;
|
||||
using Bit.Infrastructure.EntityFramework;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Infrastructure.IntegrationTest.Services;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using Xunit;
|
||||
using Xunit.v3;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Bit.Infrastructure.IntegrationTest.Utilities;
|
||||
|
||||
using TheoryDataBuilder = Func<MethodInfo, DisposalTracker, DatabaseDataAttribute, ITheoryDataRow>;
|
||||
|
||||
public class Database
|
||||
{
|
||||
public SupportedDatabaseProviders Type { get; set; }
|
||||
public string ConnectionString { get; set; } = default!;
|
||||
public bool UseEf { get; set; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
}
|
||||
|
||||
internal class TypedConfig
|
||||
{
|
||||
public Database[] Databases { get; set; } = default!;
|
||||
}
|
||||
|
||||
public class DatabaseStartup : ITestPipelineStartup
|
||||
{
|
||||
public static IReadOnlyList<TheoryDataBuilder>? Builders { get; private set; }
|
||||
|
||||
public ValueTask StartAsync(IMessageSink diagnosticMessageSink)
|
||||
{
|
||||
HashSet<SupportedDatabaseProviders> unconfiguredDatabases =
|
||||
[
|
||||
SupportedDatabaseProviders.SqlServer,
|
||||
SupportedDatabaseProviders.MySql,
|
||||
SupportedDatabaseProviders.Postgres,
|
||||
SupportedDatabaseProviders.Sqlite
|
||||
];
|
||||
|
||||
// Do startup things
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddUserSecrets<DatabaseStartup>(optional: true, reloadOnChange: false)
|
||||
.AddEnvironmentVariables("BW_TEST_")
|
||||
.AddCommandLine(Environment.GetCommandLineArgs())
|
||||
.Build();
|
||||
|
||||
var typedConfig = configuration.Get<TypedConfig>();
|
||||
|
||||
var theories = new List<TheoryDataBuilder>();
|
||||
|
||||
if (typedConfig is not { Databases: var databases })
|
||||
{
|
||||
foreach (var unconfiguredDatabase in unconfiguredDatabases)
|
||||
{
|
||||
theories.Add((mi, _, _) => new TheoryDataRow()
|
||||
.WithSkip("Unconfigured")
|
||||
.WithTestDisplayName(TestName(mi, unconfiguredDatabase))
|
||||
.WithTrait("Type", unconfiguredDatabase.ToString()));
|
||||
}
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
foreach (var database in databases)
|
||||
{
|
||||
unconfiguredDatabases.Remove(database.Type);
|
||||
if (!database.Enabled)
|
||||
{
|
||||
theories.Add((mi, _, _) => new TheoryDataRow()
|
||||
.WithSkip($"Disabled")
|
||||
.WithTestDisplayName(TestName(mi, database.Type))
|
||||
.WithTrait("Type", database.Type.ToString())
|
||||
.WithTrait("ConnectionString", database.ConnectionString));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Build service provider for database
|
||||
theories.Add((methodInfo, disposalTracker, databaseDataAttribute) =>
|
||||
{
|
||||
var sp = BuildServiceProvider(databaseDataAttribute, database);
|
||||
|
||||
return new ServiceTheoryDataRow(methodInfo, disposalTracker, sp)
|
||||
.WithTestDisplayName(TestName(methodInfo, database.Type))
|
||||
.WithTrait("Type", database.Type.ToString())
|
||||
.WithTrait("ConnectionString", database.ConnectionString);
|
||||
});
|
||||
}
|
||||
|
||||
// Add entry for all still unconfigured database types
|
||||
foreach (var unconfiguredDatabase in unconfiguredDatabases)
|
||||
{
|
||||
theories.Add((mi, _, _) => new TheoryDataRow()
|
||||
.WithSkip("Not Configured")
|
||||
.WithTestDisplayName(TestName(mi, unconfiguredDatabase))
|
||||
.WithTrait("Type", unconfiguredDatabase.ToString()));
|
||||
}
|
||||
|
||||
Builders = theories;
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
public ValueTask StopAsync()
|
||||
{
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
private static string TestName(MethodInfo methodInfo, SupportedDatabaseProviders database)
|
||||
{
|
||||
// Add containing type name to the beginning?
|
||||
return $"{methodInfo.Name}({database})";
|
||||
}
|
||||
|
||||
private IServiceProvider BuildServiceProvider(DatabaseDataAttribute databaseData, Database database)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging(builder =>
|
||||
{
|
||||
|
||||
});
|
||||
|
||||
services.AddDataProtection();
|
||||
|
||||
if (databaseData.UseFakeTimeProvider)
|
||||
{
|
||||
services.AddSingleton<TimeProvider, FakeTimeProvider>();
|
||||
}
|
||||
|
||||
services.AddSingleton(database);
|
||||
|
||||
if (database.Type == SupportedDatabaseProviders.SqlServer && !database.UseEf)
|
||||
{
|
||||
services.AddDapperRepositories(databaseData.SelfHosted);
|
||||
var globalSettings = new GlobalSettings
|
||||
{
|
||||
DatabaseProvider = "sqlServer",
|
||||
SqlServer = new GlobalSettings.SqlSettings
|
||||
{
|
||||
ConnectionString = database.ConnectionString,
|
||||
},
|
||||
};
|
||||
services.AddSingleton(globalSettings);
|
||||
services.AddSingleton<IGlobalSettings>(globalSettings);
|
||||
services.AddDistributedSqlServerCache((options) =>
|
||||
{
|
||||
options.ConnectionString = database.ConnectionString;
|
||||
options.SchemaName = "dbo";
|
||||
options.TableName = "Cache";
|
||||
});
|
||||
|
||||
if (!string.IsNullOrEmpty(databaseData.MigrationName))
|
||||
{
|
||||
services.AddSingleton<IMigrationTesterService, SqlMigrationTesterService>(
|
||||
sp => new SqlMigrationTesterService(database.ConnectionString, databaseData.MigrationName)
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
services.SetupEntityFramework(database.ConnectionString, database.Type);
|
||||
services.AddPasswordManagerEFRepositories(databaseData.SelfHosted);
|
||||
services.AddSingleton<IDistributedCache, EntityFrameworkCache>();
|
||||
|
||||
if (!string.IsNullOrEmpty(databaseData.MigrationName))
|
||||
{
|
||||
services.AddSingleton<IMigrationTesterService, EfMigrationTesterService>(sp =>
|
||||
{
|
||||
var dbContext = sp.GetRequiredService<DatabaseContext>();
|
||||
return new EfMigrationTesterService(dbContext, database.Type, databaseData.MigrationName);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
using Xunit.v3;
|
||||
|
||||
namespace Bit.Infrastructure.IntegrationTest.Utilities;
|
||||
|
||||
public class ServiceTheoryDataRow : TheoryDataRowBase
|
||||
{
|
||||
private readonly MethodInfo _testMethod;
|
||||
private readonly DisposalTracker _disposalTracker;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public ServiceTheoryDataRow(MethodInfo testMethod, DisposalTracker disposalTracker, IServiceProvider serviceProvider)
|
||||
{
|
||||
_testMethod = testMethod;
|
||||
_disposalTracker = disposalTracker;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
protected override object?[] GetData()
|
||||
{
|
||||
var parameters = _testMethod.GetParameters();
|
||||
// Create a scope for this test
|
||||
var scope = _serviceProvider.CreateAsyncScope();
|
||||
|
||||
var objects = new object[parameters.Length];
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
objects[i] = scope.ServiceProvider.GetRequiredService(parameters[i].ParameterType);
|
||||
}
|
||||
|
||||
_disposalTracker.Add(scope);
|
||||
|
||||
return objects;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user