Skip to content

Commit e91c94b

Browse files
Shira SassoonCopilot
andcommitted
test: add parser and MSAL bridge coverage for Azure CLI auth
Parser tests (4 new): - --azure-cli flag maps to args.azure_cli=True - --azure-cli --tenant maps both attributes - Absent flag defaults to False - --tenant alone works for other auth modes Bridge tests (2 new): - MsalTokenCredential.get_token returns AccessToken via Azure CLI dispatch - Invalid scope is rejected with ClientAuthenticationError Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 3c7fd91 commit e91c94b

2 files changed

Lines changed: 116 additions & 0 deletions

File tree

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
"""Tests for the MSAL bridge with Azure CLI identity type."""
5+
6+
import time
7+
from unittest.mock import MagicMock, patch
8+
9+
import pytest
10+
11+
from fabric_cli.core import fab_constant as con
12+
from fabric_cli.core.fab_auth import FabAuth
13+
from fabric_cli.core.fab_msal_bridge import MsalTokenCredential
14+
15+
16+
@pytest.fixture(autouse=True)
17+
def temp_dir_fixture(monkeypatch, tmp_path):
18+
"""Isolate FabAuth singleton for bridge tests."""
19+
monkeypatch.setattr(
20+
"fabric_cli.core.fab_state_config.config_location", lambda: str(tmp_path)
21+
)
22+
monkeypatch.delenv("FAB_TOKEN", raising=False)
23+
monkeypatch.delenv("FAB_TOKEN_ONELAKE", raising=False)
24+
monkeypatch.delenv("FAB_TOKEN_AZURE", raising=False)
25+
auth = FabAuth()
26+
auth._azure_cli_token_cache.clear()
27+
auth._cached_az_tenant = None
28+
auth._cached_az_tenant_time = 0.0
29+
auth._auth_info = {}
30+
31+
32+
class TestMsalBridgeAzureCli:
33+
"""Verify MsalTokenCredential works when identity_type is azure_cli."""
34+
35+
@patch("fabric_cli.core.fab_auth.AzureCliCredential")
36+
def test_bridge_returns_access_token_for_azure_cli(
37+
self, mock_credential_class
38+
):
39+
"""MsalTokenCredential.get_token should return an AccessToken via Azure CLI."""
40+
mock_token = MagicMock()
41+
mock_token.token = "bridge-azure-cli-token"
42+
mock_token.expires_on = int(time.time()) + 3600
43+
44+
mock_credential = MagicMock()
45+
mock_credential.get_token.return_value = mock_token
46+
mock_credential_class.return_value = mock_credential
47+
48+
auth = FabAuth()
49+
auth.set_access_mode("azure_cli")
50+
51+
credential = MsalTokenCredential(auth)
52+
result = credential.get_token(con.SCOPE_FABRIC_DEFAULT[0])
53+
54+
assert result.token == "bridge-azure-cli-token"
55+
assert result.expires_on == mock_token.expires_on
56+
57+
@patch("fabric_cli.core.fab_auth.AzureCliCredential")
58+
def test_bridge_rejects_invalid_scope(self, mock_credential_class):
59+
"""MsalTokenCredential should reject scopes not in the allowlist."""
60+
from azure.core.exceptions import ClientAuthenticationError
61+
62+
auth = FabAuth()
63+
auth.set_access_mode("azure_cli")
64+
65+
credential = MsalTokenCredential(auth)
66+
with pytest.raises(ClientAuthenticationError):
67+
credential.get_token("https://evil.example.com/.default")
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
"""Tests for the auth parser module — verifies argparse flag mapping."""
5+
6+
import argparse
7+
8+
from fabric_cli.core.fab_parser_setup import CustomArgumentParser
9+
from fabric_cli.parsers import fab_auth_parser
10+
11+
12+
def _build_auth_parser():
13+
"""Build a parser with auth subcommands registered."""
14+
parser = CustomArgumentParser()
15+
subparsers = parser.add_subparsers(dest="command")
16+
fab_auth_parser.register_parser(subparsers)
17+
return parser
18+
19+
20+
class TestAuthParserAzureCli:
21+
"""Verify --azure-cli flag is parsed correctly."""
22+
23+
def test_azure_cli_flag_sets_attribute(self):
24+
"""--azure-cli should map to args.azure_cli=True."""
25+
parser = _build_auth_parser()
26+
args = parser.parse_args(["auth", "login", "--azure-cli"])
27+
assert args.azure_cli is True
28+
29+
def test_azure_cli_flag_with_tenant(self):
30+
"""--azure-cli --tenant should set both attributes."""
31+
parser = _build_auth_parser()
32+
args = parser.parse_args(
33+
["auth", "login", "--azure-cli", "--tenant", "my-tenant-id"]
34+
)
35+
assert args.azure_cli is True
36+
assert args.tenant == "my-tenant-id"
37+
38+
def test_azure_cli_flag_absent_defaults_false(self):
39+
"""Without --azure-cli, azure_cli should be falsy."""
40+
parser = _build_auth_parser()
41+
args = parser.parse_args(["auth", "login"])
42+
assert not args.azure_cli
43+
44+
def test_tenant_flag_without_azure_cli(self):
45+
"""--tenant alone should work (used by other auth modes)."""
46+
parser = _build_auth_parser()
47+
args = parser.parse_args(["auth", "login", "--tenant", "some-tenant"])
48+
assert args.tenant == "some-tenant"
49+
assert not args.azure_cli

0 commit comments

Comments
 (0)