SIGN IN SIGN UP

[python] avoid etag/match_condition clientName collision with multiple etag headers (#10816)

# Problem

PR #10494 (which added support for custom etag wire names) broke
operations that have **more than one `Azure.Core.eTag`-typed header**.
The Azure Storage Blob `copyFromUrl` operation is a concrete example —
it carries:

- standard `If-Match` / `If-None-Match`
- custom `x-ms-source-if-match` / `x-ms-source-if-none-match`

The emitter in `http.ts` stamps `etagRole = "ifMatch"` / `"ifNoneMatch"`
onto **every** one of those headers. Then in `preprocess/__init__.py`,
`update_parameter` blindly applies `headers_convert(ETAG_MATCH_DATA)` /
`ETAG_NONE_MATCH_DATA` to every parameter with that role — overwriting
`clientName` to `"etag"` and `"match_condition"` on **both** pairs. The
operation ends up with two parameters named `etag` and two named
`match_condition`.

The slot-picker in `update_client` already chose one ifMatch/ifNoneMatch
slot per operation, but it did nothing to prevent the per-parameter
rename on the others.

# Fix

In `preprocess/__init__.py` `update_client`:

1. Collect **all** `ifMatch` and `ifNoneMatch` candidates per operation,
not just the first.
2. New `_pick_etag_slot` helper prefers the standard `If-Match` /
`If-None-Match` wire names over custom etag headers (matches
pre-PR-10494 behaviour when both are present).
3. Strip `etagRole` from non-selected candidates so `update_parameter`
leaves their natural `clientName` intact (e.g. `source_if_match`,
`source_if_none_match`).

# Result

For `copyFromUrl`:
- `If-Match` / `If-None-Match` → promoted to the `etag` /
`match_condition` pair (unchanged behaviour vs. pre-PR-10494).
- `x-ms-source-if-match` / `x-ms-source-if-none-match` → keep their
natural `source_if_match` / `source_if_none_match` client names.

No more clientName collisions.

# Tests

Adds `tests/unit/test_preprocess_etag.py` with six tests:

- `test_etag_role_preserved_when_only_standard_pair_present`
- `test_etag_role_preserved_when_only_custom_pair_present`
- `test_standard_etag_wins_over_custom_when_both_present` (regression)
- `test_first_custom_pair_chosen_when_multiple_custom_pairs_present`
- `test_synthetic_partner_still_works_with_only_one_custom_etag`
- `test_full_update_yaml_does_not_collide_client_names` (end-to-end)

Verified the three multi-etag tests **fail** on `upstream/main` and pass
with this change. All existing unit tests still pass.

# Spec referenced

The original repro is `copyFromUrl` in
`specification/storage/Microsoft.BlobStorage/routes.tsp` of
`Azure/azure-rest-api-specs`, which composes `SourceIfMatchParameter`,
`SourceIfNoneMatchParameter`, `IfMatchParameter`, and
`IfNoneMatchParameter` (all `Azure.Core.eTag`).

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
L
Libba Lawrence committed
66c5e87189dabecad413135a70b8f33e1a26f83f
Parent: 8701e7f
Committed by GitHub <noreply@github.com> on 6/1/2026, 3:19:25 PM