Skip to content

Commit f1a52b6

Browse files
committed
Add tests around MyAccountClient
1 parent e7c65d1 commit f1a52b6

File tree

4 files changed

+203
-15
lines changed

4 files changed

+203
-15
lines changed

src/auth0_server_python/auth_server/my_account_client.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from auth0_server_python.error import (
1313
ApiError,
14+
MyAccountApiError,
1415
)
1516

1617
class MyAccountClient:
@@ -30,16 +31,18 @@ async def connect_account(
3031
async with httpx.AsyncClient() as client:
3132
response = await client.post(
3233
url=f"{self.audienceIdentifier}v1/connected-accounts/connect",
33-
data=request.model_dump_json(exclude_none=True),
34+
json=request.model_dump(exclude_none=True),
3435
auth=BearerAuth(access_token)
3536
)
3637

3738
if response.status_code != 201:
3839
error_data = response.json()
39-
raise ApiError(
40-
error_data.get("error", "connect_account_error"),
41-
error_data.get(
42-
"error_description", "Connected Accounts connect request failed")
40+
raise MyAccountApiError(
41+
title=error_data.get("title"),
42+
type=error_data.get("type"),
43+
detail=error_data.get("detail"),
44+
status=error_data.get("status"),
45+
validation_errors=error_data.get("validation_errors", None)
4346
)
4447

4548
data = response.json()
@@ -54,7 +57,7 @@ async def connect_account(
5457
)
5558

5659
except Exception as e:
57-
if isinstance(e, ApiError):
60+
if isinstance(e, MyAccountApiError):
5861
raise
5962
raise ApiError(
6063
"connect_account_error",
@@ -71,16 +74,18 @@ async def complete_connect_account(
7174
async with httpx.AsyncClient() as client:
7275
response = await client.post(
7376
url=f"{self.audienceIdentifier}v1/connected-accounts/complete",
74-
data=request.model_dump_json(exclude_none=True),
77+
json=request.model_dump(exclude_none=True),
7578
auth=BearerAuth(access_token)
7679
)
7780

7881
if response.status_code != 201:
7982
error_data = response.json()
80-
raise ApiError(
81-
error_data.get("error", "connect_account_error"),
82-
error_data.get(
83-
"error_description", "Connected Accounts complete request failed")
83+
raise MyAccountApiError(
84+
title=error_data.get("title"),
85+
type=error_data.get("type"),
86+
detail=error_data.get("detail"),
87+
status=error_data.get("status"),
88+
validation_errors=error_data.get("validation_errors")
8489
)
8590

8691
data = response.json()
@@ -94,7 +99,7 @@ async def complete_connect_account(
9499
)
95100

96101
except Exception as e:
97-
if isinstance(e, ApiError):
102+
if isinstance(e, MyAccountApiError):
98103
raise
99104
raise ApiError(
100105
"connect_account_error",

src/auth0_server_python/error/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,26 @@ def __init__(self, code: str, message: str, interval: Optional[int], cause=None)
5656
super().__init__(code, message, cause)
5757
self.interval = interval
5858

59+
class MyAccountApiError(Auth0Error):
60+
"""
61+
Error raised when an API request to My Account API fails.
62+
Contains details about the original error from Auth0.
63+
"""
64+
65+
def __init__(
66+
self,
67+
title: str,
68+
type: str,
69+
detail: str,
70+
status: int,
71+
validation_errors: Optional[list[dict[str, str]]] = None
72+
):
73+
super().__init__(detail)
74+
self.title = title
75+
self.type = type
76+
self.detail = detail
77+
self.status = status
78+
self.validation_errors = validation_errors
5979

6080
class AccessTokenError(Auth0Error):
6181
"""Error raised when there's an issue with access tokens."""
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import pytest
2+
import json
3+
from unittest.mock import AsyncMock, MagicMock
4+
from unittest.mock import ANY
5+
from auth0_server_python.auth_server.my_account_client import MyAccountClient
6+
from auth0_server_python.auth_types import (
7+
ConnectAccountRequest,
8+
ConnectAccountResponse,
9+
CompleteConnectAccountRequest,
10+
CompleteConnectAccountResponse,
11+
ConnectParams
12+
)
13+
from auth0_server_python.error import (
14+
MyAccountApiError,
15+
ApiError
16+
)
17+
18+
@pytest.mark.asyncio
19+
async def test_connect_account_success(mocker):
20+
# Arrange
21+
client = MyAccountClient(domain="auth0.local")
22+
response = AsyncMock()
23+
response.status_code = 201
24+
response.json = MagicMock(return_value={
25+
"connect_uri": "https://auth0.local/connect",
26+
"auth_session": "<auth_session>",
27+
"connect_params": {"ticket": "<auth_ticket>"},
28+
"expires_in": 3600
29+
})
30+
31+
mock_post = mocker.patch("httpx.AsyncClient.post", new_callable=AsyncMock, return_value=response)
32+
request = ConnectAccountRequest(
33+
connection="<connection>",
34+
redirect_uri="<redirect_uri>",
35+
state="<state_xyz>",
36+
code_challenge="<code_challenge>",
37+
code_challenge_method="S256"
38+
)
39+
40+
# Act
41+
result = await client.connect_account(access_token="<access_token>", request=request)
42+
43+
# Assert
44+
mock_post.assert_awaited_with(
45+
url="https://auth0.local/me/v1/connected-accounts/connect",
46+
json={
47+
"connection": "<connection>",
48+
"redirect_uri": "<redirect_uri>",
49+
"state": "<state_xyz>",
50+
"code_challenge": "<code_challenge>",
51+
"code_challenge_method": "S256",
52+
},
53+
auth=ANY
54+
)
55+
assert result == ConnectAccountResponse(
56+
connect_uri="https://auth0.local/connect",
57+
auth_session="<auth_session>",
58+
connect_params=ConnectParams(ticket="<auth_ticket>"),
59+
expires_in=3600
60+
)
61+
62+
@pytest.mark.asyncio
63+
async def test_connect_account_api_response_failure(mocker):
64+
# Arrange
65+
client = MyAccountClient(domain="auth0.local")
66+
response = AsyncMock()
67+
response.status_code = 401
68+
response.json = MagicMock(return_value={
69+
"title": "Invalid Token",
70+
"type": "https://auth0.com/api-errors/A0E-401-0003",
71+
"detail": "Invalid Token",
72+
"status": 401
73+
})
74+
75+
mock_post = mocker.patch("httpx.AsyncClient.post", new_callable=AsyncMock, return_value=response)
76+
request = ConnectAccountRequest(
77+
connection="<connection>",
78+
redirect_uri="<redirect_uri>",
79+
state="<state_xyz>",
80+
code_challenge="<code_challenge>",
81+
code_challenge_method="S256"
82+
)
83+
84+
# Act
85+
86+
with pytest.raises(MyAccountApiError) as exc:
87+
await client.connect_account(access_token="<access_token>", request=request)
88+
89+
# Assert
90+
mock_post.assert_awaited_once()
91+
assert "Invalid Token" in str(exc.value)
92+
93+
94+
@pytest.mark.asyncio
95+
async def test_complete_connect_account_success(mocker):
96+
# Arrange
97+
client = MyAccountClient(domain="auth0.local")
98+
response = AsyncMock()
99+
response.status_code = 201
100+
response.json = MagicMock(return_value={
101+
"id": "<id>",
102+
"connection": "<connection>",
103+
"access_type": "<access_type>",
104+
"scopes": ["<some_scope>"],
105+
"created_at": "<created_at>",
106+
})
107+
108+
mock_post = mocker.patch("httpx.AsyncClient.post", new_callable=AsyncMock, return_value=response)
109+
request = CompleteConnectAccountRequest(
110+
auth_session="<auth_session>",
111+
connect_code="<connect_code>",
112+
redirect_uri="<redirect_uri>",
113+
)
114+
115+
# Act
116+
result = await client.complete_connect_account(access_token="<access_token>", request=request)
117+
118+
# Assert
119+
mock_post.assert_awaited_with(
120+
url="https://auth0.local/me/v1/connected-accounts/complete",
121+
json={
122+
"auth_session": "<auth_session>",
123+
"connect_code": "<connect_code>",
124+
"redirect_uri": "<redirect_uri>"
125+
},
126+
auth=ANY
127+
)
128+
assert result == CompleteConnectAccountResponse(
129+
id="<id>",
130+
connection="<connection>",
131+
access_type="<access_type>",
132+
scopes=["<some_scope>"],
133+
created_at="<created_at>",
134+
)
135+
136+
@pytest.mark.asyncio
137+
async def test_complete_connect_account_api_response_failure(mocker):
138+
# Arrange
139+
client = MyAccountClient(domain="auth0.local")
140+
response = AsyncMock()
141+
response.status_code = 401
142+
response.json = MagicMock(return_value={
143+
"title": "Invalid Token",
144+
"type": "https://auth0.com/api-errors/A0E-401-0003",
145+
"detail": "Invalid Token",
146+
"status": 401
147+
})
148+
149+
mock_post = mocker.patch("httpx.AsyncClient.post", new_callable=AsyncMock, return_value=response)
150+
request = CompleteConnectAccountRequest(
151+
auth_session="<auth_session>",
152+
connect_code="<connect_code>",
153+
redirect_uri="<redirect_uri>",
154+
)
155+
156+
# Act
157+
158+
with pytest.raises(MyAccountApiError) as exc:
159+
await client.complete_connect_account(access_token="<access_token>", request=request)
160+
161+
# Assert
162+
mock_post.assert_awaited_once()
163+
assert "Invalid Token" in str(exc.value)

src/auth0_server_python/tests/test_server_client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,10 +1404,10 @@ async def test_start_connect_account_no_redirect_uri(mocker):
14041404
# Act
14051405
with pytest.raises(MissingRequiredArgumentError) as exc:
14061406
await client.start_connect_account(
1407-
options=ConnectAccountOptions(
1408-
connection="<connection>"
1407+
options=ConnectAccountOptions(
1408+
connection="<connection>"
1409+
)
14091410
)
1410-
)
14111411

14121412
# Assert
14131413
assert "redirect_uri" in str(exc.value)

0 commit comments

Comments
 (0)