Implemented pluggable resource registration and auto-discovery mechanism for MCP Resources. Changes: - Enhanced McpResourceDescriptor with metadata (Category, Version, Parameters, Examples, Tags, IsEnabled) - Created ResourceDiscoveryService for Assembly scanning and auto-discovery - Updated McpResourceRegistry with category support and grouping methods - Enhanced ResourcesListMethodHandler to return categorized resources with full metadata - Created ResourceHealthCheckHandler for resource availability verification - Updated all existing Resources (Projects, Issues, Sprints, Users) with Categories and Versions - Updated McpServiceExtensions to use auto-discovery at startup - Added comprehensive unit tests for discovery and health check Features: ✅ New Resources automatically discovered via Assembly scanning ✅ Resources organized by category (Projects, Issues, Sprints, Users) ✅ Rich metadata for documentation (parameters, examples, tags) ✅ Health check endpoint (resources/health) for monitoring ✅ Thread-safe registry operations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
95 lines
3.2 KiB
C#
95 lines
3.2 KiB
C#
using ColaFlow.Modules.Mcp.Application.Handlers;
|
|
using ColaFlow.Modules.Mcp.Application.Services;
|
|
using ColaFlow.Modules.Mcp.Contracts.Resources;
|
|
using FluentAssertions;
|
|
using Microsoft.Extensions.Logging;
|
|
using NSubstitute;
|
|
using Xunit;
|
|
|
|
namespace ColaFlow.Modules.Mcp.Tests.Handlers;
|
|
|
|
public class ResourceHealthCheckHandlerTests
|
|
{
|
|
private readonly ILogger<ResourceHealthCheckHandler> _mockLogger;
|
|
private readonly IMcpResourceRegistry _mockRegistry;
|
|
private readonly ResourceHealthCheckHandler _handler;
|
|
|
|
public ResourceHealthCheckHandlerTests()
|
|
{
|
|
_mockLogger = Substitute.For<ILogger<ResourceHealthCheckHandler>>();
|
|
_mockRegistry = Substitute.For<IMcpResourceRegistry>();
|
|
_handler = new ResourceHealthCheckHandler(_mockLogger, _mockRegistry);
|
|
}
|
|
|
|
[Fact]
|
|
public void MethodName_ShouldBeResourcesHealth()
|
|
{
|
|
// Assert
|
|
_handler.MethodName.Should().Be("resources/health");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_WithHealthyResources_ShouldReturnHealthyStatus()
|
|
{
|
|
// Arrange
|
|
var mockResource = Substitute.For<IMcpResource>();
|
|
mockResource.Uri.Returns("test://resource");
|
|
mockResource.Name.Returns("Test Resource");
|
|
mockResource.Category.Returns("Testing");
|
|
mockResource.GetDescriptor().Returns(new McpResourceDescriptor
|
|
{
|
|
Uri = "test://resource",
|
|
Name = "Test Resource",
|
|
Category = "Testing",
|
|
IsEnabled = true,
|
|
Version = "1.0"
|
|
});
|
|
|
|
_mockRegistry.GetAllResources()
|
|
.Returns(new List<IMcpResource> { mockResource }.AsReadOnly());
|
|
|
|
// Act
|
|
var result = await _handler.HandleAsync(null, CancellationToken.None);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
var response = result as dynamic;
|
|
((string)response.status).Should().Be("healthy");
|
|
((int)response.totalResources).Should().Be(1);
|
|
((int)response.healthyResources).Should().Be(1);
|
|
((int)response.unhealthyResources).Should().Be(0);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_WithUnhealthyResource_ShouldReturnDegradedStatus()
|
|
{
|
|
// Arrange
|
|
var mockResource = Substitute.For<IMcpResource>();
|
|
mockResource.Uri.Returns("test://resource");
|
|
mockResource.Name.Returns("Test Resource");
|
|
mockResource.Category.Returns("Testing");
|
|
mockResource.GetDescriptor().Returns(new McpResourceDescriptor
|
|
{
|
|
Uri = "", // Invalid - empty URI
|
|
Name = "Test Resource",
|
|
Category = "Testing",
|
|
IsEnabled = true,
|
|
Version = "1.0"
|
|
});
|
|
|
|
_mockRegistry.GetAllResources()
|
|
.Returns(new List<IMcpResource> { mockResource }.AsReadOnly());
|
|
|
|
// Act
|
|
var result = await _handler.HandleAsync(null, CancellationToken.None);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
var response = result as dynamic;
|
|
((string)response.status).Should().Be("degraded");
|
|
((int)response.totalResources).Should().Be(1);
|
|
((int)response.healthyResources).Should().Be(0);
|
|
((int)response.unhealthyResources).Should().Be(1);
|
|
}
|
|
}
|