- _make_mock_llm_response now sets body_html/raw_text (not just text).
_create_blog_post writes body_html into blog.post.content; an unset
MagicMock attr stringified to ~66 chars, failing the 500-char assertion.
- Remove leftover `assert log.tokens_used` in then_tokens_used_recorded
that referenced a variable deleted in the prior router-level rewrite.
Full suite: 69 passed, 0 failed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PRODUCTION BUG: _create_blog_post never wrote the LLM body into the post.
Every auto-generated post was published empty. Add 'content': body_html
(content is the Odoo 17 blog.post body field; body_arch was removed).
BDD step/feature fixes (active features/ dir, not the dead tests/features/):
- body_arch → content in step + feature + given_published_blog_post
- then_non_empty_response: result.text → result.body_html (LLMResponse attr)
- llm_provider_selection feature: "provider not configured" → "not configured"
(matches LLMRouter.__init__ message; the generate() fallback never fires)
- then_tokens_used_recorded: assert on result.tokens_used (router returns a
response, it does not persist a log — that is the schedule's job)
- when_llm_router_called: configure the provider-under-test's own credential
(Background only sets the Anthropic key, so openai/gemini bailed early)
- fails-gracefully: invalid key now drives mock side_effect=UserError so
run_generation records an error log and creates no post
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PRODUCTION BUG: email template used pre-Odoo-14 Mako syntax (${} and
% for/% if), which Odoo 17 does not render — real notification emails
would show literal '${object.blog_post_id.name}' in the subject.
- subject/email_from: ${...} → {{ ... }} (inline_template engine)
- body_html: add type="qweb"; ${x} → <t t-out="x"/>;
% for → <t t-foreach t-as>; % if → <t t-if>
BDD: when_llm_router_called mocked provider.generate() returning raw
HTML in .text, but LLMRouter._parse_response expects JSON and raised
UserError before ctx['mock_generate'] was set. Now returns valid JSON
with all required fields, and records the mock before generate() runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pytest-odoo 2.x provides no 'env' pytest fixture; env only exists as
self.env in TransactionCase subclasses. Build the env from odoo.registry()
directly in the odoo_env fixture and rollback after each BDD scenario.
- _generate_template() takes positional args (res_ids, render_fields),
not keyword args; remove 'fields=' keyword from all call sites.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- mail.template has no generate_email() in Odoo 17; use _generate_template()
which takes (res_ids, render_fields) and returns rendered values per res_id
- Fix odoo_env fixture in test_bdd_steps.py to use request.getfixturevalue('env')
so pytest-bdd can resolve the pytest-odoo env fixture at scenario runtime
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixed router.generate() calls to use 'topic=' instead of 'prompt='.
Fixed post.social_ids reference to use correct field name 'itsulu_social_id'.
These corrections align with actual method signatures and model definitions.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Added a simple function-scoped fixture that wraps pytest-odoo's 'env'
fixture and re-exports it as 'odoo_env' for BDD step definitions.
This allows pytest-bdd scenarios to inject the Odoo environment
into step functions that expect the 'odoo_env' parameter.
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>
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>