@ -10,140 +10,207 @@ using Microsoft.Extensions.Configuration;
@@ -10,140 +10,207 @@ 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 ;
public class DatabaseDataAttribute : DataAttribute
{
private static IConfiguration ? _ cachedConfiguration ;
private static IConfiguration GetConfiguration ( )
{
return _ cachedConfiguration ? ? = new ConfigurationBuilder ( )
. AddUserSecrets < DatabaseDataAttribute > ( optional : true , reloadOnChange : false )
. AddEnvironmentVariables ( "BW_TEST_" )
. AddCommandLine ( Environment . GetCommandLineArgs ( ) )
. Build ( ) ;
}
public bool SelfHosted { get ; set ; }
public bool UseFakeTimeProvider { get ; set ; }
public string? MigrationName { get ; set ; }
public override IEnumerable < object [ ] > GetData ( MethodInfo testMethod )
private void AddSqlMigrationTester ( IServiceCollection services , string connectionString , string migrationName )
{
var parameters = testMethod . GetParameters ( ) ;
var config = DatabaseTheoryAttribute . GetConfiguration ( ) ;
var serviceProviders = GetDatabaseProviders ( config ) ;
services . AddSingleton < IMigrationTesterService , SqlMigrationTesterService > ( _ = > new SqlMigrationTesterService ( connectionString , migrationName ) ) ;
}
foreach ( var provider in serviceProviders )
private void AddEfMigrationTester ( IServiceCollection services , SupportedDatabaseProviders databaseType , string migrationName )
{
services . AddSingleton < IMigrationTesterService , EfMigrationTesterService > ( sp = >
{
var objects = new object [ parameters . Length ] ;
for ( var i = 0 ; i < parameters . Length ; i + + )
{
objects [ i ] = provider . GetRequiredService ( parameters [ i ] . ParameterType ) ;
}
yield return objects ;
}
var dbContext = sp . GetRequiredService < DatabaseContext > ( ) ;
return new EfMigrationTesterService ( dbContext , databaseType , migrationName ) ;
} ) ;
}
protected virtual IEnumerable < IServiceProvider > GetDatabaseProviders ( IConfiguration config )
public override ValueTask < IReadOnlyCollection < ITheoryDataRow > > GetData ( MethodInfo testMethod , DisposalTracker disposalTracker )
{
// This is for the device repository integration testing.
var userRequestExpiration = 1 5 ;
var config = GetConfiguration ( ) ;
var configureLogging = ( ILoggingBuilder builder ) = >
HashSet < SupportedDatabaseProviders > unconfiguredDatabases =
[
SupportedDatabaseProviders . MySql ,
SupportedDatabaseProviders . Postgres ,
SupportedDatabaseProviders . Sqlite ,
SupportedDatabaseProviders . SqlServer
] ;
var theories = new List < ITheoryDataRow > ( ) ;
foreach ( var database in config . GetDatabases ( ) )
{
if ( ! config . GetValue < bool > ( "Quiet" ) )
unconfiguredDatabases . Remove ( database . Type ) ;
if ( ! database . Enabled )
{
builder . AddConfiguration ( config ) ;
builder . AddConsole ( ) ;
builder . AddDebug ( ) ;
var theory = new TheoryDataRow ( )
. WithSkip ( "Not-Enabled" )
. WithTrait ( "Database" , database . Type . ToString ( ) ) ;
theory . Label = database . Type . ToString ( ) ;
theories . Add ( theory ) ;
continue ;
}
} ;
var databases = config . GetDatabases ( ) ;
var services = new ServiceCollection ( ) ;
AddCommonServices ( services ) ;
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 ,
} ,
PasswordlessAuth = new GlobalSettings . PasswordlessAuthSettings
{
UserRequestExpiration = TimeSpan . FromMinutes ( userRequestExpiration ) ,
}
} ;
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 ( ) ;
// Dapper services
AddDapperServices ( services , database ) ;
}
else
{
var efCollection = new ServiceCollection ( ) ;
AddCommonServices ( efCollection , configureLogging ) ;
efCollection . SetupEntityFramework ( database . ConnectionString , database . Type ) ;
efCollection . AddPasswordManagerEFRepositories ( SelfHosted ) ;
var globalSettings = new GlobalSettings
{
PasswordlessAuth = new GlobalSettings . PasswordlessAuthSettings
{
UserRequestExpiration = TimeSpan . FromMinutes ( userRequestExpiration ) ,
}
} ;
efCollection . AddSingleton ( globalSettings ) ;
efCollection . AddSingleton < IGlobalSettings > ( globalSettings ) ;
efCollection . AddSingleton ( database ) ;
efCollection . AddSingleton < IDistributedCache , EntityFrameworkCache > ( ) ;
if ( ! string . IsNullOrEmpty ( MigrationName ) )
{
AddEfMigrationTester ( efCollection , database . Type , MigrationName ) ;
}
yield return efCollection . BuildServiceProvider ( ) ;
// Ef services
AddEfServices ( services , database ) ;
}
var serviceProvider = services . BuildServiceProvider ( ) ;
disposalTracker . Add ( serviceProvider ) ;
var serviceTheory = new ServiceBasedTheoryDataRow ( serviceProvider , testMethod )
. WithTrait ( "Database" , database . Type . ToString ( ) )
. WithTrait ( "ConnectionString" , database . ConnectionString ) ;
serviceTheory . Label = database . Type . ToString ( ) ;
theories . Add ( serviceTheory ) ;
}
foreach ( var unconfiguredDatabase in unconfiguredDatabases )
{
var theory = new TheoryDataRow ( )
. WithSkip ( "Unconfigured" )
. WithTrait ( "Database" , unconfiguredDatabase . ToString ( ) ) ;
theory . Label = unconfiguredDatabase . ToString ( ) ;
theories . Add ( theory ) ;
}
return new ( theories ) ;
}
private void AddCommonServices ( IServiceCollection services , Action < ILoggingBuilder > configureLogging )
private void AddCommonServices ( IServiceCollection services )
{
services . AddLogging ( configureLogging ) ;
// Common services
services . AddDataProtection ( ) ;
services . AddLogging ( logging = >
{
logging . AddProvider ( new XUnitLoggerProvider ( ) ) ;
} ) ;
if ( UseFakeTimeProvider )
{
services . AddSingleton < TimeProvider , FakeTimeProvider > ( ) ;
}
}
private void AddSqlMigrationTester ( IServiceCollection services , string connectionString , string migrationNam e)
private void AddDapperServices ( IServiceCollection services , Database databas e)
{
services . AddSingleton < IMigrationTesterService , SqlMigrationTesterService > ( _ = > new SqlMigrationTesterService ( connectionString , migrationName ) ) ;
services . AddDapperRepositories ( SelfHosted ) ;
var globalSettings = new GlobalSettings
{
DatabaseProvider = "sqlServer" ,
SqlServer = new GlobalSettings . SqlSettings
{
ConnectionString = database . ConnectionString ,
} ,
PasswordlessAuth = new GlobalSettings . PasswordlessAuthSettings
{
UserRequestExpiration = TimeSpan . FromMinutes ( 1 5 ) ,
}
} ;
services . AddSingleton ( globalSettings ) ;
services . AddSingleton < IGlobalSettings > ( globalSettings ) ;
services . AddSingleton ( database ) ;
services . AddDistributedSqlServerCache ( o = >
{
o . ConnectionString = database . ConnectionString ;
o . SchemaName = "dbo" ;
o . TableName = "Cache" ;
} ) ;
if ( ! string . IsNullOrEmpty ( MigrationName ) )
{
AddSqlMigrationTester ( services , database . ConnectionString , MigrationName ) ;
}
}
private void AddEfMigrationTester ( IServiceCollection services , SupportedDatabaseProviders databaseType , string migrationName )
private void AddEfServices ( IServiceCollection services , Database database )
{
services . AddSingleton < IMigrationTesterService , EfMigrationTesterService > ( sp = >
services . SetupEntityFramework ( database . ConnectionString , database . Type ) ;
services . AddPasswordManagerEFRepositories ( SelfHosted ) ;
var globalSettings = new GlobalSettings
{
var dbContext = sp . GetRequiredService < DatabaseContext > ( ) ;
return new EfMigrationTesterService ( dbContext , databaseType , migrationName ) ;
} ) ;
PasswordlessAuth = new GlobalSettings . PasswordlessAuthSettings
{
UserRequestExpiration = TimeSpan . FromMinutes ( 1 5 ) ,
} ,
} ;
services . AddSingleton ( globalSettings ) ;
services . AddSingleton < IGlobalSettings > ( globalSettings ) ;
services . AddSingleton ( database ) ;
services . AddSingleton < IDistributedCache , EntityFrameworkCache > ( ) ;
if ( ! string . IsNullOrEmpty ( MigrationName ) )
{
AddEfMigrationTester ( services , database . Type , MigrationName ) ;
}
}
public override bool SupportsDiscoveryEnumeration ( )
{
return true ;
}
private class ServiceBasedTheoryDataRow : TheoryDataRowBase
{
private readonly IServiceProvider _ serviceProvider ;
private readonly MethodInfo _ testMethod ;
public ServiceBasedTheoryDataRow ( IServiceProvider serviceProvider , MethodInfo testMethod )
{
_ serviceProvider = serviceProvider ;
_ testMethod = testMethod ;
}
protected override object? [ ] GetData ( )
{
var parameters = _ testMethod . GetParameters ( ) ;
var services = new object? [ parameters . Length ] ;
for ( var i = 0 ; i < parameters . Length ; i + + )
{
var parameter = parameters [ i ] ;
// TODO: Could support keyed services/optional/nullable
services [ i ] = _ serviceProvider . GetRequiredService ( parameter . ParameterType ) ;
}
return services ;
}
}
}