We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/zengwenliang416/banana-image-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
"""
Shared test fixtures for Nano Banana MCP Server tests.
This module provides common fixtures for mocking services and configurations
used across the test suite.
Requirements: 6.1, 6.2, 6.3, 6.4
"""
import io
from unittest.mock import MagicMock, Mock
from PIL import Image as PILImage
import pytest
from banana_image_mcp.config.settings import (
FlashImageConfig,
GeminiConfig,
MediaResolution,
ProImageConfig,
ServerConfig,
ThinkingLevel,
)
from banana_image_mcp.services.gemini_client import GeminiClient
from banana_image_mcp.services.image_storage_service import (
ImageStorageService,
StoredImageInfo,
)
# =============================================================================
# Configuration Fixtures (Requirements 6.1)
# =============================================================================
@pytest.fixture
def mock_server_config() -> ServerConfig:
"""Create a mock ServerConfig for testing.
Returns:
ServerConfig with test API key and default settings.
"""
return ServerConfig(
gemini_api_key="test-api-key-12345",
server_name="test-banana-server",
transport="stdio",
host="127.0.0.1",
port=9000,
mask_error_details=False,
max_concurrent_requests=10,
image_output_dir="/tmp/test-banana-images",
)
@pytest.fixture
def mock_gemini_config() -> GeminiConfig:
"""Create a mock GeminiConfig for testing (legacy compatibility).
Returns:
GeminiConfig with default settings.
"""
return GeminiConfig(
model_name="gemini-2.5-flash-image",
max_images_per_request=4,
max_inline_image_size=20 * 1024 * 1024,
default_image_format="png",
request_timeout=60,
)
@pytest.fixture
def mock_flash_config() -> FlashImageConfig:
"""Create a mock FlashImageConfig for testing.
Returns:
FlashImageConfig with default Flash model settings.
"""
return FlashImageConfig(
model_name="gemini-2.5-flash-image",
max_images_per_request=4,
max_inline_image_size=20 * 1024 * 1024,
default_image_format="png",
request_timeout=60,
max_resolution=1024,
supports_thinking=False,
supports_grounding=False,
supports_media_resolution=False,
)
@pytest.fixture
def mock_pro_config() -> ProImageConfig:
"""Create a mock ProImageConfig for testing.
Returns:
ProImageConfig with default Pro model settings.
"""
return ProImageConfig(
model_name="gemini-3-pro-image-preview",
max_images_per_request=4,
max_inline_image_size=20 * 1024 * 1024,
default_image_format="png",
request_timeout=90,
max_resolution=3840,
default_resolution="high",
default_thinking_level=ThinkingLevel.HIGH,
default_media_resolution=MediaResolution.HIGH,
supports_thinking=True,
supports_grounding=True,
supports_media_resolution=True,
enable_search_grounding=True,
)
# =============================================================================
# GeminiClient Fixtures (Requirements 6.2)
# =============================================================================
@pytest.fixture
def mock_gemini_client(mock_server_config: ServerConfig, mock_gemini_config: GeminiConfig) -> Mock:
"""Create a mock GeminiClient for testing.
The mock client has:
- Mocked internal _client attribute
- Mocked models attribute for generate_content
- Mocked create_image_parts method
- Mocked extract_images method
Args:
mock_server_config: Server configuration fixture.
mock_gemini_config: Gemini configuration fixture.
Returns:
Mock GeminiClient with pre-configured mock methods.
"""
client = Mock(spec=GeminiClient)
# Mock the internal client
client._client = MagicMock()
client._client.models = MagicMock()
client._client.models.generate_content = MagicMock()
# Mock configuration attributes
client.config = mock_server_config
client.gemini_config = mock_gemini_config
# Mock create_image_parts to return empty list by default
client.create_image_parts = Mock(return_value=[])
# Mock extract_images to return sample image bytes
client.extract_images = Mock(return_value=[b"fake_image_bytes"])
# Mock generate_content to return a mock response
mock_response = MagicMock()
mock_response.candidates = [MagicMock()]
mock_response.candidates[0].content = MagicMock()
mock_response.candidates[0].content.parts = []
client.generate_content = Mock(return_value=mock_response)
return client
@pytest.fixture
def mock_flash_gemini_client(
mock_server_config: ServerConfig, mock_flash_config: FlashImageConfig
) -> Mock:
"""Create a mock GeminiClient configured for Flash model.
Args:
mock_server_config: Server configuration fixture.
mock_flash_config: Flash model configuration fixture.
Returns:
Mock GeminiClient configured for Flash model.
"""
client = Mock(spec=GeminiClient)
client._client = MagicMock()
client._client.models = MagicMock()
client.config = mock_server_config
client.gemini_config = mock_flash_config
client.create_image_parts = Mock(return_value=[])
client.extract_images = Mock(return_value=[b"flash_image_bytes"])
client.generate_content = Mock(return_value=MagicMock())
return client
@pytest.fixture
def mock_pro_gemini_client(
mock_server_config: ServerConfig, mock_pro_config: ProImageConfig
) -> Mock:
"""Create a mock GeminiClient configured for Pro model.
Args:
mock_server_config: Server configuration fixture.
mock_pro_config: Pro model configuration fixture.
Returns:
Mock GeminiClient configured for Pro model.
"""
client = Mock(spec=GeminiClient)
client._client = MagicMock()
client._client.models = MagicMock()
client.config = mock_server_config
client.gemini_config = mock_pro_config
client.create_image_parts = Mock(return_value=[])
client.extract_images = Mock(return_value=[b"pro_image_bytes"])
client.generate_content = Mock(return_value=MagicMock())
return client
# =============================================================================
# Storage Service Fixtures (Requirements 6.3)
# =============================================================================
@pytest.fixture
def mock_stored_image_info() -> StoredImageInfo:
"""Create a mock StoredImageInfo for testing.
Returns:
StoredImageInfo with predictable test values.
"""
return StoredImageInfo(
id="test-image-id-12345",
filename="test-image-id-12345.png",
full_path="/tmp/test-images/test-image-id-12345.png",
thumbnail_path="/tmp/test-images/thumbnails/test-image-id-12345_thumb.jpg",
size_bytes=1024 * 100, # 100KB
thumbnail_size_bytes=1024 * 10, # 10KB
mime_type="image/png",
created_at=1700000000.0,
expires_at=1700003600.0, # 1 hour later
width=1024,
height=768,
thumbnail_width=256,
thumbnail_height=192,
metadata={
"prompt": "test prompt",
"model": "gemini-2.5-flash-image",
},
)
@pytest.fixture
def mock_storage_service(mock_stored_image_info: StoredImageInfo) -> Mock:
"""Create a mock ImageStorageService for testing.
The mock service:
- Returns predictable StoredImageInfo objects
- Provides mock thumbnail base64 data
- Simulates storage operations without file I/O
Args:
mock_stored_image_info: StoredImageInfo fixture.
Returns:
Mock ImageStorageService with pre-configured mock methods.
"""
service = Mock(spec=ImageStorageService)
# Mock store_image to return predictable StoredImageInfo
service.store_image = Mock(return_value=mock_stored_image_info)
# Mock get_thumbnail_base64 to return a small base64 string
# This is a 1x1 red pixel JPEG encoded as base64
service.get_thumbnail_base64 = Mock(
return_value="iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=="
)
# Mock get_image_info
service.get_image_info = Mock(return_value=mock_stored_image_info)
# Mock get_image_bytes
service.get_image_bytes = Mock(return_value=b"mock_image_bytes")
# Mock list_images
service.list_images = Mock(return_value=[mock_stored_image_info])
# Mock delete_image
service.delete_image = Mock(return_value=True)
# Mock cleanup_all
service.cleanup_all = Mock(return_value=1)
# Mock get_storage_stats
service.get_storage_stats = Mock(
return_value={
"total_images": 1,
"total_size_bytes": 102400,
"total_size_mb": 0.1,
"total_thumbnail_size_bytes": 10240,
"total_thumbnail_size_kb": 10.0,
"base_directory": "/tmp/test-images",
"default_ttl_seconds": 3600,
}
)
return service
# =============================================================================
# Sample Data Fixtures (Requirements 6.4)
# =============================================================================
@pytest.fixture
def sample_image_bytes() -> bytes:
"""Generate a valid PNG image bytes for testing.
Creates a 100x100 red image in PNG format.
Returns:
Valid PNG image as bytes.
"""
# Create a simple 100x100 red image
img = PILImage.new("RGB", (100, 100), color="red")
buffer = io.BytesIO()
img.save(buffer, format="PNG")
return buffer.getvalue()
@pytest.fixture
def sample_image_bytes_large() -> bytes:
"""Generate a larger valid PNG image bytes for testing.
Creates a 1024x768 gradient image in PNG format.
Returns:
Valid PNG image as bytes.
"""
# Create a 1024x768 gradient image
img = PILImage.new("RGB", (1024, 768))
pixels = img.load()
for x in range(1024):
for y in range(768):
# Create a gradient effect
r = int(255 * x / 1024)
g = int(255 * y / 768)
b = 128
pixels[x, y] = (r, g, b)
buffer = io.BytesIO()
img.save(buffer, format="PNG")
return buffer.getvalue()
@pytest.fixture
def sample_image_base64(sample_image_bytes: bytes) -> str:
"""Generate a valid PNG image as base64 string for testing.
Args:
sample_image_bytes: PNG image bytes fixture.
Returns:
Base64 encoded PNG image string.
"""
import base64
return base64.b64encode(sample_image_bytes).decode("utf-8")
@pytest.fixture
def sample_jpeg_bytes() -> bytes:
"""Generate a valid JPEG image bytes for testing.
Creates a 100x100 blue image in JPEG format.
Returns:
Valid JPEG image as bytes.
"""
img = PILImage.new("RGB", (100, 100), color="blue")
buffer = io.BytesIO()
img.save(buffer, format="JPEG", quality=85)
return buffer.getvalue()
@pytest.fixture
def sample_webp_bytes() -> bytes:
"""Generate a valid WebP image bytes for testing.
Creates a 100x100 green image in WebP format.
Returns:
Valid WebP image as bytes.
"""
img = PILImage.new("RGB", (100, 100), color="green")
buffer = io.BytesIO()
img.save(buffer, format="WEBP", quality=85)
return buffer.getvalue()
# =============================================================================
# Helper Fixtures
# =============================================================================
@pytest.fixture
def mock_gemini_response_with_image(sample_image_bytes: bytes) -> MagicMock:
"""Create a mock Gemini API response containing an image.
Args:
sample_image_bytes: PNG image bytes fixture.
Returns:
Mock response object with image data in candidates.
"""
response = MagicMock()
# Create mock inline_data
inline_data = MagicMock()
inline_data.data = sample_image_bytes
inline_data.mime_type = "image/png"
# Create mock part
part = MagicMock()
part.inline_data = inline_data
# Create mock content
content = MagicMock()
content.parts = [part]
# Create mock candidate
candidate = MagicMock()
candidate.content = content
# Set up response
response.candidates = [candidate]
return response
@pytest.fixture
def mock_gemini_response_empty() -> MagicMock:
"""Create a mock Gemini API response with no images.
Returns:
Mock response object with empty candidates.
"""
response = MagicMock()
response.candidates = []
return response
@pytest.fixture
def mock_gemini_response_multiple_images(sample_image_bytes: bytes) -> MagicMock:
"""Create a mock Gemini API response containing multiple images.
Args:
sample_image_bytes: PNG image bytes fixture.
Returns:
Mock response object with multiple images in candidates.
"""
response = MagicMock()
parts = []
for _i in range(3):
inline_data = MagicMock()
inline_data.data = sample_image_bytes
inline_data.mime_type = "image/png"
part = MagicMock()
part.inline_data = inline_data
parts.append(part)
content = MagicMock()
content.parts = parts
candidate = MagicMock()
candidate.content = content
response.candidates = [candidate]
return response