Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,35 @@
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;
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;
Expand All @@ -31,20 +45,27 @@
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;
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;

Expand All @@ -59,20 +80,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(),
Expand All @@ -82,14 +112,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(),
];
Expand Down Expand Up @@ -123,20 +158,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(),
Expand All @@ -146,22 +190,34 @@ 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(),
];

var builder = new ServiceCollection()
.AddLogging()
.AddMemoryCache()
.AddHttpClientServices(configureDefaults: true)
.AddSingleUserCliCacheService()
.AddSingleton<ITelemetryService, NoOpTelemetryService>();

builder.AddSingleton<Azure.Mcp.Core.Services.Azure.Tenant.ITenantService>(_ => Substitute.For<Azure.Mcp.Core.Services.Azure.Tenant.ITenantService>());
builder.AddSingleton<Azure.Mcp.Core.Services.Azure.Subscription.ISubscriptionService>(_ => Substitute.For<Azure.Mcp.Core.Services.Azure.Subscription.ISubscriptionService>());
builder.AddSingleton<Azure.Mcp.Core.Services.Azure.ResourceGroup.IResourceGroupService>(_ => Substitute.For<Azure.Mcp.Core.Services.Azure.ResourceGroup.IResourceGroupService>());

foreach (var area in areaSetups)
{
area.ConfigureServices(builder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<string>(
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()
{
Expand Down Expand Up @@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -205,7 +205,7 @@ private Dictionary<string, IBaseCommand> FilterCommands(IReadOnlyDictionary<stri
var serviceArea = _commandFactory.GetServiceArea(kvp.Key);
return serviceArea == null || !IgnoredCommandGroups.Contains(serviceArea, StringComparer.OrdinalIgnoreCase);
})
.Where(kvp => _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
Expand Down