using AspNetCoreRateLimit; 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; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NoopRepos = Bit.Core.Repositories.Noop; namespace Bit.IntegrationTestCommon.Factories; public static class FactoryConstants { public const string DefaultDatabaseName = "test_database"; public const string WhitelistedIp = "1.1.1.1"; } public abstract class WebApplicationFactoryBase : WebApplicationFactory where T : class { /// /// 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. /// /// /// This will need to be set BEFORE using the Server property /// public string DatabaseName { get; set; } = Guid.NewGuid().ToString(); /// /// Configure the web host to use an EF in memory database /// protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureAppConfiguration(c => { c.SetBasePath(AppContext.BaseDirectory) .AddJsonFile("appsettings.json") .AddJsonFile("appsettings.Development.json"); c.AddUserSecrets(typeof(Identity.Startup).Assembly, optional: true); c.AddInMemoryCollection(new Dictionary { // 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" }, // Clear the redis connection string for distributed caching, forcing an in-memory implementation { "globalSettings:redis:connectionString", ""}, // Clear Storage { "globalSettings:attachment:connectionString", null}, { "globalSettings:events:connectionString", null}, { "globalSettings:send:connectionString", null}, { "globalSettings:notifications:connectionString", null}, { "globalSettings:storage:connectionString", null}, }); }); builder.ConfigureTestServices(services => { var dbContextOptions = services.First(sd => sd.ServiceType == typeof(DbContextOptions)); services.Remove(dbContextOptions); services.AddScoped(services => { return new DbContextOptionsBuilder() .UseInMemoryDatabase(DatabaseName) .UseApplicationServiceProvider(services) .Options; }); // 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(); // 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(); // 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(); var eventRepositoryService = services.First(sd => sd.ServiceType == typeof(IEventRepository)); services.Remove(eventRepositoryService); services.AddSingleton(); var mailDeliveryService = services.First(sd => sd.ServiceType == typeof(IMailDeliveryService)); services.Remove(mailDeliveryService); services.AddSingleton(); var captchaValidationService = services.First(sd => sd.ServiceType == typeof(ICaptchaValidationService)); services.Remove(captchaValidationService); services.AddSingleton(); // Disable blocking var blockingService = services.First(sd => sd.ServiceType == typeof(IBlockIpService)); services.Remove(blockingService); services.AddSingleton(); // TODO: Install and use azurite in CI pipeline var installationDeviceRepository = services.First(sd => sd.ServiceType == typeof(IInstallationDeviceRepository)); services.Remove(installationDeviceRepository); services.AddSingleton(); // TODO: Install and use azurite in CI pipeline var metaDataRepository = services.First(sd => sd.ServiceType == typeof(IMetaDataRepository)); services.Remove(metaDataRepository); services.AddSingleton(); // TODO: Install and use azurite in CI pipeline var referenceEventService = services.First(sd => sd.ServiceType == typeof(IReferenceEventService)); services.Remove(referenceEventService); services.AddSingleton(); // 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(options => { options.IpWhitelist = new List { FactoryConstants.WhitelistedIp, }; }); // Fix IP Rate Limiting services.AddSingleton(); // Disable logs services.AddSingleton(); }); } public DatabaseContext GetDatabaseContext() { var scope = Services.CreateScope(); return scope.ServiceProvider.GetRequiredService(); } public T GetService() { var scope = Services.CreateScope(); return scope.ServiceProvider.GetRequiredService(); } }