2022-06-30 01:46:41 +02:00
using AspNetCoreRateLimit ;
2022-05-20 21:24:59 +02:00
using Bit.Core.Repositories ;
using Bit.Core.Services ;
using Bit.Infrastructure.EntityFramework.Repositories ;
using Microsoft.AspNetCore.Hosting ;
using Microsoft.AspNetCore.Mvc.Testing ;
using Microsoft.AspNetCore.TestHost ;
using Microsoft.EntityFrameworkCore ;
using Microsoft.Extensions.Configuration ;
using Microsoft.Extensions.DependencyInjection ;
2022-08-29 22:06:55 +02:00
namespace Bit.IntegrationTestCommon.Factories ;
public static class FactoryConstants
2022-05-20 21:24:59 +02:00
{
2022-08-29 22:06:55 +02:00
public const string DefaultDatabaseName = "test_database" ;
public const string WhitelistedIp = "1.1.1.1" ;
}
2022-05-20 21:24:59 +02:00
2022-08-29 22:06:55 +02:00
public abstract class WebApplicationFactoryBase < T > : WebApplicationFactory < T >
where T : class
{
/// <summary>
/// The database name to use for this instance of the factory. By default it will use a shared database name so all instances will connect to the same database during it's lifetime.
/// </summary>
/// <remarks>
/// This will need to be set BEFORE using the <c>Server</c> property
/// </remarks>
2023-01-13 15:02:53 +01:00
public string DatabaseName { get ; set ; } = Guid . NewGuid ( ) . ToString ( ) ;
2022-08-29 16:24:52 +02:00
2022-08-29 22:06:55 +02:00
/// <summary>
/// Configure the web host to use an EF in memory database
/// </summary>
protected override void ConfigureWebHost ( IWebHostBuilder builder )
{
builder . ConfigureAppConfiguration ( c = >
2022-08-29 21:53:48 +02:00
{
2022-08-29 22:06:55 +02:00
c . SetBasePath ( AppContext . BaseDirectory )
. AddJsonFile ( "appsettings.json" )
. AddJsonFile ( "appsettings.Development.json" ) ;
2022-08-29 21:53:48 +02:00
2022-08-29 22:06:55 +02:00
c . AddUserSecrets ( typeof ( Identity . Startup ) . Assembly , optional : true ) ;
c . AddInMemoryCollection ( new Dictionary < string , string >
{
// Manually insert a EF provider so that ConfigureServices will add EF repositories but we will override
// DbContextOptions to use an in memory database
{ "globalSettings:databaseProvider" , "postgres" } ,
{ "globalSettings:postgreSql:connectionString" , "Host=localhost;Username=test;Password=test;Database=test" } ,
2022-08-29 16:24:52 +02:00
2022-08-29 22:06:55 +02:00
// Clear the redis connection string for distributed caching, forcing an in-memory implementation
{ "globalSettings:redis:connectionString" , "" }
2022-05-20 21:24:59 +02:00
} ) ;
2022-08-29 22:06:55 +02:00
} ) ;
2022-05-20 21:24:59 +02:00
2022-08-29 22:06:55 +02:00
builder . ConfigureTestServices ( services = >
{
var dbContextOptions = services . First ( sd = > sd . ServiceType = = typeof ( DbContextOptions < DatabaseContext > ) ) ;
services . Remove ( dbContextOptions ) ;
2023-01-18 19:16:57 +01:00
services . AddScoped ( services = >
2022-05-20 21:24:59 +02:00
{
2022-08-29 22:06:55 +02:00
return new DbContextOptionsBuilder < DatabaseContext > ( )
. UseInMemoryDatabase ( DatabaseName )
2023-01-18 19:16:57 +01:00
. UseApplicationServiceProvider ( services )
2022-08-29 22:06:55 +02:00
. Options ;
} ) ;
2022-05-20 21:24:59 +02:00
2022-08-29 22:06:55 +02:00
// QUESTION: The normal licensing service should run fine on developer machines but not in CI
// should we have a fork here to leave the normal service for developers?
// TODO: Eventually add the license file to CI
var licensingService = services . First ( sd = > sd . ServiceType = = typeof ( ILicensingService ) ) ;
services . Remove ( licensingService ) ;
services . AddSingleton < ILicensingService , NoopLicensingService > ( ) ;
2022-05-20 21:24:59 +02:00
2022-08-29 22:06:55 +02:00
// FUTURE CONSIDERATION: Add way to run this self hosted/cloud, for now it is cloud only
var pushRegistrationService = services . First ( sd = > sd . ServiceType = = typeof ( IPushRegistrationService ) ) ;
services . Remove ( pushRegistrationService ) ;
services . AddSingleton < IPushRegistrationService , NoopPushRegistrationService > ( ) ;
2022-05-20 21:24:59 +02:00
2022-08-29 22:06:55 +02:00
// Even though we are cloud we currently set this up as cloud, we can use the EF/selfhosted service
// instead of using Noop for this service
// TODO: Install and use azurite in CI pipeline
var eventWriteService = services . First ( sd = > sd . ServiceType = = typeof ( IEventWriteService ) ) ;
services . Remove ( eventWriteService ) ;
services . AddSingleton < IEventWriteService , RepositoryEventWriteService > ( ) ;
2022-05-20 21:24:59 +02:00
2022-08-29 22:06:55 +02:00
var eventRepositoryService = services . First ( sd = > sd . ServiceType = = typeof ( IEventRepository ) ) ;
services . Remove ( eventRepositoryService ) ;
services . AddSingleton < IEventRepository , EventRepository > ( ) ;
2022-05-20 21:24:59 +02:00
2022-11-16 16:30:28 +01:00
var mailDeliveryService = services . First ( sd = > sd . ServiceType = = typeof ( IMailDeliveryService ) ) ;
services . Remove ( mailDeliveryService ) ;
services . AddSingleton < IMailDeliveryService , NoopMailDeliveryService > ( ) ;
var captchaValidationService = services . First ( sd = > sd . ServiceType = = typeof ( ICaptchaValidationService ) ) ;
services . Remove ( captchaValidationService ) ;
services . AddSingleton < ICaptchaValidationService , NoopCaptchaValidationService > ( ) ;
2022-08-29 22:06:55 +02:00
// Our Rate limiter works so well that it begins to fail tests unless we carve out
// one whitelisted ip. We should still test the rate limiter though and they should change the Ip
// to something that is NOT whitelisted
services . Configure < IpRateLimitOptions > ( options = >
{
options . IpWhitelist = new List < string >
2022-05-20 21:24:59 +02:00
{
2022-08-29 22:06:55 +02:00
FactoryConstants . WhitelistedIp ,
} ;
2022-08-29 21:53:48 +02:00
} ) ;
2022-08-29 20:53:16 +02:00
2022-08-29 22:06:55 +02:00
// Fix IP Rate Limiting
services . AddSingleton < IStartupFilter , CustomStartupFilter > ( ) ;
} ) ;
}
public DatabaseContext GetDatabaseContext ( )
{
var scope = Services . CreateScope ( ) ;
return scope . ServiceProvider . GetRequiredService < DatabaseContext > ( ) ;
2022-05-20 21:24:59 +02:00
}
2023-01-13 15:02:53 +01:00
public T GetService < T > ( )
{
var scope = Services . CreateScope ( ) ;
return scope . ServiceProvider . GetRequiredService < T > ( ) ;
}
2022-05-20 21:24:59 +02:00
}