# tests/unit/services/test_vendor_domain_service.py """Unit tests for VendorDomainService.""" import uuid from datetime import UTC, datetime from unittest.mock import MagicMock, patch import pytest from pydantic import ValidationError from app.modules.tenancy.exceptions import ( DNSVerificationException, DomainAlreadyVerifiedException, DomainNotVerifiedException, DomainVerificationFailedException, MaxDomainsReachedException, VendorDomainAlreadyExistsException, VendorDomainNotFoundException, VendorNotFoundException, ) from app.modules.tenancy.models import VendorDomain from app.modules.tenancy.schemas.vendor_domain import VendorDomainCreate, VendorDomainUpdate from app.modules.tenancy.services.vendor_domain_service import vendor_domain_service # ============================================================================= # FIXTURES # ============================================================================= @pytest.fixture def test_domain(db, test_vendor): """Create a test domain for a vendor.""" unique_id = str(uuid.uuid4())[:8] domain = VendorDomain( vendor_id=test_vendor.id, domain=f"test{unique_id}.example.com", is_primary=False, is_active=False, is_verified=False, verification_token=f"token_{unique_id}", ssl_status="pending", ) db.add(domain) db.commit() db.refresh(domain) return domain @pytest.fixture def verified_domain(db, test_vendor): """Create a verified domain for a vendor.""" unique_id = str(uuid.uuid4())[:8] domain = VendorDomain( vendor_id=test_vendor.id, domain=f"verified{unique_id}.example.com", is_primary=False, is_active=True, is_verified=True, verified_at=datetime.now(UTC), verification_token=f"verified_token_{unique_id}", ssl_status="active", ) db.add(domain) db.commit() db.refresh(domain) return domain @pytest.fixture def primary_domain(db, test_vendor): """Create a primary domain for a vendor.""" unique_id = str(uuid.uuid4())[:8] domain = VendorDomain( vendor_id=test_vendor.id, domain=f"primary{unique_id}.example.com", is_primary=True, is_active=True, is_verified=True, verified_at=datetime.now(UTC), verification_token=f"primary_token_{unique_id}", ssl_status="active", ) db.add(domain) db.commit() db.refresh(domain) return domain # ============================================================================= # ADD DOMAIN TESTS # ============================================================================= @pytest.mark.unit @pytest.mark.tenancy class TestVendorDomainServiceAdd: """Test suite for adding vendor domains.""" def test_add_domain_success(self, db, test_vendor): """Test successfully adding a domain to a vendor.""" unique_id = str(uuid.uuid4())[:8] domain_data = VendorDomainCreate( domain=f"newdomain{unique_id}.example.com", is_primary=False, ) result = vendor_domain_service.add_domain(db, test_vendor.id, domain_data) db.commit() assert result is not None assert result.vendor_id == test_vendor.id assert result.domain == f"newdomain{unique_id}.example.com" assert result.is_primary is False assert result.is_verified is False assert result.is_active is False assert result.verification_token is not None def test_add_domain_as_primary(self, db, test_vendor, primary_domain): """Test adding a domain as primary unsets other primary domains.""" unique_id = str(uuid.uuid4())[:8] domain_data = VendorDomainCreate( domain=f"newprimary{unique_id}.example.com", is_primary=True, ) result = vendor_domain_service.add_domain(db, test_vendor.id, domain_data) db.commit() db.refresh(primary_domain) assert result.is_primary is True assert primary_domain.is_primary is False def test_add_domain_vendor_not_found(self, db): """Test adding domain to non-existent vendor raises exception.""" domain_data = VendorDomainCreate( domain="test.example.com", is_primary=False, ) with pytest.raises(VendorNotFoundException): vendor_domain_service.add_domain(db, 99999, domain_data) def test_add_domain_already_exists(self, db, test_vendor, test_domain): """Test adding a domain that already exists raises exception.""" domain_data = VendorDomainCreate( domain=test_domain.domain, is_primary=False, ) with pytest.raises(VendorDomainAlreadyExistsException): vendor_domain_service.add_domain(db, test_vendor.id, domain_data) def test_add_domain_max_limit_reached(self, db, test_vendor): """Test adding domain when max limit reached raises exception.""" # Create max domains for i in range(vendor_domain_service.max_domains_per_vendor): domain = VendorDomain( vendor_id=test_vendor.id, domain=f"domain{i}_{uuid.uuid4().hex[:6]}.example.com", verification_token=f"token_{i}_{uuid.uuid4().hex[:6]}", ) db.add(domain) db.commit() domain_data = VendorDomainCreate( domain="onemore.example.com", is_primary=False, ) with pytest.raises(MaxDomainsReachedException): vendor_domain_service.add_domain(db, test_vendor.id, domain_data) def test_add_domain_reserved_subdomain(self, db, test_vendor): """Test adding a domain with reserved subdomain raises exception. Note: Reserved subdomain validation happens in Pydantic schema first. """ with pytest.raises(ValidationError) as exc_info: VendorDomainCreate( domain="admin.example.com", is_primary=False, ) assert "reserved subdomain" in str(exc_info.value).lower() # ============================================================================= # GET DOMAINS TESTS # ============================================================================= @pytest.mark.unit @pytest.mark.tenancy class TestVendorDomainServiceGet: """Test suite for getting vendor domains.""" def test_get_vendor_domains_success(self, db, test_vendor, test_domain, verified_domain): """Test getting all domains for a vendor.""" domains = vendor_domain_service.get_vendor_domains(db, test_vendor.id) assert len(domains) >= 2 domain_ids = [d.id for d in domains] assert test_domain.id in domain_ids assert verified_domain.id in domain_ids def test_get_vendor_domains_empty(self, db, test_vendor): """Test getting domains for vendor with no domains.""" domains = vendor_domain_service.get_vendor_domains(db, test_vendor.id) # May have domains from other fixtures, so just check it returns a list assert isinstance(domains, list) def test_get_vendor_domains_vendor_not_found(self, db): """Test getting domains for non-existent vendor raises exception.""" with pytest.raises(VendorNotFoundException): vendor_domain_service.get_vendor_domains(db, 99999) def test_get_domain_by_id_success(self, db, test_domain): """Test getting a domain by ID.""" domain = vendor_domain_service.get_domain_by_id(db, test_domain.id) assert domain is not None assert domain.id == test_domain.id assert domain.domain == test_domain.domain def test_get_domain_by_id_not_found(self, db): """Test getting non-existent domain raises exception.""" with pytest.raises(VendorDomainNotFoundException): vendor_domain_service.get_domain_by_id(db, 99999) # ============================================================================= # UPDATE DOMAIN TESTS # ============================================================================= @pytest.mark.unit @pytest.mark.tenancy class TestVendorDomainServiceUpdate: """Test suite for updating vendor domains.""" def test_update_domain_set_primary(self, db, test_domain, primary_domain): """Test setting a domain as primary.""" update_data = VendorDomainUpdate(is_primary=True) # First verify the domain (required for activation) test_domain.is_verified = True db.commit() result = vendor_domain_service.update_domain(db, test_domain.id, update_data) db.commit() db.refresh(primary_domain) assert result.is_primary is True assert primary_domain.is_primary is False def test_update_domain_activate_verified(self, db, verified_domain): """Test activating a verified domain.""" verified_domain.is_active = False db.commit() update_data = VendorDomainUpdate(is_active=True) result = vendor_domain_service.update_domain(db, verified_domain.id, update_data) db.commit() assert result.is_active is True def test_update_domain_activate_unverified_fails(self, db, test_domain): """Test activating an unverified domain raises exception.""" update_data = VendorDomainUpdate(is_active=True) with pytest.raises(DomainNotVerifiedException): vendor_domain_service.update_domain(db, test_domain.id, update_data) def test_update_domain_not_found(self, db): """Test updating non-existent domain raises exception.""" update_data = VendorDomainUpdate(is_primary=True) with pytest.raises(VendorDomainNotFoundException): vendor_domain_service.update_domain(db, 99999, update_data) def test_update_domain_deactivate(self, db, verified_domain): """Test deactivating a domain.""" update_data = VendorDomainUpdate(is_active=False) result = vendor_domain_service.update_domain(db, verified_domain.id, update_data) db.commit() assert result.is_active is False # ============================================================================= # DELETE DOMAIN TESTS # ============================================================================= @pytest.mark.unit @pytest.mark.tenancy class TestVendorDomainServiceDelete: """Test suite for deleting vendor domains.""" def test_delete_domain_success(self, db, test_vendor): """Test successfully deleting a domain.""" # Create a domain to delete unique_id = str(uuid.uuid4())[:8] domain = VendorDomain( vendor_id=test_vendor.id, domain=f"todelete{unique_id}.example.com", verification_token=f"delete_token_{unique_id}", ) db.add(domain) db.commit() domain_id = domain.id domain_name = domain.domain result = vendor_domain_service.delete_domain(db, domain_id) db.commit() assert domain_name in result # Verify it's gone with pytest.raises(VendorDomainNotFoundException): vendor_domain_service.get_domain_by_id(db, domain_id) def test_delete_domain_not_found(self, db): """Test deleting non-existent domain raises exception.""" with pytest.raises(VendorDomainNotFoundException): vendor_domain_service.delete_domain(db, 99999) # ============================================================================= # VERIFY DOMAIN TESTS # ============================================================================= @pytest.mark.unit @pytest.mark.tenancy class TestVendorDomainServiceVerify: """Test suite for domain verification.""" @patch("dns.resolver.resolve") def test_verify_domain_success(self, mock_resolve, db, test_domain): """Test successful domain verification.""" # Mock DNS response mock_txt = MagicMock() mock_txt.to_text.return_value = f'"{test_domain.verification_token}"' mock_resolve.return_value = [mock_txt] domain, message = vendor_domain_service.verify_domain(db, test_domain.id) db.commit() assert domain.is_verified is True assert domain.verified_at is not None assert "verified successfully" in message.lower() def test_verify_domain_already_verified(self, db, verified_domain): """Test verifying already verified domain raises exception.""" with pytest.raises(DomainAlreadyVerifiedException): vendor_domain_service.verify_domain(db, verified_domain.id) def test_verify_domain_not_found(self, db): """Test verifying non-existent domain raises exception.""" with pytest.raises(VendorDomainNotFoundException): vendor_domain_service.verify_domain(db, 99999) @patch("dns.resolver.resolve") def test_verify_domain_token_not_found(self, mock_resolve, db, test_domain): """Test verification fails when token not found in DNS.""" # Mock DNS response with wrong token mock_txt = MagicMock() mock_txt.to_text.return_value = '"wrong_token"' mock_resolve.return_value = [mock_txt] with pytest.raises(DomainVerificationFailedException) as exc_info: vendor_domain_service.verify_domain(db, test_domain.id) assert "token not found" in str(exc_info.value).lower() @patch("dns.resolver.resolve") def test_verify_domain_dns_nxdomain(self, mock_resolve, db, test_domain): """Test verification fails when DNS record doesn't exist.""" import dns.resolver mock_resolve.side_effect = dns.resolver.NXDOMAIN() with pytest.raises(DomainVerificationFailedException): vendor_domain_service.verify_domain(db, test_domain.id) # ============================================================================= # VERIFICATION INSTRUCTIONS TESTS # ============================================================================= @pytest.mark.unit @pytest.mark.tenancy class TestVendorDomainServiceInstructions: """Test suite for verification instructions.""" def test_get_verification_instructions(self, db, test_domain): """Test getting verification instructions.""" instructions = vendor_domain_service.get_verification_instructions( db, test_domain.id ) assert instructions["domain"] == test_domain.domain assert instructions["verification_token"] == test_domain.verification_token assert "instructions" in instructions assert "txt_record" in instructions assert instructions["txt_record"]["type"] == "TXT" assert instructions["txt_record"]["name"] == "_wizamart-verify" assert "common_registrars" in instructions def test_get_verification_instructions_not_found(self, db): """Test getting instructions for non-existent domain raises exception.""" with pytest.raises(VendorDomainNotFoundException): vendor_domain_service.get_verification_instructions(db, 99999)