204 lines
6.2 KiB
Python
204 lines
6.2 KiB
Python
"""Test suite for Opus Orchestrator.
|
|
|
|
Tests for core functionality - these can run without API keys.
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, patch, MagicMock
|
|
|
|
from opus_orchestrator.config import (
|
|
OpusConfig,
|
|
AgentConfig,
|
|
CostConfig,
|
|
IterationConfig,
|
|
load_config_from_env,
|
|
)
|
|
|
|
|
|
class TestConfig:
|
|
"""Tests for configuration."""
|
|
|
|
def test_default_config(self):
|
|
"""Test default configuration is valid."""
|
|
config = OpusConfig()
|
|
assert config.agent.model is not None
|
|
assert config.agent.temperature == 0.7
|
|
assert config.iteration.max_critic_rounds == 5
|
|
|
|
def test_cost_config_defaults(self):
|
|
"""Test cost config has defaults."""
|
|
cost = CostConfig()
|
|
assert cost.track_usage is True
|
|
assert "gpt-4o" in cost.price_per_million_tokens
|
|
|
|
def test_iteration_config(self):
|
|
"""Test iteration config."""
|
|
iteration = IterationConfig()
|
|
assert iteration.approval_threshold == 0.8
|
|
assert iteration.auto_proceed_threshold == 0.9
|
|
assert iteration.max_critic_rounds >= iteration.min_critic_rounds
|
|
|
|
|
|
class TestSchemas:
|
|
"""Tests for Pydantic schemas."""
|
|
|
|
def test_book_intent_validation(self):
|
|
"""Test BookIntent validation."""
|
|
from opus_orchestrator.schemas import BookIntent, BookType
|
|
|
|
intent = BookIntent(
|
|
book_type=BookType.FICTION,
|
|
genre="fantasy",
|
|
target_audience="young adult",
|
|
intended_outcome="complete novel",
|
|
target_word_count=80000,
|
|
)
|
|
|
|
assert intent.book_type == BookType.FICTION
|
|
assert intent.target_word_count == 80000
|
|
|
|
def test_chapter_blueprint(self):
|
|
"""Test ChapterBlueprint validation."""
|
|
from opus_orchestrator.schemas import ChapterBlueprint
|
|
|
|
chapter = ChapterBlueprint(
|
|
chapter_number=1,
|
|
title="The Beginning",
|
|
summary="Our hero starts their journey",
|
|
word_count_target=3000,
|
|
)
|
|
|
|
assert chapter.chapter_number == 1
|
|
assert chapter.word_count_target == 3000
|
|
|
|
|
|
class TestFrameworks:
|
|
"""Tests for story frameworks."""
|
|
|
|
def test_get_framework_prompt(self):
|
|
"""Test framework prompt generation."""
|
|
from opus_orchestrator.frameworks import get_framework_prompt, StoryFramework
|
|
|
|
# Test all frameworks have prompts
|
|
for framework in StoryFramework:
|
|
prompt = get_framework_prompt(framework)
|
|
assert prompt is not None
|
|
assert len(prompt) > 0
|
|
|
|
def test_framework_for_genre(self):
|
|
"""Test framework suggestions by genre."""
|
|
from opus_orchestrator.frameworks import get_framework_for_genre
|
|
|
|
# Fantasy should suggest Hero's Journey
|
|
suggestions = get_framework_for_genre("fantasy")
|
|
assert len(suggestions) > 0
|
|
|
|
# Unknown genre should fallback
|
|
suggestions = get_framework_for_genre("unknown")
|
|
assert len(suggestions) > 0
|
|
|
|
|
|
class TestGitHubIngestor:
|
|
"""Tests for GitHub ingestion."""
|
|
|
|
def test_ingestor_without_token(self):
|
|
"""Test GitHubIngestor works without token."""
|
|
from opus_orchestrator.utils.github_ignest import GitHubIngestor
|
|
|
|
# Should not raise without token
|
|
ingestor = GitHubIngestor(token=None)
|
|
assert ingestor.headers is not None
|
|
assert "Accept" in ingestor.headers
|
|
|
|
def test_ingestor_with_token(self):
|
|
"""Test GitHubIngestor with token."""
|
|
from opus_orchestrator.utils.github_ignest import GitHubIngestor
|
|
|
|
ingestor = GitHubIngestor(token="test_token")
|
|
assert "Authorization" in ingestor.headers
|
|
|
|
|
|
class TestAgentResponse:
|
|
"""Tests for agent responses."""
|
|
|
|
def test_agent_response_success(self):
|
|
"""Test successful agent response."""
|
|
from opus_orchestrator.agents.base import AgentResponse
|
|
|
|
response = AgentResponse(
|
|
success=True,
|
|
output="Test output",
|
|
metadata={"key": "value"},
|
|
)
|
|
|
|
assert response.success is True
|
|
assert response.output == "Test output"
|
|
assert response.error is None
|
|
|
|
def test_agent_response_error(self):
|
|
"""Test error agent response."""
|
|
from opus_orchestrator.agents.base import AgentResponse
|
|
|
|
response = AgentResponse(
|
|
success=False,
|
|
output=None,
|
|
error="Something went wrong",
|
|
)
|
|
|
|
assert response.success is False
|
|
assert response.error == "Something went wrong"
|
|
|
|
|
|
# Mock tests that require API keys
|
|
class TestLLMClient:
|
|
"""Tests for LLM client (mocked)."""
|
|
|
|
@patch('opus_orchestrator.utils.llm.requests.post')
|
|
def test_sync_client_openai(self, mock_post):
|
|
"""Test synchronous OpenAI client."""
|
|
from opus_orchestrator.utils.llm import LLMClient
|
|
|
|
# Mock response
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {
|
|
"choices": [{"message": {"content": "Test response"}}]
|
|
}
|
|
mock_post.return_value = mock_response
|
|
|
|
client = LLMClient(
|
|
api_key="test_key",
|
|
provider="openai",
|
|
model="gpt-4o",
|
|
)
|
|
|
|
result = client.complete(
|
|
system_prompt="System",
|
|
user_prompt="User",
|
|
)
|
|
|
|
assert result == "Test response"
|
|
mock_post.assert_called_once()
|
|
|
|
|
|
# Integration-like tests (need environment)
|
|
class TestIntegration:
|
|
"""Integration tests - skip if no API key."""
|
|
|
|
@pytest.mark.skipif(
|
|
not __import__('os').environ.get('OPENAI_API_KEY'),
|
|
reason="No API key"
|
|
)
|
|
def test_real_api_call(self):
|
|
"""Test actual API call if key exists."""
|
|
from opus_orchestrator.utils.llm import LLMClient
|
|
|
|
client = LLMClient(provider="openai", model="gpt-4o")
|
|
|
|
result = client.complete(
|
|
system_prompt="You are a helpful assistant.",
|
|
user_prompt="Say 'test' if you receive this.",
|
|
)
|
|
|
|
assert "test" in result.lower()
|