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; 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? Builders { get; private set; } public ValueTask StartAsync(IMessageSink diagnosticMessageSink) { HashSet unconfiguredDatabases = [ SupportedDatabaseProviders.SqlServer, SupportedDatabaseProviders.MySql, SupportedDatabaseProviders.Postgres, SupportedDatabaseProviders.Sqlite ]; // Do startup things var configuration = new ConfigurationBuilder() .AddUserSecrets(optional: true, reloadOnChange: false) .AddEnvironmentVariables("BW_TEST_") .AddCommandLine(Environment.GetCommandLineArgs()) .Build(); var typedConfig = configuration.Get(); var theories = new List(); 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(); } 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(globalSettings); services.AddDistributedSqlServerCache((options) => { options.ConnectionString = database.ConnectionString; options.SchemaName = "dbo"; options.TableName = "Cache"; }); if (!string.IsNullOrEmpty(databaseData.MigrationName)) { services.AddSingleton( sp => new SqlMigrationTesterService(database.ConnectionString, databaseData.MigrationName) ); } } else { services.SetupEntityFramework(database.ConnectionString, database.Type); services.AddPasswordManagerEFRepositories(databaseData.SelfHosted); services.AddSingleton(); if (!string.IsNullOrEmpty(databaseData.MigrationName)) { services.AddSingleton(sp => { var dbContext = sp.GetRequiredService(); return new EfMigrationTesterService(dbContext, database.Type, databaseData.MigrationName); }); } } return services.BuildServiceProvider(); } }