Skip to content

Commit 9aaf36b

Browse files
committed
move tests into new credential-specific modules
1 parent f5cf659 commit 9aaf36b

6 files changed

Lines changed: 250 additions & 251 deletions

sdk/identity/azure-identity/tests/test_client_secret_credential.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
# Copyright (c) Microsoft Corporation.
33
# Licensed under the MIT License.
44
# ------------------------------------
5+
import time
6+
7+
from azure.core.credentials import AccessToken
58
from azure.core.pipeline.policies import ContentDecodePolicy, SansIOHTTPPolicy
69
from azure.identity import ClientSecretCredential
7-
from helpers import build_aad_response, mock_response
10+
from helpers import build_aad_response, mock_response, Request, validating_transport
811

912
try:
1013
from unittest.mock import Mock
@@ -26,3 +29,65 @@ def test_policies_configurable():
2629
credential.get_token("scope")
2730

2831
assert policy.on_request.called
32+
33+
34+
def test_client_secret_credential():
35+
client_id = "fake-client-id"
36+
secret = "fake-client-secret"
37+
tenant_id = "fake-tenant-id"
38+
access_token = "***"
39+
40+
transport = validating_transport(
41+
requests=[Request(url_substring=tenant_id, required_data={"client_id": client_id, "client_secret": secret})],
42+
responses=[
43+
mock_response(
44+
json_payload={
45+
"token_type": "Bearer",
46+
"expires_in": 42,
47+
"ext_expires_in": 42,
48+
"access_token": access_token,
49+
}
50+
)
51+
],
52+
)
53+
54+
token = ClientSecretCredential(tenant_id, client_id, secret, transport=transport).get_token("scope")
55+
56+
# not validating expires_on because doing so requires monkeypatching time, and this is tested elsewhere
57+
assert token.token == access_token
58+
59+
60+
def test_cache():
61+
expired = "this token's expired"
62+
now = int(time.time())
63+
expired_on = now - 3600
64+
expired_token = AccessToken(expired, expired_on)
65+
token_payload = {
66+
"access_token": expired,
67+
"expires_in": 0,
68+
"ext_expires_in": 0,
69+
"expires_on": expired_on,
70+
"not_before": now,
71+
"token_type": "Bearer",
72+
}
73+
mock_send = Mock(return_value=mock_response(json_payload=token_payload))
74+
scope = "scope"
75+
76+
credential = ClientSecretCredential(
77+
tenant_id="some-guid", client_id="client_id", client_secret="secret", transport=Mock(send=mock_send)
78+
)
79+
80+
# get_token initially returns the expired token because the credential
81+
# doesn't check whether tokens it receives from the service have expired
82+
token = credential.get_token(scope)
83+
assert token == expired_token
84+
85+
access_token = "new token"
86+
token_payload["access_token"] = access_token
87+
token_payload["expires_on"] = now + 3600
88+
valid_token = AccessToken(access_token, now + 3600)
89+
90+
# second call should observe the cached token has expired, and request another
91+
token = credential.get_token(scope)
92+
assert token == valid_token
93+
assert mock_send.call_count == 2

sdk/identity/azure-identity/tests/test_client_secret_credential_async.py

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22
# Copyright (c) Microsoft Corporation.
33
# Licensed under the MIT License.
44
# ------------------------------------
5+
import asyncio
6+
import time
7+
from unittest.mock import Mock
8+
9+
from azure.core.credentials import AccessToken
510
from azure.core.pipeline.policies import ContentDecodePolicy, SansIOHTTPPolicy
611
from azure.identity.aio import ClientSecretCredential
7-
from helpers import build_aad_response, mock_response
12+
from helpers import async_validating_transport, build_aad_response, mock_response, Request
813

914
import pytest
1015

11-
try:
12-
from unittest.mock import Mock
13-
except ImportError: # python < 3.3
14-
from mock import Mock # type: ignore
15-
1616

1717
@pytest.mark.asyncio
1818
async def test_policies_configurable():
@@ -28,3 +28,68 @@ async def send(*_, **__):
2828
await credential.get_token("scope")
2929

