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>
167 lines
7.2 KiB
Gherkin
167 lines
7.2 KiB
Gherkin
# =================================================================
|
|
# addons/itsulu_blog_publisher/features/blog_generation.feature
|
|
# =================================================================
|
|
Feature: On-demand AI blog post generation
|
|
As an ITSulu content admin
|
|
I want to trigger blog post generation with a single button click
|
|
So that I no longer need CoWork running on a Windows VM
|
|
|
|
Background:
|
|
Given the Anthropic API key is configured in Settings
|
|
And the blog "ITSulu Insights" exists in Odoo
|
|
|
|
Scenario: Generate and auto-publish a blog post from the backend
|
|
Given I am on the Blog Publisher backend form
|
|
And I enter topic "Prompt Governance in Enterprise AI"
|
|
And I select provider "anthropic" and model "claude-sonnet-4-20250514"
|
|
And I set auto-publish to True
|
|
When I click "Generate Now"
|
|
Then a blog.post record is created with a non-empty title
|
|
And the blog.post body_arch contains at least 500 characters of HTML
|
|
And the blog.post is_published is True
|
|
And the SEO fields website_meta_title and website_meta_description are populated
|
|
And a generation log entry exists with state "success"
|
|
|
|
Scenario: Generate a blog post and leave it as draft
|
|
Given I am on the Blog Publisher backend form
|
|
And I enter topic "Open Source LLMs for Business"
|
|
And I set auto-publish to False
|
|
When I click "Generate Now"
|
|
Then a blog.post record is created with a non-empty title
|
|
And the blog.post is_published is False
|
|
|
|
Scenario: LLM API call fails gracefully
|
|
Given the Anthropic API key is invalid
|
|
And I enter topic "Any topic"
|
|
And I set auto-publish to True
|
|
When I click "Generate Now"
|
|
Then no blog.post record is created
|
|
And a generation log entry exists with state "error"
|
|
And the log contains a human-readable error message
|
|
And a "Retry" button is visible on the log record
|
|
|
|
|
|
# =================================================================
|
|
# addons/itsulu_blog_publisher/features/blog_scheduling.feature
|
|
# =================================================================
|
|
Feature: Scheduled blog post generation
|
|
As an ITSulu content admin
|
|
I want to schedule three blog posts per day automatically
|
|
So that content is published without manual intervention
|
|
|
|
Background:
|
|
Given the Anthropic API key is configured in Settings
|
|
And the blog "ITSulu Insights" exists in Odoo
|
|
|
|
Scenario: Active morning slot creates a blog post when run
|
|
Given I am on the Blog Publisher backend form
|
|
And I set auto-publish to True
|
|
And I enter topic "Morning AI Topic"
|
|
When I click "Generate Now"
|
|
Then a blog.post record is created with a non-empty title
|
|
And a generation log entry exists with state "success"
|
|
|
|
Scenario: Generate a blog post and leave it as draft
|
|
Given I am on the Blog Publisher backend form
|
|
And I enter topic "Scheduled Draft Topic"
|
|
And I set auto-publish to False
|
|
When I click "Generate Now"
|
|
Then a blog.post record is created with a non-empty title
|
|
And the blog.post is_published is False
|
|
|
|
|
|
# =================================================================
|
|
# addons/itsulu_blog_publisher/features/llm_provider_selection.feature
|
|
# =================================================================
|
|
Feature: Multi-provider LLM routing
|
|
As an ITSulu admin
|
|
I want to choose which LLM provider generates each blog post
|
|
So that I can control cost, quality, and availability
|
|
|
|
Background:
|
|
Given the Anthropic API key is configured in Settings
|
|
And the blog "ITSulu Insights" exists in Odoo
|
|
|
|
Scenario: Anthropic provider generates blog content
|
|
Given provider is "anthropic" and model is "claude-sonnet-4-20250514"
|
|
When the LLM router is called with a prompt
|
|
Then the router calls the Anthropic /v1/messages endpoint
|
|
And returns a non-empty string response
|
|
|
|
Scenario: OpenAI provider generates blog content
|
|
Given provider is "openai" and model is "gpt-4o"
|
|
When the LLM router is called with a prompt
|
|
Then the router calls the OpenAI /v1/chat/completions endpoint
|
|
And returns a non-empty string response
|
|
|
|
Scenario: Gemini provider generates blog content
|
|
Given provider is "gemini" and model is "gemini-2.0-flash"
|
|
When the LLM router is called with a prompt
|
|
Then the router calls the Google Gemini API endpoint
|
|
And returns a non-empty string response
|
|
|
|
Scenario: Ollama provider generates blog content using local model
|
|
Given provider is "ollama" and model is "mistral"
|
|
And the Ollama base URL is "http://localhost:11434"
|
|
When the LLM router is called with a prompt
|
|
Then the router calls http://localhost:11434/api/chat
|
|
And returns a non-empty string response
|
|
|
|
Scenario: Unknown provider raises configuration error
|
|
Given provider is "unknown_provider" and model is "some-model"
|
|
When the LLM router is called with a prompt
|
|
Then a UserError is raised with message containing "provider not configured"
|
|
|
|
Scenario: Token usage is recorded in generation log
|
|
Given provider is "anthropic" and model is "claude-sonnet-4-20250514"
|
|
When the LLM router is called with a prompt
|
|
Then the generation log record contains tokens_used > 0
|
|
|
|
|
|
# =================================================================
|
|
# addons/itsulu_blog_publisher/features/seo_population.feature
|
|
# =================================================================
|
|
Feature: SEO fields populated on every generated blog post
|
|
As an ITSulu content admin
|
|
I want all SEO fields filled automatically
|
|
So that every post is search-engine ready at publication
|
|
|
|
Background:
|
|
Given the Anthropic API key is configured in Settings
|
|
And the blog "ITSulu Insights" exists in Odoo
|
|
|
|
Scenario: All SEO fields are populated after generation
|
|
Given I am on the Blog Publisher backend form
|
|
And I enter topic "AI Governance Enterprise"
|
|
And I set auto-publish to True
|
|
When I click "Generate Now"
|
|
Then the SEO fields website_meta_title and website_meta_description are populated
|
|
And a generation log entry exists with state "success"
|
|
|
|
|
|
# =================================================================
|
|
# addons/itsulu_blog_publisher/features/notification_email.feature
|
|
# =================================================================
|
|
Feature: Post-generation notification email
|
|
As an ITSulu content admin
|
|
I want to receive one email after each successful blog post publication
|
|
So that I have the social media copy ready to paste
|
|
|
|
Background:
|
|
Given the notification email recipient is "nicholasr@itsulu.com"
|
|
|
|
Scenario: Notification email is sent after successful auto-publish
|
|
Given a blog post was generated and auto-published
|
|
When the generation completes
|
|
Then exactly one email is sent to "nicholasr@itsulu.com"
|
|
And the email subject matches "[ITSulu Insights] Blog Post Published: {title} - {date}"
|
|
|
|
Scenario: Notification email is NOT sent for draft posts
|
|
Given the Anthropic API key is configured in Settings
|
|
And the blog "ITSulu Insights" exists in Odoo
|
|
And I am on the Blog Publisher backend form
|
|
And I enter topic "Draft Topic No Email"
|
|
And I set auto-publish to False
|
|
When I click "Generate Now"
|
|
Then a blog.post record is created with a non-empty title
|
|
And the blog.post is_published is False
|