Skip to main content
Glama
conftest.pyโ€ข8.9 kB
"""Pytest configuration and fixtures for TrueNAS MCP Server tests.""" import asyncio from typing import AsyncGenerator, Dict, Any from unittest.mock import AsyncMock, MagicMock import pytest import httpx from pydantic import SecretStr from truenas_mcp_server.config.settings import Settings from truenas_mcp_server.client.http_client import TrueNASClient # ============================================================================ # Pytest Configuration # ============================================================================ @pytest.fixture(scope="session") def event_loop(): """Create an instance of the default event loop for the test session.""" loop = asyncio.get_event_loop_policy().new_event_loop() yield loop loop.close() # ============================================================================ # Settings Fixtures # ============================================================================ @pytest.fixture def mock_settings() -> Settings: """Create mock settings for testing.""" return Settings( truenas_url="https://truenas.local", truenas_api_key=SecretStr("test-api-key-1234567890"), truenas_verify_ssl=False, environment="testing", log_level="DEBUG", enable_destructive_operations=True, http_timeout=30.0, http_pool_connections=10, http_pool_maxsize=20, http_max_retries=3, http_retry_backoff_factor=2.0, ) @pytest.fixture def production_settings() -> Settings: """Create production-like settings for testing.""" return Settings( truenas_url="https://truenas.example.com", truenas_api_key=SecretStr("prod-api-key-1234567890abcdef"), truenas_verify_ssl=True, environment="production", log_level="INFO", enable_destructive_operations=False, http_timeout=60.0, ) # ============================================================================ # HTTP Client Fixtures # ============================================================================ @pytest.fixture def mock_httpx_client() -> MagicMock: """Create a mock httpx.AsyncClient.""" client = MagicMock(spec=httpx.AsyncClient) client.is_closed = False return client @pytest.fixture async def mock_truenas_client(mock_settings: Settings) -> AsyncGenerator[TrueNASClient, None]: """Create a mock TrueNAS HTTP client.""" client = TrueNASClient(settings=mock_settings) # Don't actually connect client._client = AsyncMock(spec=httpx.AsyncClient) client._client.is_closed = False yield client # Clean up if not client._client.is_closed: await client.close() # ============================================================================ # API Response Fixtures # ============================================================================ @pytest.fixture def mock_pool_response() -> Dict[str, Any]: """Mock response for pool list API.""" return [ { "id": 1, "name": "tank", "guid": "1234567890", "status": "ONLINE", "healthy": True, "size": 4000000000000, "allocated": 1000000000000, "free": 3000000000000, "fragmentation": "5%", "topology": { "data": [ { "type": "MIRROR", "children": [ {"type": "DISK", "path": "/dev/sda", "status": "ONLINE"}, {"type": "DISK", "path": "/dev/sdb", "status": "ONLINE"}, ], } ], }, } ] @pytest.fixture def mock_dataset_response() -> Dict[str, Any]: """Mock response for dataset list API.""" return [ { "id": "tank/data", "name": "data", "pool": "tank", "type": "FILESYSTEM", "used": {"parsed": 500000000000}, "available": {"parsed": 2500000000000}, "compression": "lz4", "readonly": {"value": "off"}, "deduplication": {"value": "off"}, "mountpoint": "/mnt/tank/data", "quota": {"parsed": None}, "reservation": {"parsed": None}, } ] @pytest.fixture def mock_user_response() -> Dict[str, Any]: """Mock response for user list API.""" return [ { "id": 1000, "uid": 1000, "username": "testuser", "full_name": "Test User", "email": "test@example.com", "locked": False, "sudo_commands": [], "sudo_commands_nopasswd": [], "shell": "/bin/bash", "home": "/mnt/tank/home/testuser", "group": {"id": 1000, "bsdgrp_gid": 1000, "bsdgrp_group": "testuser"}, "groups": [1000], } ] @pytest.fixture def mock_snapshot_response() -> Dict[str, Any]: """Mock response for snapshot list API.""" return [ { "id": "tank/data@auto-2024-01-01-00-00", "name": "auto-2024-01-01-00-00", "dataset": "tank/data", "properties": { "used": {"parsed": 1000000}, "referenced": {"parsed": 500000000000}, "creation": {"parsed": "2024-01-01T00:00:00"}, }, } ] @pytest.fixture def mock_smb_share_response() -> Dict[str, Any]: """Mock response for SMB share list API.""" return [ { "id": 1, "path": "/mnt/tank/data", "name": "data", "comment": "Test SMB share", "enabled": True, "guestok": False, "ro": False, "browsable": True, "recyclebin": False, "hostsallow": [], "hostsdeny": [], } ] # ============================================================================ # HTTP Response Fixtures # ============================================================================ @pytest.fixture def mock_http_response() -> MagicMock: """Create a mock HTTP response.""" response = MagicMock(spec=httpx.Response) response.status_code = 200 response.headers = {"content-type": "application/json"} response.json.return_value = {"status": "success"} response.text = '{"status": "success"}' return response @pytest.fixture def mock_error_response() -> MagicMock: """Create a mock error HTTP response.""" response = MagicMock(spec=httpx.Response) response.status_code = 500 response.headers = {"content-type": "application/json"} response.json.return_value = {"error": "Internal Server Error"} response.text = '{"error": "Internal Server Error"}' response.raise_for_status.side_effect = httpx.HTTPStatusError( "500 Internal Server Error", request=MagicMock(), response=response ) return response # ============================================================================ # Tool Test Helpers # ============================================================================ @pytest.fixture def mock_tool_arguments() -> Dict[str, Any]: """Common tool arguments for testing.""" return { "pool_name": "tank", "dataset_name": "data", "username": "testuser", "snapshot_name": "auto-2024-01-01-00-00", "share_name": "data", } # ============================================================================ # Async Helpers # ============================================================================ @pytest.fixture def async_return(): """Helper to create async return values.""" def _async_return(value): async def _wrapper(): return value return _wrapper() return _async_return # ============================================================================ # Exception Fixtures # ============================================================================ @pytest.fixture def mock_connection_error() -> httpx.ConnectError: """Create a mock connection error.""" return httpx.ConnectError("Connection refused") @pytest.fixture def mock_timeout_error() -> httpx.TimeoutException: """Create a mock timeout error.""" return httpx.TimeoutException("Request timeout") @pytest.fixture def mock_rate_limit_response() -> MagicMock: """Create a mock rate limit response.""" response = MagicMock(spec=httpx.Response) response.status_code = 429 response.headers = { "content-type": "application/json", "X-RateLimit-Limit": "100", "X-RateLimit-Remaining": "0", "X-RateLimit-Reset": "1704067200", } response.json.return_value = {"error": "Rate limit exceeded"} response.text = '{"error": "Rate limit exceeded"}' return response

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vespo92/TrueNasCoreMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server