test: continue working on oauth client
This commit is contained in:
@@ -104,7 +104,7 @@ class NextcloudClient:
|
|||||||
|
|
||||||
async def capabilities(self):
|
async def capabilities(self):
|
||||||
response = await self._client.get(
|
response = await self._client.get(
|
||||||
"/ocs/v2.php/apps/notifications/api/v2/notifications",
|
"/ocs/v2.php/cloud/capabilities",
|
||||||
headers={"OCS-APIRequest": "true", "Accept": "application/json"},
|
headers={"OCS-APIRequest": "true", "Accept": "application/json"},
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|||||||
+19
-24
@@ -556,7 +556,9 @@ async def oauth_token() -> str:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
async def nc_oauth_client(oauth_token: str) -> AsyncGenerator[NextcloudClient, Any]:
|
async def nc_oauth_client(
|
||||||
|
interactive_oauth_token: str,
|
||||||
|
) -> AsyncGenerator[NextcloudClient, Any]:
|
||||||
"""
|
"""
|
||||||
Fixture to create a NextcloudClient instance using OAuth authentication.
|
Fixture to create a NextcloudClient instance using OAuth authentication.
|
||||||
Uses the oauth_token fixture to get an access token.
|
Uses the oauth_token fixture to get an access token.
|
||||||
@@ -570,7 +572,7 @@ async def nc_oauth_client(oauth_token: str) -> AsyncGenerator[NextcloudClient, A
|
|||||||
logger.info(f"Creating OAuth NextcloudClient for user: {username}")
|
logger.info(f"Creating OAuth NextcloudClient for user: {username}")
|
||||||
client = NextcloudClient.from_token(
|
client = NextcloudClient.from_token(
|
||||||
base_url=nextcloud_host,
|
base_url=nextcloud_host,
|
||||||
token=oauth_token,
|
token=interactive_oauth_token,
|
||||||
username=username,
|
username=username,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -587,19 +589,22 @@ async def nc_oauth_client(oauth_token: str) -> AsyncGenerator[NextcloudClient, A
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
async def nc_mcp_oauth_client_interactive() -> AsyncGenerator[ClientSession, Any]:
|
async def interactive_oauth_token() -> str:
|
||||||
"""
|
"""
|
||||||
Fixture to create an MCP client session for interactive OAuth integration tests.
|
Fixture to obtain an OAuth access token for integration tests.
|
||||||
Performs an interactive OAuth flow to obtain an access token.
|
|
||||||
|
This uses the interactive OAuth flow to get a token.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
import threading
|
import threading
|
||||||
from urllib.parse import urlparse, parse_qs
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
auth_code = None
|
auth_code = None
|
||||||
|
httpd = None
|
||||||
|
server_thread = None
|
||||||
|
|
||||||
class OAuthCallbackHandler(BaseHTTPRequestHandler):
|
class OAuthCallbackHandler(BaseHTTPRequestHandler):
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
@@ -639,7 +644,6 @@ async def nc_mcp_oauth_client_interactive() -> AsyncGenerator[ClientSession, Any
|
|||||||
token_endpoint = oidc_config.get("token_endpoint")
|
token_endpoint = oidc_config.get("token_endpoint")
|
||||||
registration_endpoint = oidc_config.get("registration_endpoint")
|
registration_endpoint = oidc_config.get("registration_endpoint")
|
||||||
authorization_endpoint = oidc_config.get("authorization_endpoint")
|
authorization_endpoint = oidc_config.get("authorization_endpoint")
|
||||||
|
|
||||||
client_info = await load_or_register_client(
|
client_info = await load_or_register_client(
|
||||||
nextcloud_url=nextcloud_host,
|
nextcloud_url=nextcloud_host,
|
||||||
registration_endpoint=registration_endpoint,
|
registration_endpoint=registration_endpoint,
|
||||||
@@ -650,7 +654,6 @@ async def nc_mcp_oauth_client_interactive() -> AsyncGenerator[ClientSession, Any
|
|||||||
|
|
||||||
auth_url = f"{authorization_endpoint}?response_type=code&client_id={client_info.client_id}&redirect_uri=http://localhost:8081&scope=openid%20profile%20email"
|
auth_url = f"{authorization_endpoint}?response_type=code&client_id={client_info.client_id}&redirect_uri=http://localhost:8081&scope=openid%20profile%20email"
|
||||||
webbrowser.open(auth_url)
|
webbrowser.open(auth_url)
|
||||||
|
|
||||||
while not auth_code:
|
while not auth_code:
|
||||||
logger.info("Sleeping until auth_code available")
|
logger.info("Sleeping until auth_code available")
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
@@ -667,23 +670,15 @@ async def nc_mcp_oauth_client_interactive() -> AsyncGenerator[ClientSession, Any
|
|||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Token response: {token_response.text}")
|
logger.info(f"Token response: {token_response.text}")
|
||||||
|
|
||||||
# Shut down the server
|
|
||||||
token_data = token_response.json()
|
token_data = token_response.json()
|
||||||
logger.info(f"Token data: {token_data}")
|
logger.info(f"Token data: {token_data}")
|
||||||
access_token = token_data.get("access_token")
|
access_token = token_data.get("access_token")
|
||||||
|
|
||||||
headers = {"Authorization": f"Bearer {access_token}"}
|
# Shut down the server
|
||||||
logger.info(f"Headers: {headers}")
|
|
||||||
async with streamablehttp_client("http://127.0.0.1:8001/mcp", headers=headers) as (
|
await http_client.get("http://localhost:8081/shutdown")
|
||||||
read_stream,
|
if httpd:
|
||||||
write_stream,
|
httpd.server_close()
|
||||||
_,
|
if server_thread:
|
||||||
):
|
server_thread.join(timeout=1)
|
||||||
async with ClientSession(read_stream, write_stream) as session:
|
return access_token
|
||||||
await session.initialize()
|
|
||||||
try:
|
|
||||||
yield session
|
|
||||||
finally:
|
|
||||||
# Shut down the server
|
|
||||||
await http_client.get("http://localhost:8081/shutdown")
|
|
||||||
|
|||||||
@@ -12,21 +12,30 @@ pytestmark = [pytest.mark.integration, pytest.mark.interactive]
|
|||||||
class TestOAuthInteractive:
|
class TestOAuthInteractive:
|
||||||
"""Test interactive OAuth authentication."""
|
"""Test interactive OAuth authentication."""
|
||||||
|
|
||||||
async def test_mcp_oauth_tool_execution_interactive(
|
async def test_oauth_client_with_interactive_flow(self, nc_oauth_client):
|
||||||
self, nc_mcp_oauth_client_interactive
|
"""Test that OAuth client created via interactive flow can access Nextcloud APIs."""
|
||||||
):
|
# Test 1: Check capabilities
|
||||||
"""Test executing a tool on the OAuth-enabled MCP server with an interactive token."""
|
capabilities = await nc_oauth_client.capabilities()
|
||||||
# Example: Execute the 'nc_notes_list' tool
|
assert capabilities is not None
|
||||||
result = await nc_mcp_oauth_client_interactive.call_tool("nc_tables_list")
|
logger.info("OAuth client (interactive) successfully fetched capabilities")
|
||||||
|
|
||||||
assert result.isError is False, f"Tool execution failed: {result.content}"
|
|
||||||
assert result.content is not None
|
|
||||||
import json
|
|
||||||
|
|
||||||
notes_list = json.loads(result.content[0].text)
|
|
||||||
|
|
||||||
assert isinstance(notes_list, list)
|
|
||||||
|
|
||||||
|
# Test 2: List notes
|
||||||
|
notes = await nc_oauth_client.notes.get_all_notes()
|
||||||
|
assert isinstance(notes, list)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Successfully executed 'nc_notes_list' tool on OAuth MCP server and got {len(notes_list)} notes."
|
f"OAuth client (interactive) successfully listed {len(notes)} notes"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Test 3: Create and delete a note
|
||||||
|
test_note = await nc_oauth_client.notes.create_note(
|
||||||
|
title="OAuth Interactive Test Note",
|
||||||
|
content="This note was created during OAuth interactive testing",
|
||||||
|
)
|
||||||
|
assert test_note is not None
|
||||||
|
assert test_note.get("id") is not None
|
||||||
|
note_id = test_note["id"]
|
||||||
|
logger.info(f"OAuth client (interactive) successfully created note {note_id}")
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
await nc_oauth_client.notes.delete_note(note_id=note_id)
|
||||||
|
logger.info(f"OAuth client (interactive) successfully deleted note {note_id}")
|
||||||
|
|||||||
Reference in New Issue
Block a user