- Deleted .env.example file as it is no longer needed. - Added .gitignore to manage ignored files and directories. - Introduced CLAUDE.md for AI provider integration documentation. - Created dev.sh for development setup and scripts. - Updated Dockerfile and Dockerfile.production for improved build processes. - Added multiple test files and directories for comprehensive testing. - Introduced new utility and service files for enhanced functionality. - Organized codebase with new directories and files for better maintainability.
739 lines
22 KiB
Python
739 lines
22 KiB
Python
"""
|
|
Test fixtures for utils components
|
|
|
|
Provides specialized fixtures for testing utils modules including:
|
|
- Mock audio data and files
|
|
- Mock Discord objects for permissions testing
|
|
- Mock AI prompt data
|
|
- Mock metrics data
|
|
- Mock configuration objects
|
|
- Error and exception scenarios
|
|
- Performance testing data
|
|
"""
|
|
|
|
import asyncio
|
|
import os
|
|
import struct
|
|
import tempfile
|
|
from datetime import datetime, timedelta, timezone
|
|
from typing import Any, Dict, List
|
|
from unittest.mock import AsyncMock, Mock
|
|
|
|
import discord
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from utils.audio_processor import AudioConfig
|
|
from utils.exceptions import AudioProcessingError, ValidationError
|
|
|
|
|
|
class AudioTestData:
|
|
"""Factory for creating audio test data."""
|
|
|
|
@staticmethod
|
|
def create_sine_wave(
|
|
frequency: float = 440.0, duration: float = 1.0, sample_rate: int = 16000
|
|
) -> np.ndarray:
|
|
"""Create sine wave audio data."""
|
|
samples = int(duration * sample_rate)
|
|
t = np.linspace(0, duration, samples, False)
|
|
return np.sin(2 * np.pi * frequency * t).astype(np.float32)
|
|
|
|
@staticmethod
|
|
def create_white_noise(
|
|
duration: float = 1.0, sample_rate: int = 16000, amplitude: float = 0.1
|
|
) -> np.ndarray:
|
|
"""Create white noise audio data."""
|
|
samples = int(duration * sample_rate)
|
|
return (np.random.random(samples) - 0.5) * 2 * amplitude
|
|
|
|
@staticmethod
|
|
def create_silence(duration: float = 1.0, sample_rate: int = 16000) -> np.ndarray:
|
|
"""Create silent audio data."""
|
|
samples = int(duration * sample_rate)
|
|
return np.zeros(samples, dtype=np.float32)
|
|
|
|
@staticmethod
|
|
def create_pcm_bytes(audio_array: np.ndarray, sample_rate: int = 16000) -> bytes:
|
|
"""Convert audio array to PCM bytes."""
|
|
# Normalize and convert to 16-bit PCM
|
|
normalized = np.clip(audio_array * 32767, -32768, 32767).astype(np.int16)
|
|
return normalized.tobytes()
|
|
|
|
@staticmethod
|
|
def create_wav_header(
|
|
data_size: int, sample_rate: int = 16000, channels: int = 1
|
|
) -> bytes:
|
|
"""Create WAV file header."""
|
|
return (
|
|
b"RIFF"
|
|
+ struct.pack("<I", data_size + 36)
|
|
+ b"WAVE"
|
|
+ b"fmt "
|
|
+ struct.pack("<I", 16) # fmt chunk size
|
|
+ struct.pack("<H", 1) # PCM format
|
|
+ struct.pack("<H", channels)
|
|
+ struct.pack("<I", sample_rate)
|
|
+ struct.pack("<I", sample_rate * channels * 2) # byte rate
|
|
+ struct.pack("<H", channels * 2) # block align
|
|
+ struct.pack("<H", 16) # bits per sample
|
|
+ b"data"
|
|
+ struct.pack("<I", data_size)
|
|
)
|
|
|
|
|
|
class DiscordTestObjects:
|
|
"""Factory for creating mock Discord objects."""
|
|
|
|
@staticmethod
|
|
def create_mock_guild(
|
|
guild_id: int = 123456789, owner_id: int = 111111111, name: str = "Test Guild"
|
|
):
|
|
"""Create mock Discord guild."""
|
|
guild = Mock(spec=discord.Guild)
|
|
guild.id = guild_id
|
|
guild.owner_id = owner_id
|
|
guild.name = name
|
|
return guild
|
|
|
|
@staticmethod
|
|
def create_mock_member(
|
|
user_id: int = 222222222, username: str = "TestUser", **permissions
|
|
):
|
|
"""Create mock Discord member with permissions."""
|
|
member = Mock(spec=discord.Member)
|
|
member.id = user_id
|
|
member.name = username
|
|
member.display_name = username
|
|
|
|
# Create guild permissions
|
|
perms = Mock()
|
|
default_permissions = {
|
|
"administrator": False,
|
|
"manage_guild": False,
|
|
"manage_messages": False,
|
|
"manage_channels": False,
|
|
"kick_members": False,
|
|
"ban_members": False,
|
|
"manage_roles": False,
|
|
"connect": False,
|
|
"speak": False,
|
|
"use_voice_activation": False,
|
|
"read_messages": True,
|
|
"send_messages": True,
|
|
"embed_links": True,
|
|
"attach_files": True,
|
|
"use_slash_commands": True,
|
|
}
|
|
default_permissions.update(permissions)
|
|
|
|
for perm, value in default_permissions.items():
|
|
setattr(perms, perm, value)
|
|
|
|
member.guild_permissions = perms
|
|
return member
|
|
|
|
@staticmethod
|
|
def create_mock_voice_channel(
|
|
channel_id: int = 333333333, name: str = "Test Voice"
|
|
):
|
|
"""Create mock Discord voice channel."""
|
|
channel = Mock(spec=discord.VoiceChannel)
|
|
channel.id = channel_id
|
|
channel.name = name
|
|
|
|
def mock_permissions_for(member):
|
|
"""Mock permissions for member in channel."""
|
|
perms = Mock()
|
|
perms.connect = True
|
|
perms.speak = True
|
|
perms.use_voice_activation = True
|
|
return perms
|
|
|
|
channel.permissions_for = mock_permissions_for
|
|
return channel
|
|
|
|
@staticmethod
|
|
def create_mock_text_channel(channel_id: int = 444444444, name: str = "Test Text"):
|
|
"""Create mock Discord text channel."""
|
|
channel = Mock(spec=discord.TextChannel)
|
|
channel.id = channel_id
|
|
channel.name = name
|
|
return channel
|
|
|
|
|
|
class PromptsTestData:
|
|
"""Factory for creating prompt test data."""
|
|
|
|
@staticmethod
|
|
def create_quote_data(
|
|
quote: str = "This is a test quote that's quite funny!",
|
|
speaker_name: str = "TestUser",
|
|
**scores,
|
|
) -> Dict[str, Any]:
|
|
"""Create quote data for testing."""
|
|
default_scores = {
|
|
"funny_score": 7.5,
|
|
"dark_score": 2.1,
|
|
"silly_score": 6.8,
|
|
"suspicious_score": 1.0,
|
|
"asinine_score": 3.2,
|
|
"overall_score": 6.5,
|
|
}
|
|
default_scores.update(scores)
|
|
|
|
return {
|
|
"quote": quote,
|
|
"speaker_name": speaker_name,
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
**default_scores,
|
|
}
|
|
|
|
@staticmethod
|
|
def create_context_data(
|
|
conversation: str = "The group was discussing funny movies and this came up.",
|
|
laughter_duration: float = 3.5,
|
|
laughter_intensity: float = 0.8,
|
|
**extras,
|
|
) -> Dict[str, Any]:
|
|
"""Create context data for testing."""
|
|
data = {
|
|
"conversation": conversation,
|
|
"laughter_duration": laughter_duration,
|
|
"laughter_intensity": laughter_intensity,
|
|
"personality": "Known for witty humor and clever observations",
|
|
"recent_interactions": "Recently active in comedy discussions",
|
|
"recent_context": "Has been making witty comments all day",
|
|
}
|
|
data.update(extras)
|
|
return data
|
|
|
|
@staticmethod
|
|
def create_user_profile_data(
|
|
username: str = "ComedyUser", quote_count: int = 5
|
|
) -> Dict[str, Any]:
|
|
"""Create user profile data for personality analysis."""
|
|
quotes = []
|
|
for i in range(quote_count):
|
|
quotes.append(
|
|
{
|
|
"quote": f"This is test quote number {i+1}",
|
|
"funny_score": 5.0 + i,
|
|
"dark_score": 1.0 + (i * 0.5),
|
|
"silly_score": 6.0 + (i * 0.3),
|
|
"timestamp": (
|
|
datetime.now(timezone.utc) - timedelta(days=i)
|
|
).isoformat(),
|
|
}
|
|
)
|
|
|
|
return {
|
|
"username": username,
|
|
"quotes": quotes,
|
|
"avg_funny_score": 7.0,
|
|
"avg_dark_score": 2.5,
|
|
"avg_silly_score": 6.5,
|
|
"primary_humor_style": "witty",
|
|
"quote_frequency": 3.2,
|
|
"active_hours": [14, 15, 19, 20, 21],
|
|
"avg_quote_length": 65,
|
|
}
|
|
|
|
|
|
class MetricsTestData:
|
|
"""Factory for creating metrics test data."""
|
|
|
|
@staticmethod
|
|
def create_metric_events(count: int = 10) -> List[Dict[str, Any]]:
|
|
"""Create metric events for testing."""
|
|
events = []
|
|
base_time = datetime.now(timezone.utc)
|
|
|
|
metric_types = ["quotes_detected", "audio_processed", "ai_requests", "errors"]
|
|
|
|
for i in range(count):
|
|
events.append(
|
|
{
|
|
"name": metric_types[i % len(metric_types)],
|
|
"value": float(i + 1),
|
|
"labels": {
|
|
"guild_id": str(123456 + (i % 3)),
|
|
"component": f"component_{i % 4}",
|
|
"status": "success" if i % 4 != 3 else "error",
|
|
},
|
|
"timestamp": base_time - timedelta(minutes=i * 5),
|
|
}
|
|
)
|
|
|
|
return events
|
|
|
|
@staticmethod
|
|
def create_system_metrics() -> Dict[str, Any]:
|
|
"""Create system metrics for testing."""
|
|
return {
|
|
"memory_rss": 1024 * 1024 * 100, # 100MB
|
|
"memory_vms": 1024 * 1024 * 200, # 200MB
|
|
"cpu_percent": 15.5,
|
|
"num_fds": 150,
|
|
"num_threads": 25,
|
|
"uptime_seconds": 3600 * 24, # 1 day
|
|
}
|
|
|
|
@staticmethod
|
|
def create_prometheus_data() -> str:
|
|
"""Create sample Prometheus metrics data."""
|
|
return """# HELP discord_quotes_detected_total Total number of quotes detected
|
|
# TYPE discord_quotes_detected_total counter
|
|
discord_quotes_detected_total{guild_id="123456",speaker_type="user"} 42.0
|
|
|
|
# HELP discord_memory_usage_bytes Current memory usage in bytes
|
|
# TYPE discord_memory_usage_bytes gauge
|
|
discord_memory_usage_bytes{type="rss"} 104857600.0
|
|
|
|
# HELP discord_errors_total Total errors by type
|
|
# TYPE discord_errors_total counter
|
|
discord_errors_total{error_type="validation",component="audio_processor"} 3.0
|
|
"""
|
|
|
|
|
|
# Pytest fixtures using the test data factories
|
|
|
|
|
|
@pytest.fixture
|
|
def audio_test_data():
|
|
"""Provide AudioTestData factory."""
|
|
return AudioTestData
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_sine_wave(audio_test_data):
|
|
"""Create sample sine wave audio."""
|
|
return audio_test_data.create_sine_wave(frequency=440, duration=2.0)
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_audio_bytes(sample_sine_wave, audio_test_data):
|
|
"""Create sample audio as PCM bytes."""
|
|
return audio_test_data.create_pcm_bytes(sample_sine_wave)
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_wav_file(sample_sine_wave, audio_test_data):
|
|
"""Create temporary WAV file with sample audio."""
|
|
pcm_data = audio_test_data.create_pcm_bytes(sample_sine_wave)
|
|
header = audio_test_data.create_wav_header(len(pcm_data))
|
|
|
|
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
|
|
f.write(header + pcm_data)
|
|
temp_path = f.name
|
|
|
|
yield temp_path
|
|
|
|
# Cleanup
|
|
if os.path.exists(temp_path):
|
|
os.unlink(temp_path)
|
|
|
|
|
|
@pytest.fixture
|
|
def audio_config():
|
|
"""Create AudioConfig instance for testing."""
|
|
return AudioConfig()
|
|
|
|
|
|
@pytest.fixture
|
|
def discord_objects():
|
|
"""Provide DiscordTestObjects factory."""
|
|
return DiscordTestObjects
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_guild(discord_objects):
|
|
"""Create mock Discord guild."""
|
|
return discord_objects.create_mock_guild()
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_owner_member(discord_objects, mock_guild):
|
|
"""Create mock guild owner member."""
|
|
return discord_objects.create_mock_member(
|
|
user_id=mock_guild.owner_id, username="GuildOwner"
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_admin_member(discord_objects):
|
|
"""Create mock admin member."""
|
|
return discord_objects.create_mock_member(
|
|
user_id=555555555, username="AdminUser", administrator=True
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_moderator_member(discord_objects):
|
|
"""Create mock moderator member."""
|
|
return discord_objects.create_mock_member(
|
|
user_id=666666666,
|
|
username="ModeratorUser",
|
|
manage_messages=True,
|
|
kick_members=True,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_regular_member(discord_objects):
|
|
"""Create mock regular member."""
|
|
return discord_objects.create_mock_member(
|
|
user_id=777777777, username="RegularUser", connect=True
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_bot_member(discord_objects):
|
|
"""Create mock bot member with standard permissions."""
|
|
return discord_objects.create_mock_member(
|
|
user_id=888888888,
|
|
username="TestBot",
|
|
read_messages=True,
|
|
send_messages=True,
|
|
embed_links=True,
|
|
attach_files=True,
|
|
use_slash_commands=True,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_voice_channel(discord_objects):
|
|
"""Create mock voice channel."""
|
|
return discord_objects.create_mock_voice_channel()
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_text_channel(discord_objects):
|
|
"""Create mock text channel."""
|
|
return discord_objects.create_mock_text_channel()
|
|
|
|
|
|
@pytest.fixture
|
|
def prompts_test_data():
|
|
"""Provide PromptsTestData factory."""
|
|
return PromptsTestData
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_quote_data(prompts_test_data):
|
|
"""Create sample quote data."""
|
|
return prompts_test_data.create_quote_data()
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_context_data(prompts_test_data):
|
|
"""Create sample context data."""
|
|
return prompts_test_data.create_context_data()
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_user_profile(prompts_test_data):
|
|
"""Create sample user profile data."""
|
|
return prompts_test_data.create_user_profile_data()
|
|
|
|
|
|
@pytest.fixture
|
|
def metrics_test_data():
|
|
"""Provide MetricsTestData factory."""
|
|
return MetricsTestData
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_metric_events(metrics_test_data):
|
|
"""Create sample metric events."""
|
|
return metrics_test_data.create_metric_events(20)
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_system_metrics(metrics_test_data):
|
|
"""Create sample system metrics."""
|
|
return metrics_test_data.create_system_metrics()
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_prometheus_data(metrics_test_data):
|
|
"""Create sample Prometheus data."""
|
|
return metrics_test_data.create_prometheus_data()
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_subprocess_success():
|
|
"""Create mock successful subprocess result."""
|
|
result = Mock()
|
|
result.returncode = 0
|
|
result.stdout = "Success output"
|
|
result.stderr = ""
|
|
return result
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_subprocess_failure():
|
|
"""Create mock failed subprocess result."""
|
|
result = Mock()
|
|
result.returncode = 1
|
|
result.stdout = "Some output"
|
|
result.stderr = "Error: Command failed"
|
|
return result
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_exceptions():
|
|
"""Create sample exceptions for testing error handling."""
|
|
return {
|
|
"validation_error": ValidationError(
|
|
"Invalid input", "test_component", "test_operation"
|
|
),
|
|
"audio_error": AudioProcessingError(
|
|
"Audio processing failed", "audio_processor", "process_audio"
|
|
),
|
|
"discord_http_error": discord.HTTPException("HTTP request failed"),
|
|
"discord_forbidden": discord.Forbidden("Access denied"),
|
|
"connection_error": ConnectionError("Network connection failed"),
|
|
"timeout_error": asyncio.TimeoutError("Operation timed out"),
|
|
"value_error": ValueError("Invalid value provided"),
|
|
"file_not_found": FileNotFoundError("Required file not found"),
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def complex_metadata():
|
|
"""Create complex metadata for testing exception contexts."""
|
|
return {
|
|
"request_id": "req_12345",
|
|
"user_data": {
|
|
"id": 999999999,
|
|
"username": "TestUser",
|
|
"permissions": ["read", "write"],
|
|
},
|
|
"operation_context": {
|
|
"start_time": datetime.now(timezone.utc).isoformat(),
|
|
"retry_count": 2,
|
|
"timeout": 30.0,
|
|
},
|
|
"performance_metrics": {
|
|
"cpu_usage": 25.5,
|
|
"memory_usage": 1024 * 1024 * 50,
|
|
"processing_time": 1.234,
|
|
},
|
|
"flags": {"debug_enabled": True, "cache_hit": False, "background_task": True},
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_ai_responses():
|
|
"""Create mock AI provider responses."""
|
|
return {
|
|
"analysis_response": {
|
|
"funny": 8.5,
|
|
"dark": 2.0,
|
|
"silly": 7.2,
|
|
"suspicious": 1.0,
|
|
"asinine": 3.5,
|
|
"reasoning": "The quote demonstrates clever wordplay with unexpected timing.",
|
|
"overall_assessment": "Highly amusing quote with good comedic timing.",
|
|
"confidence": 0.92,
|
|
},
|
|
"commentary_response": "That's the kind of humor that catches everyone off guard! 😄",
|
|
"personality_response": """This user demonstrates a consistent pattern of witty, observational humor.
|
|
They tend to find clever angles on everyday situations and have excellent timing with their comments.
|
|
Their humor style leans toward wordplay and situational comedy rather than dark or absurd humor.""",
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def performance_test_datasets():
|
|
"""Create datasets for performance testing."""
|
|
return {
|
|
"small_dataset": list(range(100)),
|
|
"medium_dataset": list(range(1000)),
|
|
"large_dataset": list(range(10000)),
|
|
"audio_samples": [
|
|
AudioTestData.create_sine_wave(freq, 0.1) for freq in [220, 440, 880, 1760]
|
|
],
|
|
"text_samples": [
|
|
f"This is test text sample number {i} with some content to process."
|
|
for i in range(500)
|
|
],
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
async def async_context_manager():
|
|
"""Create async context manager for testing."""
|
|
|
|
class TestAsyncContextManager:
|
|
def __init__(self):
|
|
self.entered = False
|
|
self.exited = False
|
|
self.exception_handled = None
|
|
|
|
async def __aenter__(self):
|
|
self.entered = True
|
|
return self
|
|
|
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
self.exited = True
|
|
self.exception_handled = exc_val
|
|
return False
|
|
|
|
return TestAsyncContextManager()
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_discord_api_responses():
|
|
"""Create mock Discord API responses for testing."""
|
|
return {
|
|
"message_response": {
|
|
"id": "123456789",
|
|
"content": "Test message",
|
|
"author": {"id": "987654321", "username": "TestUser"},
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
},
|
|
"guild_response": {
|
|
"id": "111222333",
|
|
"name": "Test Guild",
|
|
"owner_id": "444555666",
|
|
"member_count": 150,
|
|
},
|
|
"channel_response": {
|
|
"id": "777888999",
|
|
"name": "general",
|
|
"type": 0, # Text channel
|
|
"guild_id": "111222333",
|
|
},
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def error_scenarios():
|
|
"""Create various error scenarios for testing."""
|
|
return {
|
|
"rate_limit_error": discord.RateLimited(retry_after=30.0),
|
|
"permission_denied": discord.Forbidden("Missing permissions"),
|
|
"not_found": discord.NotFound("Resource not found"),
|
|
"server_error": discord.HTTPException("Internal server error"),
|
|
"timeout_error": asyncio.TimeoutError("Request timed out"),
|
|
"validation_error": ValidationError(
|
|
"Invalid input format", "validator", "check_input"
|
|
),
|
|
"processing_error": AudioProcessingError(
|
|
"Failed to process audio", "audio_processor", "convert_format"
|
|
),
|
|
}
|
|
|
|
|
|
# Utility functions for test fixtures
|
|
|
|
|
|
def create_temp_directory():
|
|
"""Create temporary directory for test files."""
|
|
temp_dir = tempfile.mkdtemp(prefix="disbord_test_")
|
|
return temp_dir
|
|
|
|
|
|
def cleanup_temp_files(*file_paths):
|
|
"""Clean up temporary files created during testing."""
|
|
for file_path in file_paths:
|
|
if file_path and os.path.exists(file_path):
|
|
try:
|
|
os.unlink(file_path)
|
|
except OSError:
|
|
pass # Ignore cleanup errors
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_directory():
|
|
"""Create temporary directory that's cleaned up after test."""
|
|
temp_dir = create_temp_directory()
|
|
yield temp_dir
|
|
|
|
# Cleanup
|
|
import shutil
|
|
|
|
if os.path.exists(temp_dir):
|
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def audio_test_files():
|
|
"""Create audio test files for session-wide use."""
|
|
files = {}
|
|
temp_dir = create_temp_directory()
|
|
|
|
try:
|
|
# Create different types of audio files
|
|
sine_wave = AudioTestData.create_sine_wave(440, 1.0)
|
|
noise = AudioTestData.create_white_noise(1.0)
|
|
silence = AudioTestData.create_silence(1.0)
|
|
|
|
for name, audio_data in [
|
|
("sine", sine_wave),
|
|
("noise", noise),
|
|
("silence", silence),
|
|
]:
|
|
pcm_data = AudioTestData.create_pcm_bytes(audio_data)
|
|
header = AudioTestData.create_wav_header(len(pcm_data))
|
|
|
|
file_path = os.path.join(temp_dir, f"{name}.wav")
|
|
with open(file_path, "wb") as f:
|
|
f.write(header + pcm_data)
|
|
|
|
files[name] = file_path
|
|
|
|
yield files
|
|
|
|
finally:
|
|
# Cleanup
|
|
import shutil
|
|
|
|
if os.path.exists(temp_dir):
|
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
|
|
|
|
# Mock factories for complex objects
|
|
|
|
|
|
class MockErrorHandlerFactory:
|
|
"""Factory for creating mock error handlers."""
|
|
|
|
@staticmethod
|
|
def create_mock_error_handler():
|
|
"""Create mock error handler for testing."""
|
|
handler = Mock()
|
|
handler.handle_error = Mock()
|
|
handler.get_error_category = Mock(return_value="test_category")
|
|
handler.get_error_severity = Mock(return_value="medium")
|
|
return handler
|
|
|
|
|
|
class MockMetricsCollectorFactory:
|
|
"""Factory for creating mock metrics collectors."""
|
|
|
|
@staticmethod
|
|
def create_mock_metrics_collector():
|
|
"""Create mock metrics collector for testing."""
|
|
collector = Mock()
|
|
collector.increment = Mock()
|
|
collector.observe_histogram = Mock()
|
|
collector.set_gauge = Mock()
|
|
collector.check_health = Mock(return_value={"status": "healthy"})
|
|
collector.export_metrics = AsyncMock(return_value="# Mock metrics data")
|
|
return collector
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_error_handler():
|
|
"""Create mock error handler."""
|
|
return MockErrorHandlerFactory.create_mock_error_handler()
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_metrics_collector():
|
|
"""Create mock metrics collector."""
|
|
return MockMetricsCollectorFactory.create_mock_metrics_collector()
|