Commonality extracted

This commit is contained in:
Cyber MacGeddon 2025-07-11 16:51:06 +01:00
parent d25c2f4d10
commit c496c53ff7
4 changed files with 225 additions and 75 deletions

View file

@ -0,0 +1,3 @@
"""
Common utilities for text completion tests
"""

View file

@ -0,0 +1,69 @@
"""
Base test patterns that can be reused across different text completion models
"""
from abc import ABC, abstractmethod
from unittest.mock import AsyncMock, MagicMock, patch
from unittest import IsolatedAsyncioTestCase
class BaseTextCompletionTestCase(IsolatedAsyncioTestCase, ABC):
"""
Base test class for text completion processors
Provides common test patterns that can be reused
"""
@abstractmethod
def get_processor_class(self):
"""Return the processor class to test"""
pass
@abstractmethod
def get_base_config(self):
"""Return base configuration for the processor"""
pass
@abstractmethod
def get_mock_patches(self):
"""Return list of patch decorators for mocking dependencies"""
pass
def create_base_config(self, **overrides):
"""Create base config with optional overrides"""
config = self.get_base_config()
config.update(overrides)
return config
def create_mock_llm_result(self, text="Test response", in_token=10, out_token=5):
"""Create a mock LLM result"""
from trustgraph.base import LlmResult
return LlmResult(text=text, in_token=in_token, out_token=out_token)
class CommonTestPatterns:
"""
Common test patterns that can be used across different models
"""
@staticmethod
def basic_initialization_test_pattern(test_instance):
"""
Test pattern for basic processor initialization
test_instance should be a BaseTextCompletionTestCase
"""
# This would contain the common pattern for initialization testing
pass
@staticmethod
def successful_generation_test_pattern(test_instance):
"""
Test pattern for successful content generation
"""
pass
@staticmethod
def error_handling_test_pattern(test_instance):
"""
Test pattern for error handling
"""
pass

View file

@ -0,0 +1,53 @@
"""
Common mocking utilities for text completion tests
"""
from unittest.mock import AsyncMock, MagicMock
class CommonMocks:
"""Common mock objects used across text completion tests"""
@staticmethod
def create_mock_async_processor_init():
"""Create mock for AsyncProcessor.__init__"""
mock = MagicMock()
mock.return_value = None
return mock
@staticmethod
def create_mock_llm_service_init():
"""Create mock for LlmService.__init__"""
mock = MagicMock()
mock.return_value = None
return mock
@staticmethod
def create_mock_response(text="Test response", prompt_tokens=10, completion_tokens=5):
"""Create a mock response object"""
response = MagicMock()
response.text = text
response.usage_metadata.prompt_token_count = prompt_tokens
response.usage_metadata.candidates_token_count = completion_tokens
return response
@staticmethod
def create_basic_config():
"""Create basic config with required fields"""
return {
'concurrency': 1,
'taskgroup': AsyncMock(),
'id': 'test-processor'
}
class MockPatches:
"""Common patch decorators for different services"""
@staticmethod
def get_base_patches():
"""Get patches that are common to all processors"""
return [
'trustgraph.base.async_processor.AsyncProcessor.__init__',
'trustgraph.base.llm_service.LlmService.__init__'
]

View file

