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.
|
||||
"""
|
||||
import json
|
||||
from unittest.mock import patch, MagicMock
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
from odoo.exceptions import UserError
|
||||
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')
|
||||
class TestLLMRouterProviderDispatch(TransactionCase):
|
||||
"""Verify that the LLM router dispatches to the correct backend provider."""
|
||||
|
|
@ -46,9 +72,7 @@ class TestLLMRouterProviderDispatch(TransactionCase):
|
|||
"""
|
||||
# ARRANGE
|
||||
from odoo.addons.itsulu_blog_publisher.services.llm_router import LLMRouter
|
||||
mock_response = MagicMock()
|
||||
mock_response.text = '<h1>AI Blog Post</h1><p>Content here...</p>'
|
||||
mock_response.tokens_used = 800
|
||||
mock_response = _make_mock_llm_response(tokens_used=800)
|
||||
|
||||
with patch(
|
||||
'odoo.addons.itsulu_blog_publisher.services.anthropic_provider'
|
||||
|
|
@ -57,18 +81,16 @@ class TestLLMRouterProviderDispatch(TransactionCase):
|
|||
) as mock_generate:
|
||||
# ACT
|
||||
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
|
||||
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):
|
||||
"""Router with provider='openai' delegates to OpenAIProvider.generate()."""
|
||||
from odoo.addons.itsulu_blog_publisher.services.llm_router import LLMRouter
|
||||
mock_response = MagicMock()
|
||||
mock_response.text = '<p>OpenAI generated content</p>'
|
||||
mock_response.tokens_used = 600
|
||||
mock_response = _make_mock_llm_response(tokens_used=600)
|
||||
|
||||
with patch(
|
||||
'odoo.addons.itsulu_blog_publisher.services.openai_provider'
|
||||
|
|
@ -76,17 +98,15 @@ class TestLLMRouterProviderDispatch(TransactionCase):
|
|||
return_value=mock_response,
|
||||
) as mock_generate:
|
||||
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()
|
||||
self.assertTrue(result.text)
|
||||
self.assertTrue(result.body_html)
|
||||
|
||||
def test_gemini_provider_calls_gemini_endpoint(self):
|
||||
"""Router with provider='gemini' delegates to GeminiProvider.generate()."""
|
||||
from odoo.addons.itsulu_blog_publisher.services.llm_router import LLMRouter
|
||||
mock_response = MagicMock()
|
||||
mock_response.text = '<p>Gemini generated content</p>'
|
||||
mock_response.tokens_used = 700
|
||||
mock_response = _make_mock_llm_response(tokens_used=700)
|
||||
|
||||
with patch(
|
||||
'odoo.addons.itsulu_blog_publisher.services.gemini_provider'
|
||||
|
|
@ -94,17 +114,15 @@ class TestLLMRouterProviderDispatch(TransactionCase):
|
|||
return_value=mock_response,
|
||||
) as mock_generate:
|
||||
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()
|
||||
self.assertTrue(result.text)
|
||||
self.assertTrue(result.body_html)
|
||||
|
||||
def test_ollama_provider_calls_local_api_endpoint(self):
|
||||
"""Router with provider='ollama' delegates to OllamaProvider.generate()."""
|
||||
from odoo.addons.itsulu_blog_publisher.services.llm_router import LLMRouter
|
||||
mock_response = MagicMock()
|
||||
mock_response.text = '<p>Mistral generated content</p>'
|
||||
mock_response.tokens_used = 500
|
||||
mock_response = _make_mock_llm_response(tokens_used=500)
|
||||
|
||||
with patch(
|
||||
'odoo.addons.itsulu_blog_publisher.services.ollama_provider'
|
||||
|
|
@ -112,10 +130,10 @@ class TestLLMRouterProviderDispatch(TransactionCase):
|
|||
return_value=mock_response,
|
||||
) as mock_generate:
|
||||
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()
|
||||
self.assertTrue(result.text)
|
||||
self.assertTrue(result.body_html)
|
||||
|
||||
def test_unknown_provider_raises_user_error(self):
|
||||
"""
|
||||
|
|
@ -126,9 +144,9 @@ class TestLLMRouterProviderDispatch(TransactionCase):
|
|||
|
||||
with self.assertRaises(UserError) as ctx:
|
||||
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):
|
||||
"""Router raises UserError when required API key param is absent."""
|
||||
|
|
@ -139,7 +157,7 @@ class TestLLMRouterProviderDispatch(TransactionCase):
|
|||
)
|
||||
with self.assertRaises(UserError) as ctx:
|
||||
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())
|
||||
# Restore for other tests
|
||||
|
|
@ -166,9 +184,7 @@ class TestLLMRouterTokenLogging(TransactionCase):
|
|||
tokens_used as a positive integer when the API returns usage data.
|
||||
"""
|
||||
from odoo.addons.itsulu_blog_publisher.services.llm_router import LLMRouter
|
||||
mock_response = MagicMock()
|
||||
mock_response.text = '<p>Content</p>'
|
||||
mock_response.tokens_used = 1234
|
||||
mock_response = _make_mock_llm_response(tokens_used=1234)
|
||||
|
||||
with patch(
|
||||
'odoo.addons.itsulu_blog_publisher.services.anthropic_provider'
|
||||
|
|
@ -176,7 +192,7 @@ class TestLLMRouterTokenLogging(TransactionCase):
|
|||
return_value=mock_response,
|
||||
):
|
||||
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.assertEqual(result.tokens_used, 1234)
|
||||
|
|
|
|||
Loading…
Reference in a new issue