"""Unit tests for ISpeechRecognitionService interface contract.""" import pytest from abc import ABC from unittest.mock import Mock from src.domain.interfaces.speech_recognition import ISpeechRecognitionService from src.domain.models.audio_content import AudioContent from src.domain.models.text_content import TextContent class TestISpeechRecognitionService: """Test cases for ISpeechRecognitionService interface contract.""" def test_interface_is_abstract(self): """Test that ISpeechRecognitionService is an abstract base class.""" assert issubclass(ISpeechRecognitionService, ABC) # Should not be able to instantiate directly with pytest.raises(TypeError): ISpeechRecognitionService() # type: ignore def test_interface_has_required_method(self): """Test that interface defines the required abstract method.""" # Check that the method exists and is abstract assert hasattr(ISpeechRecognitionService, 'transcribe') assert getattr(ISpeechRecognitionService.transcribe, '__isabstractmethod__', False) def test_method_signature(self): """Test that the method has the correct signature.""" import inspect method = ISpeechRecognitionService.transcribe signature = inspect.signature(method) # Check parameter names params = list(signature.parameters.keys()) expected_params = ['self', 'audio', 'model'] assert params == expected_params # Check return annotation assert signature.return_annotation == "'TextContent'" def test_concrete_implementation_must_implement_method(self): """Test that concrete implementations must implement the abstract method.""" class IncompleteImplementation(ISpeechRecognitionService): pass # Should not be able to instantiate without implementing abstract method with pytest.raises(TypeError, match="Can't instantiate abstract class"): IncompleteImplementation() # type: ignore def test_concrete_implementation_with_method(self): """Test that concrete implementation with method can be instantiated.""" class ConcreteImplementation(ISpeechRecognitionService): def transcribe(self, audio, model): return TextContent(text="transcribed text", language="en") # Should be able to instantiate implementation = ConcreteImplementation() assert isinstance(implementation, ISpeechRecognitionService) def test_method_contract_with_mock(self): """Test the method contract using a mock implementation.""" class MockImplementation(ISpeechRecognitionService): def __init__(self): self.mock_method = Mock() def transcribe(self, audio, model): return self.mock_method(audio, model) # Create test data audio = AudioContent( data=b"test_audio", format="wav", sample_rate=22050, duration=5.0 ) model = "whisper-base" expected_result = TextContent(text="Hello world", language="en") # Setup mock implementation = MockImplementation() implementation.mock_method.return_value = expected_result # Call method result = implementation.transcribe(audio=audio, model=model) # Verify call and result implementation.mock_method.assert_called_once_with(audio, model) assert result == expected_result def test_interface_docstring_requirements(self): """Test that the interface method has proper documentation.""" method = ISpeechRecognitionService.transcribe assert method.__doc__ is not None docstring = method.__doc__ # Check that docstring contains key information assert "Transcribe audio content to text" in docstring assert "Args:" in docstring assert "Returns:" in docstring assert "Raises:" in docstring assert "SpeechRecognitionException" in docstring def test_interface_type_hints(self): """Test that the interface uses proper type hints.""" method = ISpeechRecognitionService.transcribe annotations = getattr(method, '__annotations__', {}) assert 'audio' in annotations assert 'model' in annotations assert 'return' in annotations # Check that type annotations are correct assert annotations['audio'] == "'AudioContent'" assert annotations['model'] == str assert annotations['return'] == "'TextContent'" def test_multiple_implementations_possible(self): """Test that multiple implementations of the interface are possible.""" class WhisperImplementation(ISpeechRecognitionService): def transcribe(self, audio, model): return TextContent(text="whisper transcription", language="en") class ParakeetImplementation(ISpeechRecognitionService): def transcribe(self, audio, model): return TextContent(text="parakeet transcription", language="en") whisper = WhisperImplementation() parakeet = ParakeetImplementation() assert isinstance(whisper, ISpeechRecognitionService) assert isinstance(parakeet, ISpeechRecognitionService) assert type(whisper) != type(parakeet) def test_interface_method_can_be_called_polymorphically(self): """Test that interface methods can be called polymorphically.""" class TestImplementation(ISpeechRecognitionService): def __init__(self, transcription_text): self.transcription_text = transcription_text def transcribe(self, audio, model): return TextContent(text=self.transcription_text, language="en") # Create different implementations implementations = [ TestImplementation("first transcription"), TestImplementation("second transcription") ] # Test polymorphic usage audio = AudioContent(data=b"test", format="wav", sample_rate=22050, duration=1.0) model = "test-model" results = [] for impl in implementations: # Can call the same method on different implementations result = impl.transcribe(audio, model) results.append(result.text) assert results == ["first transcription", "second transcription"] def test_interface_inheritance_chain(self): """Test the inheritance chain of the interface.""" # Check that it inherits from ABC assert ABC in ISpeechRecognitionService.__mro__ # Check that it's at the right position in MRO mro = ISpeechRecognitionService.__mro__ assert mro[0] == ISpeechRecognitionService assert ABC in mro def test_method_parameter_validation_in_implementation(self): """Test that implementations can validate parameters.""" class ValidatingImplementation(ISpeechRecognitionService): def transcribe(self, audio, model): if not isinstance(audio, AudioContent): raise TypeError("audio must be AudioContent") if not isinstance(model, str): raise TypeError("model must be string") if not model.strip(): raise ValueError("model cannot be empty") return TextContent(text="validated transcription", language="en") impl = ValidatingImplementation() # Valid call should work audio = AudioContent(data=b"test", format="wav", sample_rate=22050, duration=1.0) result = impl.transcribe(audio, "whisper-base") assert result.text == "validated transcription" # Invalid calls should raise appropriate errors with pytest.raises(TypeError, match="audio must be AudioContent"): impl.transcribe("not audio", "whisper-base") # type: ignore with pytest.raises(TypeError, match="model must be string"): impl.transcribe(audio, 123) # type: ignore with pytest.raises(ValueError, match="model cannot be empty"): impl.transcribe(audio, "") def test_implementation_can_handle_different_models(self): """Test that implementations can handle different model types.""" class MultiModelImplementation(ISpeechRecognitionService): def transcribe(self, audio, model): model_responses = { "whisper-tiny": "tiny transcription", "whisper-base": "base transcription", "whisper-large": "large transcription", "parakeet": "parakeet transcription" } transcription = model_responses.get(model, "unknown model transcription") return TextContent(text=transcription, language="en") impl = MultiModelImplementation() audio = AudioContent(data=b"test", format="wav", sample_rate=22050, duration=1.0) # Test different models models_and_expected = [ ("whisper-tiny", "tiny transcription"), ("whisper-base", "base transcription"), ("whisper-large", "large transcription"), ("parakeet", "parakeet transcription"), ("unknown-model", "unknown model transcription") ] for model, expected_text in models_and_expected: result = impl.transcribe(audio, model) assert result.text == expected_text assert result.language == "en"