The blog.post model from website_blog has various validation constraints
that aren't relevant for testing. Using sudo() allows test records to be
created with minimal required fields only.
Changed assertRaises((ValidationError, Exception)) to assertRaises(ValidationError).
Odoo's test framework (TransactionCase) doesn't support tuple of exception classes
in assertRaises; must use single exception class.
All 7 blog_topic tests now PASS:
✅ test_topic_is_created_with_pending_state
✅ test_get_next_topic_returns_highest_priority_pending_topic
✅ test_get_next_topic_returns_none_when_queue_is_empty
✅ test_mark_topic_as_used_changes_state
✅ test_topic_can_be_linked_to_a_specific_blog
✅ test_topic_name_cannot_be_empty
✅ test_used_topic_is_excluded_from_next_topic_selection
Execution time: 0.47s (after ~60s addon installation).
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Split monolithic blog_generation.feature into separate files per feature:
* blog_generation.feature: On-demand AI blog generation (3 scenarios)
* blog_scheduling.feature: Scheduled posts (2 scenarios)
* llm_provider_selection.feature: Multi-provider routing (6 scenarios)
* seo_population.feature: SEO field population (1 scenario)
* notification_email.feature: Post-generation emails (2 scenarios)
Total: 14 BDD scenarios covering all major workflows
- Extended test_bdd_steps.py from 363 to 472 lines with new step definitions:
* Added no_email_sent() for draft post email suppression verification
* Added email_contains_title() for email content validation
* Added email_contains_social_copy() for platform copy verification
* Added blog_post_has_tags(), blog_post_has_tag() for tag verification
* Added blog_post_has_social_copy(), at_least_one_platform_enabled()
* Added log_has_correct_provider(), log_has_correct_model()
* Added log_trigger_source(), generation_duration_recorded()
Follows pytest-bdd best practices: one feature per file, each with dedicated
scenarios and step definitions. All 14 scenarios now have complete step coverage.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Implement LLMRouter class and all LLM provider classes to make tests pass:
Core implementation:
- Create ProviderResponse dataclass for provider returns (text, tokens_used)
- Update LLMRouter to unpack ProviderResponse objects
- Implement all 4 providers to return ProviderResponse:
* AnthropicProvider - calls Anthropic API with structured JSON prompts
* OpenAIProvider - calls OpenAI /v1/chat/completions endpoint
* GeminiProvider - calls Google Gemini generateContent API
* OllamaProvider - calls Ollama native or OpenAI-compatible endpoints
Router features:
- Validates provider at init time, raises UserError for unknown providers
- Reads API keys from ir.config_parameter at call time
- Builds structured prompts from templates with variable substitution
- Parses JSON response from LLM and validates required fields
- Enforces character limits on SEO and social fields
- Returns LLMResponse with full blog post structure
Services structure:
- Create services/__init__.py with exports
- Create models/__init__.py with exports
- Create tests/__init__.py with test module imports
This completes the GREEN phase for LLM Router tests.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Restructure project files to follow the addon layout:
- Move models to addons/itsulu_blog_publisher/models/
- Move services (LLM providers, routers) to addons/itsulu_blog_publisher/services/
- Move wizards to addons/itsulu_blog_publisher/wizards/
- Move views (XML templates) to addons/itsulu_blog_publisher/views/
- Move data (cron, mail templates) to addons/itsulu_blog_publisher/data/
- Move security (ACL) to addons/itsulu_blog_publisher/security/
- Move tests and factories to addons/itsulu_blog_publisher/tests/
- Move BDD features to addons/itsulu_blog_publisher/features/
- Create __init__.py files for all Python packages
This enables proper Odoo module discovery and import structure.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>