diff --git a/servers/Azure.Mcp.Server/changelog-entries/copilot-fix-sql-server-filter.yaml b/servers/Azure.Mcp.Server/changelog-entries/copilot-fix-sql-server-filter.yaml new file mode 100644 index 0000000000..34455d1f0a --- /dev/null +++ b/servers/Azure.Mcp.Server/changelog-entries/copilot-fix-sql-server-filter.yaml @@ -0,0 +1,3 @@ +changes: + - section: "Bugs Fixed" + description: "Fixed the SQL `db get` (list databases) and `elastic-pool list` tools returning resources from every server in the resource group instead of only the requested server. The queries are now scoped to the specified server." diff --git a/tools/Azure.Mcp.Tools.Sql/src/Commands/Database/DatabaseGetCommand.cs b/tools/Azure.Mcp.Tools.Sql/src/Commands/Database/DatabaseGetCommand.cs index 0cfccc479c..d55ee35c90 100644 --- a/tools/Azure.Mcp.Tools.Sql/src/Commands/Database/DatabaseGetCommand.cs +++ b/tools/Azure.Mcp.Tools.Sql/src/Commands/Database/DatabaseGetCommand.cs @@ -71,12 +71,12 @@ public override async Task ExecuteAsync(CommandContext context, cancellationToken); context.Response.Results = ResponseResult.Create( - new([database], false), + new([database]), SqlJsonContext.Default.DatabaseGetListResult); } else { - var result = await _sqlService.ListDatabasesAsync( + var databases = await _sqlService.ListDatabasesAsync( options.Server!, options.ResourceGroup!, options.Subscription!, @@ -84,7 +84,7 @@ public override async Task ExecuteAsync(CommandContext context, cancellationToken); context.Response.Results = ResponseResult.Create( - new(result?.Results ?? [], result?.AreResultsTruncated ?? false), + new(databases ?? []), SqlJsonContext.Default.DatabaseGetListResult); } } @@ -109,5 +109,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record DatabaseGetListResult(List Databases, bool AreResultsTruncated); + internal record DatabaseGetListResult(List Databases); } diff --git a/tools/Azure.Mcp.Tools.Sql/src/Commands/ElasticPool/ElasticPoolListCommand.cs b/tools/Azure.Mcp.Tools.Sql/src/Commands/ElasticPool/ElasticPoolListCommand.cs index 1dd9d99fe1..3c5082cb1d 100644 --- a/tools/Azure.Mcp.Tools.Sql/src/Commands/ElasticPool/ElasticPoolListCommand.cs +++ b/tools/Azure.Mcp.Tools.Sql/src/Commands/ElasticPool/ElasticPoolListCommand.cs @@ -52,7 +52,7 @@ public override async Task ExecuteAsync(CommandContext context, options.RetryPolicy, cancellationToken); - context.Response.Results = ResponseResult.Create(new(elasticPools?.Results ?? [], elasticPools?.AreResultsTruncated ?? false), SqlJsonContext.Default.ElasticPoolListResult); + context.Response.Results = ResponseResult.Create(new(elasticPools ?? []), SqlJsonContext.Default.ElasticPoolListResult); } catch (Exception ex) { @@ -75,5 +75,5 @@ public override async Task ExecuteAsync(CommandContext context, _ => base.GetErrorMessage(ex) }; - internal record ElasticPoolListResult(List ElasticPools, bool AreResultsTruncated); + internal record ElasticPoolListResult(List ElasticPools); } diff --git a/tools/Azure.Mcp.Tools.Sql/src/Commands/SqlJsonContext.cs b/tools/Azure.Mcp.Tools.Sql/src/Commands/SqlJsonContext.cs index ea02b87fe1..137125513f 100644 --- a/tools/Azure.Mcp.Tools.Sql/src/Commands/SqlJsonContext.cs +++ b/tools/Azure.Mcp.Tools.Sql/src/Commands/SqlJsonContext.cs @@ -36,9 +36,6 @@ namespace Azure.Mcp.Tools.Sql.Commands; [JsonSerializable(typeof(SqlDatabaseData))] [JsonSerializable(typeof(SqlDatabaseProperties))] [JsonSerializable(typeof(SqlServerAadAdministratorData))] -[JsonSerializable(typeof(SqlElasticPoolData))] -[JsonSerializable(typeof(SqlElasticPoolProperties))] -[JsonSerializable(typeof(SqlElasticPoolPerDatabaseSettings))] [JsonSerializable(typeof(SqlFirewallRuleData))] [JsonSourceGenerationOptions( PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, diff --git a/tools/Azure.Mcp.Tools.Sql/src/Services/ISqlService.cs b/tools/Azure.Mcp.Tools.Sql/src/Services/ISqlService.cs index b49deb92f9..fe5af8975a 100644 --- a/tools/Azure.Mcp.Tools.Sql/src/Services/ISqlService.cs +++ b/tools/Azure.Mcp.Tools.Sql/src/Services/ISqlService.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Azure.Mcp.Core.Services.Azure; using Azure.Mcp.Tools.Sql.Models; using Microsoft.Mcp.Core.Options; @@ -125,7 +124,7 @@ Task RenameDatabaseAsync( /// Optional retry policy options /// Cancellation token /// A list of SQL databases - Task> ListDatabasesAsync( + Task> ListDatabasesAsync( string serverName, string resourceGroup, string subscription, @@ -157,7 +156,7 @@ Task> GetEntraAdministratorsAsync( /// Optional retry policy options /// Cancellation token /// A list of SQL elastic pools - Task> GetElasticPoolsAsync( + Task> GetElasticPoolsAsync( string serverName, string resourceGroup, string subscription, diff --git a/tools/Azure.Mcp.Tools.Sql/src/Services/Models/SqlElasticPoolData.cs b/tools/Azure.Mcp.Tools.Sql/src/Services/Models/SqlElasticPoolData.cs deleted file mode 100644 index e1cc12b1c6..0000000000 --- a/tools/Azure.Mcp.Tools.Sql/src/Services/Models/SqlElasticPoolData.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Text.Json; -using System.Text.Json.Serialization; -using Azure.Mcp.Tools.Sql.Commands; - -namespace Azure.Mcp.Tools.Sql.Services.Models -{ - /// - /// A class representing the ElasticPool data model. - /// An elastic pool. - /// - internal sealed class SqlElasticPoolData - { - /// The resource ID for the resource. - [JsonPropertyName("id")] - public string? ResourceId { get; set; } - /// The type of the resource. - [JsonPropertyName("type")] - public string? ResourceType { get; set; } - /// The name of the resource. - [JsonPropertyName("name")] - public string? ResourceName { get; set; } - /// The location of the resource. - public string? Location { get; set; } - /// The database SKU. - public SqlSku? Sku { get; set; } - /// The properties of elastic pool. - public SqlElasticPoolProperties? Properties { get; set; } - - // Read the JSON response content and create a model instance from it. - public static SqlElasticPoolData? FromJson(JsonElement source) - { - return JsonSerializer.Deserialize(source, SqlJsonContext.Default.SqlElasticPoolData); - } - } -} diff --git a/tools/Azure.Mcp.Tools.Sql/src/Services/Models/SqlElasticPoolPerDatabaseSettings.cs b/tools/Azure.Mcp.Tools.Sql/src/Services/Models/SqlElasticPoolPerDatabaseSettings.cs deleted file mode 100644 index 9ccb6de1ce..0000000000 --- a/tools/Azure.Mcp.Tools.Sql/src/Services/Models/SqlElasticPoolPerDatabaseSettings.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Azure.Mcp.Tools.Sql.Services.Models -{ - /// Per database settings of an elastic pool. - internal sealed class SqlElasticPoolPerDatabaseSettings - { - /// The minimum capacity all databases are guaranteed. - public double? MinCapacity { get; set; } - /// The maximum capacity any one database can consume. - public double? MaxCapacity { get; set; } - } -} diff --git a/tools/Azure.Mcp.Tools.Sql/src/Services/Models/SqlElasticPoolProperties.cs b/tools/Azure.Mcp.Tools.Sql/src/Services/Models/SqlElasticPoolProperties.cs deleted file mode 100644 index d071109820..0000000000 --- a/tools/Azure.Mcp.Tools.Sql/src/Services/Models/SqlElasticPoolProperties.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Sql.Services.Models -{ - /// - /// A class representing the ElasticPool properties model. - /// An elastic pool properties. - /// - internal sealed class SqlElasticPoolProperties - { - /// The state of the elastic pool. - public string? State { get; set; } - /// The creation date of the elastic pool (ISO8601 format). - [JsonPropertyName("creationDate")] - public DateTimeOffset? CreatedOn { get; set; } - /// The storage limit for the database elastic pool in bytes. - public long? MaxSizeBytes { get; set; } - /// The per database settings for the elastic pool. - public SqlElasticPoolPerDatabaseSettings? PerDatabaseSettings { get; set; } - /// Whether or not this elastic pool is zone redundant, which means the replicas of this elastic pool will be spread across multiple availability zones. - [JsonPropertyName("zoneRedundant")] - public bool? IsZoneRedundant { get; set; } - /// The license type to apply for this elastic pool. - public string? LicenseType { get; set; } - } -} diff --git a/tools/Azure.Mcp.Tools.Sql/src/Services/SqlService.cs b/tools/Azure.Mcp.Tools.Sql/src/Services/SqlService.cs index 0dc73efd80..19a8e8546a 100644 --- a/tools/Azure.Mcp.Tools.Sql/src/Services/SqlService.cs +++ b/tools/Azure.Mcp.Tools.Sql/src/Services/SqlService.cs @@ -18,8 +18,28 @@ namespace Azure.Mcp.Tools.Sql.Services; public class SqlService(ISubscriptionService subscriptionService, ITenantService tenantService, ILogger logger) : BaseAzureResourceService(subscriptionService, tenantService), ISqlService { + private readonly ISubscriptionService _subscriptionService = subscriptionService; private readonly ILogger _logger = logger; + /// + /// Resolves a subscription name or ID to a subscription ID. When the value is already a + /// subscription ID no additional ARM request is made, preserving the existing network + /// behavior for ID-based callers. + /// + /// The subscription ID or name + /// Optional retry policy configuration + /// Token to observe for cancellation requests + /// The resolved subscription ID + private async Task ResolveSubscriptionIdAsync( + string subscription, + RetryPolicyOptions? retryPolicy, + CancellationToken cancellationToken) + { + return _subscriptionService.IsSubscriptionId(subscription) + ? subscription + : await _subscriptionService.GetSubscriptionIdByName(subscription, null, retryPolicy, cancellationToken); + } + /// /// Helper method to navigate the Azure resource hierarchy and retrieve a SQL Server resource. /// @@ -355,20 +375,32 @@ public async Task RenameDatabaseAsync( /// Token to observe for cancellation requests /// A list of SQL databases on the specified server /// Thrown when required parameters are null or empty - public async Task> ListDatabasesAsync( + public async Task> ListDatabasesAsync( string serverName, string resourceGroup, string subscription, RetryPolicyOptions? retryPolicy, CancellationToken cancellationToken = default) { - return await ExecuteResourceQueryAsync( - "Microsoft.Sql/servers/databases", - resourceGroup, - subscription, - retryPolicy, - ConvertToSqlDatabaseModel, - cancellationToken: cancellationToken); + ValidateRequiredParameters( + (nameof(serverName), serverName), + (nameof(resourceGroup), resourceGroup), + (nameof(subscription), subscription)); + + var subscriptionId = await ResolveSubscriptionIdAsync(subscription, retryPolicy, cancellationToken); + var sqlServerResource = await GetSqlServerResourceAsync(serverName, resourceGroup, subscriptionId, retryPolicy, cancellationToken); + + var databases = new List(); + await foreach (var database in sqlServerResource.GetSqlDatabases().GetAllAsync(cancellationToken: cancellationToken)) + { + databases.Add(ConvertToSqlDatabaseModel(database)); + } + + _logger.LogInformation( + "Successfully listed SQL databases. Server: {Server}, ResourceGroup: {ResourceGroup}, Count: {Count}", + serverName, resourceGroup, databases.Count); + + return databases; } /// @@ -429,20 +461,32 @@ public async Task> GetEntraAdministratorsAsync /// Token to observe for cancellation requests /// A list of elastic pools configured on the SQL server /// Thrown when required parameters are null or empty - public async Task> GetElasticPoolsAsync( + public async Task> GetElasticPoolsAsync( string serverName, string resourceGroup, string subscription, RetryPolicyOptions? retryPolicy, CancellationToken cancellationToken = default) { - return await ExecuteResourceQueryAsync( - "Microsoft.Sql/servers/elasticPools", - resourceGroup, - subscription, - retryPolicy, - ConvertToSqlElasticPoolModel, - cancellationToken: cancellationToken); + ValidateRequiredParameters( + (nameof(serverName), serverName), + (nameof(resourceGroup), resourceGroup), + (nameof(subscription), subscription)); + + var subscriptionId = await ResolveSubscriptionIdAsync(subscription, retryPolicy, cancellationToken); + var sqlServerResource = await GetSqlServerResourceAsync(serverName, resourceGroup, subscriptionId, retryPolicy, cancellationToken); + + var elasticPools = new List(); + await foreach (var elasticPool in sqlServerResource.GetElasticPools().GetAllAsync(cancellationToken: cancellationToken)) + { + elasticPools.Add(ConvertToSqlElasticPoolModel(elasticPool)); + } + + _logger.LogInformation( + "Successfully listed SQL elastic pools. Server: {Server}, ResourceGroup: {ResourceGroup}, Count: {Count}", + serverName, resourceGroup, elasticPools.Count); + + return elasticPools; } /// @@ -917,32 +961,31 @@ private static SqlServer ConvertToSqlServerModel(SqlServerResource serverResourc Tags: tags.Count > 0 ? tags : null); } - private static SqlElasticPool ConvertToSqlElasticPoolModel(JsonElement item) + private static SqlElasticPool ConvertToSqlElasticPoolModel(ElasticPoolResource elasticPoolResource) { - SqlElasticPoolData? elasticPool = SqlElasticPoolData.FromJson(item) - ?? throw new InvalidOperationException("Failed to parse SQL elastic pool data"); + var data = elasticPoolResource.Data; return new( - Name: elasticPool.ResourceName ?? "Unknown", - Id: elasticPool.ResourceId ?? "Unknown", - Type: elasticPool.ResourceType ?? "Unknown", - Location: elasticPool.Location, - Sku: elasticPool.Sku != null ? new( - Name: elasticPool.Sku.Name, - Tier: elasticPool.Sku.Tier, - Capacity: elasticPool.Sku.Capacity, - Family: elasticPool.Sku.Family, - Size: elasticPool.Sku.Size + Name: data.Name, + Id: data.Id.ToString(), + Type: data.ResourceType.ToString(), + Location: data.Location.ToString(), + Sku: data.Sku != null ? new( + Name: data.Sku.Name, + Tier: data.Sku.Tier, + Capacity: data.Sku.Capacity, + Family: data.Sku.Family, + Size: data.Sku.Size ) : null, - State: elasticPool.Properties?.State, - CreationDate: elasticPool.Properties?.CreatedOn, - MaxSizeBytes: elasticPool.Properties?.MaxSizeBytes, - PerDatabaseSettings: elasticPool.Properties?.PerDatabaseSettings != null ? new( - MinCapacity: elasticPool.Properties.PerDatabaseSettings.MinCapacity, - MaxCapacity: elasticPool.Properties.PerDatabaseSettings.MaxCapacity + State: data.State?.ToString(), + CreationDate: data.CreatedOn, + MaxSizeBytes: data.MaxSizeBytes, + PerDatabaseSettings: data.PerDatabaseSettings != null ? new( + MinCapacity: data.PerDatabaseSettings.MinCapacity, + MaxCapacity: data.PerDatabaseSettings.MaxCapacity ) : null, - ZoneRedundant: elasticPool.Properties?.IsZoneRedundant, - LicenseType: elasticPool.Properties?.LicenseType, + ZoneRedundant: data.IsZoneRedundant, + LicenseType: data.LicenseType?.ToString(), DatabaseDtuMin: null, // DTU properties not available in current SDK DatabaseDtuMax: null, Dtu: null, diff --git a/tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.Tests/Database/DatabaseGetCommandTests.cs b/tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.Tests/Database/DatabaseGetCommandTests.cs index 0c5cb0a323..5450b1cc2b 100644 --- a/tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.Tests/Database/DatabaseGetCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.Tests/Database/DatabaseGetCommandTests.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Net; -using Azure.Mcp.Core.Services.Azure; using Azure.Mcp.Tools.Sql.Commands.Database; using Azure.Mcp.Tools.Sql.Models; using Azure.Mcp.Tools.Sql.Services; @@ -59,7 +58,7 @@ public async Task ExecuteAsync_WithDatabaseName_ReturnsSingleDatabase() public async Task ExecuteAsync_WithoutDatabaseName_ReturnsAllDatabases() { // Arrange - var mockDatabases = new ResourceQueryResults([CreateMockDatabase("db1"), CreateMockDatabase("db2")], false); + var mockDatabases = new List { CreateMockDatabase("db1"), CreateMockDatabase("db2") }; Service.ListDatabasesAsync( Arg.Is("server1"), @@ -171,7 +170,7 @@ public async Task ExecuteAsync_ValidatesRequiredParameters(string commandArgs, b { Service .ListDatabasesAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(new ResourceQueryResults([], false)); + .Returns(new List()); Service .GetDatabaseAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(CreateMockDatabase("db1")); diff --git a/tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.Tests/ElasticPool/ElasticPoolListCommandTests.cs b/tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.Tests/ElasticPool/ElasticPoolListCommandTests.cs index 65578d7d14..c37523ca62 100644 --- a/tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.Tests/ElasticPool/ElasticPoolListCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.Tests/ElasticPool/ElasticPoolListCommandTests.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Net; -using Azure.Mcp.Core.Services.Azure; using Azure.Mcp.Tools.Sql.Commands.ElasticPool; using Azure.Mcp.Tools.Sql.Models; using Azure.Mcp.Tools.Sql.Services; @@ -28,8 +27,8 @@ public void Constructor_InitializesCommandCorrectly() public async Task ExecuteAsync_WithValidParameters_ReturnsElasticPools() { // Arrange - var mockElasticPools = new ResourceQueryResults( - [ + var mockElasticPools = new List + { new( Name: "pool1", Id: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Sql/servers/server1/elasticPools/pool1", @@ -47,7 +46,7 @@ public async Task ExecuteAsync_WithValidParameters_ReturnsElasticPools() Dtu: 100, StorageMB: 5120 ) - ], false); + }; Service.GetElasticPoolsAsync( Arg.Is("server1"), @@ -74,7 +73,7 @@ public async Task ExecuteAsync_WithValidParameters_ReturnsElasticPools() public async Task ExecuteAsync_WithEmptyList_ReturnsEmptyResults() { // Arrange - var mockElasticPools = new ResourceQueryResults([], false); + var mockElasticPools = new List(); Service.GetElasticPoolsAsync( Arg.Is("server1"), @@ -186,7 +185,7 @@ public async Task ExecuteAsync_ValidatesRequiredParameters(string args, bool sho Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(new ResourceQueryResults([], false)); + .Returns(new List()); } // Act diff --git a/tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.Tests/Services/SqlServiceTests.cs b/tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.Tests/Services/SqlServiceTests.cs new file mode 100644 index 0000000000..7ab7490340 --- /dev/null +++ b/tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.Tests/Services/SqlServiceTests.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Azure.Mcp.Core.Services.Azure.Subscription; +using Azure.Mcp.Core.Services.Azure.Tenant; +using Azure.Mcp.Tools.Sql.Services; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Options; +using NSubstitute; +using NSubstitute.ExceptionExtensions; +using Xunit; + +namespace Azure.Mcp.Tools.Sql.Tests.Services; + +public class SqlServiceTests +{ + private const string SubscriptionName = "my-subscription"; + private const string SubscriptionId = "12345678-1234-1234-1234-123456789012"; + private const string ResolveSentinel = "SqlServiceTests: subscription name resolved via ISubscriptionService"; + private const string ServerName = "server1"; + private const string ResourceGroup = "rg1"; + + private readonly ISubscriptionService _subscriptionService; + private readonly ITenantService _tenantService; + private readonly ILogger _logger; + private readonly SqlService _service; + + public SqlServiceTests() + { + _subscriptionService = Substitute.For(); + _tenantService = Substitute.For(); + _logger = Substitute.For>(); + _service = new SqlService(_subscriptionService, _tenantService, _logger); + } + + [Fact] + public async Task ListDatabasesAsync_WithSubscriptionName_ResolvesNameToId() + { + _subscriptionService.IsSubscriptionId(SubscriptionName).Returns(false); + _subscriptionService.GetSubscriptionIdByName(SubscriptionName, Arg.Any(), Arg.Any(), Arg.Any()) + .ThrowsAsync(new InvalidOperationException(ResolveSentinel)); + + var exception = await Assert.ThrowsAsync( + () => _service.ListDatabasesAsync(ServerName, ResourceGroup, SubscriptionName, null, TestContext.Current.CancellationToken)); + + Assert.Equal(ResolveSentinel, exception.Message); + await _subscriptionService.Received(1).GetSubscriptionIdByName( + SubscriptionName, Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Fact] + public async Task GetElasticPoolsAsync_WithSubscriptionName_ResolvesNameToId() + { + _subscriptionService.IsSubscriptionId(SubscriptionName).Returns(false); + _subscriptionService.GetSubscriptionIdByName(SubscriptionName, Arg.Any(), Arg.Any(), Arg.Any()) + .ThrowsAsync(new InvalidOperationException(ResolveSentinel)); + + var exception = await Assert.ThrowsAsync( + () => _service.GetElasticPoolsAsync(ServerName, ResourceGroup, SubscriptionName, null, TestContext.Current.CancellationToken)); + + Assert.Equal(ResolveSentinel, exception.Message); + await _subscriptionService.Received(1).GetSubscriptionIdByName( + SubscriptionName, Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Fact] + public async Task ListDatabasesAsync_WithSubscriptionId_SkipsNameLookup() + { + _subscriptionService.IsSubscriptionId(SubscriptionId).Returns(true); + var canceled = new CancellationToken(canceled: true); + + try + { + await _service.ListDatabasesAsync(ServerName, ResourceGroup, SubscriptionId, null, canceled); + } + catch + { + // The ARM hierarchy call is expected to fail/cancel; we only assert resolution behavior. + } + + await _subscriptionService.DidNotReceive().GetSubscriptionIdByName( + Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Fact] + public async Task GetElasticPoolsAsync_WithSubscriptionId_SkipsNameLookup() + { + _subscriptionService.IsSubscriptionId(SubscriptionId).Returns(true); + var canceled = new CancellationToken(canceled: true); + + try + { + await _service.GetElasticPoolsAsync(ServerName, ResourceGroup, SubscriptionId, null, canceled); + } + catch + { + // The ARM hierarchy call is expected to fail/cancel; we only assert resolution behavior. + } + + await _subscriptionService.DidNotReceive().GetSubscriptionIdByName( + Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); + } +} diff --git a/tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.Tests/assets.json b/tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.Tests/assets.json index 6de3f971c2..c8202d42fd 100644 --- a/tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.Tests/assets.json +++ b/tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.Tests/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "", "TagPrefix": "Azure.Mcp.Tools.Sql.Tests", - "Tag": "Azure.Mcp.Tools.Sql.Tests_a6712a8ecc" + "Tag": "Azure.Mcp.Tools.Sql.Tests_50222315f7" }