Files
orion/tests/unit/services/test_vendor_domain_service.py
Samir Boulahtit 0583dd2cc4 refactor: move letzshop endpoints to marketplace module and add vendor service tests
Move letzshop-related functionality from tenancy to marketplace module:
- Move admin letzshop routes to marketplace/routes/api/admin_letzshop.py
- Move letzshop schemas to marketplace/schemas/letzshop.py
- Remove letzshop code from tenancy module (admin_vendors, vendor_service)
- Update model exports and imports

Add comprehensive unit tests for vendor services:
- test_company_service.py: Company management operations
- test_platform_service.py: Platform management operations
- test_vendor_domain_service.py: Vendor domain operations
- test_vendor_team_service.py: Vendor team management

Update module definitions:
- billing, messaging, payments: Minor definition updates

Add architecture proposals documentation:
- Module dependency redesign session notes
- Decouple modules implementation plan
- Module decoupling proposal

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 19:25:00 +01:00

421 lines
15 KiB
Python

# 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)