fix(auth): Azure DevOps az-CLI token acquisition returns None on undecodable output (#3527)

_acquire_via_az_cli runs 'az account get-access-token' with text=True, so
subprocess.run decodes stdout with the locale encoding and raises
UnicodeDecodeError (a ValueError sibling, NOT a JSONDecodeError) when the output
can't be decoded. That escaped the except (OSError, TimeoutExpired,
JSONDecodeError, KeyError) tuple and crashed a helper whose contract is to
return str | None. Add UnicodeDecodeError to the tuple.

Test patches subprocess.run to raise UnicodeDecodeError and asserts resolve_token
returns None (fails before: the error propagated).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ali jawwad
2026-07-17 23:51:51 +05:00
committed by GitHub
parent 208d38695f
commit 41c5dfc3a1
2 changed files with 24 additions and 1 deletions

View File

@@ -76,7 +76,17 @@ class AzureDevOpsAuth(AuthProvider):
payload = _json.loads(result.stdout)
token = payload.get("accessToken", "").strip()
return token or None
except (OSError, subprocess.TimeoutExpired, _json.JSONDecodeError, KeyError):
except (
OSError,
subprocess.TimeoutExpired,
_json.JSONDecodeError,
UnicodeDecodeError,
KeyError,
):
# UnicodeDecodeError: text=True decodes az stdout with the locale
# encoding, which raises (not a JSONDecodeError) if the output isn't
# decodable — this helper's contract is to return None on any
# failure, never to propagate.
return None
@staticmethod

View File

@@ -502,6 +502,19 @@ class TestAzureDevOpsAuth:
with patch("specify_cli.authentication.azure_devops.subprocess.run", side_effect=OSError("not found")):
assert AzureDevOpsAuth().resolve_token(entry) is None
def test_resolve_token_azure_cli_undecodable_output_returns_none(self):
"""Undecodable az output returns None, not a crash. With text=True,
subprocess.run decodes stdout with the locale encoding and raises
UnicodeDecodeError (not a JSONDecodeError) when it can't — the helper's
contract is to return None on any failure."""
from unittest.mock import patch
entry = AuthConfigEntry(
hosts=("dev.azure.com",), provider="azure-devops", auth="azure-cli",
)
boom = UnicodeDecodeError("utf-8", b"\xff\xfe", 0, 1, "invalid start byte")
with patch("specify_cli.authentication.azure_devops.subprocess.run", side_effect=boom):
assert AzureDevOpsAuth().resolve_token(entry) is None
def test_resolve_token_azure_ad_success(self, monkeypatch):
"""azure-ad acquires token via OAuth2 client credentials."""
from unittest.mock import patch, MagicMock