diff --git a/colaflow-api/src/ColaFlow.API/Controllers/ProjectsController.cs b/colaflow-api/src/ColaFlow.API/Controllers/ProjectsController.cs index 0e45592..ecc636e 100644 --- a/colaflow-api/src/ColaFlow.API/Controllers/ProjectsController.cs +++ b/colaflow-api/src/ColaFlow.API/Controllers/ProjectsController.cs @@ -57,14 +57,13 @@ public class ProjectsController(IMediator mediator) : ControllerBase [FromBody] CreateProjectCommand command, CancellationToken cancellationToken = default) { - // Extract TenantId and UserId from JWT claims - var tenantId = GetTenantIdFromClaims(); + // Extract UserId from JWT claims + // Note: TenantId is now automatically extracted in the CommandHandler via ITenantContext var userId = GetUserIdFromClaims(); - // Override command with authenticated user's context + // Override command with authenticated user's ID var commandWithContext = command with { - TenantId = tenantId, OwnerId = userId }; @@ -104,15 +103,7 @@ public class ProjectsController(IMediator mediator) : ControllerBase return NoContent(); } - // Helper methods to extract claims - private Guid GetTenantIdFromClaims() - { - var tenantIdClaim = User.FindFirst("tenant_id")?.Value - ?? throw new UnauthorizedAccessException("Tenant ID not found in token"); - - return Guid.Parse(tenantIdClaim); - } - + // Helper method to extract user ID from claims private Guid GetUserIdFromClaims() { var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value diff --git a/colaflow-api/tests/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.IntegrationTests/ColaFlow.Modules.ProjectManagement.IntegrationTests.csproj b/colaflow-api/tests/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.IntegrationTests/ColaFlow.Modules.ProjectManagement.IntegrationTests.csproj new file mode 100644 index 0000000..7a3e46b --- /dev/null +++ b/colaflow-api/tests/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.IntegrationTests/ColaFlow.Modules.ProjectManagement.IntegrationTests.csproj @@ -0,0 +1,49 @@ + + + + net9.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/colaflow-api/tests/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.IntegrationTests/Infrastructure/PMWebApplicationFactory.cs b/colaflow-api/tests/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.IntegrationTests/Infrastructure/PMWebApplicationFactory.cs new file mode 100644 index 0000000..4c1ecfc --- /dev/null +++ b/colaflow-api/tests/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.IntegrationTests/Infrastructure/PMWebApplicationFactory.cs @@ -0,0 +1,100 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using ColaFlow.Modules.Identity.Infrastructure.Persistence; +using ColaFlow.Modules.IssueManagement.Infrastructure.Persistence; +using ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence; + +namespace ColaFlow.Modules.ProjectManagement.IntegrationTests.Infrastructure; + +/// +/// Custom WebApplicationFactory for ProjectManagement Integration Tests +/// Supports In-Memory database for fast, isolated tests +/// +public class PMWebApplicationFactory : WebApplicationFactory +{ + private readonly string _testDatabaseName = $"PMTestDb_{Guid.NewGuid()}"; + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + // Set environment to Testing + builder.UseEnvironment("Testing"); + + // Configure test-specific settings + builder.ConfigureAppConfiguration((context, config) => + { + // Clear existing connection strings to prevent PostgreSQL registration + config.Sources.Clear(); + + // Add minimal config for testing + config.AddInMemoryCollection(new Dictionary + { + ["ConnectionStrings:DefaultConnection"] = "", + ["ConnectionStrings:PMDatabase"] = "", + ["ConnectionStrings:IMDatabase"] = "", + ["Jwt:SecretKey"] = "test-secret-key-for-integration-tests-minimum-32-characters", + ["Jwt:Issuer"] = "ColaFlow.Test", + ["Jwt:Audience"] = "ColaFlow.Test", + ["Jwt:AccessTokenExpirationMinutes"] = "15", + ["Jwt:RefreshTokenExpirationDays"] = "7" + }); + }); + + builder.ConfigureServices(services => + { + // Register test databases with In-Memory provider + // Use the same database name for cross-context data consistency + services.AddDbContext(options => + { + options.UseInMemoryDatabase(_testDatabaseName); + options.EnableSensitiveDataLogging(); + }); + + services.AddDbContext(options => + { + options.UseInMemoryDatabase(_testDatabaseName); + options.EnableSensitiveDataLogging(); + }); + + services.AddDbContext(options => + { + options.UseInMemoryDatabase(_testDatabaseName); + options.EnableSensitiveDataLogging(); + }); + }); + } + + protected override IHost CreateHost(IHostBuilder builder) + { + var host = base.CreateHost(builder); + + // Initialize databases after host is created + using var scope = host.Services.CreateScope(); + var services = scope.ServiceProvider; + + try + { + // Initialize Identity database + var identityDb = services.GetRequiredService(); + identityDb.Database.EnsureCreated(); + + // Initialize ProjectManagement database + var pmDb = services.GetRequiredService(); + pmDb.Database.EnsureCreated(); + + // Initialize IssueManagement database + var imDb = services.GetRequiredService(); + imDb.Database.EnsureCreated(); + } + catch (Exception ex) + { + Console.WriteLine($"Error initializing test database: {ex.Message}"); + throw; + } + + return host; + } +} diff --git a/colaflow-api/tests/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.IntegrationTests/Infrastructure/TestAuthHelper.cs b/colaflow-api/tests/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.IntegrationTests/Infrastructure/TestAuthHelper.cs new file mode 100644 index 0000000..5f6b7a7 --- /dev/null +++ b/colaflow-api/tests/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.IntegrationTests/Infrastructure/TestAuthHelper.cs @@ -0,0 +1,57 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text; +using Microsoft.IdentityModel.Tokens; + +namespace ColaFlow.Modules.ProjectManagement.IntegrationTests.Infrastructure; + +/// +/// Helper class for generating JWT tokens in tests +/// +public static class TestAuthHelper +{ + private const string SecretKey = "test-secret-key-for-integration-tests-minimum-32-characters"; + private const string Issuer = "ColaFlow.Test"; + private const string Audience = "ColaFlow.Test"; + + public static string GenerateJwtToken(Guid userId, Guid tenantId, string tenantSlug, string email, string role = "Member") + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.UTF8.GetBytes(SecretKey); + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(new[] + { + new Claim(ClaimTypes.NameIdentifier, userId.ToString()), + new Claim("sub", userId.ToString()), + new Claim("tenant_id", tenantId.ToString()), + new Claim("tenantId", tenantId.ToString()), + new Claim("tenantSlug", tenantSlug), + new Claim("email", email), + new Claim("role", role) + }), + Expires = DateTime.UtcNow.AddHours(1), + Issuer = Issuer, + Audience = Audience, + SigningCredentials = new SigningCredentials( + new SymmetricSecurityKey(key), + SecurityAlgorithms.HmacSha256Signature) + }; + + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + + public static void AddAuthHeader(this HttpClient client, string token) + { + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + } + + public static void AddAuthHeader(this HttpClient client, Guid userId, Guid tenantId, string tenantSlug, string email, string role = "Member") + { + var token = GenerateJwtToken(userId, tenantId, tenantSlug, email, role); + client.AddAuthHeader(token); + } +}