test: Remove unused pytest fixtures
This commit is contained in:
@@ -62,4 +62,4 @@ jobs:
|
|||||||
NEXTCLOUD_USERNAME: "admin"
|
NEXTCLOUD_USERNAME: "admin"
|
||||||
NEXTCLOUD_PASSWORD: "admin"
|
NEXTCLOUD_PASSWORD: "admin"
|
||||||
run: |
|
run: |
|
||||||
uv run pytest -v --browser firefox -k oauth
|
uv run pytest -v --browser firefox
|
||||||
|
|||||||
@@ -1,35 +1,30 @@
|
|||||||
import click
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import uvicorn
|
|
||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncIterator
|
||||||
from contextlib import asynccontextmanager, AsyncExitStack
|
from contextlib import AsyncExitStack, asynccontextmanager
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import click
|
||||||
|
import uvicorn
|
||||||
|
from mcp.server.auth.settings import AuthSettings
|
||||||
|
from mcp.server.fastmcp import Context, FastMCP
|
||||||
from pydantic import AnyHttpUrl
|
from pydantic import AnyHttpUrl
|
||||||
from starlette.applications import Starlette
|
from starlette.applications import Starlette
|
||||||
from starlette.routing import Mount
|
from starlette.routing import Mount
|
||||||
|
|
||||||
from mcp.server.fastmcp import Context, FastMCP
|
from nextcloud_mcp_server.auth import NextcloudTokenVerifier, load_or_register_client
|
||||||
from mcp.server.auth.settings import AuthSettings
|
|
||||||
|
|
||||||
from nextcloud_mcp_server.config import setup_logging
|
|
||||||
from nextcloud_mcp_server.client import NextcloudClient
|
from nextcloud_mcp_server.client import NextcloudClient
|
||||||
|
from nextcloud_mcp_server.config import setup_logging
|
||||||
from nextcloud_mcp_server.context import get_client as get_nextcloud_client
|
from nextcloud_mcp_server.context import get_client as get_nextcloud_client
|
||||||
from nextcloud_mcp_server.auth import (
|
|
||||||
NextcloudTokenVerifier,
|
|
||||||
load_or_register_client,
|
|
||||||
)
|
|
||||||
from nextcloud_mcp_server.server import (
|
from nextcloud_mcp_server.server import (
|
||||||
configure_calendar_tools,
|
configure_calendar_tools,
|
||||||
configure_contacts_tools,
|
configure_contacts_tools,
|
||||||
|
configure_deck_tools,
|
||||||
configure_notes_tools,
|
configure_notes_tools,
|
||||||
configure_tables_tools,
|
configure_tables_tools,
|
||||||
configure_webdav_tools,
|
configure_webdav_tools,
|
||||||
configure_deck_tools,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from mcp.server.fastmcp import Context
|
|
||||||
from mcp.server.auth.provider import AccessToken
|
from mcp.server.auth.provider import AccessToken
|
||||||
|
from mcp.server.fastmcp import Context
|
||||||
|
|
||||||
from ..client import NextcloudClient
|
from ..client import NextcloudClient
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import logging
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from httpx import (
|
from httpx import (
|
||||||
|
AsyncBaseTransport,
|
||||||
AsyncClient,
|
AsyncClient,
|
||||||
|
AsyncHTTPTransport,
|
||||||
Auth,
|
Auth,
|
||||||
BasicAuth,
|
BasicAuth,
|
||||||
Request,
|
Request,
|
||||||
Response,
|
Response,
|
||||||
AsyncBaseTransport,
|
|
||||||
AsyncHTTPTransport,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..controllers.notes_search import NotesSearchController
|
from ..controllers.notes_search import NotesSearchController
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"""Base client for Nextcloud operations with shared authentication."""
|
"""Base client for Nextcloud operations with shared authentication."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from abc import ABC
|
|
||||||
|
|
||||||
from functools import wraps
|
|
||||||
import time
|
import time
|
||||||
from httpx import HTTPStatusError, codes, RequestError, AsyncClient
|
from abc import ABC
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from httpx import AsyncClient, HTTPStatusError, RequestError, codes
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
"""CardDAV client for NextCloud contacts operations."""
|
"""CardDAV client for NextCloud contacts operations."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from .base import BaseNextcloudClient
|
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
from pythonvCard4.vcard import Contact
|
from pythonvCard4.vcard import Contact
|
||||||
|
|
||||||
|
from .base import BaseNextcloudClient
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
from typing import List, Optional, Dict, Any
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from nextcloud_mcp_server.client.base import BaseNextcloudClient
|
from nextcloud_mcp_server.client.base import BaseNextcloudClient
|
||||||
from nextcloud_mcp_server.models.deck import (
|
from nextcloud_mcp_server.models.deck import (
|
||||||
DeckBoard,
|
|
||||||
DeckStack,
|
|
||||||
DeckCard,
|
|
||||||
DeckLabel,
|
|
||||||
DeckACL,
|
DeckACL,
|
||||||
DeckAttachment,
|
DeckAttachment,
|
||||||
|
DeckBoard,
|
||||||
|
DeckCard,
|
||||||
DeckComment,
|
DeckComment,
|
||||||
DeckSession,
|
|
||||||
DeckConfig,
|
DeckConfig,
|
||||||
|
DeckLabel,
|
||||||
|
DeckSession,
|
||||||
|
DeckStack,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +1,25 @@
|
|||||||
"""Pydantic models for structured MCP server responses."""
|
"""Pydantic models for structured MCP server responses."""
|
||||||
|
|
||||||
# Base models
|
# Base models
|
||||||
from .base import (
|
from .base import BaseResponse, IdResponse, StatusResponse
|
||||||
BaseResponse,
|
|
||||||
IdResponse,
|
|
||||||
StatusResponse,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Notes models
|
|
||||||
from .notes import (
|
|
||||||
Note,
|
|
||||||
NoteSearchResult,
|
|
||||||
NotesSettings,
|
|
||||||
CreateNoteResponse,
|
|
||||||
UpdateNoteResponse,
|
|
||||||
DeleteNoteResponse,
|
|
||||||
AppendContentResponse,
|
|
||||||
SearchNotesResponse,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Calendar models
|
# Calendar models
|
||||||
from .calendar import (
|
from .calendar import (
|
||||||
|
AvailabilitySlot,
|
||||||
|
BulkOperationResponse,
|
||||||
|
BulkOperationResult,
|
||||||
Calendar,
|
Calendar,
|
||||||
CalendarEvent,
|
CalendarEvent,
|
||||||
CalendarEventSummary,
|
CalendarEventSummary,
|
||||||
CreateEventResponse,
|
CreateEventResponse,
|
||||||
UpdateEventResponse,
|
|
||||||
DeleteEventResponse,
|
|
||||||
ListEventsResponse,
|
|
||||||
ListCalendarsResponse,
|
|
||||||
AvailabilitySlot,
|
|
||||||
FindAvailabilityResponse,
|
|
||||||
BulkOperationResult,
|
|
||||||
BulkOperationResponse,
|
|
||||||
CreateMeetingResponse,
|
CreateMeetingResponse,
|
||||||
UpcomingEventsResponse,
|
DeleteEventResponse,
|
||||||
|
FindAvailabilityResponse,
|
||||||
|
ListCalendarsResponse,
|
||||||
|
ListEventsResponse,
|
||||||
ManageCalendarResponse,
|
ManageCalendarResponse,
|
||||||
|
UpcomingEventsResponse,
|
||||||
|
UpdateEventResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Contacts models
|
# Contacts models
|
||||||
@@ -43,38 +27,50 @@ from .contacts import (
|
|||||||
AddressBook,
|
AddressBook,
|
||||||
Contact,
|
Contact,
|
||||||
ContactField,
|
ContactField,
|
||||||
|
CreateAddressBookResponse,
|
||||||
|
CreateContactResponse,
|
||||||
|
DeleteAddressBookResponse,
|
||||||
|
DeleteContactResponse,
|
||||||
ListAddressBooksResponse,
|
ListAddressBooksResponse,
|
||||||
ListContactsResponse,
|
ListContactsResponse,
|
||||||
CreateContactResponse,
|
|
||||||
UpdateContactResponse,
|
UpdateContactResponse,
|
||||||
DeleteContactResponse,
|
)
|
||||||
CreateAddressBookResponse,
|
|
||||||
DeleteAddressBookResponse,
|
# Notes models
|
||||||
|
from .notes import (
|
||||||
|
AppendContentResponse,
|
||||||
|
CreateNoteResponse,
|
||||||
|
DeleteNoteResponse,
|
||||||
|
Note,
|
||||||
|
NoteSearchResult,
|
||||||
|
NotesSettings,
|
||||||
|
SearchNotesResponse,
|
||||||
|
UpdateNoteResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Tables models
|
# Tables models
|
||||||
from .tables import (
|
from .tables import (
|
||||||
|
CreateRowResponse,
|
||||||
|
DeleteRowResponse,
|
||||||
|
GetSchemaResponse,
|
||||||
|
ListTablesResponse,
|
||||||
|
ReadTableResponse,
|
||||||
Table,
|
Table,
|
||||||
TableColumn,
|
TableColumn,
|
||||||
TableRow,
|
TableRow,
|
||||||
TableView,
|
|
||||||
TableSchema,
|
TableSchema,
|
||||||
ListTablesResponse,
|
TableView,
|
||||||
GetSchemaResponse,
|
|
||||||
ReadTableResponse,
|
|
||||||
CreateRowResponse,
|
|
||||||
UpdateRowResponse,
|
UpdateRowResponse,
|
||||||
DeleteRowResponse,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# WebDAV models
|
# WebDAV models
|
||||||
from .webdav import (
|
from .webdav import (
|
||||||
FileInfo,
|
|
||||||
DirectoryListing,
|
|
||||||
ReadFileResponse,
|
|
||||||
WriteFileResponse,
|
|
||||||
CreateDirectoryResponse,
|
CreateDirectoryResponse,
|
||||||
DeleteResourceResponse,
|
DeleteResourceResponse,
|
||||||
|
DirectoryListing,
|
||||||
|
FileInfo,
|
||||||
|
ReadFileResponse,
|
||||||
|
WriteFileResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Optional, Dict, Any, Union
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, field_validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from .calendar import configure_calendar_tools
|
from .calendar import configure_calendar_tools
|
||||||
|
from .contacts import configure_contacts_tools
|
||||||
|
from .deck import configure_deck_tools
|
||||||
from .notes import configure_notes_tools
|
from .notes import configure_notes_tools
|
||||||
from .tables import configure_tables_tools
|
from .tables import configure_tables_tools
|
||||||
from .webdav import configure_webdav_tools
|
from .webdav import configure_webdav_tools
|
||||||
from .contacts import configure_contacts_tools
|
|
||||||
from .deck import configure_deck_tools
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"configure_calendar_tools",
|
"configure_calendar_tools",
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ from typing import Optional
|
|||||||
from mcp.server.fastmcp import Context, FastMCP
|
from mcp.server.fastmcp import Context, FastMCP
|
||||||
|
|
||||||
from nextcloud_mcp_server.context import get_client
|
from nextcloud_mcp_server.context import get_client
|
||||||
from nextcloud_mcp_server.models.calendar import (
|
from nextcloud_mcp_server.models.calendar import Calendar, ListCalendarsResponse
|
||||||
Calendar,
|
|
||||||
ListCalendarsResponse,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ from mcp.server.fastmcp import Context, FastMCP
|
|||||||
|
|
||||||
from nextcloud_mcp_server.context import get_client
|
from nextcloud_mcp_server.context import get_client
|
||||||
from nextcloud_mcp_server.models.deck import (
|
from nextcloud_mcp_server.models.deck import (
|
||||||
|
CardOperationResponse,
|
||||||
|
CreateBoardResponse,
|
||||||
|
CreateCardResponse,
|
||||||
|
CreateLabelResponse,
|
||||||
|
CreateStackResponse,
|
||||||
DeckBoard,
|
DeckBoard,
|
||||||
DeckStack,
|
|
||||||
DeckCard,
|
DeckCard,
|
||||||
DeckLabel,
|
DeckLabel,
|
||||||
CreateBoardResponse,
|
DeckStack,
|
||||||
CreateStackResponse,
|
|
||||||
StackOperationResponse,
|
|
||||||
CreateCardResponse,
|
|
||||||
CardOperationResponse,
|
|
||||||
CreateLabelResponse,
|
|
||||||
LabelOperationResponse,
|
LabelOperationResponse,
|
||||||
|
StackOperationResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from httpx import HTTPStatusError
|
from httpx import HTTPStatusError
|
||||||
|
from mcp.server.fastmcp import Context, FastMCP
|
||||||
from mcp.shared.exceptions import McpError
|
from mcp.shared.exceptions import McpError
|
||||||
from mcp.types import ErrorData
|
from mcp.types import ErrorData
|
||||||
|
|
||||||
from mcp.server.fastmcp import Context, FastMCP
|
|
||||||
|
|
||||||
from nextcloud_mcp_server.context import get_client
|
from nextcloud_mcp_server.context import get_client
|
||||||
from nextcloud_mcp_server.models.notes import (
|
from nextcloud_mcp_server.models.notes import (
|
||||||
Note,
|
|
||||||
NotesSettings,
|
|
||||||
CreateNoteResponse,
|
|
||||||
UpdateNoteResponse,
|
|
||||||
DeleteNoteResponse,
|
|
||||||
AppendContentResponse,
|
AppendContentResponse,
|
||||||
SearchNotesResponse,
|
CreateNoteResponse,
|
||||||
|
DeleteNoteResponse,
|
||||||
|
Note,
|
||||||
NoteSearchResult,
|
NoteSearchResult,
|
||||||
|
NotesSettings,
|
||||||
|
SearchNotesResponse,
|
||||||
|
UpdateNoteResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
+8
-110
@@ -572,109 +572,6 @@ async def temporary_board_with_card(
|
|||||||
logger.error(f"Unexpected error deleting temporary card {card.id}: {e}")
|
logger.error(f"Unexpected error deleting temporary card {card.id}: {e}")
|
||||||
|
|
||||||
|
|
||||||
async def get_oauth_token(nextcloud_url: str, username: str, password: str) -> str:
|
|
||||||
"""
|
|
||||||
Get an OAuth access token from Nextcloud OIDC using Client Credentials flow.
|
|
||||||
|
|
||||||
This is a helper function for testing only - it bypasses the normal OAuth flow
|
|
||||||
to directly obtain a token for automated testing.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
nextcloud_url: Nextcloud base URL
|
|
||||||
username: Nextcloud username
|
|
||||||
password: Nextcloud password
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Access token string
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
Exception: If token acquisition fails
|
|
||||||
"""
|
|
||||||
from nextcloud_mcp_server.auth.client_registration import load_or_register_client
|
|
||||||
|
|
||||||
logger.info(f"Getting OAuth token for testing from {nextcloud_url}")
|
|
||||||
|
|
||||||
# Perform OIDC discovery
|
|
||||||
async with httpx.AsyncClient() as http_client:
|
|
||||||
discovery_url = f"{nextcloud_url}/.well-known/openid-configuration"
|
|
||||||
logger.debug(f"Fetching OIDC discovery from: {discovery_url}")
|
|
||||||
|
|
||||||
discovery_response = await http_client.get(discovery_url)
|
|
||||||
if discovery_response.status_code != 200:
|
|
||||||
raise Exception(f"OIDC discovery failed: {discovery_response.status_code}")
|
|
||||||
|
|
||||||
oidc_config = discovery_response.json()
|
|
||||||
token_endpoint = oidc_config.get("token_endpoint")
|
|
||||||
registration_endpoint = oidc_config.get("registration_endpoint")
|
|
||||||
|
|
||||||
if not token_endpoint or not registration_endpoint:
|
|
||||||
raise Exception("OIDC discovery missing required endpoints")
|
|
||||||
|
|
||||||
logger.debug(f"Token endpoint: {token_endpoint}")
|
|
||||||
logger.debug(f"Registration endpoint: {registration_endpoint}")
|
|
||||||
|
|
||||||
# Get or register an OAuth client
|
|
||||||
client_info = await load_or_register_client(
|
|
||||||
nextcloud_url=nextcloud_url,
|
|
||||||
registration_endpoint=registration_endpoint,
|
|
||||||
storage_path=".nextcloud_oauth_test_client.json",
|
|
||||||
redirect_uris=["http://localhost:8000/oauth/callback"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Use client credentials to get a token via client_credentials grant
|
|
||||||
token_response = await http_client.post(
|
|
||||||
token_endpoint,
|
|
||||||
data={
|
|
||||||
"grant_type": "client_credentials",
|
|
||||||
"client_id": client_info.client_id,
|
|
||||||
"client_secret": client_info.client_secret,
|
|
||||||
"scope": "openid profile email",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if token_response.status_code != 200:
|
|
||||||
logger.error(f"Failed to get OAuth token: {token_response.text}")
|
|
||||||
raise Exception(f"Token request failed: {token_response.status_code}")
|
|
||||||
|
|
||||||
token_data = token_response.json()
|
|
||||||
access_token = token_data.get("access_token")
|
|
||||||
|
|
||||||
if not access_token:
|
|
||||||
raise Exception("No access_token in response")
|
|
||||||
|
|
||||||
logger.info("Successfully obtained OAuth access token for testing")
|
|
||||||
return access_token
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
async def oauth_token() -> str:
|
|
||||||
"""
|
|
||||||
Fixture to obtain an OAuth access token for integration tests.
|
|
||||||
|
|
||||||
This uses the Resource Owner Password flow to get a token without
|
|
||||||
requiring interactive browser authentication.
|
|
||||||
"""
|
|
||||||
nextcloud_host = os.getenv("NEXTCLOUD_HOST")
|
|
||||||
username = os.getenv("NEXTCLOUD_USERNAME")
|
|
||||||
password = os.getenv("NEXTCLOUD_PASSWORD")
|
|
||||||
|
|
||||||
if not all([nextcloud_host, username, password]):
|
|
||||||
pytest.skip(
|
|
||||||
"OAuth token fixture requires NEXTCLOUD_HOST, USERNAME, and PASSWORD"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Wait for Nextcloud to be ready
|
|
||||||
if not await wait_for_nextcloud(nextcloud_host):
|
|
||||||
pytest.fail(f"Nextcloud server at {nextcloud_host} is not ready")
|
|
||||||
|
|
||||||
try:
|
|
||||||
token = await get_oauth_token(nextcloud_host, username, password)
|
|
||||||
return token
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to obtain OAuth token: {e}")
|
|
||||||
pytest.skip(f"Could not obtain OAuth token for testing: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
async def nc_oauth_client_interactive(
|
async def nc_oauth_client_interactive(
|
||||||
interactive_oauth_token: str,
|
interactive_oauth_token: str,
|
||||||
@@ -771,9 +668,9 @@ def oauth_callback_server():
|
|||||||
# Skip interactive tests in CI environments
|
# Skip interactive tests in CI environments
|
||||||
if os.getenv("GITHUB_ACTIONS"):
|
if os.getenv("GITHUB_ACTIONS"):
|
||||||
pytest.skip("Skipping interactive OAuth tests in GitHub Actions CI")
|
pytest.skip("Skipping interactive OAuth tests in GitHub Actions CI")
|
||||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
||||||
import threading
|
import threading
|
||||||
from urllib.parse import urlparse, parse_qs
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
# Use a mutable container to share state across threads
|
# Use a mutable container to share state across threads
|
||||||
auth_state = {"code": None}
|
auth_state = {"code": None}
|
||||||
@@ -843,6 +740,10 @@ def oauth_callback_server():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
"GITHUB_ACTIONS" in os.environ,
|
||||||
|
reason="Unable to access interactive browser in GitHub Actions",
|
||||||
|
)
|
||||||
async def interactive_oauth_token(oauth_callback_server) -> str:
|
async def interactive_oauth_token(oauth_callback_server) -> str:
|
||||||
"""
|
"""
|
||||||
Fixture to obtain an OAuth access token for integration tests.
|
Fixture to obtain an OAuth access token for integration tests.
|
||||||
@@ -852,12 +753,9 @@ async def interactive_oauth_token(oauth_callback_server) -> str:
|
|||||||
|
|
||||||
Automatically skips when running in GitHub Actions CI.
|
Automatically skips when running in GitHub Actions CI.
|
||||||
"""
|
"""
|
||||||
# Skip interactive tests in CI environments
|
|
||||||
if os.getenv("GITHUB_ACTIONS"):
|
|
||||||
pytest.skip("Skipping interactive OAuth tests in GitHub Actions CI")
|
|
||||||
|
|
||||||
import webbrowser
|
|
||||||
import time
|
import time
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
from nextcloud_mcp_server.auth.client_registration import load_or_register_client
|
from nextcloud_mcp_server.auth.client_registration import load_or_register_client
|
||||||
|
|
||||||
@@ -948,7 +846,7 @@ async def playwright_oauth_token(browser) -> str:
|
|||||||
- Browser fixture provided by pytest-playwright-asyncio
|
- Browser fixture provided by pytest-playwright-asyncio
|
||||||
- See: https://playwright.dev/python/docs/test-runners
|
- See: https://playwright.dev/python/docs/test-runners
|
||||||
"""
|
"""
|
||||||
from urllib.parse import urlparse, parse_qs
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
nextcloud_host = os.getenv("NEXTCLOUD_HOST")
|
nextcloud_host = os.getenv("NEXTCLOUD_HOST")
|
||||||
username = os.getenv("NEXTCLOUD_USERNAME")
|
username = os.getenv("NEXTCLOUD_USERNAME")
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import pytest
|
|||||||
from httpx import HTTPStatusError
|
from httpx import HTTPStatusError
|
||||||
|
|
||||||
from nextcloud_mcp_server.client import NextcloudClient
|
from nextcloud_mcp_server.client import NextcloudClient
|
||||||
from nextcloud_mcp_server.models.deck import DeckStack, DeckCard, DeckLabel
|
from nextcloud_mcp_server.models.deck import DeckCard, DeckLabel, DeckStack
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
pytestmark = pytest.mark.integration
|
pytestmark = pytest.mark.integration
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"""Test error propagation in the MCP server for various error scenarios."""
|
"""Test error propagation in the MCP server for various error scenarios."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from mcp import ClientSession
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from mcp import ClientSession
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ are present in calendar events and contacts during round-trip operations.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import pytest
|
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,35 +9,28 @@ logger = logging.getLogger(__name__)
|
|||||||
pytestmark = [pytest.mark.integration, pytest.mark.oauth]
|
pytestmark = [pytest.mark.integration, pytest.mark.oauth]
|
||||||
|
|
||||||
|
|
||||||
class TestOAuthInteractive:
|
async def test_oauth_client_with_interactive_flow(nc_oauth_client_interactive):
|
||||||
"""Test interactive OAuth authentication with manual browser login."""
|
"""Test that OAuth client created via interactive flow can access Nextcloud APIs."""
|
||||||
|
# Test 1: Check capabilities
|
||||||
|
capabilities = await nc_oauth_client_interactive.capabilities()
|
||||||
|
assert capabilities is not None
|
||||||
|
logger.info("OAuth client (interactive) successfully fetched capabilities")
|
||||||
|
|
||||||
async def test_oauth_client_with_interactive_flow(
|
# Test 2: List notes
|
||||||
self, nc_oauth_client_interactive
|
notes = await nc_oauth_client_interactive.notes.get_all_notes()
|
||||||
):
|
assert isinstance(notes, list)
|
||||||
"""Test that OAuth client created via interactive flow can access Nextcloud APIs."""
|
logger.info(f"OAuth client (interactive) successfully listed {len(notes)} notes")
|
||||||
# Test 1: Check capabilities
|
|
||||||
capabilities = await nc_oauth_client_interactive.capabilities()
|
|
||||||
assert capabilities is not None
|
|
||||||
logger.info("OAuth client (interactive) successfully fetched capabilities")
|
|
||||||
|
|
||||||
# Test 2: List notes
|
# Test 3: Create and delete a note
|
||||||
notes = await nc_oauth_client_interactive.notes.get_all_notes()
|
test_note = await nc_oauth_client_interactive.notes.create_note(
|
||||||
assert isinstance(notes, list)
|
title="OAuth Interactive Test Note",
|
||||||
logger.info(
|
content="This note was created during OAuth interactive testing",
|
||||||
f"OAuth client (interactive) successfully listed {len(notes)} notes"
|
)
|
||||||
)
|
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}")
|
||||||
|
|
||||||
# Test 3: Create and delete a note
|
# Clean up
|
||||||
test_note = await nc_oauth_client_interactive.notes.create_note(
|
await nc_oauth_client_interactive.notes.delete_note(note_id=note_id)
|
||||||
title="OAuth Interactive Test Note",
|
logger.info(f"OAuth client (interactive) successfully deleted note {note_id}")
|
||||||
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_interactive.notes.delete_note(note_id=note_id)
|
|
||||||
logger.info(f"OAuth client (interactive) successfully deleted note {note_id}")
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"""Unit tests for Pydantic models and serialization."""
|
"""Unit tests for Pydantic models and serialization."""
|
||||||
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from nextcloud_mcp_server.models.base import BaseResponse
|
from nextcloud_mcp_server.models.base import BaseResponse
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user