"""Tests for Microsoft Graph API client."""
import base64
from datetime import datetime
import httpx
import pytest
import respx
from sharepoint_mcp.graph import GraphAPIError, GraphClient
from sharepoint_mcp.models import (
FileContent,
FileListResult,
FileMetadata,
Library,
Site,
)
@pytest.mark.asyncio
@respx.mock
async def test_get_sites(
mock_graph_client: GraphClient,
sample_site_response: dict,
):
"""Test getting list of sites."""
respx.get("https://graph.microsoft.com/v1.0/sites?search=*").mock(
return_value=httpx.Response(200, json=sample_site_response)
)
sites = await mock_graph_client.get_sites()
assert len(sites) == 2
assert isinstance(sites[0], Site)
assert sites[0].id == "site-1"
assert sites[0].name == "Marketing Site"
assert sites[0].url == "https://contoso.sharepoint.com/sites/marketing"
assert sites[0].description == "Marketing team site"
@pytest.mark.asyncio
@respx.mock
async def test_get_sites_with_search(
mock_graph_client: GraphClient,
sample_site_response: dict,
):
"""Test searching for sites."""
respx.get("https://graph.microsoft.com/v1.0/sites?search=Marketing").mock(
return_value=httpx.Response(200, json=sample_site_response)
)
sites = await mock_graph_client.get_sites(search="Marketing")
assert len(sites) == 2
@pytest.mark.asyncio
@respx.mock
async def test_get_libraries(
mock_graph_client: GraphClient,
sample_library_response: dict,
):
"""Test getting libraries for a site."""
respx.get("https://graph.microsoft.com/v1.0/sites/site-1/drives").mock(
return_value=httpx.Response(200, json=sample_library_response)
)
libraries = await mock_graph_client.get_libraries("site-1")
assert len(libraries) == 2
assert isinstance(libraries[0], Library)
assert libraries[0].id == "drive-1"
assert libraries[0].name == "Documents"
assert libraries[0].item_count == 42
@pytest.mark.asyncio
@respx.mock
async def test_list_files(
mock_graph_client: GraphClient,
sample_file_response: dict,
):
"""Test listing files in a library."""
respx.get("https://graph.microsoft.com/v1.0/sites/site-1/drives/drive-1/root/children?$top=50").mock(
return_value=httpx.Response(200, json=sample_file_response)
)
result = await mock_graph_client.list_files("site-1", "drive-1")
assert isinstance(result, FileListResult)
assert len(result.files) == 2
assert isinstance(result.files[0], FileMetadata)
assert result.files[0].id == "file-1"
assert result.files[0].name == "report.pdf"
assert result.files[0].size == 1024000
assert result.next_cursor is not None
@pytest.mark.asyncio
@respx.mock
async def test_list_files_with_folder_path(
mock_graph_client: GraphClient,
sample_file_response: dict,
):
"""Test listing files in a specific folder."""
respx.get(
"https://graph.microsoft.com/v1.0/sites/site-1/drives/drive-1/root:/Projects:/children?$top=50"
).mock(return_value=httpx.Response(200, json=sample_file_response))
result = await mock_graph_client.list_files("site-1", "drive-1", folder_path="Projects")
assert len(result.files) == 2
@pytest.mark.asyncio
@respx.mock
async def test_get_file_metadata(
mock_graph_client: GraphClient,
):
"""Test getting file metadata."""
file_data = {
"id": "file-1",
"name": "document.docx",
"size": 50000,
"webUrl": "https://contoso.sharepoint.com/sites/site/doc.docx",
"createdDateTime": "2025-01-01T10:00:00Z",
"lastModifiedDateTime": "2025-01-05T14:30:00Z",
"file": {"mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
"createdBy": {"user": {"displayName": "John Doe"}},
"lastModifiedBy": {"user": {"displayName": "Jane Smith"}},
}
respx.get("https://graph.microsoft.com/v1.0/sites/site-1/drive/items/file-1").mock(
return_value=httpx.Response(200, json=file_data)
)
metadata = await mock_graph_client.get_file_metadata("site-1", "file-1")
assert isinstance(metadata, FileMetadata)
assert metadata.id == "file-1"
assert metadata.name == "document.docx"
assert metadata.size == 50000
assert metadata.created_by == "John Doe"
assert metadata.modified_by == "Jane Smith"
@pytest.mark.asyncio
@respx.mock
async def test_download_text_file(
mock_graph_client: GraphClient,
):
"""Test downloading a text file."""
# Mock metadata request
file_data = {
"id": "file-1",
"name": "readme.txt",
"size": 100,
"webUrl": "https://contoso.sharepoint.com/readme.txt",
"createdDateTime": "2025-01-01T10:00:00Z",
"lastModifiedDateTime": "2025-01-01T10:00:00Z",
"file": {"mimeType": "text/plain"},
"createdBy": {"user": {"displayName": "Test User"}},
"lastModifiedBy": {"user": {"displayName": "Test User"}},
}
respx.get("https://graph.microsoft.com/v1.0/sites/site-1/drive/items/file-1").mock(
return_value=httpx.Response(200, json=file_data)
)
# Mock content download
file_content = b"Hello, World!\nThis is a test file."
respx.get("https://graph.microsoft.com/v1.0/sites/site-1/drive/items/file-1/content").mock(
return_value=httpx.Response(200, content=file_content)
)
result = await mock_graph_client.download_file("site-1", "file-1")
assert isinstance(result, FileContent)
assert result.name == "readme.txt"
assert result.mime_type == "text/plain"
assert result.is_text is True
assert result.content == "Hello, World!\nThis is a test file."
@pytest.mark.asyncio
@respx.mock
async def test_download_binary_file(
mock_graph_client: GraphClient,
):
"""Test downloading a binary file (returns base64)."""
# Mock metadata request
file_data = {
"id": "file-2",
"name": "image.png",
"size": 1000,
"webUrl": "https://contoso.sharepoint.com/image.png",
"createdDateTime": "2025-01-01T10:00:00Z",
"lastModifiedDateTime": "2025-01-01T10:00:00Z",
"file": {"mimeType": "image/png"},
"createdBy": {"user": {"displayName": "Test User"}},
"lastModifiedBy": {"user": {"displayName": "Test User"}},
}
respx.get("https://graph.microsoft.com/v1.0/sites/site-1/drive/items/file-2").mock(
return_value=httpx.Response(200, json=file_data)
)
# Mock content download
file_content = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR"
respx.get("https://graph.microsoft.com/v1.0/sites/site-1/drive/items/file-2/content").mock(
return_value=httpx.Response(200, content=file_content)
)
result = await mock_graph_client.download_file("site-1", "file-2")
assert isinstance(result, FileContent)
assert result.name == "image.png"
assert result.mime_type == "image/png"
assert result.is_text is False
# Verify content is base64 encoded
decoded = base64.b64decode(result.content)
assert decoded == file_content
@pytest.mark.asyncio
@respx.mock
async def test_download_file_too_large(
mock_graph_client: GraphClient,
):
"""Test that downloading a file > 10MB raises an error."""
file_data = {
"id": "file-3",
"name": "large.zip",
"size": 20 * 1024 * 1024, # 20MB
"webUrl": "https://contoso.sharepoint.com/large.zip",
"createdDateTime": "2025-01-01T10:00:00Z",
"lastModifiedDateTime": "2025-01-01T10:00:00Z",
"file": {"mimeType": "application/zip"},
"createdBy": {"user": {"displayName": "Test User"}},
"lastModifiedBy": {"user": {"displayName": "Test User"}},
}
respx.get("https://graph.microsoft.com/v1.0/sites/site-1/drive/items/file-3").mock(
return_value=httpx.Response(200, json=file_data)
)
with pytest.raises(GraphAPIError, match="File too large"):
await mock_graph_client.download_file("site-1", "file-3")
@pytest.mark.asyncio
@respx.mock
async def test_get_current_user(
mock_graph_client: GraphClient,
sample_user_response: dict,
):
"""Test getting current user information."""
respx.get("https://graph.microsoft.com/v1.0/me").mock(
return_value=httpx.Response(200, json=sample_user_response)
)
user = await mock_graph_client.get_current_user()
assert user["id"] == "user-1"
assert user["displayName"] == "Test User"
assert user["mail"] == "test.user@contoso.com"
@pytest.mark.asyncio
async def test_close_client(mock_graph_client: GraphClient):
"""Test closing the HTTP client."""
# Initialize client by making a request
await mock_graph_client._get_client()
assert mock_graph_client._client is not None
# Close it
await mock_graph_client.close()
assert mock_graph_client._client is None