using ColaFlow.Modules.Mcp.Application.Services; using ColaFlow.Modules.Mcp.Contracts.Resources; using FluentAssertions; using Microsoft.Extensions.Logging; using NSubstitute; namespace ColaFlow.Modules.Mcp.Tests.Services; /// /// Unit tests for McpResourceRegistry /// public class McpResourceRegistryTests { private readonly ILogger _logger; private readonly McpResourceRegistry _sut; public McpResourceRegistryTests() { _logger = Substitute.For>(); _sut = new McpResourceRegistry(_logger); } [Fact] public void RegisterResource_AddsResourceToRegistry() { // Arrange var resource = CreateMockResource("colaflow://test", "Test Resource"); // Act _sut.RegisterResource(resource); // Assert var resources = _sut.GetAllResources(); resources.Should().ContainSingle(); resources[0].Uri.Should().Be("colaflow://test"); } [Fact] public void RegisterResource_WithDuplicateUri_Overwrites() { // Arrange var resource1 = CreateMockResource("colaflow://test", "Test Resource 1"); var resource2 = CreateMockResource("colaflow://test", "Test Resource 2"); // Act _sut.RegisterResource(resource1); _sut.RegisterResource(resource2); // Assert var resources = _sut.GetAllResources(); resources.Count.Should().Be(2); // Both are in the list var foundResource = _sut.GetResourceByUri("colaflow://test"); foundResource!.Name.Should().Be("Test Resource 2"); // But only the last one is returned } [Fact] public void GetResourceByUri_WithExactMatch_ReturnsResource() { // Arrange var resource = CreateMockResource("colaflow://projects.list", "Projects List"); _sut.RegisterResource(resource); // Act var result = _sut.GetResourceByUri("colaflow://projects.list"); // Assert result.Should().NotBeNull(); result!.Uri.Should().Be("colaflow://projects.list"); } [Fact] public void GetResourceByUri_WithTemplateMatch_ReturnsResource() { // Arrange var resource = CreateMockResource("colaflow://projects.get/{id}", "Project Details"); _sut.RegisterResource(resource); // Act var result = _sut.GetResourceByUri("colaflow://projects.get/123"); // Assert result.Should().NotBeNull(); result!.Uri.Should().Be("colaflow://projects.get/{id}"); } [Fact] public void GetResourceByUri_WithNonExistentUri_ReturnsNull() { // Act var result = _sut.GetResourceByUri("colaflow://nonexistent"); // Assert result.Should().BeNull(); } [Fact] public void GetResourceDescriptors_ReturnsAllDescriptors() { // Arrange var resource1 = CreateMockResource("colaflow://test1", "Test 1"); var resource2 = CreateMockResource("colaflow://test2", "Test 2"); _sut.RegisterResource(resource1); _sut.RegisterResource(resource2); // Act var descriptors = _sut.GetResourceDescriptors(); // Assert descriptors.Should().HaveCount(2); descriptors.Should().Contain(d => d.Uri == "colaflow://test1"); descriptors.Should().Contain(d => d.Uri == "colaflow://test2"); } [Fact] public void GetAllResources_ReturnsReadOnlyList() { // Arrange var resource = CreateMockResource("colaflow://test", "Test"); _sut.RegisterResource(resource); // Act var resources = _sut.GetAllResources(); // Assert resources.Should().BeAssignableTo>(); } private IMcpResource CreateMockResource(string uri, string name) { var resource = Substitute.For(); resource.UriReturns(uri); resource.NameReturns(name); resource.DescriptionReturns($"Description for {name}"); resource.MimeTypeReturns("application/json"); resource.GetContentAsync(Arg.Any(), Arg.Any()) Returns(new McpResourceContent { Uri = uri, MimeType = "application/json", Text = "{}" }); return resource; } [Fact] public void GetCategories_ShouldReturnAllUniqueCategories() { // Arrange var mockLogger = Substitute.For>(); var registry = new McpResourceRegistry(mockLogger); var resource1 = Substitute.For(); resource1.Setup(r => r.Uri)Returns("test://resource1"); resource1.Setup(r => r.Name)Returns("Resource 1"); resource1.Setup(r => r.Category)Returns("Projects"); var resource2 = Substitute.For(); resource2.Setup(r => r.Uri)Returns("test://resource2"); resource2.Setup(r => r.Name)Returns("Resource 2"); resource2.Setup(r => r.Category)Returns("Issues"); var resource3 = Substitute.For(); resource3.Setup(r => r.Uri)Returns("test://resource3"); resource3.Setup(r => r.Name)Returns("Resource 3"); resource3.Setup(r => r.Category)Returns("Projects"); // Duplicate category registry.RegisterResource(resource1.Object); registry.RegisterResource(resource2.Object); registry.RegisterResource(resource3.Object); // Act var categories = registry.GetCategories(); // Assert Assert.Equal(2, categories.Count); Assert.Contains("Projects", categories); Assert.Contains("Issues", categories); } [Fact] public void GetResourcesByCategory_ShouldReturnOnlyResourcesInCategory() { // Arrange var mockLogger = Substitute.For>(); var registry = new McpResourceRegistry(mockLogger); var projectResource1 = Substitute.For(); projectResource1.Setup(r => r.Uri)Returns("test://project1"); projectResource1.Setup(r => r.Name)Returns("Project 1"); projectResource1.Setup(r => r.Category)Returns("Projects"); var projectResource2 = Substitute.For(); projectResource2.Setup(r => r.Uri)Returns("test://project2"); projectResource2.Setup(r => r.Name)Returns("Project 2"); projectResource2.Setup(r => r.Category)Returns("Projects"); var issueResource = Substitute.For(); issueResource.Setup(r => r.Uri)Returns("test://issue1"); issueResource.Setup(r => r.Name)Returns("Issue 1"); issueResource.Setup(r => r.Category)Returns("Issues"); registry.RegisterResource(projectResource1.Object); registry.RegisterResource(projectResource2.Object); registry.RegisterResource(issueResource.Object); // Act var projectResources = registry.GetResourcesByCategory("Projects"); // Assert Assert.Equal(2, projectResources.Count); Assert.All(projectResources, r => Assert.Equal("Projects", r.Category)); } [Fact] public void GetResourcesGroupedByCategory_ShouldGroupCorrectly() { // Arrange var mockLogger = Substitute.For>(); var registry = new McpResourceRegistry(mockLogger); var resource1 = Substitute.For(); resource1.Setup(r => r.Uri)Returns("test://resource1"); resource1.Setup(r => r.Name)Returns("Resource 1"); resource1.Setup(r => r.Category)Returns("Projects"); var resource2 = Substitute.For(); resource2.Setup(r => r.Uri)Returns("test://resource2"); resource2.Setup(r => r.Name)Returns("Resource 2"); resource2.Setup(r => r.Category)Returns("Issues"); var resource3 = Substitute.For(); resource3.Setup(r => r.Uri)Returns("test://resource3"); resource3.Setup(r => r.Name)Returns("Resource 3"); resource3.Setup(r => r.Category)Returns("Projects"); registry.RegisterResource(resource1.Object); registry.RegisterResource(resource2.Object); registry.RegisterResource(resource3.Object); // Act var grouped = registry.GetResourcesGroupedByCategory(); // Assert Assert.Equal(2, grouped.Count); Assert.True(grouped.ContainsKey("Projects")); Assert.True(grouped.ContainsKey("Issues")); Assert.Equal(2, grouped["Projects"].Count); Assert.Single(grouped["Issues"]); } [Fact] public void GetDescriptor_ShouldIncludeEnhancedMetadata() { // Arrange var mockLogger = Substitute.For>(); var registry = new McpResourceRegistry(mockLogger); var mockResource = Substitute.For(); mockResource(r => r.Uri)Returns("test://resource"); mockResource(r => r.Name)Returns("Test Resource"); mockResource(r => r.Description)Returns("Test description"); mockResource(r => r.MimeType)Returns("application/json"); mockResource(r => r.Category)Returns("Testing"); mockResource(r => r.Version)Returns("1.0"); mockResource(r => r.GetDescriptor())Returns(new McpResourceDescriptor { Uri = "test://resource", Name = "Test Resource", Description = "Test description", MimeType = "application/json", Category = "Testing", Version = "1.0", Parameters = new Dictionary { { "id", "Resource ID" } }, Examples = new List { "GET test://resource?id=123" }, Tags = new List { "test", "example" }, IsEnabled = true }); registry.RegisterResource(mockResource); // Act var descriptors = registry.GetResourceDescriptors(); // Assert Assert.Single(descriptors); var descriptor = descriptors[0]; Assert.Equal("Testing", descriptor.Category); Assert.Equal("1.0", descriptor.Version); Assert.NotNull(descriptor.Parameters); Assert.NotNull(descriptor.Examples); Assert.NotNull(descriptor.Tags); Assert.True(descriptor.IsEnabled); } }