From 6635313f5869793a83fc65088727e8ad9a627816 Mon Sep 17 00:00:00 2001 From: Nicholas Riegel Date: Sat, 30 May 2026 11:44:24 -0400 Subject: [PATCH] port(14.0): fix web_ribbon view attr + convert tests to setUp/self.env - views/blog_schedule_views.xml: web_ribbon invisible="active" (Odoo 17 bare-expr syntax) -> attrs="{'invisible': [('active','=',True)]}". This was the view-validation error blocking module install on Odoo 14. - tests: Odoo 14 TransactionCase exposes self.env in setUp(), not cls.env in setUpClass() (that pattern is Odoo 15+). Converted all 13 setUpClass blocks across 6 test files to setUp(self) + self.env/self.factory. Co-Authored-By: Claude Opus 4.8 --- Dockerfile | 14 ++++--- .../tests/test_blog_generation_log.py | 16 ++++--- .../tests/test_blog_post_social.py | 29 ++++++------- .../tests/test_blog_schedule.py | 20 ++++----- .../tests/test_blog_topic.py | 7 ++-- .../tests/test_llm_router.py | 24 +++++------ .../tests/test_performance.py | 42 +++++++++---------- .../views/blog_schedule_views.xml | 2 +- 8 files changed, 71 insertions(+), 83 deletions(-) diff --git a/Dockerfile b/Dockerfile index 670e9af..54e75f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,14 @@ FROM odoo:14.0 -# Install Python testing dependencies using the system Python +# Install Python testing dependencies. odoo:14.0 ships Python 3.7, so pin to +# the last releases that still support 3.7 (pytest 8 / pytest-bdd 7 / pytest-html 4 +# all require 3.8+). RUN python3 -m pip install --no-cache-dir \ - pytest \ - pytest-odoo \ - pytest-bdd \ - pytest-cov \ - pytest-html \ + "pytest>=7,<8" \ + "pytest-odoo<2" \ + "pytest-bdd>=6,<7" \ + "pytest-cov<5" \ + "pytest-html<4" \ requests # Copy addon to Odoo addons path diff --git a/addons/itsulu_blog_publisher/tests/test_blog_generation_log.py b/addons/itsulu_blog_publisher/tests/test_blog_generation_log.py index 9656e81..0c1397e 100644 --- a/addons/itsulu_blog_publisher/tests/test_blog_generation_log.py +++ b/addons/itsulu_blog_publisher/tests/test_blog_generation_log.py @@ -14,11 +14,10 @@ from .factories import BlogPublisherFactory class TestBlogGenerationLogCreation(TransactionCase): """Verify that generation log records capture the correct metadata.""" - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.factory = BlogPublisherFactory(cls.env) - cls.blog = cls.factory.blog(name='ITSulu Insights') + def setUp(self): + super().setUp() + self.factory = BlogPublisherFactory(self.env) + self.blog = self.factory.blog(name='ITSulu Insights') def test_successful_log_record_is_created_with_correct_fields(self): """ @@ -105,10 +104,9 @@ class TestBlogGenerationLogCreation(TransactionCase): class TestBlogGenerationLogRetry(TransactionCase): """Verify that failed logs expose a working Retry action.""" - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.factory = BlogPublisherFactory(cls.env) + def setUp(self): + super().setUp() + self.factory = BlogPublisherFactory(self.env) def test_error_log_action_retry_returns_wizard_action(self): """ 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 44e137a..61f4652 100644 --- a/addons/itsulu_blog_publisher/tests/test_blog_post_social.py +++ b/addons/itsulu_blog_publisher/tests/test_blog_post_social.py @@ -16,11 +16,10 @@ from .factories import BlogPublisherFactory class TestSEOPopulation(TransactionCase): """Verify that all SEO fields are correctly populated after blog post generation.""" - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.factory = BlogPublisherFactory(cls.env) - cls.blog = cls.factory.blog(name='ITSulu Insights') + def setUp(self): + super().setUp() + self.factory = BlogPublisherFactory(self.env) + self.blog = self.factory.blog(name='ITSulu Insights') def test_generated_post_has_non_empty_meta_title(self): """ @@ -125,11 +124,10 @@ class TestSEOPopulation(TransactionCase): class TestBlogPostSocialModel(TransactionCase): """Verify the itsulu.blog.post.social model stores all platform copy correctly.""" - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.factory = BlogPublisherFactory(cls.env) - cls.blog = cls.factory.blog(name='ITSulu Insights') + def setUp(self): + super().setUp() + self.factory = BlogPublisherFactory(self.env) + self.blog = self.factory.blog(name='ITSulu Insights') def test_social_record_is_linked_one_to_one_with_blog_post(self): """Each blog post has at most one social record.""" @@ -200,12 +198,11 @@ class TestNotificationEmail(TransactionCase): structure matching the [ITSulu Insights] template in the uploaded .eml. """ - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.factory = BlogPublisherFactory(cls.env) - cls.blog = cls.factory.blog(name='ITSulu Insights') - cls.env['ir.config_parameter'].sudo().set_param( + def setUp(self): + super().setUp() + self.factory = BlogPublisherFactory(self.env) + self.blog = self.factory.blog(name='ITSulu Insights') + self.env['ir.config_parameter'].sudo().set_param( 'itsulu_blog_publisher.notification_emails', 'nicholasr@itsulu.com', ) diff --git a/addons/itsulu_blog_publisher/tests/test_blog_schedule.py b/addons/itsulu_blog_publisher/tests/test_blog_schedule.py index cc5e0ea..48db1be 100644 --- a/addons/itsulu_blog_publisher/tests/test_blog_schedule.py +++ b/addons/itsulu_blog_publisher/tests/test_blog_schedule.py @@ -15,11 +15,10 @@ from .factories import BlogPublisherFactory class TestBlogScheduleConfiguration(TransactionCase): """Verify that schedule slot records are configured correctly.""" - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.factory = BlogPublisherFactory(cls.env) - cls.blog = cls.factory.blog(name='ITSulu Insights') + def setUp(self): + super().setUp() + self.factory = BlogPublisherFactory(self.env) + self.blog = self.factory.blog(name='ITSulu Insights') def test_schedule_slot_is_created_with_correct_defaults(self): """ @@ -69,12 +68,11 @@ class TestBlogScheduleExecution(TransactionCase): LLM calls are mocked — we are testing orchestration, not the LLM. """ - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.factory = BlogPublisherFactory(cls.env) - cls.blog = cls.factory.blog(name='ITSulu Insights') - cls.env['ir.config_parameter'].sudo().set_param( + def setUp(self): + super().setUp() + self.factory = BlogPublisherFactory(self.env) + self.blog = self.factory.blog(name='ITSulu Insights') + self.env['ir.config_parameter'].sudo().set_param( 'itsulu_blog_publisher.anthropic_api_key', 'sk-ant-test-key' ) diff --git a/addons/itsulu_blog_publisher/tests/test_blog_topic.py b/addons/itsulu_blog_publisher/tests/test_blog_topic.py index e9b3b87..3dd43eb 100644 --- a/addons/itsulu_blog_publisher/tests/test_blog_topic.py +++ b/addons/itsulu_blog_publisher/tests/test_blog_topic.py @@ -15,10 +15,9 @@ from .factories import BlogPublisherFactory class TestBlogTopicQueueManagement(TransactionCase): """Verify that the topic queue picks topics in priority order.""" - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.factory = BlogPublisherFactory(cls.env) + def setUp(self): + super().setUp() + self.factory = BlogPublisherFactory(self.env) def test_topic_is_created_with_pending_state(self): """ diff --git a/addons/itsulu_blog_publisher/tests/test_llm_router.py b/addons/itsulu_blog_publisher/tests/test_llm_router.py index 1abc262..1d1184b 100644 --- a/addons/itsulu_blog_publisher/tests/test_llm_router.py +++ b/addons/itsulu_blog_publisher/tests/test_llm_router.py @@ -42,21 +42,20 @@ def _make_mock_llm_response(tokens_used=800): class TestLLMRouterProviderDispatch(TransactionCase): """Verify that the LLM router dispatches to the correct backend provider.""" - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.factory = BlogPublisherFactory(cls.env) + def setUp(self): + super().setUp() + self.factory = BlogPublisherFactory(self.env) # Store a known-valid stub API key in ir.config_parameter - cls.env['ir.config_parameter'].sudo().set_param( + self.env['ir.config_parameter'].sudo().set_param( 'itsulu_blog_publisher.anthropic_api_key', 'sk-ant-test-key' ) - cls.env['ir.config_parameter'].sudo().set_param( + self.env['ir.config_parameter'].sudo().set_param( 'itsulu_blog_publisher.openai_api_key', 'sk-openai-test-key' ) - cls.env['ir.config_parameter'].sudo().set_param( + self.env['ir.config_parameter'].sudo().set_param( 'itsulu_blog_publisher.gemini_api_key', 'gemini-test-key' ) - cls.env['ir.config_parameter'].sudo().set_param( + self.env['ir.config_parameter'].sudo().set_param( 'itsulu_blog_publisher.ollama_base_url', 'http://localhost:11434' ) @@ -170,11 +169,10 @@ class TestLLMRouterProviderDispatch(TransactionCase): class TestLLMRouterTokenLogging(TransactionCase): """Verify that token usage is captured from provider responses.""" - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.factory = BlogPublisherFactory(cls.env) - cls.env['ir.config_parameter'].sudo().set_param( + def setUp(self): + super().setUp() + self.factory = BlogPublisherFactory(self.env) + self.env['ir.config_parameter'].sudo().set_param( 'itsulu_blog_publisher.anthropic_api_key', 'sk-ant-test-key' ) diff --git a/addons/itsulu_blog_publisher/tests/test_performance.py b/addons/itsulu_blog_publisher/tests/test_performance.py index 84c478f..d4216cc 100644 --- a/addons/itsulu_blog_publisher/tests/test_performance.py +++ b/addons/itsulu_blog_publisher/tests/test_performance.py @@ -16,12 +16,11 @@ _logger = logging.getLogger(__name__) class TestGenerationLatency(TransactionCase): """Measure time from run_generation() call to blog.post creation.""" - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.factory = BlogPublisherFactory(cls.env) - cls.blog = cls.factory.blog(name='ITSulu Insights') - cls.env['ir.config_parameter'].sudo().set_param( + def setUp(self): + super().setUp() + self.factory = BlogPublisherFactory(self.env) + self.blog = self.factory.blog(name='ITSulu Insights') + self.env['ir.config_parameter'].sudo().set_param( 'itsulu_blog_publisher.anthropic_api_key', 'sk-ant-test-key' ) @@ -103,12 +102,11 @@ class TestGenerationLatency(TransactionCase): class TestQueryCount(TransactionCase): """Verify N+1 query patterns don't exist in critical paths.""" - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.factory = BlogPublisherFactory(cls.env) - cls.blog = cls.factory.blog(name='ITSulu Insights') - cls.env['ir.config_parameter'].sudo().set_param( + def setUp(self): + super().setUp() + self.factory = BlogPublisherFactory(self.env) + self.blog = self.factory.blog(name='ITSulu Insights') + self.env['ir.config_parameter'].sudo().set_param( 'itsulu_blog_publisher.anthropic_api_key', 'sk-ant-test-key' ) @@ -179,11 +177,10 @@ class TestQueryCount(TransactionCase): class TestTokenUsageBaseline(TransactionCase): """Establish token usage baseline for cost tracking.""" - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.factory = BlogPublisherFactory(cls.env) - cls.blog = cls.factory.blog(name='ITSulu Insights') + def setUp(self): + super().setUp() + self.factory = BlogPublisherFactory(self.env) + self.blog = self.factory.blog(name='ITSulu Insights') def test_typical_post_uses_800_to_1200_tokens(self): """ @@ -231,12 +228,11 @@ class TestTokenUsageBaseline(TransactionCase): class TestConcurrentGeneration(TransactionCase): """Test that concurrent post generation handles contention correctly.""" - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.factory = BlogPublisherFactory(cls.env) - cls.blog = cls.factory.blog(name='ITSulu Insights') - cls.env['ir.config_parameter'].sudo().set_param( + def setUp(self): + super().setUp() + self.factory = BlogPublisherFactory(self.env) + self.blog = self.factory.blog(name='ITSulu Insights') + self.env['ir.config_parameter'].sudo().set_param( 'itsulu_blog_publisher.anthropic_api_key', 'sk-ant-test-key' ) diff --git a/addons/itsulu_blog_publisher/views/blog_schedule_views.xml b/addons/itsulu_blog_publisher/views/blog_schedule_views.xml index 9ec9463..c6ce9d4 100644 --- a/addons/itsulu_blog_publisher/views/blog_schedule_views.xml +++ b/addons/itsulu_blog_publisher/views/blog_schedule_views.xml @@ -38,7 +38,7 @@ + attrs="{'invisible': [('active', '=', True)]}"/>