fix(hosting): require merchant or prospect for site creation
- Schema: add merchant_id/prospect_id with model_validator requiring at least one. Remove from-prospect endpoint (unified into POST /sites) - Service: rewrite create() — if merchant_id use it directly, if prospect_id auto-create merchant from prospect data. Remove system merchant hack entirely. Extract _create_merchant_from_prospect helper. - Simplify accept_proposal() — merchant already exists at creation, only creates subscription and marks prospect converted - Tests: update all create calls with merchant_id, replace from-prospect tests with prospect_id + validation tests Closes docs/proposals/hosting-site-creation-fix.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -55,6 +55,7 @@ class TestHostedSiteService:
|
||||
db,
|
||||
{
|
||||
"business_name": f"New Business {unique}",
|
||||
"merchant_id": system_merchant.id,
|
||||
"contact_email": f"test-{unique}@example.com",
|
||||
},
|
||||
)
|
||||
@@ -72,6 +73,7 @@ class TestHostedSiteService:
|
||||
db,
|
||||
{
|
||||
"business_name": f"Full Business {unique}",
|
||||
"merchant_id": system_merchant.id,
|
||||
"contact_name": "Jane Doe",
|
||||
"contact_email": f"jane-{unique}@example.com",
|
||||
"contact_phone": "+352 999 888",
|
||||
@@ -251,75 +253,65 @@ class TestHostedSiteLifecycle:
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.hosting
|
||||
class TestHostedSiteFromProspect:
|
||||
"""Tests for creating hosted sites from prospects."""
|
||||
"""Tests for creating hosted sites from prospects via prospect_id."""
|
||||
|
||||
def setup_method(self):
|
||||
self.service = HostedSiteService()
|
||||
|
||||
def test_create_from_prospect(self, db, hosting_platform, system_merchant):
|
||||
"""Test creating a hosted site from a prospect."""
|
||||
from app.modules.prospecting.models import Prospect
|
||||
def test_create_with_prospect_id(self, db, hosting_platform):
|
||||
"""Test creating a hosted site with prospect_id auto-creates merchant."""
|
||||
from app.modules.prospecting.models import Prospect, ProspectContact
|
||||
|
||||
unique = uuid.uuid4().hex[:8]
|
||||
prospect = Prospect(
|
||||
channel="digital",
|
||||
domain_name=f"prospect-{uuid.uuid4().hex[:8]}.lu",
|
||||
domain_name=f"prospect-{unique}.lu",
|
||||
business_name="Prospect Business",
|
||||
status="active",
|
||||
has_website=True,
|
||||
)
|
||||
db.add(prospect)
|
||||
db.flush()
|
||||
|
||||
# Add email contact (needed for merchant creation)
|
||||
db.add(ProspectContact(
|
||||
prospect_id=prospect.id,
|
||||
contact_type="email",
|
||||
value=f"hello-{unique}@test.lu",
|
||||
is_primary=True,
|
||||
))
|
||||
db.commit()
|
||||
db.refresh(prospect)
|
||||
|
||||
site = self.service.create_from_prospect(db, prospect.id)
|
||||
site = self.service.create(
|
||||
db,
|
||||
{
|
||||
"business_name": "Prospect Business",
|
||||
"prospect_id": prospect.id,
|
||||
"contact_email": f"hello-{unique}@test.lu",
|
||||
},
|
||||
)
|
||||
db.commit()
|
||||
|
||||
assert site.id is not None
|
||||
assert site.prospect_id == prospect.id
|
||||
assert site.business_name == "Prospect Business"
|
||||
assert site.store.merchant is not None
|
||||
|
||||
def test_create_from_prospect_with_contacts(
|
||||
self, db, hosting_platform, system_merchant
|
||||
):
|
||||
"""Test creating from prospect pre-fills contact info."""
|
||||
from app.modules.prospecting.models import Prospect, ProspectContact
|
||||
def test_create_without_merchant_or_prospect_raises(self, db, hosting_platform):
|
||||
"""Test that creating without merchant_id or prospect_id raises ValueError."""
|
||||
with pytest.raises(ValueError, match="Either merchant_id or prospect_id"):
|
||||
self.service.create(
|
||||
db,
|
||||
{"business_name": "No Owner"},
|
||||
)
|
||||
|
||||
prospect = Prospect(
|
||||
channel="digital",
|
||||
domain_name=f"contacts-{uuid.uuid4().hex[:8]}.lu",
|
||||
business_name="Contact Business",
|
||||
status="active",
|
||||
)
|
||||
db.add(prospect)
|
||||
db.flush()
|
||||
|
||||
email_contact = ProspectContact(
|
||||
prospect_id=prospect.id,
|
||||
contact_type="email",
|
||||
value="hello@test.lu",
|
||||
is_primary=True,
|
||||
)
|
||||
phone_contact = ProspectContact(
|
||||
prospect_id=prospect.id,
|
||||
contact_type="phone",
|
||||
value="+352 111 222",
|
||||
is_primary=True,
|
||||
)
|
||||
db.add_all([email_contact, phone_contact])
|
||||
db.commit()
|
||||
db.refresh(prospect)
|
||||
|
||||
site = self.service.create_from_prospect(db, prospect.id)
|
||||
db.commit()
|
||||
|
||||
assert site.contact_email == "hello@test.lu"
|
||||
assert site.contact_phone == "+352 111 222"
|
||||
|
||||
def test_create_from_nonexistent_prospect(
|
||||
self, db, hosting_platform, system_merchant
|
||||
):
|
||||
"""Test creating from non-existent prospect raises exception."""
|
||||
def test_create_with_nonexistent_prospect_raises(self, db, hosting_platform):
|
||||
"""Test creating with non-existent prospect raises exception."""
|
||||
from app.modules.prospecting.exceptions import ProspectNotFoundException
|
||||
|
||||
with pytest.raises(ProspectNotFoundException):
|
||||
self.service.create_from_prospect(db, 99999)
|
||||
self.service.create(
|
||||
db,
|
||||
{"business_name": "Ghost", "prospect_id": 99999},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user