// ----------------------------------------------------------------------- // // Copyright (c) lanedirt. All rights reserved. // Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. // // ----------------------------------------------------------------------- namespace AliasVault.IntegrationTests; using System.Reflection; using AliasServerDb; using AliasServerDb.Configuration; using AliasVault.Logging; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Npgsql; /// /// Builder class for creating a test host for services in order to run integration tests against them. This class /// contains common logic such as creating a temporary database. /// public class AbstractTestHostBuilder : IAsyncDisposable { /// /// The DbContextFactory instance that is created for the test. /// private IAliasServerDbContextFactory _dbContextFactory = null!; /// /// The cached DbContext instance that can be used during the test. /// private AliasServerDbContext? _dbContext; /// /// The temporary database name for the test. /// private string? _tempDbName; /// /// Returns the DbContext instance for the test. This can be used to seed the database with test data. /// /// AliasServerDbContext instance. public AliasServerDbContext GetDbContext() { if (_dbContext != null) { return _dbContext; } _dbContext = _dbContextFactory.CreateDbContext(); return _dbContext; } /// /// Returns the DbContext instance for the test. This can be used to seed the database with test data. /// /// AliasServerDbContext instance. public async Task GetDbContextAsync() { return await _dbContextFactory.CreateDbContextAsync(); } /// /// Disposes of the test host and cleans up the temporary database. /// /// A representing the asynchronous operation. public async ValueTask DisposeAsync() { if (_dbContext != null) { await _dbContext.DisposeAsync(); _dbContext = null; } if (!string.IsNullOrEmpty(_tempDbName)) { // Create a connection to 'postgres' database to drop the test database using var conn = new NpgsqlConnection( "Host=localhost;Port=5433;Database=postgres;Username=aliasvault;Password=password"); await conn.OpenAsync(); // First terminate existing connections using (var cmd = conn.CreateCommand()) { cmd.CommandText = $""" SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '{_tempDbName}'; """; await cmd.ExecuteNonQueryAsync(); } // Then drop the database using (var cmd = conn.CreateCommand()) { cmd.CommandText = $""" DROP DATABASE IF EXISTS "{_tempDbName}"; """; await cmd.ExecuteNonQueryAsync(); } GC.SuppressFinalize(this); } } /// /// Creates a new test host builder with test database connection already configured. /// /// IHost. protected IHostBuilder CreateBuilder() { _tempDbName = $"aliasdb_test_{Guid.NewGuid()}"; // Create a connection to 'postgres' database to ensure the test database exists AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); using (var conn = new NpgsqlConnection("Host=localhost;Port=5433;Database=postgres;Username=aliasvault;Password=password")) { conn.Open(); using (var cmd = conn.CreateCommand()) { cmd.CommandText = $""" CREATE DATABASE "{_tempDbName}"; """; cmd.ExecuteNonQuery(); } } // Create a connection to the new test database var dbConnection = new NpgsqlConnection($"Host=localhost;Port=5433;Database={_tempDbName};Username=aliasvault;Password=password"); var builder = Host.CreateDefaultBuilder() .ConfigureServices((context, services) => { // Override configuration var configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json", optional: true) .AddInMemoryCollection(new Dictionary { ["DatabaseProvider"] = "postgresql", ["ConnectionStrings:AliasServerDbContext"] = dbConnection.ConnectionString, }) .Build(); services.AddSingleton(configuration); services.AddAliasVaultDatabaseConfiguration(configuration); services.ConfigureLogging(configuration, Assembly.GetExecutingAssembly().GetName().Name!, "logs"); // Ensure the in-memory database is populated with tables var serviceProvider = services.BuildServiceProvider(); using (var scope = serviceProvider.CreateScope()) { _dbContextFactory = scope.ServiceProvider.GetRequiredService(); var dbContext = _dbContextFactory.CreateDbContext(); dbContext.Database.Migrate(); } }); return builder; } }