Files
ColaFlow/colaflow-api/tests/Modules/Mcp/ColaFlow.Modules.Mcp.Tests/Domain/Exceptions/McpExceptionTests.cs
Yaojia Wang c00c909489 feat(backend): Implement Story 5.4 - MCP Error Handling & Logging
Implement comprehensive error handling and structured logging for MCP module.

**Exception Hierarchy**:
- Created McpException base class with JSON-RPC error mapping
- Implemented 8 specific exception types (Parse, InvalidRequest, MethodNotFound, etc.)
- Each exception maps to correct HTTP status code (401, 403, 404, 422, 400, 500)

**Middleware**:
- McpCorrelationIdMiddleware: Generates/extracts correlation ID for request tracking
- McpExceptionHandlerMiddleware: Global exception handler with JSON-RPC error responses
- McpLoggingMiddleware: Request/response logging with sensitive data sanitization

**Serilog Integration**:
- Configured structured logging with Console and File sinks
- Log rotation (daily, 30-day retention)
- Correlation ID enrichment in all log entries

**Features**:
- Correlation ID propagation across request chain
- Structured logging with TenantId, UserId, ApiKeyId
- Sensitive data sanitization (API keys, passwords)
- Performance metrics (request duration, slow request warnings)
- JSON-RPC 2.0 compliant error responses

**Testing**:
- 174 tests passing (all MCP module tests)
- Unit tests for all exception classes
- Unit tests for all middleware components
- 100% coverage of error mapping and HTTP status codes

**Files Added**:
- 9 exception classes in Domain/Exceptions/
- 3 middleware classes in Infrastructure/Middleware/
- 4 test files with comprehensive coverage

**Files Modified**:
- Program.cs: Serilog configuration
- McpServiceExtensions.cs: Middleware pipeline registration
- JsonRpcError.cs: Added parameterless constructor for deserialization
- MCP Infrastructure .csproj: Added Serilog package reference

**Verification**:
 All 174 MCP module tests passing
 Build successful with no errors
 Exception-to-HTTP-status mapping verified
 Correlation ID propagation tested
 Sensitive data sanitization verified

Story: docs/stories/sprint_5/story_5_4.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-08 21:08:12 +01:00

159 lines
4.9 KiB
C#

using ColaFlow.Modules.Mcp.Contracts.JsonRpc;
using ColaFlow.Modules.Mcp.Domain.Exceptions;
using FluentAssertions;
namespace ColaFlow.Modules.Mcp.Tests.Domain.Exceptions;
/// <summary>
/// Unit tests for MCP exception classes
/// </summary>
public class McpExceptionTests
{
[Fact]
public void McpParseException_ShouldHaveCorrectErrorCode()
{
// Arrange & Act
var exception = new McpParseException("Invalid JSON");
// Assert
exception.ErrorCode.Should().Be(JsonRpcErrorCode.ParseError);
exception.Message.Should().Be("Invalid JSON");
exception.GetHttpStatusCode().Should().Be(400);
}
[Fact]
public void McpInvalidRequestException_ShouldHaveCorrectErrorCode()
{
// Arrange & Act
var exception = new McpInvalidRequestException("Missing required field");
// Assert
exception.ErrorCode.Should().Be(JsonRpcErrorCode.InvalidRequest);
exception.Message.Should().Be("Missing required field");
exception.GetHttpStatusCode().Should().Be(400);
}
[Fact]
public void McpMethodNotFoundException_ShouldHaveCorrectErrorCode()
{
// Arrange & Act
var exception = new McpMethodNotFoundException("unknown_method");
// Assert
exception.ErrorCode.Should().Be(JsonRpcErrorCode.MethodNotFound);
exception.Message.Should().Be("Method not found: unknown_method");
exception.GetHttpStatusCode().Should().Be(404);
}
[Fact]
public void McpInvalidParamsException_ShouldHaveCorrectErrorCode()
{
// Arrange & Act
var exception = new McpInvalidParamsException("Invalid parameter type");
// Assert
exception.ErrorCode.Should().Be(JsonRpcErrorCode.InvalidParams);
exception.Message.Should().Be("Invalid parameter type");
exception.GetHttpStatusCode().Should().Be(400);
}
[Fact]
public void McpUnauthorizedException_ShouldHaveCorrectErrorCode()
{
// Arrange & Act
var exception = new McpUnauthorizedException("Invalid API key");
// Assert
exception.ErrorCode.Should().Be(JsonRpcErrorCode.Unauthorized);
exception.Message.Should().Be("Invalid API key");
exception.GetHttpStatusCode().Should().Be(401);
}
[Fact]
public void McpForbiddenException_ShouldHaveCorrectErrorCode()
{
// Arrange & Act
var exception = new McpForbiddenException("Insufficient permissions");
// Assert
exception.ErrorCode.Should().Be(JsonRpcErrorCode.Forbidden);
exception.Message.Should().Be("Insufficient permissions");
exception.GetHttpStatusCode().Should().Be(403);
}
[Fact]
public void McpNotFoundException_ShouldHaveCorrectErrorCode()
{
// Arrange & Act
var exception = new McpNotFoundException("Task", "task-123");
// Assert
exception.ErrorCode.Should().Be(JsonRpcErrorCode.NotFound);
exception.Message.Should().Be("Task not found: task-123");
exception.GetHttpStatusCode().Should().Be(404);
}
[Fact]
public void McpValidationException_ShouldHaveCorrectErrorCode()
{
// Arrange & Act
var errorData = new { field = "name", error = "required" };
var exception = new McpValidationException("Validation failed", errorData);
// Assert
exception.ErrorCode.Should().Be(JsonRpcErrorCode.ValidationFailed);
exception.Message.Should().Be("Validation failed");
exception.ErrorData.Should().Be(errorData);
exception.GetHttpStatusCode().Should().Be(422);
}
[Fact]
public void ToJsonRpcError_ShouldConvertToJsonRpcError()
{
// Arrange
var errorData = new { detail = "test" };
var exception = new McpValidationException("Test error", errorData);
// Act
var jsonRpcError = exception.ToJsonRpcError();
// Assert
jsonRpcError.Code.Should().Be((int)JsonRpcErrorCode.ValidationFailed);
jsonRpcError.Message.Should().Be("Test error");
jsonRpcError.Data.Should().Be(errorData);
}
[Fact]
public void McpParseException_WithInnerException_ShouldPreserveInnerException()
{
// Arrange
var innerException = new InvalidOperationException("Root cause");
// Act
var exception = new McpParseException("Parse failed", innerException);
// Assert
exception.InnerException.Should().Be(innerException);
exception.InnerException.Message.Should().Be("Root cause");
}
[Fact]
public void McpException_WithErrorData_ShouldPreserveErrorData()
{
// Arrange
var errorData = new
{
field = "email",
error = "invalid format",
value = "not-an-email"
};
// Act
var exception = new McpValidationException("Validation failed", errorData);
// Assert
exception.ErrorData.Should().NotBeNull();
exception.ErrorData.Should().Be(errorData);
}
}