feat(phase-6): Complete testing and deployment setup
Testing: - Add pytest configuration (pytest.ini) - Add test fixtures (tests/conftest.py) - Add ContentGenerator tests (13 tests) - Add ContentScheduler tests (16 tests) - Add PublisherManager tests (16 tests) - All 45 tests passing Production Docker: - Add docker-compose.prod.yml with healthchecks, resource limits - Add Dockerfile.prod with multi-stage build, non-root user - Add nginx.prod.conf with SSL, rate limiting, security headers - Add .env.prod.example template Maintenance Scripts: - Add backup.sh for database and media backups - Add restore.sh for database restoration - Add cleanup.sh for log rotation and Docker cleanup - Add healthcheck.sh with Telegram alerts Documentation: - Add DEPLOY.md with complete deployment guide Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
180
tests/test_content_generator.py
Normal file
180
tests/test_content_generator.py
Normal file
@@ -0,0 +1,180 @@
|
||||
"""
|
||||
Tests for ContentGenerator service.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch, AsyncMock
|
||||
|
||||
|
||||
class TestContentGenerator:
|
||||
"""Tests for the ContentGenerator class."""
|
||||
|
||||
@pytest.fixture
|
||||
def generator(self, mock_settings):
|
||||
"""Create a ContentGenerator instance with mocked client."""
|
||||
with patch('app.services.content_generator.OpenAI') as mock_openai:
|
||||
mock_client = MagicMock()
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock()]
|
||||
mock_response.choices[0].message.content = "Test content #AI #Tech"
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
mock_openai.return_value = mock_client
|
||||
|
||||
from app.services.content_generator import ContentGenerator
|
||||
gen = ContentGenerator()
|
||||
gen._client = mock_client
|
||||
yield gen
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_tip_tech(self, generator):
|
||||
"""Test generating a tech tip."""
|
||||
result = await generator.generate_tip_tech(
|
||||
category="seguridad",
|
||||
platform="x"
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert len(result) > 0
|
||||
generator.client.chat.completions.create.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_tip_tech_with_template(self, generator):
|
||||
"""Test generating a tech tip with a template."""
|
||||
result = await generator.generate_tip_tech(
|
||||
category="productividad",
|
||||
platform="threads",
|
||||
template="Tip del día: {tip}"
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
call_args = generator.client.chat.completions.create.call_args
|
||||
assert "template" in str(call_args).lower() or "TEMPLATE" in str(call_args)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_product_post(self, generator, sample_product):
|
||||
"""Test generating a product post."""
|
||||
result = await generator.generate_product_post(
|
||||
product=sample_product,
|
||||
platform="instagram"
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
call_args = generator.client.chat.completions.create.call_args
|
||||
messages = call_args.kwargs.get('messages', call_args[1].get('messages', []))
|
||||
|
||||
# Verify product info was included in prompt
|
||||
user_message = messages[-1]['content']
|
||||
assert sample_product['name'] in user_message
|
||||
# Price is formatted with commas, check for the value
|
||||
assert "15,999" in user_message or "15999" in user_message
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_service_post(self, generator, sample_service):
|
||||
"""Test generating a service post."""
|
||||
result = await generator.generate_service_post(
|
||||
service=sample_service,
|
||||
platform="facebook"
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
call_args = generator.client.chat.completions.create.call_args
|
||||
messages = call_args.kwargs.get('messages', call_args[1].get('messages', []))
|
||||
|
||||
user_message = messages[-1]['content']
|
||||
assert sample_service['name'] in user_message
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_thread(self, generator):
|
||||
"""Test generating a thread."""
|
||||
generator.client.chat.completions.create.return_value.choices[0].message.content = \
|
||||
"1/ Post uno\n2/ Post dos\n3/ Post tres"
|
||||
|
||||
result = await generator.generate_thread(
|
||||
topic="Cómo proteger tu contraseña",
|
||||
num_posts=3
|
||||
)
|
||||
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 3
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_response_suggestion(self, generator, sample_interaction):
|
||||
"""Test generating response suggestions."""
|
||||
generator.client.chat.completions.create.return_value.choices[0].message.content = \
|
||||
"1. Respuesta corta\n2. Respuesta media\n3. Respuesta larga"
|
||||
|
||||
result = await generator.generate_response_suggestion(
|
||||
interaction_content=sample_interaction['content'],
|
||||
interaction_type=sample_interaction['type']
|
||||
)
|
||||
|
||||
assert isinstance(result, list)
|
||||
assert len(result) <= 3
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_adapt_content_for_platform(self, generator):
|
||||
"""Test adapting content for different platforms."""
|
||||
original = "Este es un tip muy largo sobre seguridad informática con muchos detalles"
|
||||
|
||||
result = await generator.adapt_content_for_platform(
|
||||
content=original,
|
||||
target_platform="x"
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
call_args = generator.client.chat.completions.create.call_args
|
||||
messages = call_args.kwargs.get('messages', call_args[1].get('messages', []))
|
||||
|
||||
user_message = messages[-1]['content']
|
||||
assert "280" in user_message # X character limit
|
||||
|
||||
def test_get_system_prompt(self, generator, mock_settings):
|
||||
"""Test that system prompt includes business info."""
|
||||
prompt = generator._get_system_prompt()
|
||||
|
||||
assert mock_settings.BUSINESS_NAME in prompt
|
||||
assert mock_settings.BUSINESS_LOCATION in prompt
|
||||
|
||||
def test_lazy_initialization_without_api_key(self):
|
||||
"""Test that client raises error without API key."""
|
||||
with patch('app.services.content_generator.settings') as mock:
|
||||
mock.DEEPSEEK_API_KEY = None
|
||||
|
||||
from app.services.content_generator import ContentGenerator
|
||||
gen = ContentGenerator()
|
||||
|
||||
with pytest.raises(ValueError, match="DEEPSEEK_API_KEY"):
|
||||
_ = gen.client
|
||||
|
||||
|
||||
class TestCharacterLimits:
|
||||
"""Tests for character limit handling."""
|
||||
|
||||
@pytest.mark.parametrize("platform,expected_limit", [
|
||||
("x", 280),
|
||||
("threads", 500),
|
||||
("instagram", 2200),
|
||||
("facebook", 500),
|
||||
])
|
||||
@pytest.mark.asyncio
|
||||
async def test_platform_character_limits(self, platform, expected_limit, mock_settings):
|
||||
"""Test that correct character limits are used per platform."""
|
||||
with patch('app.services.content_generator.OpenAI') as mock_openai:
|
||||
mock_client = MagicMock()
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock()]
|
||||
mock_response.choices[0].message.content = "Test"
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
mock_openai.return_value = mock_client
|
||||
|
||||
from app.services.content_generator import ContentGenerator
|
||||
gen = ContentGenerator()
|
||||
gen._client = mock_client
|
||||
|
||||
await gen.generate_tip_tech("test", platform)
|
||||
|
||||
call_args = mock_client.chat.completions.create.call_args
|
||||
messages = call_args.kwargs.get('messages', call_args[1].get('messages', []))
|
||||
user_message = messages[-1]['content']
|
||||
|
||||
assert str(expected_limit) in user_message
|
||||
Reference in New Issue
Block a user