fix(backend): Fix integration test failures - GlobalExceptionHandler and test isolation
Fixed 8 failing integration tests by addressing two root causes: 1. GlobalExceptionHandler returning incorrect HTTP status codes - Added handling for UnauthorizedAccessException → 401 - Added handling for ArgumentException/InvalidOperationException → 400 - Added handling for DbUpdateException (duplicate key) → 409 - Now correctly maps exception types to HTTP status codes 2. Test isolation issue with shared HttpClient - Modified DatabaseFixture to create new HttpClient for each test - Prevents Authorization header pollution between tests - Ensures clean test state for authentication tests Test Results: - Before: 23/31 passed (8 failed) - After: 31/31 passed (0 failed) Changes: - Enhanced GlobalExceptionHandler with proper status code mapping - Fixed DatabaseFixture.Client to create isolated instances - All authentication and RBAC tests now pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ using System.Diagnostics;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Exceptions;
|
||||
|
||||
namespace ColaFlow.API.Handlers;
|
||||
@@ -25,7 +26,7 @@ public sealed class GlobalExceptionHandler : IExceptionHandler
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Log with appropriate level based on exception type
|
||||
if (exception is ValidationException or DomainException or NotFoundException)
|
||||
if (exception is ValidationException or DomainException or NotFoundException or UnauthorizedAccessException or ArgumentException)
|
||||
{
|
||||
_logger.LogWarning(exception, "Client error occurred: {Message}", exception.Message);
|
||||
}
|
||||
@@ -39,6 +40,10 @@ public sealed class GlobalExceptionHandler : IExceptionHandler
|
||||
ValidationException validationEx => CreateValidationProblemDetails(httpContext, validationEx),
|
||||
DomainException domainEx => CreateDomainProblemDetails(httpContext, domainEx),
|
||||
NotFoundException notFoundEx => CreateNotFoundProblemDetails(httpContext, notFoundEx),
|
||||
UnauthorizedAccessException unauthorizedEx => CreateUnauthorizedProblemDetails(httpContext, unauthorizedEx),
|
||||
ArgumentException argumentEx => CreateBadRequestProblemDetails(httpContext, argumentEx),
|
||||
InvalidOperationException invalidOpEx => CreateBadRequestProblemDetails(httpContext, invalidOpEx),
|
||||
DbUpdateException dbUpdateEx when IsDuplicateKeyViolation(dbUpdateEx) => CreateConflictProblemDetails(httpContext, dbUpdateEx),
|
||||
_ => CreateInternalServerErrorProblemDetails(httpContext, exception)
|
||||
};
|
||||
|
||||
@@ -48,6 +53,15 @@ public sealed class GlobalExceptionHandler : IExceptionHandler
|
||||
return true; // Exception handled
|
||||
}
|
||||
|
||||
private static bool IsDuplicateKeyViolation(DbUpdateException exception)
|
||||
{
|
||||
// Check for duplicate key violation in SQL Server or PostgreSQL
|
||||
var innerException = exception.InnerException?.Message ?? string.Empty;
|
||||
return innerException.Contains("duplicate key", StringComparison.OrdinalIgnoreCase) ||
|
||||
innerException.Contains("unique constraint", StringComparison.OrdinalIgnoreCase) ||
|
||||
innerException.Contains("IX_", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static ProblemDetails CreateValidationProblemDetails(
|
||||
HttpContext context,
|
||||
ValidationException exception)
|
||||
@@ -110,6 +124,60 @@ public sealed class GlobalExceptionHandler : IExceptionHandler
|
||||
};
|
||||
}
|
||||
|
||||
private static ProblemDetails CreateUnauthorizedProblemDetails(
|
||||
HttpContext context,
|
||||
UnauthorizedAccessException exception)
|
||||
{
|
||||
return new ProblemDetails
|
||||
{
|
||||
Type = "https://tools.ietf.org/html/rfc7235#section-3.1",
|
||||
Title = "Unauthorized",
|
||||
Status = StatusCodes.Status401Unauthorized,
|
||||
Detail = exception.Message,
|
||||
Instance = context.Request.Path,
|
||||
Extensions =
|
||||
{
|
||||
["traceId"] = Activity.Current?.Id ?? context.TraceIdentifier
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ProblemDetails CreateBadRequestProblemDetails(
|
||||
HttpContext context,
|
||||
Exception exception)
|
||||
{
|
||||
return new ProblemDetails
|
||||
{
|
||||
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
|
||||
Title = "Bad Request",
|
||||
Status = StatusCodes.Status400BadRequest,
|
||||
Detail = exception.Message,
|
||||
Instance = context.Request.Path,
|
||||
Extensions =
|
||||
{
|
||||
["traceId"] = Activity.Current?.Id ?? context.TraceIdentifier
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ProblemDetails CreateConflictProblemDetails(
|
||||
HttpContext context,
|
||||
DbUpdateException exception)
|
||||
{
|
||||
return new ProblemDetails
|
||||
{
|
||||
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.8",
|
||||
Title = "Conflict",
|
||||
Status = StatusCodes.Status409Conflict,
|
||||
Detail = "A resource with the same identifier already exists.",
|
||||
Instance = context.Request.Path,
|
||||
Extensions =
|
||||
{
|
||||
["traceId"] = Activity.Current?.Id ?? context.TraceIdentifier
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ProblemDetails CreateInternalServerErrorProblemDetails(
|
||||
HttpContext context,
|
||||
Exception exception)
|
||||
|
||||
@@ -8,18 +8,28 @@ namespace ColaFlow.Modules.Identity.IntegrationTests.Infrastructure;
|
||||
public class DatabaseFixture : IDisposable
|
||||
{
|
||||
public ColaFlowWebApplicationFactory Factory { get; }
|
||||
public HttpClient Client { get; }
|
||||
|
||||
// Note: Client property is kept for backward compatibility but creates new instances
|
||||
// Tests should call CreateClient() for isolation to avoid shared state issues
|
||||
public HttpClient Client => CreateClient();
|
||||
|
||||
public DatabaseFixture()
|
||||
{
|
||||
// Use In-Memory Database for fast, isolated tests
|
||||
Factory = new ColaFlowWebApplicationFactory(useInMemoryDatabase: true);
|
||||
Client = Factory.CreateClient();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new HttpClient for each test to ensure test isolation
|
||||
/// Prevents Authorization header sharing between tests
|
||||
/// </summary>
|
||||
public HttpClient CreateClient()
|
||||
{
|
||||
return Factory.CreateClient();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Client?.Dispose();
|
||||
Factory?.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user