Commit all scripts
Some checks failed
Code Coverage / Generate Coverage Report (push) Has been cancelled
Tests / Run Tests (9.0.x) (push) Has been cancelled
Tests / Docker Build Test (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled

This commit is contained in:
Yaojia Wang
2025-11-03 17:19:20 +01:00
parent ebdd4ee0d7
commit 4183b10b39
24 changed files with 4917 additions and 11 deletions

View File

@@ -0,0 +1,26 @@
namespace ColaFlow.Modules.Identity.IntegrationTests.Infrastructure;
/// <summary>
/// Database Fixture for In-Memory Database Tests
/// Implements IClassFixture for xUnit test lifecycle management
/// Each test class gets its own isolated database instance
/// </summary>
public class DatabaseFixture : IDisposable
{
public ColaFlowWebApplicationFactory Factory { get; }
public HttpClient Client { get; }
public DatabaseFixture()
{
// Use In-Memory Database for fast, isolated tests
Factory = new ColaFlowWebApplicationFactory(useInMemoryDatabase: true);
Client = Factory.CreateClient();
}
public void Dispose()
{
Client?.Dispose();
Factory?.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,65 @@
using ColaFlow.Modules.Identity.Infrastructure.Persistence;
using Microsoft.Extensions.DependencyInjection;
namespace ColaFlow.Modules.Identity.IntegrationTests.Infrastructure;
/// <summary>
/// Database Fixture for Real PostgreSQL Database Tests
/// Use this for more realistic integration tests that verify actual database behavior
/// Requires PostgreSQL to be running on localhost
/// </summary>
public class RealDatabaseFixture : IDisposable
{
public ColaFlowWebApplicationFactory Factory { get; }
public HttpClient Client { get; }
private readonly string _testDatabaseName;
public RealDatabaseFixture()
{
_testDatabaseName = $"test_{Guid.NewGuid():N}";
// Use Real PostgreSQL Database
Factory = new ColaFlowWebApplicationFactory(
useInMemoryDatabase: false,
testDatabaseName: _testDatabaseName
);
Client = Factory.CreateClient();
// Clean up any existing test data
CleanupDatabase();
}
private void CleanupDatabase()
{
using var scope = Factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<IdentityDbContext>();
// Clear all data from test database
db.RefreshTokens.RemoveRange(db.RefreshTokens);
db.Users.RemoveRange(db.Users);
db.Tenants.RemoveRange(db.Tenants);
db.SaveChanges();
}
public void Dispose()
{
try
{
// Clean up test database
using (var scope = Factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<IdentityDbContext>();
db.Database.EnsureDeleted();
}
}
catch
{
// Ignore cleanup errors
}
Client?.Dispose();
Factory?.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,108 @@
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Json;
using System.Security.Claims;
namespace ColaFlow.Modules.Identity.IntegrationTests.Infrastructure;
/// <summary>
/// Helper class for authentication-related test operations
/// Provides utilities for registration, login, token parsing, and common test scenarios
/// </summary>
public static class TestAuthHelper
{
/// <summary>
/// Register a new tenant and return the access token and refresh token
/// </summary>
public static async Task<(string accessToken, string refreshToken)> RegisterAndGetTokensAsync(
HttpClient client,
string? tenantSlug = null,
string? email = null,
string? password = null)
{
var slug = tenantSlug ?? $"test-{Guid.NewGuid():N}";
var adminEmail = email ?? $"admin-{Guid.NewGuid():N}@test.com";
var adminPassword = password ?? "Admin@1234";
var request = new
{
tenantName = "Test Corp",
tenantSlug = slug,
subscriptionPlan = "Professional",
adminEmail,
adminPassword,
adminFullName = "Test Admin"
};
var response = await client.PostAsJsonAsync("/api/tenants/register", request);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<RegisterResponse>();
return (result!.AccessToken, result.RefreshToken);
}
/// <summary>
/// Login with credentials and return tokens
/// </summary>
public static async Task<(string accessToken, string refreshToken)> LoginAndGetTokensAsync(
HttpClient client,
string tenantSlug,
string email,
string password)
{
var request = new
{
tenantSlug,
email,
password
};
var response = await client.PostAsJsonAsync("/api/auth/login", request);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<LoginResponse>();
return (result!.AccessToken, result.RefreshToken);
}
/// <summary>
/// Parse JWT token and extract claims
/// </summary>
public static IEnumerable<Claim> ParseJwtToken(string token)
{
var handler = new JwtSecurityTokenHandler();
var jwtToken = handler.ReadJwtToken(token);
return jwtToken.Claims;
}
/// <summary>
/// Get specific claim value from token
/// </summary>
public static string? GetClaimValue(string token, string claimType)
{
var claims = ParseJwtToken(token);
return claims.FirstOrDefault(c => c.Type == claimType)?.Value;
}
/// <summary>
/// Verify token contains expected role
/// </summary>
public static bool HasRole(string token, string role)
{
var claims = ParseJwtToken(token);
return claims.Any(c => c.Type == "role" && c.Value == role) ||
claims.Any(c => c.Type == "tenant_role" && c.Value == role);
}
}
// Response DTOs
public record RegisterResponse(string AccessToken, string RefreshToken);
public record LoginResponse(string AccessToken, string RefreshToken);
public record RefreshResponse(string AccessToken, string RefreshToken);
public record UserInfoResponse(
string UserId,
string TenantId,
string Email,
string FullName,
string TenantSlug,
string TenantRole);