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>
110 lines
3.9 KiB
Python
110 lines
3.9 KiB
Python
# addons/itsulu_blog_publisher/tests/factories.py
|
|
"""
|
|
Test data factory for itsulu_blog_publisher.
|
|
Use this instead of demo data. Never rely on records seeded in another test.
|
|
"""
|
|
|
|
|
|
class BlogPublisherFactory:
|
|
def __init__(self, env):
|
|
self.env = env
|
|
self._counter = 0
|
|
|
|
def _seq(self):
|
|
self._counter += 1
|
|
return self._counter
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# Core Odoo blog records #
|
|
# ------------------------------------------------------------------ #
|
|
|
|
def blog(self, **kw):
|
|
defaults = {
|
|
'name': f'Test Blog {self._seq()}',
|
|
}
|
|
defaults.update(kw)
|
|
return self.env['blog.blog'].create(defaults)
|
|
|
|
def blog_tag(self, **kw):
|
|
defaults = {
|
|
'name': f'Test Tag {self._seq()}',
|
|
}
|
|
defaults.update(kw)
|
|
return self.env['blog.tag'].create(defaults)
|
|
|
|
def blog_post(self, blog=None, **kw):
|
|
blog = blog or self.blog()
|
|
defaults = {
|
|
'name': f'Test Post {self._seq()}',
|
|
'blog_id': blog.id,
|
|
'website_published': False,
|
|
'is_published': False,
|
|
'body_arch': '<p>Test body content for blog post.</p>',
|
|
'website_meta_title': '',
|
|
'website_meta_description': '',
|
|
'website_meta_keywords': '',
|
|
}
|
|
defaults.update(kw)
|
|
return self.env['blog.post'].create(defaults)
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# itsulu_blog_publisher models #
|
|
# ------------------------------------------------------------------ #
|
|
|
|
def blog_topic(self, **kw):
|
|
"""itsulu.blog.topic — a planned topic in the queue."""
|
|
defaults = {
|
|
'name': f'Test Topic {self._seq()}',
|
|
'priority': 'normal',
|
|
'state': 'pending',
|
|
'notes': '',
|
|
}
|
|
defaults.update(kw)
|
|
return self.env['itsulu.blog.topic'].create(defaults)
|
|
|
|
def blog_schedule(self, blog=None, **kw):
|
|
"""itsulu.blog.schedule — a cron slot configuration."""
|
|
blog = blog or self.blog()
|
|
defaults = {
|
|
'name': f'Test Schedule {self._seq()}',
|
|
'slot': 'morning',
|
|
'trigger_time': 8.0, # 08:00
|
|
'active': True,
|
|
'blog_id': blog.id,
|
|
'llm_provider': 'anthropic',
|
|
'llm_model': 'claude-sonnet-4-20250514',
|
|
'auto_publish': True,
|
|
}
|
|
defaults.update(kw)
|
|
return self.env['itsulu.blog.schedule'].create(defaults)
|
|
|
|
def generation_log(self, blog_post=None, **kw):
|
|
"""itsulu.blog.generation.log — one attempt record."""
|
|
defaults = {
|
|
'state': 'success',
|
|
'trigger_source': 'manual',
|
|
'llm_provider': 'anthropic',
|
|
'llm_model': 'claude-sonnet-4-20250514',
|
|
'tokens_used': 1500,
|
|
'duration_seconds': 12.0,
|
|
'error_message': '',
|
|
'blog_post_id': blog_post.id if blog_post else False,
|
|
}
|
|
defaults.update(kw)
|
|
return self.env['itsulu.blog.generation.log'].create(defaults)
|
|
|
|
def blog_post_social(self, blog_post=None, **kw):
|
|
"""itsulu.blog.post.social — social media copy for one post."""
|
|
blog_post = blog_post or self.blog_post()
|
|
defaults = {
|
|
'blog_post_id': blog_post.id,
|
|
'twitter_post_a': 'Twitter copy A placeholder.',
|
|
'twitter_post_b': 'Twitter copy B placeholder.',
|
|
'bluesky_post_a': 'BlueSky copy A placeholder.',
|
|
'bluesky_post_b': 'BlueSky copy B placeholder.',
|
|
'mastodon_post': 'Mastodon copy placeholder.',
|
|
'linkedin_post': 'LinkedIn copy placeholder.',
|
|
'sources_referenced': '',
|
|
}
|
|
defaults.update(kw)
|
|
return self.env['itsulu.blog.post.social'].create(defaults)
|