fix: update test_llm_router to use topic parameter and valid JSON mocks
This commit is contained in:
parent
f26f896fe4
commit
02318799bb
1 changed files with 43 additions and 27 deletions
|
|
@ -6,12 +6,38 @@ Behaviour: route LLM calls to the correct provider, record token usage,
|
||||||
|
|
||||||
RED PHASE — all tests here FAIL until llm_router and provider services exist.
|
RED PHASE — all tests here FAIL until llm_router and provider services exist.
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import patch, MagicMock
|
||||||
from odoo.tests import TransactionCase, tagged
|
from odoo.tests import TransactionCase, tagged
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
from .factories import BlogPublisherFactory
|
from .factories import BlogPublisherFactory
|
||||||
|
|
||||||
|
|
||||||
|
def _make_mock_llm_response(tokens_used=800):
|
||||||
|
"""Create a mock LLM response with valid JSON structure."""
|
||||||
|
response_json = {
|
||||||
|
"title": "Test Blog Post",
|
||||||
|
"body_html": "<h1>Test</h1><p>Content here.</p>",
|
||||||
|
"meta_title": "Test Meta Title",
|
||||||
|
"meta_description": "Test meta description",
|
||||||
|
"meta_keywords": "test, blog",
|
||||||
|
"tags": ["test", "blog"],
|
||||||
|
"social": {
|
||||||
|
"twitter_a": "Tweet A {{URL}}",
|
||||||
|
"twitter_b": "Tweet B {{URL}}",
|
||||||
|
"bluesky_a": "BlueSky A {{URL}}",
|
||||||
|
"bluesky_b": "BlueSky B {{URL}}",
|
||||||
|
"mastodon": "Mastodon {{URL}}",
|
||||||
|
"linkedin": "LinkedIn {{URL}}"
|
||||||
|
},
|
||||||
|
"sources": []
|
||||||
|
}
|
||||||
|
mock_response = MagicMock()
|
||||||
|
mock_response.text = json.dumps(response_json)
|
||||||
|
mock_response.tokens_used = tokens_used
|
||||||
|
return mock_response
|
||||||
|
|
||||||
|
|
||||||
@tagged('post_install', '-at_install', 'itsulu_blog_publisher', 'llm_router')
|
@tagged('post_install', '-at_install', 'itsulu_blog_publisher', 'llm_router')
|
||||||
class TestLLMRouterProviderDispatch(TransactionCase):
|
class TestLLMRouterProviderDispatch(TransactionCase):
|
||||||
"""Verify that the LLM router dispatches to the correct backend provider."""
|
"""Verify that the LLM router dispatches to the correct backend provider."""
|
||||||
|
|
@ -46,9 +72,7 @@ class TestLLMRouterProviderDispatch(TransactionCase):
|
||||||
"""
|
"""
|
||||||
# ARRANGE
|
# ARRANGE
|
||||||
from odoo.addons.itsulu_blog_publisher.services.llm_router import LLMRouter
|
from odoo.addons.itsulu_blog_publisher.services.llm_router import LLMRouter
|
||||||
mock_response = MagicMock()
|
mock_response = _make_mock_llm_response(tokens_used=800)
|
||||||
mock_response.text = '<h1>AI Blog Post</h1><p>Content here...</p>'
|
|
||||||
mock_response.tokens_used = 800
|
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
'odoo.addons.itsulu_blog_publisher.services.anthropic_provider'
|
'odoo.addons.itsulu_blog_publisher.services.anthropic_provider'
|
||||||
|
|
@ -57,18 +81,16 @@ class TestLLMRouterProviderDispatch(TransactionCase):
|
||||||
) as mock_generate:
|
) as mock_generate:
|
||||||
# ACT
|
# ACT
|
||||||
router = LLMRouter(self.env, provider='anthropic', model='claude-sonnet-4-20250514')
|
router = LLMRouter(self.env, provider='anthropic', model='claude-sonnet-4-20250514')
|
||||||
result = router.generate(prompt='Write a blog post about AI trends')
|
result = router.generate(topic='Write a blog post about AI trends')
|
||||||
|
|
||||||
# ASSERT
|
# ASSERT
|
||||||
mock_generate.assert_called_once()
|
mock_generate.assert_called_once()
|
||||||
self.assertTrue(result.text, "Router must return non-empty text from Anthropic")
|
self.assertTrue(result.body_html, "Router must return non-empty body_html from Anthropic")
|
||||||
|
|
||||||
def test_openai_provider_calls_openai_endpoint(self):
|
def test_openai_provider_calls_openai_endpoint(self):
|
||||||
"""Router with provider='openai' delegates to OpenAIProvider.generate()."""
|
"""Router with provider='openai' delegates to OpenAIProvider.generate()."""
|
||||||
from odoo.addons.itsulu_blog_publisher.services.llm_router import LLMRouter
|
from odoo.addons.itsulu_blog_publisher.services.llm_router import LLMRouter
|
||||||
mock_response = MagicMock()
|
mock_response = _make_mock_llm_response(tokens_used=600)
|
||||||
mock_response.text = '<p>OpenAI generated content</p>'
|
|
||||||
mock_response.tokens_used = 600
|
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
'odoo.addons.itsulu_blog_publisher.services.openai_provider'
|
'odoo.addons.itsulu_blog_publisher.services.openai_provider'
|
||||||
|
|
@ -76,17 +98,15 @@ class TestLLMRouterProviderDispatch(TransactionCase):
|
||||||
return_value=mock_response,
|
return_value=mock_response,
|
||||||
) as mock_generate:
|
) as mock_generate:
|
||||||
router = LLMRouter(self.env, provider='openai', model='gpt-4o')
|
router = LLMRouter(self.env, provider='openai', model='gpt-4o')
|
||||||
result = router.generate(prompt='Write a blog post about cloud computing')
|
result = router.generate(topic='Write a blog post about cloud computing')
|
||||||
|
|
||||||
mock_generate.assert_called_once()
|
mock_generate.assert_called_once()
|
||||||
self.assertTrue(result.text)
|
self.assertTrue(result.body_html)
|
||||||
|
|
||||||
def test_gemini_provider_calls_gemini_endpoint(self):
|
def test_gemini_provider_calls_gemini_endpoint(self):
|
||||||
"""Router with provider='gemini' delegates to GeminiProvider.generate()."""
|
"""Router with provider='gemini' delegates to GeminiProvider.generate()."""
|
||||||
from odoo.addons.itsulu_blog_publisher.services.llm_router import LLMRouter
|
from odoo.addons.itsulu_blog_publisher.services.llm_router import LLMRouter
|
||||||
mock_response = MagicMock()
|
mock_response = _make_mock_llm_response(tokens_used=700)
|
||||||
mock_response.text = '<p>Gemini generated content</p>'
|
|
||||||
mock_response.tokens_used = 700
|
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
'odoo.addons.itsulu_blog_publisher.services.gemini_provider'
|
'odoo.addons.itsulu_blog_publisher.services.gemini_provider'
|
||||||
|
|
@ -94,17 +114,15 @@ class TestLLMRouterProviderDispatch(TransactionCase):
|
||||||
return_value=mock_response,
|
return_value=mock_response,
|
||||||
) as mock_generate:
|
) as mock_generate:
|
||||||
router = LLMRouter(self.env, provider='gemini', model='gemini-2.0-flash')
|
router = LLMRouter(self.env, provider='gemini', model='gemini-2.0-flash')
|
||||||
result = router.generate(prompt='Write a blog post about automation')
|
result = router.generate(topic='Write a blog post about automation')
|
||||||
|
|
||||||
mock_generate.assert_called_once()
|
mock_generate.assert_called_once()
|
||||||
self.assertTrue(result.text)
|
self.assertTrue(result.body_html)
|
||||||
|
|
||||||
def test_ollama_provider_calls_local_api_endpoint(self):
|
def test_ollama_provider_calls_local_api_endpoint(self):
|
||||||
"""Router with provider='ollama' delegates to OllamaProvider.generate()."""
|
"""Router with provider='ollama' delegates to OllamaProvider.generate()."""
|
||||||
from odoo.addons.itsulu_blog_publisher.services.llm_router import LLMRouter
|
from odoo.addons.itsulu_blog_publisher.services.llm_router import LLMRouter
|
||||||
mock_response = MagicMock()
|
mock_response = _make_mock_llm_response(tokens_used=500)
|
||||||
mock_response.text = '<p>Mistral generated content</p>'
|
|
||||||
mock_response.tokens_used = 500
|
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
'odoo.addons.itsulu_blog_publisher.services.ollama_provider'
|
'odoo.addons.itsulu_blog_publisher.services.ollama_provider'
|
||||||
|
|
@ -112,10 +130,10 @@ class TestLLMRouterProviderDispatch(TransactionCase):
|
||||||
return_value=mock_response,
|
return_value=mock_response,
|
||||||
) as mock_generate:
|
) as mock_generate:
|
||||||
router = LLMRouter(self.env, provider='ollama', model='mistral')
|
router = LLMRouter(self.env, provider='ollama', model='mistral')
|
||||||
result = router.generate(prompt='Write a blog post about open source AI')
|
result = router.generate(topic='Write a blog post about open source AI')
|
||||||
|
|
||||||
mock_generate.assert_called_once()
|
mock_generate.assert_called_once()
|
||||||
self.assertTrue(result.text)
|
self.assertTrue(result.body_html)
|
||||||
|
|
||||||
def test_unknown_provider_raises_user_error(self):
|
def test_unknown_provider_raises_user_error(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -126,9 +144,9 @@ class TestLLMRouterProviderDispatch(TransactionCase):
|
||||||
|
|
||||||
with self.assertRaises(UserError) as ctx:
|
with self.assertRaises(UserError) as ctx:
|
||||||
router = LLMRouter(self.env, provider='unknown_provider', model='some-model')
|
router = LLMRouter(self.env, provider='unknown_provider', model='some-model')
|
||||||
router.generate(prompt='This should fail')
|
router.generate(topic='This should fail')
|
||||||
|
|
||||||
self.assertIn('provider not configured', str(ctx.exception).lower())
|
self.assertIn('not configured', str(ctx.exception).lower())
|
||||||
|
|
||||||
def test_missing_api_key_raises_user_error(self):
|
def test_missing_api_key_raises_user_error(self):
|
||||||
"""Router raises UserError when required API key param is absent."""
|
"""Router raises UserError when required API key param is absent."""
|
||||||
|
|
@ -139,7 +157,7 @@ class TestLLMRouterProviderDispatch(TransactionCase):
|
||||||
)
|
)
|
||||||
with self.assertRaises(UserError) as ctx:
|
with self.assertRaises(UserError) as ctx:
|
||||||
router = LLMRouter(self.env, provider='anthropic', model='claude-sonnet-4-20250514')
|
router = LLMRouter(self.env, provider='anthropic', model='claude-sonnet-4-20250514')
|
||||||
router.generate(prompt='Fail fast')
|
router.generate(topic='Fail fast')
|
||||||
|
|
||||||
self.assertIn('api key', str(ctx.exception).lower())
|
self.assertIn('api key', str(ctx.exception).lower())
|
||||||
# Restore for other tests
|
# Restore for other tests
|
||||||
|
|
@ -166,9 +184,7 @@ class TestLLMRouterTokenLogging(TransactionCase):
|
||||||
tokens_used as a positive integer when the API returns usage data.
|
tokens_used as a positive integer when the API returns usage data.
|
||||||
"""
|
"""
|
||||||
from odoo.addons.itsulu_blog_publisher.services.llm_router import LLMRouter
|
from odoo.addons.itsulu_blog_publisher.services.llm_router import LLMRouter
|
||||||
mock_response = MagicMock()
|
mock_response = _make_mock_llm_response(tokens_used=1234)
|
||||||
mock_response.text = '<p>Content</p>'
|
|
||||||
mock_response.tokens_used = 1234
|
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
'odoo.addons.itsulu_blog_publisher.services.anthropic_provider'
|
'odoo.addons.itsulu_blog_publisher.services.anthropic_provider'
|
||||||
|
|
@ -176,7 +192,7 @@ class TestLLMRouterTokenLogging(TransactionCase):
|
||||||
return_value=mock_response,
|
return_value=mock_response,
|
||||||
):
|
):
|
||||||
router = LLMRouter(self.env, provider='anthropic', model='claude-sonnet-4-20250514')
|
router = LLMRouter(self.env, provider='anthropic', model='claude-sonnet-4-20250514')
|
||||||
result = router.generate(prompt='Token test')
|
result = router.generate(topic='Token test')
|
||||||
|
|
||||||
self.assertGreater(result.tokens_used, 0)
|
self.assertGreater(result.tokens_used, 0)
|
||||||
self.assertEqual(result.tokens_used, 1234)
|
self.assertEqual(result.tokens_used, 1234)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue