diff --git a/servers/Azure.Mcp.Server/README.md b/servers/Azure.Mcp.Server/README.md index a2734c6e9b..2981a3edb6 100644 --- a/servers/Azure.Mcp.Server/README.md +++ b/servers/Azure.Mcp.Server/README.md @@ -1260,6 +1260,7 @@ Example prompts that generate Azure CLI commands: * "List all resilience recovery plans in service group 'my-service-group'" * "Get the recovery plan 'my-recovery-plan' in service group 'my-service-group'" * "List the recovery jobs of recovery plan 'my-recovery-plan' in service group 'my-service-group'" +* "Update resilience drill 'my-drill' in service group 'my-service-group' to use manual RBAC setup" * "Create a Basic resilience usage plan 'my-plan' in resource group 'my-rg'" * "Enroll service group 'my-service-group' into usage plan 'my-plan' in resource group 'my-rg'" diff --git a/servers/Azure.Mcp.Server/changelog-entries/resilience-drill-update.yml b/servers/Azure.Mcp.Server/changelog-entries/resilience-drill-update.yml new file mode 100644 index 0000000000..c57c79348d --- /dev/null +++ b/servers/Azure.Mcp.Server/changelog-entries/resilience-drill-update.yml @@ -0,0 +1,3 @@ +changes: + - section: "Features Added" + description: "Added the resilience drill update tool for changing supporting resource placement, RBAC setup mode, and recovery plan association." \ No newline at end of file diff --git a/servers/Azure.Mcp.Server/docs/azmcp-commands.md b/servers/Azure.Mcp.Server/docs/azmcp-commands.md index 43743eecf4..d05a190f34 100644 --- a/servers/Azure.Mcp.Server/docs/azmcp-commands.md +++ b/servers/Azure.Mcp.Server/docs/azmcp-commands.md @@ -3753,6 +3753,14 @@ azmcp resilience drill get --subscription \ --service-group \ [--name ] +# Update mutable properties of a resilience drill +# ✅ Destructive | ✅ Idempotent | ❌ OpenWorld | ❌ ReadOnly | ❌ Secret | ❌ LocalRequired +azmcp resilience drill update --service-group \ + --drill \ + [--subscription --region ] \ + [--rbac-setup-mode ] \ + [--recovery-plan ] + # Get a resource (target) of a drill, or list all resources of the drill (omit --name) # ❌ Destructive | ✅ Idempotent | ❌ OpenWorld | ✅ ReadOnly | ❌ Secret | ❌ LocalRequired azmcp resilience drill resource get --subscription \ diff --git a/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md b/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md index bbdea7bdca..a5c3e848ca 100644 --- a/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md +++ b/servers/Azure.Mcp.Server/docs/e2eTestPrompts.md @@ -902,6 +902,8 @@ The `Interaction` column describes whether a prompt can invoke its tool immediat |:----------|:----------| | resilience_drill_get | List all resilience drills in service group | none | | resilience_drill_get | Get the details of resilience drill in service group | none | +| resilience_drill_update | Update resilience drill in service group to use manual RBAC setup | none | +| resilience_drill_update | Associate recovery plan with resilience drill in service group | none | | resilience_drill_resource_get | List all drill resources for resilience drill in service group | none | | resilience_drill_resource_get | List all drill targets for resilience drill in service group | none | | resilience_drill_resource_get | Show the resources targeted by resilience drill in service group | none | diff --git a/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json b/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json index cae5c11389..833b00355c 100644 --- a/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json +++ b/servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json @@ -45,7 +45,7 @@ }, { "name": "create_azure_resilience_management_resources", - "description": "Create or update Azure Resilience Management resources, including usage plans and usage plan enrollments, for Azure service groups.", + "description": "Create or update Azure Resilience Management resources, including usage plans, usage plan enrollments, and drills, for Azure service groups.", "toolMetadata": { "destructive": { "value": true, @@ -73,6 +73,7 @@ } }, "mappedToolList": [ + "resilience_drill_update", "resilience_usageplan_create", "resilience_usageplan_enrollment_create" ] diff --git a/tools/Azure.Mcp.Tools.ResilienceManagement/src/Commands/Drills/DrillUpdateCommand.cs b/tools/Azure.Mcp.Tools.ResilienceManagement/src/Commands/Drills/DrillUpdateCommand.cs new file mode 100644 index 0000000000..3efcbbb425 --- /dev/null +++ b/tools/Azure.Mcp.Tools.ResilienceManagement/src/Commands/Drills/DrillUpdateCommand.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Net; +using Azure.Mcp.Tools.ResilienceManagement.Models; +using Azure.Mcp.Tools.ResilienceManagement.Options.Drills; +using Azure.Mcp.Tools.ResilienceManagement.Services; +using Microsoft.Extensions.Logging; +using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Models.Command; + +namespace Azure.Mcp.Tools.ResilienceManagement.Commands.Drills; + +[CommandMetadata( + Id = "9c4ad84e-7056-46ad-a857-8a81dce0c676", + Name = "update", + Title = "Update Resilience Drill", + Description = """ + Updates an existing resilience drill in an Azure service group. Use for requests such as "Update + resilience drill in service group to use manual RBAC setup" and + "Associate recovery plan with resilience drill in service group + ". Changes the drill's RBAC setup mode, associates or links a recovery plan with the + drill, or changes the supporting-resource subscription and region together. This tool modifies the drill; + it does not get drill details or get a recovery plan. Only supplied properties are changed. + """, + Destructive = true, + Idempotent = true, + OpenWorld = false, + ReadOnly = false, + Secret = false, + LocalRequired = false)] +public sealed class DrillUpdateCommand(ILogger logger, IResilienceManagementService resilienceManagementService) + : AuthenticatedCommand +{ + private readonly ILogger _logger = logger; + private readonly IResilienceManagementService _resilienceManagementService = resilienceManagementService; + + public override void ValidateOptions(DrillUpdateOptions options, ValidationResult validationResult) + { + base.ValidateOptions(options, validationResult); + + if (string.IsNullOrWhiteSpace(options.Drill) || options.Drill.Contains('/')) + { + validationResult.Errors.Add("The drill name must be a non-empty single path segment."); + } + + if (string.IsNullOrWhiteSpace(options.ServiceGroup) || options.ServiceGroup.Contains('/')) + { + validationResult.Errors.Add("The service group name must be a non-empty single path segment."); + } + + if (options.RecoveryPlan is { } recoveryPlan && (string.IsNullOrWhiteSpace(recoveryPlan) || recoveryPlan.Contains('/'))) + { + validationResult.Errors.Add("The recovery plan name must be a non-empty single path segment."); + } + + if (options.Subscription is not null && string.IsNullOrWhiteSpace(options.Subscription)) + { + validationResult.Errors.Add("The subscription must not be empty."); + } + + if (options.Region is not null && string.IsNullOrWhiteSpace(options.Region)) + { + validationResult.Errors.Add("The region must not be empty."); + } + + if (string.IsNullOrWhiteSpace(options.Subscription) != string.IsNullOrWhiteSpace(options.Region)) + { + validationResult.Errors.Add("Subscription and region must be specified together."); + } + + if (string.IsNullOrWhiteSpace(options.Subscription) && options.RbacSetupMode is null && string.IsNullOrWhiteSpace(options.RecoveryPlan)) + { + validationResult.Errors.Add("Specify at least one property to update: subscription and region, RBAC setup mode, or recovery plan."); + } + } + + public override async Task ExecuteAsync(CommandContext context, DrillUpdateOptions options, CancellationToken cancellationToken) + { + try + { + var drill = await _resilienceManagementService.UpdateDrillAsync( + options.ServiceGroup, + options.Drill, + options.Subscription, + options.Region, + options.RbacSetupMode, + options.RecoveryPlan, + options.Tenant, + options.RetryPolicy, + cancellationToken); + + context.Response.Results = ResponseResult.Create( + new DrillUpdateCommandResult(drill), + ResilienceManagementJsonContext.Default.DrillUpdateCommandResult); + } + catch (Exception ex) + { + _logger.LogError(ex, + "Error updating drill. ServiceGroup: {ServiceGroup}, Drill: {Drill}, Subscription: {Subscription}, Region: {Region}.", + options.ServiceGroup, options.Drill, options.Subscription, options.Region); + HandleException(context, ex); + } + + return context.Response; + } + + protected override string GetErrorMessage(Exception ex) => ex switch + { + RequestFailedException reqEx when reqEx.Status == (int)HttpStatusCode.Conflict => + "The drill could not be updated because it conflicts with the current resource state.", + RequestFailedException reqEx when reqEx.Status == (int)HttpStatusCode.Forbidden => + "Authorization failed updating the drill. Verify you have the required permissions.", + RequestFailedException reqEx when reqEx.Status == (int)HttpStatusCode.NotFound => + "The drill, service group, subscription, or recovery plan was not found.", + RequestFailedException => + "The drill update request failed. Verify the request parameters and try again.", + _ => base.GetErrorMessage(ex) + }; + + public sealed record DrillUpdateCommandResult(DrillInfo Drill); +} diff --git a/tools/Azure.Mcp.Tools.ResilienceManagement/src/Commands/ResilienceManagementJsonContext.cs b/tools/Azure.Mcp.Tools.ResilienceManagement/src/Commands/ResilienceManagementJsonContext.cs index d461536ca4..06a09abfcf 100644 --- a/tools/Azure.Mcp.Tools.ResilienceManagement/src/Commands/ResilienceManagementJsonContext.cs +++ b/tools/Azure.Mcp.Tools.ResilienceManagement/src/Commands/ResilienceManagementJsonContext.cs @@ -45,6 +45,7 @@ namespace Azure.Mcp.Tools.ResilienceManagement.Commands; [JsonSerializable(typeof(DrillInfo))] [JsonSerializable(typeof(DrillResourceInfo))] [JsonSerializable(typeof(DrillGetCommand.DrillGetCommandResult))] +[JsonSerializable(typeof(DrillUpdateCommand.DrillUpdateCommandResult))] [JsonSerializable(typeof(DrillResourceGetCommand.DrillResourceGetCommandResult))] [JsonSerializable(typeof(RecoveryPlanGetCommand.RecoveryPlanGetCommandResult))] [JsonSerializable(typeof(RecoveryResourceGetCommand.RecoveryResourceGetCommandResult))] diff --git a/tools/Azure.Mcp.Tools.ResilienceManagement/src/Models/DrillRbacSetupMode.cs b/tools/Azure.Mcp.Tools.ResilienceManagement/src/Models/DrillRbacSetupMode.cs new file mode 100644 index 0000000000..df2d91fb39 --- /dev/null +++ b/tools/Azure.Mcp.Tools.ResilienceManagement/src/Models/DrillRbacSetupMode.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Azure.Mcp.Tools.ResilienceManagement.Models; + +public enum DrillRbacSetupMode +{ + AutomatedCustomRole, + AutomatedBuiltinRoles, + Manual +} diff --git a/tools/Azure.Mcp.Tools.ResilienceManagement/src/Options/Drills/DrillUpdateOption.cs b/tools/Azure.Mcp.Tools.ResilienceManagement/src/Options/Drills/DrillUpdateOption.cs new file mode 100644 index 0000000000..8f48ccb422 --- /dev/null +++ b/tools/Azure.Mcp.Tools.ResilienceManagement/src/Options/Drills/DrillUpdateOption.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Azure.Mcp.Core.Options; +using Azure.Mcp.Tools.ResilienceManagement.Models; +using Microsoft.Mcp.Core.Options; + +namespace Azure.Mcp.Tools.ResilienceManagement.Options.Drills; + +public sealed class DrillUpdateOptions +{ + [Option(Description = ResilienceManagementOptionDescriptions.ServiceGroup)] + public required string ServiceGroup { get; set; } + + [Option(Description = "The name of the resilience drill to update.")] + public required string Drill { get; set; } + + [Option(Description = "The subscription ID or name where the drill's supporting resources will be created. Specify together with region.")] + public string? Subscription { get; set; } + + [Option(Description = "The Azure region where the drill's supporting resources will be created. Specify together with subscription.")] + public string? Region { get; set; } + + [Option(Description = "The RBAC setup mode. Supported values: AutomatedCustomRole, AutomatedBuiltinRoles, Manual.")] + public DrillRbacSetupMode? RbacSetupMode { get; set; } + + [Option(Description = "The recovery plan name in the same service group to associate with the drill.")] + public string? RecoveryPlan { get; set; } + + [Option(Description = OptionDescriptions.Tenant)] + public string? Tenant { get; set; } + + [OptionContainer(Prefix = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } +} diff --git a/tools/Azure.Mcp.Tools.ResilienceManagement/src/ResilienceManagementSetup.cs b/tools/Azure.Mcp.Tools.ResilienceManagement/src/ResilienceManagementSetup.cs index 8f5ae7ddb9..81210ec93b 100644 --- a/tools/Azure.Mcp.Tools.ResilienceManagement/src/ResilienceManagementSetup.cs +++ b/tools/Azure.Mcp.Tools.ResilienceManagement/src/ResilienceManagementSetup.cs @@ -41,6 +41,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); } @@ -122,6 +123,7 @@ and high availability and disaster recovery requirements. resilienceManagement.AddSubGroup(drills); drills.AddCommand(serviceProvider); + drills.AddCommand(serviceProvider); // Create resource subgroup under drill var drillResources = new CommandGroup("resource", "Resilience drill resource operations - Commands for listing and getting the resources (targets) of a resilience drill."); diff --git a/tools/Azure.Mcp.Tools.ResilienceManagement/src/Services/IResilienceManagementService.cs b/tools/Azure.Mcp.Tools.ResilienceManagement/src/Services/IResilienceManagementService.cs index 39b04691bb..547f950f69 100644 --- a/tools/Azure.Mcp.Tools.ResilienceManagement/src/Services/IResilienceManagementService.cs +++ b/tools/Azure.Mcp.Tools.ResilienceManagement/src/Services/IResilienceManagementService.cs @@ -51,6 +51,8 @@ public interface IResilienceManagementService Task GetDrillAsync(string serviceGroup, string drill, string? tenant = null, RetryPolicyOptions? retryPolicy = null, CancellationToken cancellationToken = default); + Task UpdateDrillAsync(string serviceGroup, string drill, string? subscription = null, string? region = null, DrillRbacSetupMode? rbacSetupMode = null, string? recoveryPlan = null, string? tenant = null, RetryPolicyOptions? retryPolicy = null, CancellationToken cancellationToken = default); + Task> ListDrillResourcesAsync(string serviceGroup, string drill, string? tenant = null, RetryPolicyOptions? retryPolicy = null, CancellationToken cancellationToken = default); Task GetDrillResourceAsync(string serviceGroup, string drill, string drillResource, string? tenant = null, RetryPolicyOptions? retryPolicy = null, CancellationToken cancellationToken = default); diff --git a/tools/Azure.Mcp.Tools.ResilienceManagement/src/Services/ResilienceManagementService.cs b/tools/Azure.Mcp.Tools.ResilienceManagement/src/Services/ResilienceManagementService.cs index 9146f91c5b..da066fbcae 100644 --- a/tools/Azure.Mcp.Tools.ResilienceManagement/src/Services/ResilienceManagementService.cs +++ b/tools/Azure.Mcp.Tools.ResilienceManagement/src/Services/ResilienceManagementService.cs @@ -6,6 +6,7 @@ using Azure.Mcp.Core.Services.Azure; using Azure.Mcp.Tools.ResilienceManagement.Models; using Azure.ResourceManager; +using Azure.ResourceManager.Models; using Azure.ResourceManager.ResilienceManagement; using Azure.ResourceManager.ResilienceManagement.Models; using Microsoft.Mcp.Core.Options; @@ -524,6 +525,59 @@ public async Task GetDrillAsync(string serviceGroup, string drill, st SystemData: root.TryGetProperty("systemData", out JsonElement systemDataElement) ? systemDataElement.Clone() : default); } + public async Task UpdateDrillAsync(string serviceGroup, string drill, string? subscription = null, string? region = null, DrillRbacSetupMode? rbacSetupMode = null, string? recoveryPlan = null, string? tenant = null, RetryPolicyOptions? retryPolicy = null, CancellationToken cancellationToken = default) + { + var subscriptionId = subscription is null || AzureService.IsSubscriptionId(subscription) + ? subscription + : (await AzureService.GetSubscription(subscription, tenant, retryPolicy, cancellationToken)).Data.SubscriptionId; + + ArmClient armClient = await CreateArmClientAsync(tenantIdOrName: tenant, retryPolicy: retryPolicy, cancellationToken: cancellationToken); + + var drillId = ResilienceManagementDrillResource.CreateResourceIdentifier(serviceGroup, drill); + ResilienceManagementDrillResource drillResource = armClient.GetResilienceManagementDrillResource(drillId); + var properties = new DrillUpdateProperties(); + var associatedIdentity = new ResilienceManagementAssociatedIdentity(ManagedServiceIdentityType.SystemAssigned); + + if (subscriptionId is not null && region is not null) + { + properties.DrillAssetProperties = new AssetPropertiesOfDrill(subscriptionId, region); + } + + if (rbacSetupMode is not null) + { + properties.RbacSetupMode = new ResilienceManagementRbacSetupMode(rbacSetupMode.Value.ToString()); + } + + if (recoveryPlan is not null) + { + properties.RecoveryPlanProperties = ArmResilienceManagementModelFactory.RecoveryPlanPropertiesOfDrill( + associatedIdentity, + RecoveryPlanResource.CreateResourceIdentifier(serviceGroup, recoveryPlan), + recoveryPlanResourceExcludedCount: null); + } + + var patch = new ResilienceManagementDrillPatch + { + Properties = properties + }; + + ArmOperation operation = await drillResource.UpdateAsync(WaitUntil.Started, patch, cancellationToken); + await WaitForLroCompletionAsync(operation, cancellationToken); + + Response response = await drillResource.GetAsync(cancellationToken); + using JsonDocument document = JsonDocument.Parse(response.GetRawResponse().Content.ToMemory()); + JsonElement root = document.RootElement; + + return new DrillInfo( + Id: root.TryGetProperty("id", out JsonElement idElement) ? idElement.GetString() ?? string.Empty : string.Empty, + Name: root.TryGetProperty("name", out JsonElement nameElement) ? nameElement.GetString() ?? string.Empty : string.Empty, + ResourceType: root.TryGetProperty("type", out JsonElement typeElement) ? typeElement.GetString() : null, + Location: root.TryGetProperty("location", out JsonElement locationElement) ? locationElement.GetString() : null, + Tags: GetTagsOrNull(root), + Properties: root.TryGetProperty("properties", out JsonElement propertiesElement) ? propertiesElement.Clone() : default, + SystemData: root.TryGetProperty("systemData", out JsonElement systemDataElement) ? systemDataElement.Clone() : default); + } + public async Task> ListDrillResourcesAsync(string serviceGroup, string drill, string? tenant = null, RetryPolicyOptions? retryPolicy = null, CancellationToken cancellationToken = default) { ArmClient armClient = await CreateArmClientAsync(tenantIdOrName: tenant, retryPolicy: retryPolicy, cancellationToken: cancellationToken); diff --git a/tools/Azure.Mcp.Tools.ResilienceManagement/tests/Azure.Mcp.Tools.ResilienceManagement.Tests/Drills/DrillUpdateCommandTests.cs b/tools/Azure.Mcp.Tools.ResilienceManagement/tests/Azure.Mcp.Tools.ResilienceManagement.Tests/Drills/DrillUpdateCommandTests.cs new file mode 100644 index 0000000000..5d0d70330e --- /dev/null +++ b/tools/Azure.Mcp.Tools.ResilienceManagement/tests/Azure.Mcp.Tools.ResilienceManagement.Tests/Drills/DrillUpdateCommandTests.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Net; +using Azure.Mcp.Tools.ResilienceManagement.Commands; +using Azure.Mcp.Tools.ResilienceManagement.Commands.Drills; +using Azure.Mcp.Tools.ResilienceManagement.Models; +using Azure.Mcp.Tools.ResilienceManagement.Services; +using Microsoft.Mcp.Core.Options; +using Microsoft.Mcp.Tests.Client; +using NSubstitute; +using NSubstitute.ExceptionExtensions; +using Xunit; + +namespace Azure.Mcp.Tools.ResilienceManagement.Tests.Drills; + +public sealed class DrillUpdateCommandTests : CommandUnitTestsBase +{ + private const string ValidArgs = "--service-group sg1 --drill drill1 --rbac-setup-mode Manual"; + + [Fact] + public void Constructor_InitializesCommandCorrectly() + { + var command = Command.GetCommand(); + Assert.Equal("update", command.Name); + Assert.NotNull(command.Description); + Assert.NotEmpty(command.Description); + } + + [Theory] + [InlineData(ValidArgs, true)] + [InlineData("--service-group sg1 --drill drill1 --subscription sub --region westus2", true)] + [InlineData("--drill drill1 --rbac-setup-mode Manual", false)] + [InlineData("--service-group sg1 --rbac-setup-mode Manual", false)] + [InlineData("--service-group sg1 --drill drill1", false)] + [InlineData("--service-group sg1 --drill drill1 --subscription sub", false)] + [InlineData("")] + public async Task ExecuteAsync_ValidatesInputCorrectly(string args, bool shouldSucceed = false) + { + if (shouldSucceed) + { + ConfigureUpdatedDrill(); + } + + var response = await ExecuteCommandAsync(args); + + Assert.Equal(shouldSucceed ? HttpStatusCode.OK : HttpStatusCode.BadRequest, response.Status); + } + + [Fact] + public async Task ExecuteAsync_ReturnsUpdatedDrill() + { + ConfigureUpdatedDrill(); + + var response = await ExecuteCommandAsync(ValidArgs); + + var result = ValidateAndDeserializeResponse(response, ResilienceManagementJsonContext.Default.DrillUpdateCommandResult); + Assert.Equal("drill1", result.Drill.Name); + await Service.Received(1).UpdateDrillAsync( + "sg1", + "drill1", + null, + null, + DrillRbacSetupMode.Manual, + null, + null, + Arg.Any(), + Arg.Any()); + } + + [Theory] + [InlineData(HttpStatusCode.Conflict, "conflicts with the current resource state")] + [InlineData(HttpStatusCode.Forbidden, "Authorization failed")] + [InlineData(HttpStatusCode.NotFound, "not found")] + [InlineData(HttpStatusCode.BadRequest, "request failed")] + public async Task ExecuteAsync_SanitizesRequestFailedException(HttpStatusCode status, string expectedMessage) + { + Service.UpdateDrillAsync( + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any()) + .ThrowsAsync(new RequestFailedException((int)status, "Sensitive provider details")); + + var response = await ExecuteCommandAsync(ValidArgs); + + Assert.Equal(status, response.Status); + Assert.Contains(expectedMessage, response.Message, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("Sensitive provider details", response.Message); + } + + private void ConfigureUpdatedDrill() + { + Service.UpdateDrillAsync( + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any()) + .Returns(new DrillInfo("id1", "drill1")); + } +} diff --git a/tools/Azure.Mcp.Tools.ResilienceManagement/tests/Azure.Mcp.Tools.ResilienceManagement.Tests/ResilienceManagementCommandTests.cs b/tools/Azure.Mcp.Tools.ResilienceManagement/tests/Azure.Mcp.Tools.ResilienceManagement.Tests/ResilienceManagementCommandTests.cs index c180436ca6..cc53231896 100644 --- a/tools/Azure.Mcp.Tools.ResilienceManagement/tests/Azure.Mcp.Tools.ResilienceManagement.Tests/ResilienceManagementCommandTests.cs +++ b/tools/Azure.Mcp.Tools.ResilienceManagement/tests/Azure.Mcp.Tools.ResilienceManagement.Tests/ResilienceManagementCommandTests.cs @@ -128,6 +128,26 @@ public async Task Should_list_drills() (id.GetString()?.EndsWith(drillName, StringComparison.OrdinalIgnoreCase) ?? false)); } + [Fact] + public async Task Should_update_drill() + { + var serviceGroup = RegisterOrRetrieveDeploymentOutputVariable("serviceGroupName", "SERVICEGROUPNAME"); + var drillName = RegisterOrRetrieveDeploymentOutputVariable("drillName", "DRILLNAME"); + + var result = await CallToolAsync( + "resilience_drill_update", + new() + { + { "tenant", Settings.TenantId }, + { "service-group", serviceGroup }, + { "drill", drillName }, + { "rbac-setup-mode", "AutomatedBuiltinRoles" } + }); + + var drill = result.AssertProperty("drill"); + Assert.False(string.IsNullOrEmpty(drill.AssertProperty("name").GetString())); + } + [Fact] public async Task Should_list_drill_resources() { diff --git a/tools/Azure.Mcp.Tools.ResilienceManagement/tests/Azure.Mcp.Tools.ResilienceManagement.Tests/assets.json b/tools/Azure.Mcp.Tools.ResilienceManagement/tests/Azure.Mcp.Tools.ResilienceManagement.Tests/assets.json index 40b0950007..29e5d5a339 100644 --- a/tools/Azure.Mcp.Tools.ResilienceManagement/tests/Azure.Mcp.Tools.ResilienceManagement.Tests/assets.json +++ b/tools/Azure.Mcp.Tools.ResilienceManagement/tests/Azure.Mcp.Tools.ResilienceManagement.Tests/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "", "TagPrefix": "Azure.Mcp.Tools.ResilienceManagement.Tests", - "Tag": "Azure.Mcp.Tools.ResilienceManagement.Tests_cb0c443485" + "Tag": "Azure.Mcp.Tools.ResilienceManagement.Tests_324d5d41d0" } \ No newline at end of file