3030
assert policy.on_request.called
31+
32+
33+
@pytest.mark.asyncio
34+
async def test_client_secret_credential():
35+
client_id = "fake-client-id"
36+
secret = "fake-client-secret"
37+
tenant_id = "fake-tenant-id"
38+
access_token = "***"
39+
40+
transport = async_validating_transport(
41+
requests=[Request(url_substring=tenant_id, required_data={"client_id": client_id, "client_secret": secret})],
42+
responses=[
43+
mock_response(
44+
json_payload={
45+
"token_type": "Bearer",
46+
"expires_in": 42,
47+
"ext_expires_in": 42,
48+
"access_token": access_token,
49+
}
50+
)
51+
],
52+
)
53+
54+
token = await ClientSecretCredential(
55+
tenant_id=tenant_id, client_id=client_id, client_secret=secret, transport=transport
56+
).get_token("scope")
57+
58+
# not validating expires_on because doing so requires monkeypatching time, and this is tested elsewhere
59+
assert token.token == access_token
60+
61+
62+
@pytest.mark.asyncio
63+
async def test_cache():
64+
expired = "this token's expired"
65+
now = int(time.time())
66+
expired_on = now - 3600
67+
expired_token = AccessToken(expired, expired_on)
68+
token_payload = {
69+
"access_token": expired,
70+
"expires_in": 0,
71+
"ext_expires_in": 0,
72+
"expires_on": expired_on,
73+
"not_before": now,
74+
"token_type": "Bearer",
75+
}
76+
mock_send = Mock(return_value=mock_response(json_payload=token_payload))
77+
transport = Mock(send=asyncio.coroutine(mock_send))
78+
scope = "scope"
79+
80+
credential = ClientSecretCredential("tenant-id", "client-id", "secret", transport=transport)
81+
82+
# get_token initially returns the expired token because the credential
83+
# doesn't check whether tokens it receives from the service have expired
84+
token = await credential.get_token(scope)
85+
assert token == expired_token
86+
87+
access_token = "new token"
88+
token_payload["access_token"] = access_token
89+
token_payload["expires_on"] = now + 3600
90+
valid_token = AccessToken(access_token, now + 3600)
91+
92+
# second call should observe the cached token has expired, and request another
93+
token = await credential.get_token(scope)
94+
assert token == valid_token
95+
assert mock_send.call_count == 2

sdk/identity/azure-identity/tests/test_device_code_credential.py

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22
# Copyright (c) Microsoft Corporation.
33
# Licensed under the MIT License.
44
# ------------------------------------
5-
from azure.core.pipeline.policies import ContentDecodePolicy, SansIOHTTPPolicy
5+
import datetime
6+
7+
from azure.core.exceptions import ClientAuthenticationError
8+
from azure.core.pipeline.policies import SansIOHTTPPolicy
69
from azure.identity import DeviceCodeCredential
10+
import pytest
11+
712
from helpers import build_aad_response, get_discovery_response, mock_response, Request, validating_transport
813

914
try:
@@ -39,3 +44,76 @@ def test_policies_configurable():
3944
credential.get_token("scope")
4045

4146
assert policy.on_request.called
47+
48+
49+
def test_device_code_credential():
50+
expected_token = "access-token"
51+
user_code = "user-code"
52+
verification_uri = "verification-uri"
53+
expires_in = 42
54+
55+
transport = validating_transport(
56+
requests=[Request()] * 3, # not validating requests because they're formed by MSAL
57+
responses=[
58+
# expected requests: discover tenant, start device code flow, poll for completion
59+
mock_response(json_payload={"authorization_endpoint": "https://a/b", "token_endpoint": "https://a/b"}),
60+
mock_response(
61+
json_payload={
62+
"device_code": "_",
63+
"user_code": user_code,
64+
"verification_uri": verification_uri,
65+
"expires_in": expires_in,
66+
}
67+
),
68+
mock_response(
69+
json_payload={
70+
"access_token": expected_token,
71+
"expires_in": expires_in,
72+
"scope": "scope",
73+
"token_type": "Bearer",
74+
"refresh_token": "_",
75+
}
76+
),
77+
],
78+
)
79+
80+
callback = Mock()
81+
credential = DeviceCodeCredential(
82+
client_id="_", prompt_callback=callback, transport=transport, instance_discovery=False
83+
)
84+
85+
now = datetime.datetime.utcnow()
86+
token = credential.get_token("scope")
87+
assert token.token == expected_token
88+
89+
# prompt_callback should have been called as documented
90+
assert callback.call_count == 1
91+
uri, code, expires_on = callback.call_args[0]
92+
assert uri == verification_uri
93+
assert code == user_code
94+
95+
# validating expires_on exactly would require depending on internals of the credential and
96+
# patching time, so we'll be satisfied if expires_on is a datetime at least expires_in
97+
# seconds later than our call to get_token
98+
assert isinstance(expires_on, datetime.datetime)
99+
assert expires_on - now >= datetime.timedelta(seconds=expires_in)
100+
101+
102+
def test_timeout():
103+
transport = validating_transport(
104+
requests=[Request()] * 3, # not validating requests because they're formed by MSAL
105+
responses=[
106+
# expected requests: discover tenant, start device code flow, poll for completion
107+
mock_response(json_payload={"authorization_endpoint": "https://a/b", "token_endpoint": "https://a/b"}),
108+
mock_response(json_payload={"device_code": "_", "user_code": "_", "verification_uri": "_"}),
109+
mock_response(json_payload={"error": "authorization_pending"}),
110+
],
111+
)
112+
113+
credential = DeviceCodeCredential(
114+
client_id="_", prompt_callback=Mock(), transport=transport, timeout=0.01, instance_discovery=False
115+
)
116+
117+
with pytest.raises(ClientAuthenticationError) as ex:
118+
credential.get_token("scope")
119+
assert "timed out" in ex.value.message.lower()

0 commit comments

Comments
 (0)