diff --git a/CleanArchitecture.Api/appsettings.Integration.json b/CleanArchitecture.Api/appsettings.Integration.json index 973f184..446fd0c 100644 --- a/CleanArchitecture.Api/appsettings.Integration.json +++ b/CleanArchitecture.Api/appsettings.Integration.json @@ -10,6 +10,6 @@ "Host": "localhost", "Username": "guest", "Password": "guest", - "Enabled": "False" + "Enabled": "True" } } diff --git a/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj b/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj index dba5f41..b2e8bfd 100644 --- a/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj +++ b/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj @@ -5,6 +5,7 @@ enable false + true diff --git a/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj b/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj index aa672b4..e788e28 100644 --- a/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj +++ b/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj @@ -5,6 +5,7 @@ enable false + true diff --git a/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj b/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj index 6f4594c..3267fbe 100644 --- a/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj +++ b/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj @@ -5,6 +5,7 @@ enable false + true diff --git a/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj b/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj index 6531f2a..4a23d64 100644 --- a/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj +++ b/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj @@ -5,6 +5,7 @@ enable false + true @@ -12,17 +13,17 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -34,5 +35,7 @@ - + + + diff --git a/CleanArchitecture.IntegrationTests/Constants/Configuration.cs b/CleanArchitecture.IntegrationTests/Constants/Configuration.cs new file mode 100644 index 0000000..a7774c6 --- /dev/null +++ b/CleanArchitecture.IntegrationTests/Constants/Configuration.cs @@ -0,0 +1,8 @@ +namespace CleanArchitecture.IntegrationTests.Constants; + +public static class Configuration +{ + public const int RedisPort = 6379; + public const int MsSqlPort = 1433; + public const int RabbitMqPort = 5673; +} diff --git a/CleanArchitecture.IntegrationTests/Controller/TenantControllerTests.cs b/CleanArchitecture.IntegrationTests/Controller/TenantControllerTests.cs index 5b5abfd..d406a4d 100644 --- a/CleanArchitecture.IntegrationTests/Controller/TenantControllerTests.cs +++ b/CleanArchitecture.IntegrationTests/Controller/TenantControllerTests.cs @@ -7,24 +7,17 @@ using CleanArchitecture.Application.ViewModels.Tenants; using CleanArchitecture.IntegrationTests.Extensions; using CleanArchitecture.IntegrationTests.Fixtures; using FluentAssertions; -using Xunit; -using Xunit.Priority; namespace CleanArchitecture.IntegrationTests.Controller; -[Collection("IntegrationTests")] -[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] -public sealed class TenantControllerTests : IClassFixture +public sealed class TenantControllerTests { - private readonly TenantTestFixture _fixture; + private readonly TenantTestFixture _fixture = new(); - public TenantControllerTests(TenantTestFixture fixture) - { - _fixture = fixture; - } + [OneTimeSetUp] + public async Task Setup() => await _fixture.SeedTestData(); - [Fact] - [Priority(0)] + [Test, Order(0)] public async Task Should_Get_Tenant_By_Id() { var response = await _fixture.ServerClient.GetAsync($"/api/v1/Tenant/{_fixture.CreatedTenantId}"); @@ -41,8 +34,7 @@ public sealed class TenantControllerTests : IClassFixture message.Data.Users.Count().Should().Be(1); } - [Fact] - [Priority(5)] + [Test, Order(1)] public async Task Should_Get_All_Tenants() { var response = await _fixture.ServerClient.GetAsync( @@ -63,8 +55,7 @@ public sealed class TenantControllerTests : IClassFixture .Users.Count().Should().Be(1); } - [Fact] - [Priority(10)] + [Test, Order(2)] public async Task Should_Create_Tenant() { var request = new CreateTenantViewModel("Test Tenant 2"); @@ -89,8 +80,7 @@ public sealed class TenantControllerTests : IClassFixture tenantMessage.Data.Name.Should().Be(request.Name); } - [Fact] - [Priority(15)] + [Test, Order(3)] public async Task Should_Update_Tenant() { var request = new UpdateTenantViewModel(_fixture.CreatedTenantId, "Test Tenant 3"); @@ -117,8 +107,7 @@ public sealed class TenantControllerTests : IClassFixture tenantMessage.Data.Name.Should().Be(request.Name); } - [Fact] - [Priority(20)] + [Test, Order(4)] public async Task Should_Delete_Tenant() { var response = await _fixture.ServerClient.DeleteAsync($"/api/v1/Tenant/{_fixture.CreatedTenantId}"); diff --git a/CleanArchitecture.IntegrationTests/Controller/UserControllerTests.cs b/CleanArchitecture.IntegrationTests/Controller/UserControllerTests.cs index 5d4b3d7..ab858ed 100644 --- a/CleanArchitecture.IntegrationTests/Controller/UserControllerTests.cs +++ b/CleanArchitecture.IntegrationTests/Controller/UserControllerTests.cs @@ -10,24 +10,17 @@ using CleanArchitecture.IntegrationTests.Extensions; using CleanArchitecture.IntegrationTests.Fixtures; using CleanArchitecture.IntegrationTests.Infrastructure.Auth; using FluentAssertions; -using Xunit; -using Xunit.Priority; namespace CleanArchitecture.IntegrationTests.Controller; -[Collection("IntegrationTests")] -[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] -public sealed class UserControllerTests : IClassFixture +public sealed class UserControllerTests { - private readonly UserTestFixture _fixture; + private readonly UserTestFixture _fixture = new(); - public UserControllerTests(UserTestFixture fixture) - { - _fixture = fixture; - } + [OneTimeSetUp] + public async Task Setup() => await GlobalSetupFixture.RespawnDatabaseAsync(); - [Fact] - [Priority(0)] + [Test, Order(0)] public async Task Should_Get_All_User() { var response = await _fixture.ServerClient.GetAsync("/api/v1/user"); @@ -50,8 +43,7 @@ public sealed class UserControllerTests : IClassFixture currentUser.LastName.Should().Be(TestAuthenticationOptions.LastName); } - [Fact] - [Priority(5)] + [Test, Order(1)] public async Task Should_Get_User_By_Id() { var response = await _fixture.ServerClient.GetAsync("/api/v1/user/" + TestAuthenticationOptions.TestUserId); @@ -70,8 +62,7 @@ public sealed class UserControllerTests : IClassFixture content.LastName.Should().Be(TestAuthenticationOptions.LastName); } - [Fact] - [Priority(10)] + [Test, Order(2)] public async Task Should_Create_User() { var user = new CreateUserViewModel( @@ -89,8 +80,7 @@ public sealed class UserControllerTests : IClassFixture message?.Data.Should().NotBeEmpty(); } - [Fact] - [Priority(15)] + [Test, Order(3)] public async Task Should_Login_User() { var user = new LoginUserViewModel( @@ -105,8 +95,7 @@ public sealed class UserControllerTests : IClassFixture message?.Data.Should().NotBeEmpty(); } - [Fact] - [Priority(20)] + [Test, Order(4)] public async Task Should_Get_The_Current_Active_Users() { var response = await _fixture.ServerClient.GetAsync("/api/v1/user/me"); @@ -125,8 +114,7 @@ public sealed class UserControllerTests : IClassFixture content.LastName.Should().Be(TestAuthenticationOptions.LastName); } - [Fact] - [Priority(25)] + [Test, Order(5)] public async Task Should_Update_User() { var user = new UpdateUserViewModel( @@ -167,8 +155,7 @@ public sealed class UserControllerTests : IClassFixture userContent.Role.Should().Be(user.Role); } - [Fact] - [Priority(30)] + [Test, Order(6)] public async Task Should_Change_User_Password() { var user = new ChangePasswordViewModel( @@ -201,8 +188,7 @@ public sealed class UserControllerTests : IClassFixture loginMessage?.Data.Should().NotBeEmpty(); } - [Fact] - [Priority(35)] + [Test, Order(7)] public async Task Should_Delete_User() { var response = await _fixture.ServerClient.DeleteAsync("/api/v1/user/" + TestAuthenticationOptions.TestUserId); diff --git a/CleanArchitecture.IntegrationTests/Fixtures/AccessorFixture.cs b/CleanArchitecture.IntegrationTests/Fixtures/AccessorFixture.cs deleted file mode 100644 index a98b43d..0000000 --- a/CleanArchitecture.IntegrationTests/Fixtures/AccessorFixture.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Threading.Tasks; -using CleanArchitecture.IntegrationTests.Infrastructure; -using Xunit; - -namespace CleanArchitecture.IntegrationTests.Fixtures; - -public sealed class AccessorFixture : IAsyncLifetime -{ - public static string TestRunDbName { get; } = $"CleanArchitecture-Integration-{Guid.NewGuid()}"; - - public async Task DisposeAsync() - { - var db = DatabaseAccessor.GetOrCreateAsync(TestRunDbName); - await db.DisposeAsync(); - - var redis = RedisAccessor.GetOrCreateAsync(); - await redis.DisposeAsync(); - - var rabbit = RabbitmqAccessor.GetOrCreateAsync(); - await rabbit.DisposeAsync(); - } - - public async Task InitializeAsync() - { - var db = DatabaseAccessor.GetOrCreateAsync(TestRunDbName); - await db.InitializeAsync(); - - var redis = RedisAccessor.GetOrCreateAsync(); - await redis.InitializeAsync(); - - var rabbit = RabbitmqAccessor.GetOrCreateAsync(); - await rabbit.InitializeAsync(); - } -} \ No newline at end of file diff --git a/CleanArchitecture.IntegrationTests/Fixtures/TenantTestFixture.cs b/CleanArchitecture.IntegrationTests/Fixtures/TenantTestFixture.cs index 0d9a821..7416837 100644 --- a/CleanArchitecture.IntegrationTests/Fixtures/TenantTestFixture.cs +++ b/CleanArchitecture.IntegrationTests/Fixtures/TenantTestFixture.cs @@ -1,7 +1,9 @@ using System; +using System.Threading.Tasks; using CleanArchitecture.Domain.Entities; using CleanArchitecture.Domain.Enums; using CleanArchitecture.Infrastructure.Database; +using Microsoft.Extensions.DependencyInjection; namespace CleanArchitecture.IntegrationTests.Fixtures; @@ -9,9 +11,11 @@ public sealed class TenantTestFixture : TestFixtureBase { public Guid CreatedTenantId { get; } = Guid.NewGuid(); - protected override void SeedTestData(ApplicationDbContext context) + public async Task SeedTestData() { - base.SeedTestData(context); + await GlobalSetupFixture.RespawnDatabaseAsync(); + + using var context = Factory.Services.GetRequiredService(); context.Tenants.Add(new Tenant( CreatedTenantId, @@ -26,6 +30,6 @@ public sealed class TenantTestFixture : TestFixtureBase "Test User", UserRole.User)); - context.SaveChanges(); + await context.SaveChangesAsync(); } } \ No newline at end of file diff --git a/CleanArchitecture.IntegrationTests/Fixtures/TestFixtureBase.cs b/CleanArchitecture.IntegrationTests/Fixtures/TestFixtureBase.cs index 3ba93ae..f5b8290 100644 --- a/CleanArchitecture.IntegrationTests/Fixtures/TestFixtureBase.cs +++ b/CleanArchitecture.IntegrationTests/Fixtures/TestFixtureBase.cs @@ -1,18 +1,11 @@ using System; using System.Net.Http; -using System.Threading.Tasks; -using CleanArchitecture.Domain.Constants; -using CleanArchitecture.Domain.Entities; -using CleanArchitecture.Domain.Enums; -using CleanArchitecture.Infrastructure.Database; using CleanArchitecture.IntegrationTests.Infrastructure; -using CleanArchitecture.IntegrationTests.Infrastructure.Auth; using Microsoft.Extensions.DependencyInjection; -using Xunit; namespace CleanArchitecture.IntegrationTests.Fixtures; -public class TestFixtureBase : IAsyncLifetime +public class TestFixtureBase { public HttpClient ServerClient { get; } protected CleanArchitectureWebApplicationFactory Factory { get; } @@ -21,64 +14,16 @@ public class TestFixtureBase : IAsyncLifetime { Factory = new CleanArchitectureWebApplicationFactory( RegisterCustomServicesHandler, - useTestAuthentication, - AccessorFixture.TestRunDbName); + useTestAuthentication); ServerClient = Factory.CreateClient(); ServerClient.Timeout = TimeSpan.FromMinutes(5); } - protected virtual void SeedTestData(ApplicationDbContext context) - { - } - - private async Task PrepareDatabaseAsync() - { - await Factory.RespawnDatabaseAsync(); - - using var scope = Factory.Services.CreateScope(); - await using var dbContext = scope.ServiceProvider.GetRequiredService(); - - dbContext.Tenants.Add(new Tenant( - Ids.Seed.TenantId, - "Admin Tenant")); - - dbContext.Users.Add(new User( - Ids.Seed.UserId, - Ids.Seed.TenantId, - "admin@email.com", - "Admin", - "User", - "$2a$12$Blal/uiFIJdYsCLTMUik/egLbfg3XhbnxBC6Sb5IKz2ZYhiU/MzL2", - UserRole.Admin)); - - dbContext.Users.Add(new User( - TestAuthenticationOptions.TestUserId, - Ids.Seed.TenantId, - TestAuthenticationOptions.Email, - TestAuthenticationOptions.FirstName, - TestAuthenticationOptions.LastName, - TestAuthenticationOptions.Password, - UserRole.Admin)); - - SeedTestData(dbContext); - await dbContext.SaveChangesAsync(); - } - protected virtual void RegisterCustomServicesHandler( IServiceCollection services, ServiceProvider serviceProvider, IServiceProvider scopedServices) { } - - public async Task InitializeAsync() - { - await PrepareDatabaseAsync(); - } - - public Task DisposeAsync() - { - return Task.CompletedTask; - } } \ No newline at end of file diff --git a/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetTenantsByIdsTestFixture.cs b/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetTenantsByIdsTestFixture.cs index fdfe5a6..05c381a 100644 --- a/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetTenantsByIdsTestFixture.cs +++ b/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetTenantsByIdsTestFixture.cs @@ -1,7 +1,9 @@ using System; +using System.Threading.Tasks; using CleanArchitecture.Domain.Entities; using CleanArchitecture.Infrastructure.Database; using Grpc.Net.Client; +using Microsoft.Extensions.DependencyInjection; namespace CleanArchitecture.IntegrationTests.Fixtures.gRPC; @@ -18,14 +20,16 @@ public sealed class GetTenantsByIdsTestFixture : TestFixtureBase }); } - protected override void SeedTestData(ApplicationDbContext context) + public async Task SeedTestData() { - base.SeedTestData(context); + await GlobalSetupFixture.RespawnDatabaseAsync(); + + using var context = Factory.Services.GetRequiredService(); var tenant = CreateTenant(); context.Tenants.Add(tenant); - context.SaveChanges(); + await context.SaveChangesAsync(); } public Tenant CreateTenant() diff --git a/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetUsersByIdsTestFixture.cs b/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetUsersByIdsTestFixture.cs index 22ce59b..09fa30e 100644 --- a/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetUsersByIdsTestFixture.cs +++ b/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetUsersByIdsTestFixture.cs @@ -1,9 +1,11 @@ using System; +using System.Threading.Tasks; using CleanArchitecture.Domain.Constants; using CleanArchitecture.Domain.Entities; using CleanArchitecture.Domain.Enums; using CleanArchitecture.Infrastructure.Database; using Grpc.Net.Client; +using Microsoft.Extensions.DependencyInjection; namespace CleanArchitecture.IntegrationTests.Fixtures.gRPC; @@ -20,14 +22,16 @@ public sealed class GetUsersByIdsTestFixture : TestFixtureBase }); } - protected override void SeedTestData(ApplicationDbContext context) + public async Task SeedTestData() { - base.SeedTestData(context); + await GlobalSetupFixture.RespawnDatabaseAsync(); + + using var context = Factory.Services.GetRequiredService(); var user = CreateUser(); context.Users.Add(user); - context.SaveChanges(); + await context.SaveChangesAsync(); } public User CreateUser() diff --git a/CleanArchitecture.IntegrationTests/GlobalSetupFixture.cs b/CleanArchitecture.IntegrationTests/GlobalSetupFixture.cs new file mode 100644 index 0000000..7805a7d --- /dev/null +++ b/CleanArchitecture.IntegrationTests/GlobalSetupFixture.cs @@ -0,0 +1,75 @@ +using System; +using System.Threading.Tasks; +using CleanArchitecture.IntegrationTests.Constants; +using Respawn; +using Testcontainers.MsSql; +using Testcontainers.RabbitMq; +using Testcontainers.Redis; + +namespace CleanArchitecture.IntegrationTests; + +[SetUpFixture] +internal class GlobalSetupFixture +{ + private static Respawner? s_respawner; + + public static MsSqlContainer DatabaseContainer { get; } = new MsSqlBuilder() + .WithPortBinding(Configuration.MsSqlPort, assignRandomHostPort: true) + .Build(); + + public static RedisContainer RedisContainer { get; } = new RedisBuilder() + .WithPortBinding(Configuration.RedisPort, assignRandomHostPort: true) + .Build(); + + public static RabbitMqContainer RabbitContainer { get; } = new RabbitMqBuilder() + .WithPortBinding(Configuration.RabbitMqPort, assignRandomHostPort: true) + .Build(); + + public static string DatabaseConnectionString { get; private set; } = string.Empty; + + [OneTimeSetUp] + public async Task SetUp() + { + await DatabaseContainer.StartAsync(); + await RedisContainer.StartAsync(); + await RabbitContainer.StartAsync(); + + DatabaseConnectionString = DatabaseContainer + .GetConnectionString() + .Replace("Database=master", $"Database=clean-architecture-{Guid.NewGuid()}"); + } + + [OneTimeTearDown] + public async Task TearDown() + { + await DatabaseContainer.DisposeAsync(); + await RedisContainer.DisposeAsync(); + await RabbitContainer.DisposeAsync(); + } + + public static async Task RespawnDatabaseAsync() + { + if (s_respawner is null) + { + try + { + s_respawner = await Respawner.CreateAsync( + DatabaseConnectionString, + new RespawnerOptions + { + TablesToIgnore = ["__EFMigrationsHistory"] + }); + } + catch (Exception ex) + { + // Creation of the respawner can fail if the database has not been created yet + TestContext.WriteLine($"Failed to create respawner: {ex.Message}"); + } + } + + if (s_respawner is not null) + { + await s_respawner.ResetAsync(DatabaseConnectionString); + } + } +} diff --git a/CleanArchitecture.IntegrationTests/Infrastructure/CleanArchitectureWebApplicationFactory.cs b/CleanArchitecture.IntegrationTests/Infrastructure/CleanArchitectureWebApplicationFactory.cs index 2fde656..8a17ef9 100644 --- a/CleanArchitecture.IntegrationTests/Infrastructure/CleanArchitectureWebApplicationFactory.cs +++ b/CleanArchitecture.IntegrationTests/Infrastructure/CleanArchitectureWebApplicationFactory.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; -using System.Threading.Tasks; -using CleanArchitecture.Infrastructure.Database; +using CleanArchitecture.IntegrationTests.Constants; using CleanArchitecture.IntegrationTests.Infrastructure.Auth; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; @@ -12,26 +11,20 @@ namespace CleanArchitecture.IntegrationTests.Infrastructure; public sealed class CleanArchitectureWebApplicationFactory : WebApplicationFactory { - public delegate void AddCustomSeedDataHandler(ApplicationDbContext context); - public delegate void RegisterCustomServicesHandler( IServiceCollection services, ServiceProvider serviceProvider, IServiceProvider scopedServices); - private readonly string _instanceDatabaseName; - private readonly bool _addTestAuthentication; private readonly RegisterCustomServicesHandler? _registerCustomServicesHandler; public CleanArchitectureWebApplicationFactory( RegisterCustomServicesHandler? registerCustomServicesHandler, - bool addTestAuthentication, - string instanceDatabaseName) + bool addTestAuthentication) { _registerCustomServicesHandler = registerCustomServicesHandler; _addTestAuthentication = addTestAuthentication; - _instanceDatabaseName = instanceDatabaseName; } protected override void ConfigureWebHost(IWebHostBuilder builder) @@ -40,31 +33,18 @@ public sealed class CleanArchitectureWebApplicationFactory : WebApplicationFacto base.ConfigureWebHost(builder); - var configuration = new ConfigurationBuilder() - .Build(); - - builder.ConfigureAppConfiguration(configurationBuilder => + builder.ConfigureAppConfiguration(configuration => { - configurationBuilder.AddEnvironmentVariables(); + var redisPort = GlobalSetupFixture.RedisContainer.GetMappedPublicPort(Configuration.RedisPort); - var dbAccessor = DatabaseAccessor.GetOrCreateAsync(_instanceDatabaseName); - var redisAccessor = RedisAccessor.GetOrCreateAsync(); - var rabbitAccessor = RabbitmqAccessor.GetOrCreateAsync(); - - // Overwrite default connection strings - configurationBuilder.AddInMemoryCollection([ + configuration.AddInMemoryCollection([ new KeyValuePair( "ConnectionStrings:DefaultConnection", - dbAccessor.GetConnectionString()), + GlobalSetupFixture.DatabaseConnectionString), new KeyValuePair( - "RedisHostName", - redisAccessor.GetConnectionString()), - new KeyValuePair( - "RabbitMQ:Host", - rabbitAccessor.GetConnectionString()) + "RedisStackExchange:RedisConfigString", + $"localhost:{redisPort},abortConnect=true") ]); - - configuration = configurationBuilder.Build(); }); builder.ConfigureServices(services => @@ -82,38 +62,7 @@ public sealed class CleanArchitectureWebApplicationFactory : WebApplicationFacto using var scope = sp.CreateScope(); var scopedServices = scope.ServiceProvider; - - var dbAccessor = DatabaseAccessor.GetOrCreateAsync(_instanceDatabaseName); - dbAccessor.CreateDatabase(scopedServices); - - var redisAccessor = RedisAccessor.GetOrCreateAsync(); - redisAccessor.RegisterRedis(services, configuration); - - var rabbitAccessor = RabbitmqAccessor.GetOrCreateAsync(); - rabbitAccessor.RegisterRabbitmq(services, configuration); - _registerCustomServicesHandler?.Invoke(services, sp, scopedServices); }); } - - public async Task RespawnDatabaseAsync() - { - var dbAccessor = DatabaseAccessor.GetOrCreateAsync(_instanceDatabaseName); - await dbAccessor.RespawnDatabaseAsync(); - - var redisAccessor = RedisAccessor.GetOrCreateAsync(); - redisAccessor.ResetRedis(); - } - - public override async ValueTask DisposeAsync() - { - var dbAccessor = DatabaseAccessor.GetOrCreateAsync(_instanceDatabaseName); - await dbAccessor.DisposeAsync(); - - var redisAccessor = RedisAccessor.GetOrCreateAsync(); - await redisAccessor.DisposeAsync(); - - var rabbitAccessor = RabbitmqAccessor.GetOrCreateAsync(); - await rabbitAccessor.DisposeAsync(); - } } \ No newline at end of file diff --git a/CleanArchitecture.IntegrationTests/Infrastructure/DatabaseAccessor.cs b/CleanArchitecture.IntegrationTests/Infrastructure/DatabaseAccessor.cs deleted file mode 100644 index ab1d837..0000000 --- a/CleanArchitecture.IntegrationTests/Infrastructure/DatabaseAccessor.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Threading.Tasks; -using CleanArchitecture.Infrastructure.Database; -using CleanArchitecture.Infrastructure.Extensions; -using Microsoft.Data.SqlClient; -using Microsoft.Extensions.DependencyInjection; -using Respawn; -using Testcontainers.MsSql; - -namespace CleanArchitecture.IntegrationTests.Infrastructure; - -public sealed class DatabaseAccessor -{ - private static readonly ConcurrentDictionary s_accessors = new(); - - private readonly string _instanceDatabaseName; - private bool _databaseCreated = false; - private readonly object _databaseCreationLock = new(); - - private const string _dbPassword = "234#AD224fD#ss"; - private static readonly MsSqlContainer s_dbContainer = new MsSqlBuilder() - .WithPassword(_dbPassword) - .WithPortBinding(1433) - .Build(); - - public DatabaseAccessor(string instanceName) - { - _instanceDatabaseName = instanceName; - } - - public async Task InitializeAsync() - { - await s_dbContainer.StartAsync(); - } - - public ApplicationDbContext CreateDatabase(IServiceProvider scopedServices) - { - var applicationDbContext = scopedServices.GetRequiredService(); - - lock (_databaseCreationLock) - { - if (_databaseCreated) - { - return applicationDbContext; - } - - applicationDbContext.EnsureMigrationsApplied(); - - var eventsContext = scopedServices.GetRequiredService(); - eventsContext.EnsureMigrationsApplied(); - - var notificationsContext = scopedServices.GetRequiredService(); - notificationsContext.EnsureMigrationsApplied(); - } - - _databaseCreated = true; - - return applicationDbContext; - } - - public async ValueTask DisposeAsync() - { - // Reset the database to its original state - var dropScript = $@" - USE MASTER; - - ALTER DATABASE [{_instanceDatabaseName}] - SET multi_user WITH ROLLBACK IMMEDIATE; - - ALTER DATABASE [{_instanceDatabaseName}] - SET SINGLE_USER WITH ROLLBACK IMMEDIATE; - - DROP DATABASE [{_instanceDatabaseName}];"; - - await using (var con = new SqlConnection(GetConnectionString())) - { - await con.OpenAsync(); - - var cmd = new SqlCommand(dropScript, con); - await cmd.ExecuteNonQueryAsync(); - } - - await s_dbContainer.DisposeAsync(); - } - - public async Task RespawnDatabaseAsync() - { - var connectionString = GetConnectionString(); - - var respawn = await Respawner.CreateAsync( - connectionString, - new RespawnerOptions - { - TablesToIgnore = ["__EFMigrationsHistory"] - }); - - await respawn.ResetAsync(connectionString); - } - - public string GetConnectionString() - { - var conBuilder = new SqlConnectionStringBuilder() - { - DataSource = s_dbContainer.Hostname, - InitialCatalog = _instanceDatabaseName, - IntegratedSecurity = false, - Password = _dbPassword, - UserID = "sa", - TrustServerCertificate = true - }; - - return conBuilder.ToString(); - } - - public static DatabaseAccessor GetOrCreateAsync(string instanceName) - { - if (!s_accessors.TryGetValue(instanceName, out _)) - { - var accessor = new DatabaseAccessor(instanceName); - s_accessors.TryAdd(instanceName, accessor); - } - - return s_accessors[instanceName]; - } -} \ No newline at end of file diff --git a/CleanArchitecture.IntegrationTests/Infrastructure/RabbitmqAccessor.cs b/CleanArchitecture.IntegrationTests/Infrastructure/RabbitmqAccessor.cs deleted file mode 100644 index 0e5ea8d..0000000 --- a/CleanArchitecture.IntegrationTests/Infrastructure/RabbitmqAccessor.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections.Concurrent; -using System.Linq; -using System.Threading.Tasks; -using CleanArchitecture.Domain.Rabbitmq; -using CleanArchitecture.Domain.Rabbitmq.Extensions; -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Testcontainers.RabbitMq; -using RabbitMqConfiguration = CleanArchitecture.Domain.Rabbitmq.RabbitMqConfiguration; - -namespace CleanArchitecture.IntegrationTests.Infrastructure; - -public sealed class RabbitmqAccessor -{ - private static readonly ConcurrentDictionary s_accessors = new(); - - private static readonly RabbitMqContainer s_rabbitContainer = new RabbitMqBuilder() - .WithPortBinding(5672) - .Build(); - - public async Task InitializeAsync() - { - await s_rabbitContainer.StartAsync(); - } - - public async ValueTask DisposeAsync() - { - await s_rabbitContainer.DisposeAsync(); - } - - public string GetConnectionString() - { - return s_rabbitContainer.GetConnectionString(); - } - - public void RegisterRabbitmq(IServiceCollection serviceCollection, IConfiguration configuration) - { - var rabbitService = serviceCollection.FirstOrDefault(x => - x.ServiceType == typeof(RabbitMqHandler)); - - if (rabbitService != null) - { - serviceCollection.Remove(rabbitService); - } - - var rabbitConfig = serviceCollection.FirstOrDefault(x => - x.ServiceType == typeof(RabbitMqConfiguration)); - - if (rabbitConfig != null) - { - serviceCollection.Remove(rabbitConfig); - } - - serviceCollection.AddRabbitMqHandler( - configuration, - "RabbitMQ"); - } - - public static RabbitmqAccessor GetOrCreateAsync() - { - return s_accessors.GetOrAdd("rabbimq", _ => new RabbitmqAccessor()); - } -} \ No newline at end of file diff --git a/CleanArchitecture.IntegrationTests/Infrastructure/RedisAccessor.cs b/CleanArchitecture.IntegrationTests/Infrastructure/RedisAccessor.cs deleted file mode 100644 index f461ab8..0000000 --- a/CleanArchitecture.IntegrationTests/Infrastructure/RedisAccessor.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Collections.Concurrent; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using StackExchange.Redis; -using Testcontainers.Redis; - -namespace CleanArchitecture.IntegrationTests.Infrastructure; - -public sealed class RedisAccessor -{ - private static readonly ConcurrentDictionary s_accessors = new(); - - private static readonly RedisContainer s_redisContainer = new RedisBuilder() - .WithPortBinding(6379) - .Build(); - - public async Task InitializeAsync() - { - await s_redisContainer.StartAsync(); - } - - public async ValueTask DisposeAsync() - { - await s_redisContainer.DisposeAsync(); - } - - public string GetConnectionString() - { - return s_redisContainer.GetConnectionString(); - } - - public void RegisterRedis(IServiceCollection serviceCollection, IConfiguration configuration) - { - var distributedCache = serviceCollection.FirstOrDefault(x => - x.ServiceType == typeof(IDistributedCache)); - - if (distributedCache != null) - { - serviceCollection.Remove(distributedCache); - } - - serviceCollection.AddStackExchangeRedisCache(options => - { - options.Configuration = configuration["RedisHostName"]; - options.InstanceName = "clean-architecture"; - }); - } - - public void ResetRedis() - { - var redis = ConnectionMultiplexer.Connect(GetConnectionString()); - - var db = redis.GetDatabase(); - - var endpoints = redis.GetEndPoints(); - foreach (var endpoint in endpoints) - { - var server = redis.GetServer(endpoint); - var keys = server.Keys(); - foreach (var key in keys) - { - db.KeyDelete(key); - } - } - - redis.Close(); - } - - public static RedisAccessor GetOrCreateAsync() - { - return s_accessors.GetOrAdd("redis", _ => new RedisAccessor()); - } -} \ No newline at end of file diff --git a/CleanArchitecture.IntegrationTests/IntegrationTestsCollection.cs b/CleanArchitecture.IntegrationTests/IntegrationTestsCollection.cs deleted file mode 100644 index f44a4e5..0000000 --- a/CleanArchitecture.IntegrationTests/IntegrationTestsCollection.cs +++ /dev/null @@ -1,10 +0,0 @@ -using CleanArchitecture.IntegrationTests.Fixtures; -using Xunit; - -namespace CleanArchitecture.IntegrationTests; - -[CollectionDefinition("IntegrationTests", DisableParallelization = true)] -public sealed class IntegrationTestsCollection : - ICollectionFixture -{ -} \ No newline at end of file diff --git a/CleanArchitecture.IntegrationTests/UtilityTests/AuthTests.cs b/CleanArchitecture.IntegrationTests/UtilityTests/AuthTests.cs index 7f3f26b..eea7c8f 100644 --- a/CleanArchitecture.IntegrationTests/UtilityTests/AuthTests.cs +++ b/CleanArchitecture.IntegrationTests/UtilityTests/AuthTests.cs @@ -2,33 +2,30 @@ using System.Net; using System.Threading.Tasks; using CleanArchitecture.IntegrationTests.Fixtures; using FluentAssertions; -using Xunit; -using Xunit.Priority; namespace CleanArchitecture.IntegrationTests.UtilityTests; -[Collection("IntegrationTests")] -[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] -public sealed class AuthTests : IClassFixture +public sealed class AuthTests { - private readonly AuthTestFixure _fixture; + private readonly AuthTestFixure _fixture = new(); - public AuthTests(AuthTestFixure fixture) - { - _fixture = fixture; - } + [OneTimeSetUp] + public async Task Setup() => await GlobalSetupFixture.RespawnDatabaseAsync(); + + [Datapoints] + public string[] values = + [ + "/api/v1/user", + "/api/v1/user/me", + "/api/v1/user/d74b112a-ece0-443d-9b4f-85bc418822ca", + "/api/v1/tenant", + "/api/v1/tenant/d74b112a-ece0-443d-9b4f-85bc418822ca" + ]; [Theory] - [InlineData("/api/v1/user")] - [InlineData("/api/v1/user/me")] - [InlineData("/api/v1/user/d74b112a-ece0-443d-9b4f-85bc418822ca")] - [InlineData("/api/v1/tenant")] - [InlineData("/api/v1/tenant/d74b112a-ece0-443d-9b4f-85bc418822ca")] - public async Task Should_Get_Unauthorized_If_Trying_To_Call_Endpoint_Without_Token( - string url) + public async Task Should_Get_Unauthorized_If_Trying_To_Call_Endpoint_Without_Token(string url) { var response = await _fixture.ServerClient.GetAsync(url); - response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); } } \ No newline at end of file diff --git a/CleanArchitecture.IntegrationTests/UtilityTests/HealthChecksTests.cs b/CleanArchitecture.IntegrationTests/UtilityTests/HealthChecksTests.cs index 779f085..ec66a52 100644 --- a/CleanArchitecture.IntegrationTests/UtilityTests/HealthChecksTests.cs +++ b/CleanArchitecture.IntegrationTests/UtilityTests/HealthChecksTests.cs @@ -4,24 +4,17 @@ using CleanArchitecture.IntegrationTests.Fixtures; using FluentAssertions; using Microsoft.Extensions.Diagnostics.HealthChecks; using Newtonsoft.Json.Linq; -using Xunit; -using Xunit.Priority; namespace CleanArchitecture.IntegrationTests.UtilityTests; -[Collection("IntegrationTests")] -[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] -public sealed class HealthChecksTests : IClassFixture +public sealed class HealthChecksTests { - private readonly AuthTestFixure _fixture; + private readonly AuthTestFixure _fixture = new(); - public HealthChecksTests(AuthTestFixure fixture) - { - _fixture = fixture; - } + [OneTimeSetUp] + public async Task Setup() => await GlobalSetupFixture.RespawnDatabaseAsync(); - [Fact] - [Priority(0)] + [Test, Order(0)] public async Task Should_Return_Healthy() { var response = await _fixture.ServerClient.GetAsync("/healthz"); diff --git a/CleanArchitecture.IntegrationTests/gRPC/GetTenantsByIdsTests.cs b/CleanArchitecture.IntegrationTests/gRPC/GetTenantsByIdsTests.cs index bef8dbf..a101f4e 100644 --- a/CleanArchitecture.IntegrationTests/gRPC/GetTenantsByIdsTests.cs +++ b/CleanArchitecture.IntegrationTests/gRPC/GetTenantsByIdsTests.cs @@ -4,23 +4,17 @@ using System.Threading.Tasks; using CleanArchitecture.IntegrationTests.Fixtures.gRPC; using CleanArchitecture.Proto.Tenants; using FluentAssertions; -using Xunit; -using Xunit.Priority; namespace CleanArchitecture.IntegrationTests.gRPC; -[Collection("IntegrationTests")] -[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] -public sealed class GetTenantsByIdsTests : IClassFixture +public sealed class GetTenantsByIdsTests { - private readonly GetTenantsByIdsTestFixture _fixture; + private readonly GetTenantsByIdsTestFixture _fixture = new(); - public GetTenantsByIdsTests(GetTenantsByIdsTestFixture fixture) - { - _fixture = fixture; - } + [OneTimeSetUp] + public async Task Setup() => await _fixture.SeedTestData(); - [Fact] + [Test] public async Task Should_Get_Tenants_By_Ids() { var client = new TenantsApi.TenantsApiClient(_fixture.GrpcChannel); diff --git a/CleanArchitecture.IntegrationTests/gRPC/GetUsersByIdsTests.cs b/CleanArchitecture.IntegrationTests/gRPC/GetUsersByIdsTests.cs index 2353300..ab4a95c 100644 --- a/CleanArchitecture.IntegrationTests/gRPC/GetUsersByIdsTests.cs +++ b/CleanArchitecture.IntegrationTests/gRPC/GetUsersByIdsTests.cs @@ -3,23 +3,17 @@ using System.Threading.Tasks; using CleanArchitecture.IntegrationTests.Fixtures.gRPC; using CleanArchitecture.Proto.Users; using FluentAssertions; -using Xunit; -using Xunit.Priority; namespace CleanArchitecture.IntegrationTests.gRPC; -[Collection("IntegrationTests")] -[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] -public sealed class GetUsersByIdsTests : IClassFixture +public sealed class GetUsersByIdsTests { - private readonly GetUsersByIdsTestFixture _fixture; + private readonly GetUsersByIdsTestFixture _fixture = new(); - public GetUsersByIdsTests(GetUsersByIdsTestFixture fixture) - { - _fixture = fixture; - } + [OneTimeSetUp] + public async Task Setup() => await _fixture.SeedTestData(); - [Fact] + [Test] public async Task Should_Get_Users_By_Ids() { var client = new UsersApi.UsersApiClient(_fixture.GrpcChannel);