Skip to content

azurebackup: fix telemetry-triaged bugs NEW-1, NEW-3, NEW-4 - #2726

Merged
Shraddha Jain (shrja-ms) merged 5 commits into
microsoft:mainfrom
shrja-ms:user/azurebackup-fix-new-bugs-new1-new3-new4
May 27, 2026
Merged

azurebackup: fix telemetry-triaged bugs NEW-1, NEW-3, NEW-4#2726
Shraddha Jain (shrja-ms) merged 5 commits into
microsoft:mainfrom
shrja-ms:user/azurebackup-fix-new-bugs-new1-new3-new4

Conversation

@shrja-ms

@shrja-ms Shraddha Jain (shrja-ms) commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes three telemetry-triaged Azure Backup MCP bugs from the most recent report (NEW-1, NEW-3, NEW-4) in a single PR.

Bug Symptom (before) Behavior (after)
NEW-1 When both RSV and DPP vault listings failed, azmcp_azurebackup_vault_get (and azmcp_azurebackup_governance_find-unprotected) leaked a raw AggregateException to the client, so the actual HTTP status / error code / message from the backends were buried. A single meaningful exception is thrown. If both backends fail with the same HTTP status (e.g. 401/403), the inner RequestFailedException is surfaced directly. Otherwise both inner messages are folded into one InvalidOperationException.
NEW-3 Passing a subscription name (display name) in --subscription caused FormatException from Azure.Core.ResourceIdentifier.SubscriptionId for every azmcp_azurebackup_* command. This violated the documented MCP contract that --subscription accepts both subscription IDs and names. Every public AzureBackupService method (24 sites) resolves the input via ISubscriptionService.GetSubscription before constructing any ARM ResourceIdentifier. GUID inputs short-circuit through Guid.TryParse so the existing happy path makes zero extra calls.
NEW-4 azmcp_azurebackup_protectableitem_list was leaking a 500 ArgumentException from the service layer when callers passed an unrecognised --workload-type. A System.CommandLine validator on the command rejects the unknown value as a 400 ValidationError with a friendly message listing the supported aliases. The validator's alias set mirrors exactly the existing service-layer guard (RsvBackupOperations.NormalizeWorkloadTypeForFilter); the service-layer guard is intentionally left in place as defense-in-depth.

Tests

  • Azure.Mcp.Tools.AzureBackup.UnitTests: 584 / 584 passing (was 581 before; +3 net new tests beyond the existing test that was updated to assert the new NEW-1 behavior).
    • NEW-1: ListVaultsAsync_BothFailWithSameStatus_ThrowsInnerRequestFailedException, ListVaultsAsync_BothFailWithDifferentExceptions_ThrowsInvalidOperationWithBothMessages.
    • NEW-3: ListVaultsAsync_WhenSubscriptionIsName_ResolvesToGuidBeforeCallingOps, ListVaultsAsync_WhenSubscriptionIsGuid_DoesNotCallSubscriptionService (asserts ISubscriptionService.DidNotReceive().GetSubscription(...) on the GUID happy path).
    • NEW-4: 19 positive cases (one per canonical token + alias, including case-insensitivity) and 4 negative cases (unknown, DPP-only, garbage, and an OData-injection-style input); the negative cases also assert Service.DidNotReceive().
    • Existing AzureBackupServiceTests migrated to GUID-literal subscriptions so they exercise the Guid.TryParse short-circuit without any mock setup.
    • AzureBackupSetupTests DI container updated to register ISubscriptionService for the new constructor parameter.
  • Live tests: no re-recording required. The NEW-4 change is a pre-existing-validator pre-check; the NEW-1 change only affects the both-backends-failing path which has no recording; the NEW-3 change short-circuits on GUID input, and every existing recording uses subscription GUIDs.
  • Tool build (Azure.Mcp.Tools.AzureBackup/src) and server build (servers/Azure.Mcp.Server): clean, 0 warnings / 0 errors.

Files

  • tools/Azure.Mcp.Tools.AzureBackup/src/Services/AzureBackupService.cs — NEW-1 + NEW-3 fixes (new ResolveSubscriptionIdAsync helper, new BuildBothVaultListingsFailedException helper, ctor now takes ISubscriptionService, one-line patches at the top of all 24 public async methods).
  • tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectableItem/ProtectableItemListCommand.cs — NEW-4 validator registration.
  • tools/Azure.Mcp.Tools.AzureBackup/src/Services/WorkloadTypeNormalizer.cs — NEW-4 helper (alias set mirrors RSV service-layer guard).
  • tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.UnitTests/Services/AzureBackupServiceTests.cs — new NEW-1 + NEW-3 tests, existing tests migrated to GUID subscriptions.
  • tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.UnitTests/AzureBackupSetupTests.cs — DI container registers ISubscriptionService.
  • tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.UnitTests/ProtectableItem/ProtectableItemListCommandTests.cs — NEW-4 tests.
  • servers/Azure.Mcp.Server/changelog-entries/1779703098177.yaml — changelog entry (3 "Bugs Fixed" bullets).

Invoking Livetests

Copilot submitted PRs are not trustworthy by default. Users with write access to the repo need to validate the contents of this PR before leaving a comment with the text /azp run mcp - pullrequest - live. This will trigger the necessary livetest workflows to complete required validation.

Adds a System.CommandLine validator on ProtectableItemListCommand so that an unrecognised --workload-type token is rejected as a 400 ValidationError, with a friendly message naming the supported aliases, instead of leaking through to the service layer where it surfaces as a 500 ArgumentException from the protectable-items REST filter call.

Introduces a small WorkloadTypeNormalizer helper whose alias set mirrors exactly the existing service-layer guard in RsvBackupOperations.NormalizeWorkloadTypeForFilter (kept in sync by hand; the helper carries an explicit comment to that effect). The service-layer guard is intentionally left untouched as defense-in-depth.

