Files
ss-tools/backend/src/services/__tests__/test_llm_provider.py

149 lines
5.1 KiB
Python

# [DEF:__tests__/test_llm_provider:Module]
# @RELATION: VERIFIES -> ../llm_provider.py
# @PURPOSE: Contract testing for LLMProviderService and EncryptionManager
# [/DEF:__tests__/test_llm_provider:Module]
import pytest
import os
from unittest.mock import MagicMock
from sqlalchemy.orm import Session
from cryptography.fernet import Fernet
from src.services.llm_provider import EncryptionManager, LLMProviderService
from src.models.llm import LLMProvider
from src.plugins.llm_analysis.models import LLMProviderConfig, LLMProviderType
# [DEF:_test_encryption_key_fixture:Global]
# @PURPOSE: Ensure encryption-dependent provider tests run with a valid Fernet key.
# @RELATION: DEPENDS_ON ->[pytest:Module]
os.environ.setdefault("ENCRYPTION_KEY", Fernet.generate_key().decode())
# [/DEF:_test_encryption_key_fixture:Global]
# @TEST_CONTRACT: EncryptionManagerModel -> Invariants
# @TEST_INVARIANT: symmetric_encryption
# [DEF:test_encryption_cycle:Function]
# @RELATION: BINDS_TO -> __tests__/test_llm_provider
def test_encryption_cycle():
"""Verify encrypted data can be decrypted back to original string."""
manager = EncryptionManager()
original = "secret_api_key_123"
encrypted = manager.encrypt(original)
assert encrypted != original
assert manager.decrypt(encrypted) == original
# @TEST_EDGE: empty_string_encryption
# [/DEF:test_encryption_cycle:Function]
# [DEF:test_empty_string_encryption:Function]
# @RELATION: BINDS_TO -> __tests__/test_llm_provider
def test_empty_string_encryption():
manager = EncryptionManager()
original = ""
encrypted = manager.encrypt(original)
assert manager.decrypt(encrypted) == ""
# @TEST_EDGE: decrypt_invalid_data
# [/DEF:test_empty_string_encryption:Function]
# [DEF:test_decrypt_invalid_data:Function]
# @RELATION: BINDS_TO -> __tests__/test_llm_provider
def test_decrypt_invalid_data():
manager = EncryptionManager()
with pytest.raises(Exception):
manager.decrypt("not-encrypted-string")
# @TEST_FIXTURE: mock_db_session
# [/DEF:test_decrypt_invalid_data:Function]
@pytest.fixture
def mock_db():
return MagicMock(spec=Session)
@pytest.fixture
def service(mock_db):
return LLMProviderService(db=mock_db)
# [DEF:test_get_all_providers:Function]
# @RELATION: BINDS_TO -> __tests__/test_llm_provider
def test_get_all_providers(service, mock_db):
service.get_all_providers()
mock_db.query.assert_called()
mock_db.query().all.assert_called()
# [/DEF:test_get_all_providers:Function]
# [DEF:test_create_provider:Function]
# @RELATION: BINDS_TO -> __tests__/test_llm_provider
def test_create_provider(service, mock_db):
config = LLMProviderConfig(
provider_type=LLMProviderType.OPENAI,
name="Test OpenAI",
base_url="https://api.openai.com",
api_key="sk-test",
default_model="gpt-4",
is_active=True
)
provider = service.create_provider(config)
mock_db.add.assert_called()
mock_db.commit.assert_called()
# Verify API key was encrypted
assert provider.api_key != "sk-test"
# Decrypt to verify it matches
assert EncryptionManager().decrypt(provider.api_key) == "sk-test"
# [/DEF:test_create_provider:Function]
# [DEF:test_get_decrypted_api_key:Function]
# @RELATION: BINDS_TO -> __tests__/test_llm_provider
def test_get_decrypted_api_key(service, mock_db):
# Setup mock provider
encrypted_key = EncryptionManager().encrypt("secret-value")
mock_provider = LLMProvider(id="p1", api_key=encrypted_key)
mock_db.query().filter().first.return_value = mock_provider
key = service.get_decrypted_api_key("p1")
assert key == "secret-value"
# [/DEF:test_get_decrypted_api_key:Function]
# [DEF:test_get_decrypted_api_key_not_found:Function]
# @RELATION: BINDS_TO -> __tests__/test_llm_provider
def test_get_decrypted_api_key_not_found(service, mock_db):
mock_db.query().filter().first.return_value = None
assert service.get_decrypted_api_key("missing") is None
# [/DEF:test_get_decrypted_api_key_not_found:Function]
# [DEF:test_update_provider_ignores_masked_placeholder_api_key:Function]
# @RELATION: BINDS_TO -> __tests__/test_llm_provider
def test_update_provider_ignores_masked_placeholder_api_key(service, mock_db):
existing_encrypted = EncryptionManager().encrypt("secret-value")
mock_provider = LLMProvider(
id="p1",
provider_type="openai",
name="Existing",
base_url="https://api.openai.com/v1",
api_key=existing_encrypted,
default_model="gpt-4o",
is_active=True,
)
mock_db.query().filter().first.return_value = mock_provider
config = LLMProviderConfig(
id="p1",
provider_type=LLMProviderType.OPENAI,
name="Existing",
base_url="https://api.openai.com/v1",
api_key="********",
default_model="gpt-4o",
is_active=False,
)
updated = service.update_provider("p1", config)
assert updated is mock_provider
assert updated.api_key == existing_encrypted
assert EncryptionManager().decrypt(updated.api_key) == "secret-value"
assert updated.is_active is False
# [/DEF:test_update_provider_ignores_masked_placeholder_api_key:Function]