@ -4,49 +4,19 @@ Pytest configuration and fixtures for text completion tests
import pytest
from unittest.mock import MagicMock, AsyncMock
from trustgraph.schema import TextCompletionRequest, TextCompletionResponse
from trustgraph.base import LlmResult
@pytest.fixture
def mock_vertexai_credentials():
"""Mock Google Cloud service account credentials"""
return MagicMock()
# === Common Fixtures for All Text Completion Models ===
@pytest.fixture
def mock_vertexai_model():
"""Mock VertexAI GenerativeModel"""
mock_model = MagicMock()
mock_response = MagicMock()
mock_response.text = "Test response"
mock_response.usage_metadata.prompt_token_count = 10
mock_response.usage_metadata.candidates_token_count = 5
mock_model.generate_content.return_value = mock_response
return mock_model
@pytest.fixture
def sample_text_completion_request():
"""Sample TextCompletionRequest for testing"""
return TextCompletionRequest(
id="test-request-id",
prompt="Test prompt",
system="Test system prompt",
temperature=0.7,
max_output=1024
)
@pytest.fixture
def sample_text_completion_response():
"""Sample TextCompletionResponse for testing"""
return TextCompletionResponse(
id="test-response-id",
response="Test response",
in_token=10,
out_token=5,
model="gemini-2.0-flash-001"
)
def base_processor_config():
"""Base configuration required by all processors"""
return {
'concurrency': 1,
'taskgroup': AsyncMock(),
'id': 'test-processor'
}
@pytest.fixture
@ -60,16 +30,19 @@ def sample_llm_result():
@pytest.fixture
def vertexai_processor_config():
"""Default configuration for VertexAI processor"""
return {
'region': 'us-central1',
'model': 'gemini-2.0-flash-001',
'temperature': 0.0,
'max_output': 8192,
'private_key': 'private.json',
'concurrency': 1
}
def mock_async_processor_init():
"""Mock AsyncProcessor.__init__ to avoid infrastructure requirements"""
mock = MagicMock()
mock.return_value = None
return mock
@pytest.fixture
def mock_llm_service_init():
"""Mock LlmService.__init__ to avoid infrastructure requirements"""
mock = MagicMock()
mock.return_value = None
return mock
@pytest.fixture
@ -92,21 +65,66 @@ def mock_pulsar_producer():
return AsyncMock()
@pytest.fixture(autouse=True)
def mock_env_vars(monkeypatch):
"""Mock environment variables for testing"""
monkeypatch.setenv("GOOGLE_CLOUD_PROJECT", "test-project")
monkeypatch.setenv("GOOGLE_APPLICATION_CREDENTIALS", "/path/to/test-credentials.json")
@pytest.fixture
def mock_flow_processor_config():
"""Mock flow processor configuration"""
return {
'service_id': 'test-vertexai-service',
'flow_name': 'test-flow',
'consumer_name': 'test-consumer'
}
def mock_async_context_manager():
"""Mock async context manager for testing"""
class MockAsyncContextManager:
def __init__(self, return_value):
self.return_value = return_value
async def __aenter__(self):
return self.return_value
async def __aexit__(self, exc_type, exc_val, exc_tb):
pass
return MockAsyncContextManager
# === VertexAI Specific Fixtures ===
@pytest.fixture
def mock_vertexai_credentials():
"""Mock Google Cloud service account credentials"""
return MagicMock()
@pytest.fixture
def mock_vertexai_model():
"""Mock VertexAI GenerativeModel"""
mock_model = MagicMock()
mock_response = MagicMock()
mock_response.text = "Test response"
mock_response.usage_metadata.prompt_token_count = 10
mock_response.usage_metadata.candidates_token_count = 5
mock_model.generate_content.return_value = mock_response
return mock_model
@pytest.fixture
def vertexai_processor_config(base_processor_config):
"""Default configuration for VertexAI processor"""
config = base_processor_config.copy()
config.update({
'region': 'us-central1',
'model': 'gemini-2.0-flash-001',
'temperature': 0.0,
'max_output': 8192,
'private_key': 'private.json'
})
return config
@pytest.fixture
def mock_safety_settings():
"""Mock safety settings for VertexAI"""
from unittest.mock import MagicMock
safety_settings = []
for i in range(4): # 4 safety categories
setting = MagicMock()
@ -136,24 +154,31 @@ def mock_vertexai_exception():
return ResourceExhausted("Test resource exhausted error")
@pytest.fixture(autouse=True)
def mock_env_vars(monkeypatch):
"""Mock environment variables for testing"""
monkeypatch.setenv("GOOGLE_CLOUD_PROJECT", "test-project")
monkeypatch.setenv("GOOGLE_APPLICATION_CREDENTIALS", "/path/to/test-credentials.json")
# === Ollama Specific Fixtures (for next implementation) ===
@pytest.fixture
def ollama_processor_config(base_processor_config):
"""Default configuration for Ollama processor"""
config = base_processor_config.copy()
config.update({
'model': 'llama2',
'temperature': 0.0,
'max_output': 8192,
'host': 'localhost',
'port': 11434
})
return config
@pytest.fixture
def mock_async_context_manager():
"""Mock async context manager for testing"""
class MockAsyncContextManager:
def __init__(self, return_value):
self.return_value = return_value
async def __aenter__(self):
return self.return_value
async def __aexit__(self, exc_type, exc_val, exc_tb):
pass
return MockAsyncContextManager
def mock_ollama_client():
"""Mock Ollama client"""
mock_client = MagicMock()
mock_response = {
'response': 'Test response from Ollama',
'done': True,
'eval_count': 5,
'prompt_eval_count': 10
}
mock_client.generate.return_value = mock_response
return mock_client