Tests: 19 positive cases (one per canonical token + alias, plus a case-insensitivity case) and 4 negative cases including an OData-injection-style input. The negative tests also assert that the service is never invoked once validation has rejected the input.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses Azure Backup bug NEW-4 by validating --workload-type for azmcp_azurebackup_protectableitem_list at the System.CommandLine boundary, so unrecognized values are rejected as a 400 validation error (instead of surfacing a service-layer exception back to the client).

Changes:

  • Added a command-layer validator on ProtectableItemListCommand to reject unknown --workload-type values with a friendly supported-values message.
  • Introduced WorkloadTypeNormalizer to centralize the supported workload-type alias set used by the validator.
  • Added unit tests covering accepted aliases/casing and rejected unknown values; added a server changelog entry.

Invoking Livetests

Copilot submitted PRs are not trustworthy by default. Users with write access to the repo need to validate the contents of this PR before leaving a comment with the text /azp run mcp - pullrequest - live. This will trigger the necessary livetest workflows to complete required validation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
tools/Azure.Mcp.Tools.AzureBackup/src/Commands/ProtectableItem/ProtectableItemListCommand.cs Adds --workload-type command validator to fail fast on unknown values.
tools/Azure.Mcp.Tools.AzureBackup/src/Services/WorkloadTypeNormalizer.cs New helper encapsulating workload-type alias recognition and error formatting.
tools/Azure.Mcp.Tools.AzureBackup/tests/Azure.Mcp.Tools.AzureBackup.UnitTests/ProtectableItem/ProtectableItemListCommandTests.cs Adds positive/negative validation tests ensuring service is not called on invalid input.
servers/Azure.Mcp.Server/changelog-entries/1779703098177.yaml Documents the bug fix in the server changelog entry format.

Comment thread tools/Azure.Mcp.Tools.AzureBackup/src/Services/WorkloadTypeNormalizer.cs Outdated
…bscription name resolution)

NEW-1: When both RSV and DPP vault listings fail in ListVaultsAsync and FindUnprotectedResourcesAsync, surface a single meaningful exception instead of leaking AggregateException. If both inners are RequestFailedException with the same HTTP status, throw the inner directly so status/error-code/message reach the user; otherwise wrap both inners in a single InvalidOperationException whose Message includes both.

NEW-3: Resolve subscription name -> GUID via ISubscriptionService at the top of every public AzureBackupService method (24 sites) before delegating to RSV/DPP ops that construct ARM ResourceIdentifier. GUID inputs short-circuit via Guid.TryParse so no extra service call happens in the existing happy path.

- AzureBackupService ctor gains ISubscriptionService.
- 2 NEW-1 unit tests (same-status / different-exceptions) and 2 NEW-3 unit tests (name resolved + GUID short-circuit) added.
- AzureBackupSetupTests DI container updated to register ISubscriptionService.
- Existing AzureBackupServiceTests migrated to GUID-literal subscriptions so they exercise the Guid.TryParse short-circuit without mock setup.
@shrja-ms Shraddha Jain (shrja-ms) changed the title azurebackup: reject unknown --workload-type at command boundary (NEW-4) azurebackup: fix telemetry-triaged bugs NEW-1, NEW-3, NEW-4 May 25, 2026
- Drop the HasOptionResult gate in the --workload-type validator and read the value via GetValueOrDefault; this ensures whitespace-only inputs (which System.CommandLine may report as 'no result') still fail validation at the command boundary rather than slipping past to the service layer.

- IsSupported already returns false for whitespace, so the new validator treats whitespace-only values as 'Unknown workload type' just like other unsupported tokens. Two whitespace InlineData cases added to ExecuteAsync_RejectsUnknownWorkloadType_AsValidationError to lock this in.

- Remove the unsubstantiated remark in WorkloadTypeNormalizer that claimed a unit test asserts the two alias sets agree; no such test exists and adding one would require reflection into a private switch in RsvBackupOperations. The 'kept in sync by hand' note remains.
Comment thread tools/Azure.Mcp.Tools.AzureBackup/src/Services/AzureBackupService.cs Outdated
Comment thread tools/Azure.Mcp.Tools.AzureBackup/src/Services/AzureBackupService.cs Outdated
Comment thread tools/Azure.Mcp.Tools.AzureBackup/src/Services/WorkloadTypeNormalizer.cs Outdated
Comment thread tools/Azure.Mcp.Tools.AzureBackup/src/Services/WorkloadTypeNormalizer.cs Outdated
- NEW-1 (alzimmermsft): when both RSV and DPP vault listings fail with the
  same HTTP status, no longer drop the DPP exception. Both inner messages
  are now always folded into a single InvalidOperationException so a 403
  from RSV and a 403 from DPP (which can carry different ErrorCodes /
  messages) both reach the user. RSV exception preserved as InnerException.
- NEW-3 (alzimmermsft nit): in CreateVaultAsync, run the synchronous
  VaultTypeResolver.ValidateVaultType check before ResolveSubscriptionIdAsync
  so invalid --vault-type fails fast without a possible ARM network call.
  Apply the same reordering to ListProtectableItemsAsync (the IsDpp check).
- NEW-4 (alzimmermsft nits): WorkloadTypeNormalizer.SupportedTokensDescription
  drops the parentheses around aliases, and FormatUnknownMessage now states
  '(case-insensitive)' in the error message.
- Updated ListVaultsAsync_BothFailWithSameStatus_* unit test to assert the
  new combined-message behavior.
@shrja-ms
Shraddha Jain (shrja-ms) merged commit 97a665b into microsoft:main May 27, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants