test: add unit tests for template_message and email_sender services
- Achieve 100% coverage for both services
This commit is contained in:
parent
9cb29cd400
commit
def50f709f
818
apps/notification/tests/unit_tests/test_services.py
Normal file
818
apps/notification/tests/unit_tests/test_services.py
Normal file
@ -0,0 +1,818 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import Mock, AsyncMock, patch
|
||||||
|
import re
|
||||||
|
from backend.services.template_message_service import TemplateMessageService
|
||||||
|
from backend.services.email_sender_service import EmailSenderService
|
||||||
|
from backend.models.models import MessageTemplateDoc, EmailSenderDoc
|
||||||
|
|
||||||
|
class TestTemplateMessageServiceUnit:
|
||||||
|
"""Unit tests for TemplateMessageService"""
|
||||||
|
|
||||||
|
def test_service_initialization(self):
|
||||||
|
"""Test service can be initialized."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
assert service is not None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_update_global_template_success(self):
|
||||||
|
"""Test successful global template update."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock template
|
||||||
|
mock_template = Mock()
|
||||||
|
mock_template.tenant_id = None # Global template
|
||||||
|
mock_template.set = AsyncMock()
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.get
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.get', new_callable=AsyncMock, return_value=mock_template):
|
||||||
|
result = await service.update_global_template("template_id", {"subject": "New Subject"})
|
||||||
|
|
||||||
|
mock_template.set.assert_called_once_with({"subject": "New Subject"})
|
||||||
|
assert result == {"success": True}
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_global_template(self):
|
||||||
|
"""Test creating global template."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock template
|
||||||
|
mock_template = Mock()
|
||||||
|
mock_template.tenant_id = "some_tenant"
|
||||||
|
mock_template.create = AsyncMock(return_value=mock_template)
|
||||||
|
|
||||||
|
# Test global template creation
|
||||||
|
result = await service.create_global_template(mock_template)
|
||||||
|
|
||||||
|
# Verify tenant_id is set to None
|
||||||
|
assert mock_template.tenant_id is None
|
||||||
|
mock_template.create.assert_called_once()
|
||||||
|
assert result == mock_template
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_update_global_template_not_found(self):
|
||||||
|
"""Test updating non-existent global template."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.get returning None
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.get', new_callable=AsyncMock, return_value=None):
|
||||||
|
with pytest.raises(PermissionError, match="Not a global template"):
|
||||||
|
await service.update_global_template("template_id", {"subject": "New Subject"})
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_update_global_template_not_global(self):
|
||||||
|
"""Test updating tenant template as global template."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock template (tenant template)
|
||||||
|
mock_template = Mock()
|
||||||
|
mock_template.tenant_id = "tenant_123" # Not global
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.get
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.get', new_callable=AsyncMock, return_value=mock_template):
|
||||||
|
with pytest.raises(PermissionError, match="Not a global template"):
|
||||||
|
await service.update_global_template("template_id", {"subject": "New Subject"})
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_delete_global_template_success(self):
|
||||||
|
"""Test successful global template deletion."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock template
|
||||||
|
mock_template = Mock()
|
||||||
|
mock_template.tenant_id = None # Global template
|
||||||
|
mock_template.delete = AsyncMock()
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.get
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.get', new_callable=AsyncMock, return_value=mock_template):
|
||||||
|
result = await service.delete_global_template("template_id")
|
||||||
|
|
||||||
|
mock_template.delete.assert_called_once()
|
||||||
|
assert result == {"success": True}
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_delete_global_template_not_found(self):
|
||||||
|
"""Test deleting non-existent global template."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.get returning None
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.get', new_callable=AsyncMock, return_value=None):
|
||||||
|
with pytest.raises(PermissionError, match="Not a global template"):
|
||||||
|
await service.delete_global_template("template_id")
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_delete_global_template_not_global(self):
|
||||||
|
"""Test deleting tenant template as global template."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock template (tenant template)
|
||||||
|
mock_template = Mock()
|
||||||
|
mock_template.tenant_id = "tenant_123" # Not global
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.get
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.get', new_callable=AsyncMock, return_value=mock_template):
|
||||||
|
with pytest.raises(PermissionError, match="Not a global template"):
|
||||||
|
await service.delete_global_template("template_id")
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_template(self):
|
||||||
|
"""Test getting template by template_id, tenant_id, and region."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock template
|
||||||
|
mock_template = Mock()
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.find_one
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.find_one', new_callable=AsyncMock, return_value=mock_template):
|
||||||
|
result = await service.get_template("template_id", "tenant_123", 1)
|
||||||
|
|
||||||
|
assert result == mock_template
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_list_global_templates(self):
|
||||||
|
"""Test listing global templates."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock templates list
|
||||||
|
mock_templates = [Mock(), Mock()]
|
||||||
|
mock_query = Mock()
|
||||||
|
mock_query.to_list = AsyncMock(return_value=mock_templates)
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.find
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.find', return_value=mock_query):
|
||||||
|
result = await service.list_global_templates(1)
|
||||||
|
|
||||||
|
assert result == mock_templates
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_list_tenant_templates(self):
|
||||||
|
"""Test listing tenant templates."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock templates list
|
||||||
|
mock_templates = [Mock(), Mock()]
|
||||||
|
mock_query = Mock()
|
||||||
|
mock_query.to_list = AsyncMock(return_value=mock_templates)
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.find
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.find', return_value=mock_query):
|
||||||
|
result = await service.list_tenant_templates("tenant_123", 1)
|
||||||
|
|
||||||
|
assert result == mock_templates
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_assign_template_to_tenant_already_assigned(self):
|
||||||
|
"""Test assigning template that tenant already has."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock global template
|
||||||
|
mock_global_template = Mock()
|
||||||
|
mock_global_template.template_id = "template_1"
|
||||||
|
mock_global_template.region = 1
|
||||||
|
mock_global_template.subject = "Test Subject"
|
||||||
|
mock_global_template.body = "Test Body"
|
||||||
|
|
||||||
|
# Mock existing tenant template
|
||||||
|
mock_existing_template = Mock()
|
||||||
|
|
||||||
|
# Create a mock that returns different values based on the query
|
||||||
|
async def mock_find_one(query):
|
||||||
|
if query.get("tenant_id") is None: # Global template query
|
||||||
|
return mock_global_template
|
||||||
|
else: # Tenant template query
|
||||||
|
return mock_existing_template # Template already exists
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.find_one
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.find_one', side_effect=mock_find_one):
|
||||||
|
result = await service.assign_template_to_tenant(["template_1"], 1, "tenant_123")
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0]["template_id"] == "template_1"
|
||||||
|
assert result[0]["success"] is False
|
||||||
|
assert "Template already assigned" in result[0]["msg"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_assign_template_to_tenant_not_found(self):
|
||||||
|
"""Test assigning non-existent template to tenant."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.find_one returning None for global template
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.find_one', new_callable=AsyncMock, return_value=None):
|
||||||
|
result = await service.assign_template_to_tenant(["non_existent_template"], 1, "tenant_123")
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0]["template_id"] == "non_existent_template"
|
||||||
|
assert result[0]["success"] is False
|
||||||
|
assert "Template not found" in result[0]["msg"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_assign_template_to_tenant_success_complex(self):
|
||||||
|
"""Test successful template assignment to tenant with complex mocking."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock global template
|
||||||
|
mock_global_template = Mock()
|
||||||
|
mock_global_template.template_id = "template_1"
|
||||||
|
mock_global_template.region = 1
|
||||||
|
mock_global_template.subject = "Test Subject"
|
||||||
|
mock_global_template.body = "Test Body"
|
||||||
|
|
||||||
|
# Mock new template
|
||||||
|
mock_new_template = Mock()
|
||||||
|
mock_new_template.id = "new_id"
|
||||||
|
mock_new_template.create = AsyncMock()
|
||||||
|
|
||||||
|
# Create a simple async mock function
|
||||||
|
async def mock_find_one(query):
|
||||||
|
if query.get("tenant_id") is None: # Global template query
|
||||||
|
return mock_global_template
|
||||||
|
else: # Tenant template query
|
||||||
|
return None # No existing template
|
||||||
|
|
||||||
|
# Mock the entire MessageTemplateDoc class
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc') as mock_doc_class:
|
||||||
|
# Set up the find_one method
|
||||||
|
mock_doc_class.find_one = mock_find_one
|
||||||
|
# Set up the constructor
|
||||||
|
mock_doc_class.return_value = mock_new_template
|
||||||
|
|
||||||
|
result = await service.assign_template_to_tenant(["template_1"], 1, "tenant_123")
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0]["template_id"] == "template_1"
|
||||||
|
assert result[0]["success"] is True
|
||||||
|
assert "template_db_id" in result[0]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_template_tenant_success(self):
|
||||||
|
"""Test creating tenant template."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock template
|
||||||
|
mock_template = Mock()
|
||||||
|
mock_template.tenant_id = None
|
||||||
|
mock_template.create = AsyncMock(return_value=mock_template)
|
||||||
|
|
||||||
|
result = await service.create_template(mock_template, "tenant_123")
|
||||||
|
|
||||||
|
# Verify tenant_id is set
|
||||||
|
assert mock_template.tenant_id == "tenant_123"
|
||||||
|
mock_template.create.assert_called_once()
|
||||||
|
assert result == mock_template
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_update_template_tenant_success(self):
|
||||||
|
"""Test successful template update."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock template
|
||||||
|
mock_template = Mock()
|
||||||
|
mock_template.tenant_id = "tenant_123"
|
||||||
|
mock_template.set = AsyncMock()
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.get
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.get', new_callable=AsyncMock, return_value=mock_template):
|
||||||
|
result = await service.update_template("template_id", "tenant_123", {"subject": "New Subject"})
|
||||||
|
|
||||||
|
mock_template.set.assert_called_once_with({"subject": "New Subject"})
|
||||||
|
assert result == {"success": True}
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_update_template_tenant_not_found(self):
|
||||||
|
"""Test updating non-existent template."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.get returning None
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.get', new_callable=AsyncMock, return_value=None):
|
||||||
|
with pytest.raises(PermissionError, match="Forbidden"):
|
||||||
|
await service.update_template("template_id", "tenant_123", {"subject": "New Subject"})
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_update_template_tenant_forbidden(self):
|
||||||
|
"""Test template update with wrong tenant."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock template with different tenant
|
||||||
|
mock_template = Mock()
|
||||||
|
mock_template.tenant_id = "tenant_456" # Different tenant
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.get
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.get', new_callable=AsyncMock, return_value=mock_template):
|
||||||
|
with pytest.raises(PermissionError, match="Forbidden"):
|
||||||
|
await service.update_template("template_id", "tenant_123", {"subject": "New Subject"})
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_delete_template_tenant_success(self):
|
||||||
|
"""Test successful template deletion."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock template
|
||||||
|
mock_template = Mock()
|
||||||
|
mock_template.tenant_id = "tenant_123"
|
||||||
|
mock_template.delete = AsyncMock()
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.get
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.get', new_callable=AsyncMock, return_value=mock_template):
|
||||||
|
result = await service.delete_template("template_id", "tenant_123")
|
||||||
|
|
||||||
|
mock_template.delete.assert_called_once()
|
||||||
|
assert result == {"success": True}
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_delete_template_tenant_not_found(self):
|
||||||
|
"""Test deleting non-existent template."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.get returning None
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.get', new_callable=AsyncMock, return_value=None):
|
||||||
|
with pytest.raises(PermissionError, match="Forbidden"):
|
||||||
|
await service.delete_template("template_id", "tenant_123")
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_delete_template_tenant_forbidden(self):
|
||||||
|
"""Test deleting template with wrong tenant."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock template with different tenant
|
||||||
|
mock_template = Mock()
|
||||||
|
mock_template.tenant_id = "tenant_456" # Different tenant
|
||||||
|
|
||||||
|
# Mock MessageTemplateDoc.get
|
||||||
|
with patch('backend.services.template_message_service.MessageTemplateDoc.get', new_callable=AsyncMock, return_value=mock_template):
|
||||||
|
with pytest.raises(PermissionError, match="Forbidden"):
|
||||||
|
await service.delete_template("template_id", "tenant_123")
|
||||||
|
|
||||||
|
|
||||||
|
def test_template_rendering_logic(self):
|
||||||
|
"""Test template rendering logic without database."""
|
||||||
|
# Mock template data (simulating what would come from database)
|
||||||
|
mock_template = Mock()
|
||||||
|
mock_template.subject = "Hello {name}, your code is {code}"
|
||||||
|
mock_template.body = "Welcome {name}! Your verification code is {code}. Please use it within {expiry} minutes."
|
||||||
|
|
||||||
|
# Extract placeholders with regex
|
||||||
|
subject_placeholders = re.findall(r'\{(\w+)\}', mock_template.subject)
|
||||||
|
body_placeholders = re.findall(r'\{(\w+)\}', mock_template.body)
|
||||||
|
|
||||||
|
# Analyze count and type of placeholders
|
||||||
|
print(f"Subject placeholders: {subject_placeholders}") # ['name', 'code']
|
||||||
|
print(f"Body placeholders: {body_placeholders}") # ['name', 'code', 'expiry']
|
||||||
|
|
||||||
|
# Count placeholders
|
||||||
|
subject_count = len(subject_placeholders)
|
||||||
|
body_count = len(body_placeholders)
|
||||||
|
total_unique = len(set(subject_placeholders + body_placeholders))
|
||||||
|
|
||||||
|
print(f"Subject placeholder count: {subject_count}")
|
||||||
|
print(f"Body placeholder count: {body_count}")
|
||||||
|
print(f"Total unique placeholders: {total_unique}")
|
||||||
|
|
||||||
|
# Validate placeholder types
|
||||||
|
assert "name" in subject_placeholders
|
||||||
|
assert "code" in subject_placeholders
|
||||||
|
assert "name" in body_placeholders
|
||||||
|
assert "code" in body_placeholders
|
||||||
|
assert "expiry" in body_placeholders
|
||||||
|
|
||||||
|
# Get all required properties
|
||||||
|
required_properties = set(subject_placeholders + body_placeholders)
|
||||||
|
print(f"Required properties: {required_properties}") # {'name', 'code', 'expiry'}
|
||||||
|
|
||||||
|
# Test properties
|
||||||
|
properties = {
|
||||||
|
"name": "John Doe",
|
||||||
|
"code": "123456",
|
||||||
|
"expiry": "10"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate all required properties are provided
|
||||||
|
missing_properties = required_properties - set(properties.keys())
|
||||||
|
assert len(missing_properties) == 0, f"Missing properties: {missing_properties}"
|
||||||
|
|
||||||
|
# Test rendering logic
|
||||||
|
subject = mock_template.subject.format(**properties)
|
||||||
|
body = mock_template.body.format(**properties)
|
||||||
|
|
||||||
|
assert subject == "Hello John Doe, your code is 123456"
|
||||||
|
assert body == "Welcome John Doe! Your verification code is 123456. Please use it within 10 minutes."
|
||||||
|
|
||||||
|
# Test error handling when missing properties
|
||||||
|
incomplete_properties = {"name": "John Doe"} # Missing code and expiry
|
||||||
|
missing_properties = required_properties - set(incomplete_properties.keys())
|
||||||
|
assert len(missing_properties) == 2
|
||||||
|
assert "code" in missing_properties
|
||||||
|
assert "expiry" in missing_properties
|
||||||
|
|
||||||
|
# Validate that KeyError is raised when missing properties
|
||||||
|
with pytest.raises(KeyError):
|
||||||
|
mock_template.subject.format(**incomplete_properties)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_render_template_success(self):
|
||||||
|
"""Test successful template rendering."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock template
|
||||||
|
mock_template = Mock()
|
||||||
|
mock_template.subject = "Hello {name}"
|
||||||
|
mock_template.body = "Welcome {name}!"
|
||||||
|
|
||||||
|
properties = {"name": "John"}
|
||||||
|
|
||||||
|
# Test successful rendering
|
||||||
|
subject, body = await service.render_template(mock_template, properties)
|
||||||
|
assert subject == "Hello John"
|
||||||
|
assert body == "Welcome John!"
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_render_template_missing_property(self):
|
||||||
|
"""Test template rendering with missing property."""
|
||||||
|
service = TemplateMessageService()
|
||||||
|
|
||||||
|
# Mock template
|
||||||
|
mock_template = Mock()
|
||||||
|
mock_template.subject = "Hello {name}"
|
||||||
|
mock_template.body = "Welcome {name}!"
|
||||||
|
|
||||||
|
properties = {} # Missing "name"
|
||||||
|
|
||||||
|
# Test error handling
|
||||||
|
with pytest.raises(ValueError, match="Missing template parameter"):
|
||||||
|
await service.render_template(mock_template, properties)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_complex_template_placeholder_analysis(self):
|
||||||
|
"""Test analyzing complex templates with multiple placeholders."""
|
||||||
|
# Complex template with various types of placeholders
|
||||||
|
complex_template = Mock()
|
||||||
|
complex_template.subject = "Interview {type} for {position} - {company}"
|
||||||
|
complex_template.body = """
|
||||||
|
Dear {name},
|
||||||
|
|
||||||
|
Your {type} interview for the {position} position at {company} has been scheduled.
|
||||||
|
|
||||||
|
Details:
|
||||||
|
- Date: {date}
|
||||||
|
- Time: {time}
|
||||||
|
- Duration: {duration} minutes
|
||||||
|
- Interviewer: {interviewer}
|
||||||
|
- Location: {location}
|
||||||
|
|
||||||
|
Please confirm your attendance by replying to this email.
|
||||||
|
|
||||||
|
Best regards,
|
||||||
|
{company} HR Team
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Extract placeholders
|
||||||
|
subject_placeholders = re.findall(r'\{(\w+)\}', complex_template.subject)
|
||||||
|
body_placeholders = re.findall(r'\{(\w+)\}', complex_template.body)
|
||||||
|
|
||||||
|
# Analyze placeholder statistics
|
||||||
|
all_placeholders = subject_placeholders + body_placeholders
|
||||||
|
unique_placeholders = list(set(all_placeholders))
|
||||||
|
|
||||||
|
# Test counts - update based on actual count
|
||||||
|
assert len(subject_placeholders) == 3
|
||||||
|
assert len(body_placeholders) == 10 # Updated: actually 10 placeholders
|
||||||
|
assert len(unique_placeholders) == 9 # Total unique placeholders
|
||||||
|
|
||||||
|
# Test specific placeholders
|
||||||
|
expected_placeholders = {
|
||||||
|
"type", "position", "company", "name", "date",
|
||||||
|
"time", "duration", "interviewer", "location"
|
||||||
|
}
|
||||||
|
assert set(unique_placeholders) == expected_placeholders
|
||||||
|
|
||||||
|
# Test placeholder frequency
|
||||||
|
placeholder_frequency = {}
|
||||||
|
for placeholder in all_placeholders:
|
||||||
|
placeholder_frequency[placeholder] = placeholder_frequency.get(placeholder, 0) + 1
|
||||||
|
|
||||||
|
# Check which placeholders appear multiple times
|
||||||
|
repeated_placeholders = {k: v for k, v in placeholder_frequency.items() if v > 1}
|
||||||
|
assert "company" in repeated_placeholders
|
||||||
|
assert repeated_placeholders["company"] == 3 # Appears in subject and twice in body
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TestEmailSenderServiceUnit:
|
||||||
|
"""Unit tests for EmailSenderService """
|
||||||
|
|
||||||
|
def test_service_initialization(self):
|
||||||
|
"""Test service can be initialized."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
assert service is not None
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_email_sender_with_doc(self):
|
||||||
|
"""Test getting email senders when document exists."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
# Mock document
|
||||||
|
mock_doc = Mock()
|
||||||
|
mock_doc.email_senders = ["test@example.com", "admin@example.com"]
|
||||||
|
|
||||||
|
# Mock EmailSenderDoc.find_one
|
||||||
|
with patch('backend.services.email_sender_service.EmailSenderDoc.find_one', new_callable=AsyncMock, return_value=mock_doc):
|
||||||
|
result = await service.get_email_sender("tenant_123")
|
||||||
|
|
||||||
|
assert result == ["test@example.com", "admin@example.com"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_email_sender_no_doc(self):
|
||||||
|
"""Test getting email senders when document doesn't exist."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
# Mock EmailSenderDoc.find_one returning None
|
||||||
|
with patch('backend.services.email_sender_service.EmailSenderDoc.find_one', new_callable=AsyncMock, return_value=None):
|
||||||
|
result = await service.get_email_sender("tenant_123")
|
||||||
|
|
||||||
|
assert result == []
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_set_email_sender_existing_doc(self):
|
||||||
|
"""Test setting email senders for existing document."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
# Mock document
|
||||||
|
mock_doc = Mock()
|
||||||
|
mock_doc.email_senders = ["new@example.com"]
|
||||||
|
mock_doc.set = AsyncMock()
|
||||||
|
|
||||||
|
# Mock EmailSenderDoc.find_one
|
||||||
|
with patch('backend.services.email_sender_service.EmailSenderDoc.find_one', new_callable=AsyncMock, return_value=mock_doc):
|
||||||
|
result = await service.set_email_sender("tenant_123", ["new@example.com"])
|
||||||
|
|
||||||
|
mock_doc.set.assert_called_once_with({"email_senders": ["new@example.com"]})
|
||||||
|
assert result["success"] is True
|
||||||
|
assert result["email_senders"] == ["new@example.com"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_set_email_sender_new_doc(self):
|
||||||
|
"""Test setting email senders for new document."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
# Mock document that will be returned by the constructor
|
||||||
|
mock_doc = Mock()
|
||||||
|
mock_doc.email_senders = ["new@example.com"]
|
||||||
|
mock_doc.create = AsyncMock()
|
||||||
|
|
||||||
|
# Create a simple async mock function
|
||||||
|
async def mock_find_one(query):
|
||||||
|
return None # No existing document
|
||||||
|
|
||||||
|
# Mock the entire EmailSenderDoc class
|
||||||
|
with patch('backend.services.email_sender_service.EmailSenderDoc') as mock_doc_class:
|
||||||
|
# Set up the find_one method
|
||||||
|
mock_doc_class.find_one = mock_find_one
|
||||||
|
# Set up the constructor
|
||||||
|
mock_doc_class.return_value = mock_doc
|
||||||
|
|
||||||
|
result = await service.set_email_sender("tenant_123", ["new@example.com"])
|
||||||
|
|
||||||
|
# Verify the constructor was called
|
||||||
|
mock_doc_class.assert_called_once_with(tenant_id="tenant_123", email_senders=["new@example.com"])
|
||||||
|
mock_doc.create.assert_called_once()
|
||||||
|
assert result["success"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_add_email_senders_no_senders(self):
|
||||||
|
"""Test adding email senders with no senders provided."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
result = await service.add_email_senders("tenant_123", None)
|
||||||
|
|
||||||
|
assert result["success"] is False
|
||||||
|
assert "No sender provided" in result["msg"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_add_email_senders_not_list(self):
|
||||||
|
"""Test adding email senders with non-list input."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
result = await service.add_email_senders("tenant_123", "not_a_list")
|
||||||
|
|
||||||
|
assert result["success"] is False
|
||||||
|
assert "No sender provided" in result["msg"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_add_email_senders_existing_doc(self):
|
||||||
|
"""Test adding email senders to existing document."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
# Mock document
|
||||||
|
mock_doc = Mock()
|
||||||
|
mock_doc.email_senders = ["existing@example.com"]
|
||||||
|
mock_doc.set = AsyncMock()
|
||||||
|
|
||||||
|
# Mock EmailSenderDoc.find_one
|
||||||
|
with patch('backend.services.email_sender_service.EmailSenderDoc.find_one', new_callable=AsyncMock, return_value=mock_doc):
|
||||||
|
result = await service.add_email_senders("tenant_123", ["new@example.com"])
|
||||||
|
|
||||||
|
# Verify the set was called with the correct email_senders (order doesn't matter)
|
||||||
|
mock_doc.set.assert_called_once()
|
||||||
|
call_args = mock_doc.set.call_args[0][0]
|
||||||
|
assert set(call_args["email_senders"]) == {"existing@example.com", "new@example.com"}
|
||||||
|
assert result["success"] is True
|
||||||
|
assert "new@example.com" in result["email_senders"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_add_email_senders_all_exist(self):
|
||||||
|
"""Test adding email senders when all already exist."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
# Mock document
|
||||||
|
mock_doc = Mock()
|
||||||
|
mock_doc.email_senders = ["existing@example.com"]
|
||||||
|
|
||||||
|
# Mock EmailSenderDoc.find_one
|
||||||
|
with patch('backend.services.email_sender_service.EmailSenderDoc.find_one', new_callable=AsyncMock, return_value=mock_doc):
|
||||||
|
result = await service.add_email_senders("tenant_123", ["existing@example.com"])
|
||||||
|
|
||||||
|
assert result["success"] is False
|
||||||
|
assert "All senders already exist" in result["msg"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_add_email_senders_empty_list(self):
|
||||||
|
"""Test adding empty list of email senders."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
result = await service.add_email_senders("tenant_123", [])
|
||||||
|
|
||||||
|
assert result["success"] is False
|
||||||
|
assert "No sender provided" in result["msg"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_add_email_senders_new_doc_simple(self):
|
||||||
|
"""Test adding email senders when document doesn't exist with simple mocking."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
# Mock document that will be returned by the constructor
|
||||||
|
mock_doc = Mock()
|
||||||
|
mock_doc.email_senders = ["new@example.com"]
|
||||||
|
mock_doc.create = AsyncMock()
|
||||||
|
|
||||||
|
# Create a simple async mock function
|
||||||
|
async def mock_find_one(query):
|
||||||
|
return None # No existing document
|
||||||
|
|
||||||
|
# Mock the entire EmailSenderDoc class
|
||||||
|
with patch('backend.services.email_sender_service.EmailSenderDoc') as mock_doc_class:
|
||||||
|
# Set up the find_one method
|
||||||
|
mock_doc_class.find_one = mock_find_one
|
||||||
|
# Set up the constructor
|
||||||
|
mock_doc_class.return_value = mock_doc
|
||||||
|
|
||||||
|
result = await service.add_email_senders("tenant_123", ["new@example.com"])
|
||||||
|
|
||||||
|
# Verify the constructor was called
|
||||||
|
mock_doc_class.assert_called_once_with(tenant_id="tenant_123", email_senders=["new@example.com"])
|
||||||
|
mock_doc.create.assert_called_once()
|
||||||
|
assert result["success"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_remove_email_senders_no_doc(self):
|
||||||
|
"""Test removing email senders when document doesn't exist."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
# Mock EmailSenderDoc.find_one returning None
|
||||||
|
with patch('backend.services.email_sender_service.EmailSenderDoc.find_one', new_callable=AsyncMock, return_value=None):
|
||||||
|
result = await service.remove_email_senders("tenant_123", ["test@example.com"])
|
||||||
|
|
||||||
|
assert result["success"] is False
|
||||||
|
assert "No sender found" in result["msg"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_remove_email_senders_no_senders(self):
|
||||||
|
"""Test removing email senders when document has no senders."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
# Mock document with no senders
|
||||||
|
mock_doc = Mock()
|
||||||
|
mock_doc.email_senders = []
|
||||||
|
|
||||||
|
# Mock EmailSenderDoc.find_one
|
||||||
|
with patch('backend.services.email_sender_service.EmailSenderDoc.find_one', new_callable=AsyncMock, return_value=mock_doc):
|
||||||
|
result = await service.remove_email_senders("tenant_123", ["test@example.com"])
|
||||||
|
|
||||||
|
assert result["success"] is False
|
||||||
|
assert "No sender found" in result["msg"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_remove_email_senders_success(self):
|
||||||
|
"""Test successful email sender removal."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
# Mock document
|
||||||
|
mock_doc = Mock()
|
||||||
|
mock_doc.email_senders = ["test@example.com", "admin@example.com"]
|
||||||
|
mock_doc.set = AsyncMock()
|
||||||
|
|
||||||
|
# Mock EmailSenderDoc.find_one
|
||||||
|
with patch('backend.services.email_sender_service.EmailSenderDoc.find_one', new_callable=AsyncMock, return_value=mock_doc):
|
||||||
|
result = await service.remove_email_senders("tenant_123", ["test@example.com"])
|
||||||
|
|
||||||
|
mock_doc.set.assert_called_once_with({"email_senders": ["admin@example.com"]})
|
||||||
|
assert result["success"] is True
|
||||||
|
assert result["remaining"] == ["admin@example.com"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_remove_email_senders_no_match(self):
|
||||||
|
"""Test removing email senders when none match."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
# Mock document
|
||||||
|
mock_doc = Mock()
|
||||||
|
mock_doc.email_senders = ["admin@example.com"]
|
||||||
|
|
||||||
|
# Mock EmailSenderDoc.find_one
|
||||||
|
with patch('backend.services.email_sender_service.EmailSenderDoc.find_one', new_callable=AsyncMock, return_value=mock_doc):
|
||||||
|
result = await service.remove_email_senders("tenant_123", ["test@example.com"])
|
||||||
|
|
||||||
|
assert result["success"] is False
|
||||||
|
assert "No sender matched for removal" in result["msg"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_clear_email_senders_success(self):
|
||||||
|
"""Test successful email sender clearing."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
# Mock document
|
||||||
|
mock_doc = Mock()
|
||||||
|
mock_doc.set = AsyncMock()
|
||||||
|
|
||||||
|
# Mock EmailSenderDoc.find_one
|
||||||
|
with patch('backend.services.email_sender_service.EmailSenderDoc.find_one', new_callable=AsyncMock, return_value=mock_doc):
|
||||||
|
result = await service.clear_email_senders("tenant_123")
|
||||||
|
|
||||||
|
mock_doc.set.assert_called_once_with({"email_senders": []})
|
||||||
|
assert result["success"] is True
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_clear_email_senders_no_doc(self):
|
||||||
|
"""Test clearing email senders when document doesn't exist."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
# Mock EmailSenderDoc.find_one returning None
|
||||||
|
with patch('backend.services.email_sender_service.EmailSenderDoc.find_one', new_callable=AsyncMock, return_value=None):
|
||||||
|
result = await service.clear_email_senders("tenant_123")
|
||||||
|
|
||||||
|
assert result["success"] is False
|
||||||
|
assert "No sender config found" in result["msg"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_delete_email_sender_success(self):
|
||||||
|
"""Test successful email sender deletion."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
# Mock document
|
||||||
|
mock_doc = Mock()
|
||||||
|
mock_doc.delete = AsyncMock()
|
||||||
|
|
||||||
|
# Mock EmailSenderDoc.find_one
|
||||||
|
with patch('backend.services.email_sender_service.EmailSenderDoc.find_one', new_callable=AsyncMock, return_value=mock_doc):
|
||||||
|
result = await service.delete_email_sender("tenant_123")
|
||||||
|
|
||||||
|
mock_doc.delete.assert_called_once()
|
||||||
|
assert result["success"] is True
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_delete_email_sender_no_doc(self):
|
||||||
|
"""Test deleting email sender when document doesn't exist."""
|
||||||
|
service = EmailSenderService()
|
||||||
|
|
||||||
|
# Mock EmailSenderDoc.find_one returning None
|
||||||
|
with patch('backend.services.email_sender_service.EmailSenderDoc.find_one', new_callable=AsyncMock, return_value=None):
|
||||||
|
result = await service.delete_email_sender("tenant_123")
|
||||||
|
|
||||||
|
assert result["success"] is False
|
||||||
|
assert "No sender config found" in result["msg"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user