From a923e81e642f2648d0eeee9462ab336b52aff31b Mon Sep 17 00:00:00 2001 From: shashax42 Date: Thu, 19 Feb 2026 08:04:44 +0900 Subject: [PATCH 1/2] test: register all 14 missing tool areas in CommandFactoryHelpers for consolidated tool validation Add 14 tool area setups that were present in production Program.cs but missing from the test CommandFactoryHelpers, causing consolidated tool tests to skip validation for those areas' commands. Added areas: Advisor, ApplicationInsights, AppService, AzureMigrate, Communication, Compute, ConfidentialLedger, EventHubs, FileShares, Policy, Pricing, SignalR, Speech, StorageSync. Also add new tests for ConsolidatedToolDiscoveryStrategy: - MapsAllRegisteredCommands: verifies consolidated count equals filtered source count (no unmapped commands) - WithAllAreas_HasSubstantialCommandCount: ensures >200 commands are mapped with all areas registered - AreMetadataEqual unit tests: null handling and value comparison --- .../Areas/Server/CommandFactoryHelpers.cs | 42 +++++++++ .../ConsolidatedToolDiscoveryStrategyTests.cs | 87 +++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs index 5e331c3560..faac7e7d00 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs @@ -8,19 +8,28 @@ using Azure.Mcp.Core.Configuration; using Azure.Mcp.Core.Services.Telemetry; using Azure.Mcp.Tools.Acr; +using Azure.Mcp.Tools.Advisor; using Azure.Mcp.Tools.Aks; using Azure.Mcp.Tools.AppConfig; using Azure.Mcp.Tools.AppLens; +using Azure.Mcp.Tools.ApplicationInsights; +using Azure.Mcp.Tools.AppService; using Azure.Mcp.Tools.Authorization; using Azure.Mcp.Tools.AzureBestPractices; using Azure.Mcp.Tools.AzureIsv; +using Azure.Mcp.Tools.AzureMigrate; using Azure.Mcp.Tools.AzureTerraformBestPractices; using Azure.Mcp.Tools.BicepSchema; using Azure.Mcp.Tools.CloudArchitect; +using Azure.Mcp.Tools.Communication; +using Azure.Mcp.Tools.Compute; +using Azure.Mcp.Tools.ConfidentialLedger; using Azure.Mcp.Tools.Cosmos; using Azure.Mcp.Tools.Deploy; using Azure.Mcp.Tools.EventGrid; +using Azure.Mcp.Tools.EventHubs; using Azure.Mcp.Tools.Extension; +using Azure.Mcp.Tools.FileShares; using Azure.Mcp.Tools.Foundry; using Azure.Mcp.Tools.FunctionApp; using Azure.Mcp.Tools.Grafana; @@ -31,14 +40,19 @@ using Azure.Mcp.Tools.Marketplace; using Azure.Mcp.Tools.Monitor; using Azure.Mcp.Tools.MySql; +using Azure.Mcp.Tools.Policy; using Azure.Mcp.Tools.Postgres; +using Azure.Mcp.Tools.Pricing; using Azure.Mcp.Tools.Quota; using Azure.Mcp.Tools.Redis; using Azure.Mcp.Tools.ResourceHealth; using Azure.Mcp.Tools.Search; using Azure.Mcp.Tools.ServiceBus; +using Azure.Mcp.Tools.SignalR; +using Azure.Mcp.Tools.Speech; using Azure.Mcp.Tools.Sql; using Azure.Mcp.Tools.Storage; +using Azure.Mcp.Tools.StorageSync; using Azure.Mcp.Tools.VirtualDesktop; using Azure.Mcp.Tools.Workbooks; using Microsoft.Extensions.DependencyInjection; @@ -59,20 +73,29 @@ public static ICommandFactory CreateCommandFactory(IServiceProvider? serviceProv // Tool areas new AcrSetup(), + new AdvisorSetup(), new AksSetup(), new AppConfigSetup(), new AppLensSetup(), + new ApplicationInsightsSetup(), + new AppServiceSetup(), new AuthorizationSetup(), new AzureBestPracticesSetup(), new AzureIsvSetup(), new ManagedLustreSetup(), + new AzureMigrateSetup(), new AzureTerraformBestPracticesSetup(), new BicepSchemaSetup(), new CloudArchitectSetup(), + new CommunicationSetup(), + new ComputeSetup(), + new ConfidentialLedgerSetup(), new CosmosSetup(), new DeploySetup(), new EventGridSetup(), + new EventHubsSetup(), new ExtensionSetup(), + new FileSharesSetup(), new FoundrySetup(), new FunctionAppSetup(), new GrafanaSetup(), @@ -82,14 +105,19 @@ public static ICommandFactory CreateCommandFactory(IServiceProvider? serviceProv new MarketplaceSetup(), new MonitorSetup(), new MySqlSetup(), + new PolicySetup(), new PostgresSetup(), + new PricingSetup(), new QuotaSetup(), new RedisSetup(), new ResourceHealthSetup(), new SearchSetup(), new ServiceBusSetup(), + new SignalRSetup(), + new SpeechSetup(), new SqlSetup(), new StorageSetup(), + new StorageSyncSetup(), new VirtualDesktopSetup(), new WorkbooksSetup(), ]; @@ -123,20 +151,29 @@ public static IServiceCollection SetupCommonServices() // Tool areas new AcrSetup(), + new AdvisorSetup(), new AksSetup(), new AppConfigSetup(), new AppLensSetup(), + new ApplicationInsightsSetup(), + new AppServiceSetup(), new AuthorizationSetup(), new AzureBestPracticesSetup(), new AzureIsvSetup(), new ManagedLustreSetup(), + new AzureMigrateSetup(), new AzureTerraformBestPracticesSetup(), new BicepSchemaSetup(), new CloudArchitectSetup(), + new CommunicationSetup(), + new ComputeSetup(), + new ConfidentialLedgerSetup(), new CosmosSetup(), new DeploySetup(), new EventGridSetup(), + new EventHubsSetup(), new ExtensionSetup(), + new FileSharesSetup(), new FoundrySetup(), new FunctionAppSetup(), new GrafanaSetup(), @@ -146,14 +183,19 @@ public static IServiceCollection SetupCommonServices() new MarketplaceSetup(), new MonitorSetup(), new MySqlSetup(), + new PolicySetup(), new PostgresSetup(), + new PricingSetup(), new QuotaSetup(), new RedisSetup(), new ResourceHealthSetup(), new SearchSetup(), new ServiceBusSetup(), + new SignalRSetup(), + new SpeechSetup(), new SqlSetup(), new StorageSetup(), + new StorageSyncSetup(), new VirtualDesktopSetup(), new WorkbooksSetup(), ]; diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategyTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategyTests.cs index c59b23267e..558f1fd06b 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategyTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategyTests.cs @@ -6,6 +6,7 @@ using Azure.Mcp.Core.Commands; using Azure.Mcp.Core.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Mcp.Core.Commands; using Xunit; namespace Azure.Mcp.Core.UnitTests.Areas.Server.Commands.Discovery; @@ -65,6 +66,46 @@ public void CreateConsolidatedCommandFactory_WithDefaultOptions_ReturnsCommandFa Assert.True(factory.AllCommands.Count > 10); } + [Fact] + public void CreateConsolidatedCommandFactory_MapsAllRegisteredCommands() + { + // Arrange + var sourceFactory = CommandFactoryHelpers.CreateCommandFactory(); + var strategy = CreateStrategy(commandFactory: sourceFactory); + + // Act - in DEBUG, CreateConsolidatedCommandFactory throws if any commands are unmapped + // or if metadata mismatches are detected + var consolidatedFactory = strategy.CreateConsolidatedCommandFactory(); + + // Assert - consolidated factory should map all non-ignored source commands + var ignoredGroups = new HashSet( + ConsolidatedToolDiscoveryStrategy.IgnoredCommandGroups, + StringComparer.OrdinalIgnoreCase); + + var expectedCount = sourceFactory.AllCommands.Count(kvp => + { + var area = sourceFactory.GetServiceArea(kvp.Key); + return area == null || !ignoredGroups.Contains(area); + }); + + Assert.Equal(expectedCount, consolidatedFactory.AllCommands.Count); + } + + [Fact] + public void CreateConsolidatedCommandFactory_WithAllAreas_HasSubstantialCommandCount() + { + // Arrange + var strategy = CreateStrategy(); + + // Act + var factory = strategy.CreateConsolidatedCommandFactory(); + + // Assert - with all tool areas registered, expect a substantial number of commands + Assert.True(factory.AllCommands.Count > 200, + $"Expected more than 200 consolidated commands but found {factory.AllCommands.Count}. " + + "Ensure CommandFactoryHelpers registers all tool areas matching production Program.cs."); + } + [Fact] public void CreateConsolidatedCommandFactory_WithNamespaceFilter_FiltersCommands() { @@ -116,4 +157,50 @@ public void CreateConsolidatedCommandFactory_HandlesEmptyNamespaceFilter() var allCommands = factory.AllCommands; Assert.True(allCommands.Count > 0); } + + [Fact] + public void AreMetadataEqual_BothNull_ReturnsTrue() + { + Assert.True(ConsolidatedToolDiscoveryStrategy.AreMetadataEqual(null, null)); + } + + [Fact] + public void AreMetadataEqual_OneNull_ReturnsFalse() + { + var metadata = new ToolMetadata { Destructive = false, ReadOnly = true }; + Assert.False(ConsolidatedToolDiscoveryStrategy.AreMetadataEqual(metadata, null)); + Assert.False(ConsolidatedToolDiscoveryStrategy.AreMetadataEqual(null, metadata)); + } + + [Fact] + public void AreMetadataEqual_MatchingValues_ReturnsTrue() + { + var metadata1 = new ToolMetadata + { + Destructive = false, + Idempotent = true, + OpenWorld = false, + ReadOnly = true, + Secret = false, + LocalRequired = false + }; + var metadata2 = new ToolMetadata + { + Destructive = false, + Idempotent = true, + OpenWorld = false, + ReadOnly = true, + Secret = false, + LocalRequired = false + }; + Assert.True(ConsolidatedToolDiscoveryStrategy.AreMetadataEqual(metadata1, metadata2)); + } + + [Fact] + public void AreMetadataEqual_DifferentValues_ReturnsFalse() + { + var metadata1 = new ToolMetadata { Destructive = false, ReadOnly = true }; + var metadata2 = new ToolMetadata { Destructive = true, ReadOnly = true }; + Assert.False(ConsolidatedToolDiscoveryStrategy.AreMetadataEqual(metadata1, metadata2)); + } } From 3ad2b73d940d4560f2809b290b551ea62342af2e Mon Sep 17 00:00:00 2001 From: shashax42 Date: Thu, 19 Feb 2026 08:46:46 +0900 Subject: [PATCH 2/2] fix: consolidated tool discovery tests + ReadOnly default --- .../Areas/Server/CommandFactoryHelpers.cs | 14 ++++++++++++++ .../Discovery/ConsolidatedToolDiscoveryStrategy.cs | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs index faac7e7d00..f4b8be07bc 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs @@ -6,6 +6,11 @@ using Azure.Mcp.Core.Areas.Subscription; using Azure.Mcp.Core.Commands; using Azure.Mcp.Core.Configuration; +using Azure.Mcp.Core.Extensions; +using Azure.Mcp.Core.Services.Azure.ResourceGroup; +using Azure.Mcp.Core.Services.Azure.Subscription; +using Azure.Mcp.Core.Services.Azure.Tenant; +using Azure.Mcp.Core.Services.Caching; using Azure.Mcp.Core.Services.Telemetry; using Azure.Mcp.Tools.Acr; using Azure.Mcp.Tools.Advisor; @@ -56,9 +61,11 @@ using Azure.Mcp.Tools.VirtualDesktop; using Azure.Mcp.Tools.Workbooks; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Areas; using ModelContextProtocol.Protocol; +using NSubstitute; namespace Azure.Mcp.Core.UnitTests.Areas.Server; @@ -202,8 +209,15 @@ public static IServiceCollection SetupCommonServices() var builder = new ServiceCollection() .AddLogging() + .AddMemoryCache() + .AddHttpClientServices(configureDefaults: true) + .AddSingleUserCliCacheService() .AddSingleton(); + builder.AddSingleton(_ => Substitute.For()); + builder.AddSingleton(_ => Substitute.For()); + builder.AddSingleton(_ => Substitute.For()); + foreach (var area in areaSetups) { area.ConfigureServices(builder); diff --git a/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategy.cs b/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategy.cs index 1fb93d7265..9673fcab95 100644 --- a/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategy.cs +++ b/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategy.cs @@ -78,7 +78,7 @@ public ICommandFactory CreateConsolidatedCommandFactory() #if DEBUG // In debug mode, validate that all tools in MappedToolList found a match when conditions are met - if (_options.Value.ReadOnly == false && (_options.Value.Namespace == null || _options.Value.Namespace.Length == 0)) + if ((_options.Value.ReadOnly ?? false) == false && (_options.Value.Namespace == null || _options.Value.Namespace.Length == 0)) { if (consolidatedTool.MappedToolList != null) { @@ -205,7 +205,7 @@ private Dictionary FilterCommands(IReadOnlyDictionary _options.Value.ReadOnly == false || kvp.Value.Metadata.ReadOnly == true) + .Where(kvp => (_options.Value.ReadOnly ?? false) == false || kvp.Value.Metadata.ReadOnly == true) .Where(kvp => { // Filter by namespace if specified