diff --git a/CLAUDE.md b/CLAUDE.md index 990e754..ac15242 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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) | diff --git a/addons/itsulu_blog_publisher/tests/test_bdd_steps.py b/addons/itsulu_blog_publisher/tests/test_bdd_steps.py index 211fc02..c82b24c 100644 --- a/addons/itsulu_blog_publisher/tests/test_bdd_steps.py +++ b/addons/itsulu_blog_publisher/tests/test_bdd_steps.py @@ -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( diff --git a/addons/itsulu_blog_publisher/tests/test_blog_post_social.py b/addons/itsulu_blog_publisher/tests/test_blog_post_social.py index 20913da..44e137a 100644 --- a/addons/itsulu_blog_publisher/tests/test_blog_post_social.py +++ b/addons/itsulu_blog_publisher/tests/test_blog_post_social.py @@ -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):