From def50f709f193c796120ace94a535dac1b11c323 Mon Sep 17 00:00:00 2001 From: YuehuCao Date: Mon, 28 Jul 2025 11:03:01 +0800 Subject: [PATCH] test: add unit tests for template_message and email_sender services - Achieve 100% coverage for both services --- .../tests/unit_tests/test_services.py | 818 ++++++++++++++++++ 1 file changed, 818 insertions(+) create mode 100644 apps/notification/tests/unit_tests/test_services.py diff --git a/apps/notification/tests/unit_tests/test_services.py b/apps/notification/tests/unit_tests/test_services.py new file mode 100644 index 0000000..648d455 --- /dev/null +++ b/apps/notification/tests/unit_tests/test_services.py @@ -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"] + + + + + + + + + + + + + + + + \ No newline at end of file