Zendesk who_am_i tool. --------- Co-authored-by: Francisco Liberal <francisco@arcade.dev>
330 lines
12 KiB
Python
330 lines
12 KiB
Python
from unittest.mock import Mock, patch
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from arcade_zendesk.who_am_i_util import (
|
|
WhoAmIResponse,
|
|
_extract_organization_info,
|
|
_extract_user_info,
|
|
_get_current_user,
|
|
_get_organization_info,
|
|
build_who_am_i_response,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_context():
|
|
"""Create a mock ToolContext for testing."""
|
|
context = Mock()
|
|
context.get_secret.return_value = "test-subdomain"
|
|
context.get_auth_token_or_empty.return_value = "test-token"
|
|
return context
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_user_data():
|
|
"""Sample user data from Zendesk API."""
|
|
return {
|
|
"id": 12345,
|
|
"name": "John Doe",
|
|
"email": "john.doe@example.com",
|
|
"role": "admin",
|
|
"active": True,
|
|
"verified": True,
|
|
"locale": "en-US",
|
|
"time_zone": "America/New_York",
|
|
"organization_id": 67890,
|
|
"created_at": "2023-01-01T00:00:00Z",
|
|
"updated_at": "2023-12-01T00:00:00Z",
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_organization_data():
|
|
"""Sample organization data from Zendesk API."""
|
|
return {
|
|
"id": 67890,
|
|
"name": "Example Corp",
|
|
"domain_names": ["example.com", "example.org"],
|
|
"created_at": "2022-01-01T00:00:00Z",
|
|
"updated_at": "2023-11-01T00:00:00Z",
|
|
"details": "Main organization",
|
|
"notes": "Primary customer",
|
|
"group_id": 123,
|
|
"shared_tickets": True,
|
|
"shared_comments": False,
|
|
}
|
|
|
|
|
|
class TestBuildWhoAmIResponse:
|
|
"""Test the main build_who_am_i_response function."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_build_complete_response(
|
|
self, mock_context, sample_user_data, sample_organization_data
|
|
):
|
|
"""Test building a complete who am I response."""
|
|
with (
|
|
patch("arcade_zendesk.who_am_i_util._get_current_user") as mock_get_user,
|
|
patch("arcade_zendesk.who_am_i_util._get_organization_info") as mock_get_org,
|
|
):
|
|
mock_get_user.return_value = sample_user_data
|
|
mock_get_org.return_value = sample_organization_data
|
|
|
|
result = await build_who_am_i_response(mock_context)
|
|
|
|
assert isinstance(result, dict)
|
|
assert result["user_id"] == 12345
|
|
assert result["name"] == "John Doe"
|
|
assert result["email"] == "john.doe@example.com"
|
|
assert result["role"] == "admin"
|
|
assert result["active"] is True
|
|
assert result["verified"] is True
|
|
assert result["locale"] == "en-US"
|
|
assert result["time_zone"] == "America/New_York"
|
|
assert result["organization_id"] == 67890
|
|
assert result["organization_name"] == "Example Corp"
|
|
assert result["organization_domains"] == ["example.com", "example.org"]
|
|
assert result["zendesk_access"] is True
|
|
|
|
mock_get_user.assert_called_once_with(mock_context)
|
|
mock_get_org.assert_called_once_with(mock_context, 67890)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_build_response_without_organization(self, mock_context, sample_user_data):
|
|
"""Test building response when user has no organization."""
|
|
user_data_no_org = sample_user_data.copy()
|
|
del user_data_no_org["organization_id"]
|
|
|
|
with (
|
|
patch("arcade_zendesk.who_am_i_util._get_current_user") as mock_get_user,
|
|
patch("arcade_zendesk.who_am_i_util._get_organization_info") as mock_get_org,
|
|
):
|
|
mock_get_user.return_value = user_data_no_org
|
|
mock_get_org.return_value = {}
|
|
|
|
result = await build_who_am_i_response(mock_context)
|
|
|
|
assert result["user_id"] == 12345
|
|
assert result["name"] == "John Doe"
|
|
assert result["zendesk_access"] is True
|
|
assert "organization_name" not in result
|
|
assert "organization_domains" not in result
|
|
|
|
mock_get_org.assert_called_once_with(mock_context, None)
|
|
|
|
|
|
class TestGetCurrentUser:
|
|
"""Test the _get_current_user function."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_current_user_success(self, mock_context, sample_user_data):
|
|
"""Test successful user retrieval."""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {"user": sample_user_data}
|
|
mock_response.raise_for_status.return_value = None
|
|
|
|
with patch("httpx.AsyncClient") as mock_client:
|
|
mock_client.return_value.__aenter__.return_value.get.return_value = mock_response
|
|
|
|
result = await _get_current_user(mock_context)
|
|
|
|
assert result == sample_user_data
|
|
mock_client.return_value.__aenter__.return_value.get.assert_called_once_with(
|
|
"https://test-subdomain.zendesk.com/api/v2/users/me",
|
|
headers={
|
|
"Authorization": "Bearer test-token",
|
|
"Content-Type": "application/json",
|
|
},
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_current_user_http_error(self, mock_context):
|
|
"""Test user retrieval with HTTP error."""
|
|
with patch("httpx.AsyncClient") as mock_client:
|
|
mock_client.return_value.__aenter__.return_value.get.side_effect = (
|
|
httpx.HTTPStatusError("404 Not Found", request=Mock(), response=Mock())
|
|
)
|
|
|
|
with pytest.raises(httpx.HTTPStatusError):
|
|
await _get_current_user(mock_context)
|
|
|
|
|
|
class TestGetOrganizationInfo:
|
|
"""Test the _get_organization_info function."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_organization_info_success(self, mock_context, sample_organization_data):
|
|
"""Test successful organization retrieval."""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {"organization": sample_organization_data}
|
|
mock_response.raise_for_status.return_value = None
|
|
|
|
with patch("httpx.AsyncClient") as mock_client:
|
|
mock_client.return_value.__aenter__.return_value.get.return_value = mock_response
|
|
|
|
result = await _get_organization_info(mock_context, 67890)
|
|
|
|
assert result == sample_organization_data
|
|
mock_client.return_value.__aenter__.return_value.get.assert_called_once_with(
|
|
"https://test-subdomain.zendesk.com/api/v2/organizations/67890",
|
|
headers={
|
|
"Authorization": "Bearer test-token",
|
|
"Content-Type": "application/json",
|
|
},
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_organization_info_no_id(self, mock_context):
|
|
"""Test organization retrieval with no organization ID."""
|
|
result = await _get_organization_info(mock_context, None)
|
|
assert result == {}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_organization_info_http_error(self, mock_context):
|
|
"""Test organization retrieval with HTTP error."""
|
|
with patch("httpx.AsyncClient") as mock_client:
|
|
mock_client.return_value.__aenter__.return_value.get.side_effect = (
|
|
httpx.HTTPStatusError("404 Not Found", request=Mock(), response=Mock())
|
|
)
|
|
|
|
with pytest.raises(httpx.HTTPStatusError):
|
|
await _get_organization_info(mock_context, 67890)
|
|
|
|
|
|
class TestExtractUserInfo:
|
|
"""Test the _extract_user_info function."""
|
|
|
|
def test_extract_complete_user_info(self, sample_user_data):
|
|
"""Test extracting complete user information."""
|
|
result = _extract_user_info(sample_user_data)
|
|
|
|
expected = {
|
|
"user_id": 12345,
|
|
"name": "John Doe",
|
|
"email": "john.doe@example.com",
|
|
"role": "admin",
|
|
"active": True,
|
|
"verified": True,
|
|
"locale": "en-US",
|
|
"time_zone": "America/New_York",
|
|
"organization_id": 67890,
|
|
}
|
|
|
|
assert result == expected
|
|
|
|
def test_extract_partial_user_info(self):
|
|
"""Test extracting partial user information."""
|
|
partial_data = {
|
|
"id": 12345,
|
|
"name": "John Doe",
|
|
"email": "john.doe@example.com",
|
|
}
|
|
|
|
result = _extract_user_info(partial_data)
|
|
|
|
expected = {
|
|
"user_id": 12345,
|
|
"name": "John Doe",
|
|
"email": "john.doe@example.com",
|
|
}
|
|
|
|
assert result == expected
|
|
|
|
def test_extract_empty_user_info(self):
|
|
"""Test extracting from empty user data."""
|
|
result = _extract_user_info({})
|
|
assert result == {}
|
|
|
|
@pytest.mark.parametrize(
|
|
"field,value,expected_key",
|
|
[
|
|
("active", False, "active"),
|
|
("verified", False, "verified"),
|
|
("active", True, "active"),
|
|
("verified", True, "verified"),
|
|
],
|
|
)
|
|
def test_extract_boolean_fields(self, field, value, expected_key):
|
|
"""Test extracting boolean fields correctly."""
|
|
user_data = {field: value}
|
|
result = _extract_user_info(user_data)
|
|
assert result[expected_key] == value
|
|
|
|
|
|
class TestExtractOrganizationInfo:
|
|
"""Test the _extract_organization_info function."""
|
|
|
|
def test_extract_complete_organization_info(self, sample_organization_data):
|
|
"""Test extracting complete organization information."""
|
|
result = _extract_organization_info(sample_organization_data)
|
|
|
|
assert result["organization_name"] == "Example Corp"
|
|
assert result["organization_domains"] == ["example.com", "example.org"]
|
|
|
|
def test_extract_organization_info_no_domains(self):
|
|
"""Test extracting organization info without domain names."""
|
|
org_data = {
|
|
"name": "Example Corp",
|
|
"domain_names": [],
|
|
}
|
|
|
|
result = _extract_organization_info(org_data)
|
|
|
|
assert result["organization_name"] == "Example Corp"
|
|
assert "organization_domains" not in result
|
|
|
|
def test_extract_organization_info_multiple_domains(self):
|
|
"""Test extracting organization info with multiple domains."""
|
|
org_data = {
|
|
"name": "Example Corp",
|
|
"domain_names": ["primary.com", "secondary.com", "tertiary.com"],
|
|
}
|
|
|
|
result = _extract_organization_info(org_data)
|
|
|
|
assert result["organization_name"] == "Example Corp"
|
|
assert result["organization_domains"] == ["primary.com", "secondary.com", "tertiary.com"]
|
|
|
|
def test_extract_empty_organization_info(self):
|
|
"""Test extracting from empty organization data."""
|
|
result = _extract_organization_info({})
|
|
assert "organization_name" not in result
|
|
assert "organization_domains" not in result
|
|
assert result == {}
|
|
|
|
|
|
class TestWhoAmIResponseType:
|
|
"""Test the WhoAmIResponse TypedDict."""
|
|
|
|
def test_typed_dict_structure(self):
|
|
"""Test that WhoAmIResponse accepts expected fields."""
|
|
response: WhoAmIResponse = {
|
|
"user_id": 12345,
|
|
"name": "John Doe",
|
|
"email": "john.doe@example.com",
|
|
"role": "admin",
|
|
"active": True,
|
|
"verified": True,
|
|
"locale": "en-US",
|
|
"time_zone": "America/New_York",
|
|
"organization_id": 67890,
|
|
"organization_name": "Example Corp",
|
|
"organization_domains": ["example.com", "example.org"],
|
|
"zendesk_access": True,
|
|
}
|
|
|
|
assert response["user_id"] == 12345
|
|
assert response["zendesk_access"] is True
|
|
|
|
def test_typed_dict_partial(self):
|
|
"""Test that WhoAmIResponse works with partial data."""
|
|
response: WhoAmIResponse = {
|
|
"user_id": 12345,
|
|
"name": "John Doe",
|
|
"zendesk_access": True,
|
|
}
|
|
|
|
assert response["user_id"] == 12345
|
|
assert response["zendesk_access"] is True
|