fix: use _render_field() for template rendering; fix wizard import name

- Replace _generate_template() (returns unrendered Mako) with
  _render_field(field, res_ids) from MailRenderMixin — returns
  {res_id: rendered_string} with Mako fully evaluated
- Fix wizard import: class is BlogGenerateWizard, not GenerateNowWizard
- Update CLAUDE.md with correct pytest-odoo 2.x BDD env fixture pattern

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nicholas Riegel 2026-05-30 02:53:57 -04:00
parent 3281298f5e
commit 687b1bfa0f
3 changed files with 10 additions and 9 deletions

View file

@ -850,8 +850,9 @@ post = self.env['blog.post'].sudo().create({
| Mail test: `mail.mail.subject` is `False` instead of string | Template subject field not rendered by send_mail() | Verify template fields are populated in DB; render manually if needed |
| Odoo 17: `blog.post` doesn't accept `body` or `body_arch` fields | Fields renamed/removed in Odoo 17 | Use factory pattern with `.sudo().create()` to bypass validation; post content should be managed via editor interface, not direct assignment |
| Odoo 17: Inverse relationship not loaded in template context | Relations lazy-load; mail.template context may not include reverse relations | Use explicit search in Mako (`for loop`) instead of relying on inverse field; or fetch record with prefetch in the render context |
| Odoo 17: `mail.template` has no `generate_email()` | Method removed in Odoo 17; API changed entirely | Use `template._generate_template([res_id], ['subject', 'body_html'])` — returns `{res_id: {field: value}}`. Never use `generate_email(res_id)` (old Odoo 16 API) |
| Odoo 17: `mail.template` has no `generate_email()` | Method removed in Odoo 17; API changed entirely | Use `template._generate_template([res_id], ['subject', 'body_html'])` positional args only, returns `{res_id: {field: value}}`. Never use `generate_email(res_id)` (old Odoo 16 API) |
| conftest.py inside addon dir causes `Invalid import` | pytest adds addon dir to sys.path, bypassing `odoo.addons.*` namespace | Never put conftest.py inside the addon package. Place at repo root or at `/mnt/extra-addons/conftest.py` (parent of addon dir) |
| BDD test: `fixture 'env' not found` (pytest-odoo 2.x) | pytest-odoo 2.x does NOT provide an `env` pytest fixture — env only exists as `self.env` inside TransactionCase | In BDD step fixtures, build env directly: `registry = odoo.registry(request.config.getoption('--odoo-database')); with registry.cursor() as cr: env = Environment(cr, odoo.SUPERUSER_ID, {}); yield env; cr.rollback()` |
| Test: `IndexError: tuple index out of range` when accessing `mock.call_args[0][0]` | Mock method called with keyword-only args; `call_args[0]` is empty tuple `()` | Use `mock.call_args[1].get('key')` for kwargs; or check `mock.called` before accessing `call_args` |
| Test: TransactionCase gets `InFailedSqlTransaction` in subsequent tests | Previous test called `self.env.cr.commit()` breaking savepoint chain | Replace `commit()` with `flush_all()` in code being tested; `commit()` is only allowed in non-test code in production |
| Test: Mock response returns HTML but code expects JSON | Mock return values must match the data format expected by code under test | Create helper function to generate mocks with correct structure (e.g., JSON string in `.text` field for LLM routers) |

View file

@ -122,7 +122,7 @@ def when_click_generate_now(odoo_env, ctx):
The actual model method is action_generate_now on itsulu.blog.schedule
or a wizard model. We mock the LLM call.
"""
from odoo.addons.itsulu_blog_publisher.wizards.generate_now_wizard import GenerateNowWizard
from odoo.addons.itsulu_blog_publisher.wizards.generate_now_wizard import BlogGenerateWizard as GenerateNowWizard
mock_resp = _make_mock_llm_response(ctx.get('topic', 'AI'))
with patch(

View file

@ -254,10 +254,10 @@ class TestNotificationEmail(TransactionCase):
# ACT — render the template synchronously to check the subject
template = self.env.ref('itsulu_blog_publisher.email_template_blog_published')
rendered = template._generate_template([log.id], ['subject'])
rendered = template._render_field('subject', [log.id])
# ASSERT
subject = rendered[log.id].get('subject', '')
# ASSERT — _render_field returns {res_id: rendered_string}
subject = rendered[log.id] or ''
self.assertTrue(subject, "Rendered email subject must be non-empty")
self.assertIn('[ITSulu Insights] Blog Post Published:', subject,
f"Unexpected subject: {subject}")
@ -300,8 +300,8 @@ class TestNotificationEmail(TransactionCase):
# Verify template renders all platform copy in the body
template = self.env.ref('itsulu_blog_publisher.email_template_blog_published')
rendered = template._generate_template([log.id], ['body_html'])
body = rendered[log.id].get('body_html', '')
rendered = template._render_field('body_html', [log.id])
body = rendered[log.id] or ''
self.assertIn('Twitter A copy', body, "Body must contain Twitter copy")
self.assertIn('LinkedIn copy', body, "Body must contain LinkedIn copy")
@ -319,8 +319,8 @@ class TestNotificationEmail(TransactionCase):
# ASSERT — render the template synchronously to check the URL appears in the body
template = self.env.ref('itsulu_blog_publisher.email_template_blog_published')
rendered = template._generate_template([log.id], ['body_html'])
body = rendered[log.id].get('body_html', '')
rendered = template._render_field('body_html', [log.id])
body = rendered[log.id] or ''
self.assertIn('itsulu.com', body, "Body must contain itsulu.com URL")
def test_notification_email_is_not_sent_for_draft_posts(self):