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);
+ }
+}