- Fix admin header.html logout to not use localStorage.clear() which was clearing vendor/customer tokens too - Add tests for signup access_token generation - Test that token is returned in response - Test that token can authenticate API calls - Test that vendor_token cookie is set 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
795 lines
26 KiB
Python
795 lines
26 KiB
Python
# tests/integration/api/v1/platform/test_signup.py
|
|
"""Integration tests for platform signup API endpoints.
|
|
|
|
Tests the /api/v1/platform/signup/* endpoints.
|
|
"""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from models.database.company import Company
|
|
from models.database.subscription import TierCode
|
|
from models.database.user import User
|
|
from models.database.vendor import Vendor
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_stripe_service():
|
|
"""Mock the Stripe service for tests."""
|
|
with patch("app.services.platform_signup_service.stripe_service") as mock:
|
|
mock.create_customer.return_value = "cus_test_123"
|
|
mock.create_setup_intent.return_value = MagicMock(
|
|
id="seti_test_123",
|
|
client_secret="seti_test_123_secret_abc",
|
|
status="requires_payment_method",
|
|
)
|
|
mock.get_setup_intent.return_value = MagicMock(
|
|
id="seti_test_123",
|
|
status="succeeded",
|
|
payment_method="pm_test_123",
|
|
)
|
|
mock.attach_payment_method_to_customer.return_value = None
|
|
yield mock
|
|
|
|
|
|
@pytest.fixture
|
|
def signup_session(client):
|
|
"""Create a signup session for testing."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/start",
|
|
json={"tier_code": TierCode.PROFESSIONAL.value, "is_annual": False},
|
|
)
|
|
return response.json()["session_id"]
|
|
|
|
|
|
@pytest.fixture
|
|
def existing_user(db, auth_manager):
|
|
"""Create an existing user for testing duplicate email."""
|
|
user = User(
|
|
email="existing@example.com",
|
|
username="existing_user",
|
|
hashed_password=auth_manager.hash_password("password123"),
|
|
first_name="Existing",
|
|
last_name="User",
|
|
role="vendor",
|
|
is_active=True,
|
|
)
|
|
db.add(user)
|
|
db.commit()
|
|
return user
|
|
|
|
|
|
@pytest.fixture
|
|
def claimed_owner_user(db, auth_manager):
|
|
"""Create an owner user for the claimed vendor."""
|
|
user = User(
|
|
email="claimed_owner@test.com",
|
|
username="claimed_owner",
|
|
hashed_password=auth_manager.hash_password("testpass123"),
|
|
role="vendor",
|
|
is_active=True,
|
|
)
|
|
db.add(user)
|
|
db.commit()
|
|
return user
|
|
|
|
|
|
@pytest.fixture
|
|
def claimed_letzshop_vendor(db, claimed_owner_user):
|
|
"""Create a vendor that has already claimed a Letzshop shop."""
|
|
company = Company(
|
|
name="Claimed Company",
|
|
owner_user_id=claimed_owner_user.id,
|
|
contact_email="claimed@test.com",
|
|
)
|
|
db.add(company)
|
|
db.flush()
|
|
|
|
vendor = Vendor(
|
|
company_id=company.id,
|
|
vendor_code="CLAIMED",
|
|
subdomain="claimed",
|
|
name="Claimed Vendor",
|
|
contact_email="claimed@test.com",
|
|
is_active=True,
|
|
letzshop_vendor_slug="already-claimed-shop",
|
|
)
|
|
db.add(vendor)
|
|
db.commit()
|
|
return vendor
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.api
|
|
@pytest.mark.platform
|
|
class TestSignupStartAPI:
|
|
"""Test signup start endpoint at /api/v1/platform/signup/start."""
|
|
|
|
def test_start_signup_success(self, client):
|
|
"""Test starting a signup session."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/start",
|
|
json={"tier_code": TierCode.ESSENTIAL.value, "is_annual": False},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "session_id" in data
|
|
assert data["tier_code"] == TierCode.ESSENTIAL.value
|
|
assert data["is_annual"] is False
|
|
|
|
def test_start_signup_with_annual_billing(self, client):
|
|
"""Test starting signup with annual billing."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/start",
|
|
json={"tier_code": TierCode.PROFESSIONAL.value, "is_annual": True},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["is_annual"] is True
|
|
|
|
def test_start_signup_all_tiers(self, client):
|
|
"""Test starting signup for all valid tiers."""
|
|
for tier in [TierCode.ESSENTIAL, TierCode.PROFESSIONAL, TierCode.BUSINESS, TierCode.ENTERPRISE]:
|
|
response = client.post(
|
|
"/api/v1/platform/signup/start",
|
|
json={"tier_code": tier.value, "is_annual": False},
|
|
)
|
|
assert response.status_code == 200
|
|
assert response.json()["tier_code"] == tier.value
|
|
|
|
def test_start_signup_invalid_tier(self, client):
|
|
"""Test starting signup with invalid tier code."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/start",
|
|
json={"tier_code": "invalid_tier", "is_annual": False},
|
|
)
|
|
|
|
assert response.status_code == 422 # ValidationException
|
|
data = response.json()
|
|
assert "invalid tier" in data["message"].lower()
|
|
|
|
def test_start_signup_session_id_is_unique(self, client):
|
|
"""Test that each signup session gets a unique ID."""
|
|
response1 = client.post(
|
|
"/api/v1/platform/signup/start",
|
|
json={"tier_code": TierCode.ESSENTIAL.value, "is_annual": False},
|
|
)
|
|
response2 = client.post(
|
|
"/api/v1/platform/signup/start",
|
|
json={"tier_code": TierCode.ESSENTIAL.value, "is_annual": False},
|
|
)
|
|
|
|
assert response1.json()["session_id"] != response2.json()["session_id"]
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.api
|
|
@pytest.mark.platform
|
|
class TestClaimVendorAPI:
|
|
"""Test claim vendor endpoint at /api/v1/platform/signup/claim-vendor."""
|
|
|
|
def test_claim_vendor_success(self, client, signup_session):
|
|
"""Test claiming a Letzshop vendor."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/claim-vendor",
|
|
json={
|
|
"session_id": signup_session,
|
|
"letzshop_slug": "my-new-shop",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["session_id"] == signup_session
|
|
assert data["letzshop_slug"] == "my-new-shop"
|
|
assert data["vendor_name"] is not None
|
|
|
|
def test_claim_vendor_with_vendor_id(self, client, signup_session):
|
|
"""Test claiming vendor with Letzshop vendor ID."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/claim-vendor",
|
|
json={
|
|
"session_id": signup_session,
|
|
"letzshop_slug": "my-shop",
|
|
"letzshop_vendor_id": "letz_vendor_123",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["letzshop_slug"] == "my-shop"
|
|
|
|
def test_claim_vendor_invalid_session(self, client):
|
|
"""Test claiming vendor with invalid session."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/claim-vendor",
|
|
json={
|
|
"session_id": "invalid_session_id",
|
|
"letzshop_slug": "my-shop",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 404
|
|
data = response.json()
|
|
assert "not found" in data["message"].lower()
|
|
|
|
def test_claim_vendor_already_claimed(self, client, signup_session, claimed_letzshop_vendor):
|
|
"""Test claiming a vendor that's already claimed."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/claim-vendor",
|
|
json={
|
|
"session_id": signup_session,
|
|
"letzshop_slug": "already-claimed-shop",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 409 # ConflictException
|
|
data = response.json()
|
|
assert "already claimed" in data["message"].lower()
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.api
|
|
@pytest.mark.platform
|
|
class TestCreateAccountAPI:
|
|
"""Test create account endpoint at /api/v1/platform/signup/create-account."""
|
|
|
|
def test_create_account_success(self, client, signup_session, mock_stripe_service):
|
|
"""Test creating an account."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/create-account",
|
|
json={
|
|
"session_id": signup_session,
|
|
"email": "newuser@example.com",
|
|
"password": "SecurePass123!",
|
|
"first_name": "John",
|
|
"last_name": "Doe",
|
|
"company_name": "Test Company",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["session_id"] == signup_session
|
|
assert data["user_id"] > 0
|
|
assert data["vendor_id"] > 0
|
|
assert data["stripe_customer_id"] == "cus_test_123"
|
|
|
|
def test_create_account_with_phone(self, client, signup_session, mock_stripe_service):
|
|
"""Test creating an account with phone number."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/create-account",
|
|
json={
|
|
"session_id": signup_session,
|
|
"email": "user2@example.com",
|
|
"password": "SecurePass123!",
|
|
"first_name": "Jane",
|
|
"last_name": "Smith",
|
|
"company_name": "Another Company",
|
|
"phone": "+352 123 456 789",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["user_id"] > 0
|
|
|
|
def test_create_account_invalid_session(self, client, mock_stripe_service):
|
|
"""Test creating account with invalid session."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/create-account",
|
|
json={
|
|
"session_id": "invalid_session",
|
|
"email": "test@example.com",
|
|
"password": "SecurePass123!",
|
|
"first_name": "Test",
|
|
"last_name": "User",
|
|
"company_name": "Test Co",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 404
|
|
|
|
def test_create_account_duplicate_email(
|
|
self, client, signup_session, existing_user, mock_stripe_service
|
|
):
|
|
"""Test creating account with existing email."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/create-account",
|
|
json={
|
|
"session_id": signup_session,
|
|
"email": "existing@example.com",
|
|
"password": "SecurePass123!",
|
|
"first_name": "Another",
|
|
"last_name": "User",
|
|
"company_name": "Duplicate Co",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 409 # ConflictException
|
|
data = response.json()
|
|
assert "already exists" in data["message"].lower()
|
|
|
|
def test_create_account_invalid_email(self, client, signup_session):
|
|
"""Test creating account with invalid email format."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/create-account",
|
|
json={
|
|
"session_id": signup_session,
|
|
"email": "not-an-email",
|
|
"password": "SecurePass123!",
|
|
"first_name": "Test",
|
|
"last_name": "User",
|
|
"company_name": "Test Co",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 422 # Validation error
|
|
|
|
def test_create_account_with_letzshop_claim(self, client, mock_stripe_service):
|
|
"""Test creating account after claiming Letzshop vendor."""
|
|
# Start signup
|
|
start_response = client.post(
|
|
"/api/v1/platform/signup/start",
|
|
json={"tier_code": TierCode.PROFESSIONAL.value, "is_annual": False},
|
|
)
|
|
session_id = start_response.json()["session_id"]
|
|
|
|
# Claim vendor
|
|
client.post(
|
|
"/api/v1/platform/signup/claim-vendor",
|
|
json={
|
|
"session_id": session_id,
|
|
"letzshop_slug": "my-shop-claim",
|
|
},
|
|
)
|
|
|
|
# Create account
|
|
response = client.post(
|
|
"/api/v1/platform/signup/create-account",
|
|
json={
|
|
"session_id": session_id,
|
|
"email": "shop@example.com",
|
|
"password": "SecurePass123!",
|
|
"first_name": "Shop",
|
|
"last_name": "Owner",
|
|
"company_name": "My Shop",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["vendor_id"] > 0
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.api
|
|
@pytest.mark.platform
|
|
class TestSetupPaymentAPI:
|
|
"""Test setup payment endpoint at /api/v1/platform/signup/setup-payment."""
|
|
|
|
def test_setup_payment_success(self, client, signup_session, mock_stripe_service):
|
|
"""Test setting up payment after account creation."""
|
|
# Create account first
|
|
client.post(
|
|
"/api/v1/platform/signup/create-account",
|
|
json={
|
|
"session_id": signup_session,
|
|
"email": "payment@example.com",
|
|
"password": "SecurePass123!",
|
|
"first_name": "Payment",
|
|
"last_name": "Test",
|
|
"company_name": "Payment Co",
|
|
},
|
|
)
|
|
|
|
# Setup payment
|
|
response = client.post(
|
|
"/api/v1/platform/signup/setup-payment",
|
|
json={"session_id": signup_session},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["session_id"] == signup_session
|
|
assert "client_secret" in data
|
|
assert data["stripe_customer_id"] == "cus_test_123"
|
|
|
|
def test_setup_payment_invalid_session(self, client, mock_stripe_service):
|
|
"""Test setup payment with invalid session."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/setup-payment",
|
|
json={"session_id": "invalid_session"},
|
|
)
|
|
|
|
assert response.status_code == 404
|
|
|
|
def test_setup_payment_without_account(self, client, signup_session, mock_stripe_service):
|
|
"""Test setup payment without creating account first."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/setup-payment",
|
|
json={"session_id": signup_session},
|
|
)
|
|
|
|
assert response.status_code == 422 # ValidationException
|
|
data = response.json()
|
|
assert "account not created" in data["message"].lower()
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.api
|
|
@pytest.mark.platform
|
|
class TestCompleteSignupAPI:
|
|
"""Test complete signup endpoint at /api/v1/platform/signup/complete."""
|
|
|
|
def test_complete_signup_success(self, client, signup_session, mock_stripe_service, db):
|
|
"""Test completing signup after payment setup."""
|
|
# Create account
|
|
client.post(
|
|
"/api/v1/platform/signup/create-account",
|
|
json={
|
|
"session_id": signup_session,
|
|
"email": "complete@example.com",
|
|
"password": "SecurePass123!",
|
|
"first_name": "Complete",
|
|
"last_name": "User",
|
|
"company_name": "Complete Co",
|
|
},
|
|
)
|
|
|
|
# Setup payment
|
|
client.post(
|
|
"/api/v1/platform/signup/setup-payment",
|
|
json={"session_id": signup_session},
|
|
)
|
|
|
|
# Complete signup
|
|
response = client.post(
|
|
"/api/v1/platform/signup/complete",
|
|
json={
|
|
"session_id": signup_session,
|
|
"setup_intent_id": "seti_test_123",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert "vendor_code" in data
|
|
assert "vendor_id" in data
|
|
assert "redirect_url" in data
|
|
assert "trial_ends_at" in data
|
|
|
|
def test_complete_signup_returns_access_token(
|
|
self, client, signup_session, mock_stripe_service, db
|
|
):
|
|
"""Test that completing signup returns a valid JWT access token for auto-login."""
|
|
# Create account
|
|
client.post(
|
|
"/api/v1/platform/signup/create-account",
|
|
json={
|
|
"session_id": signup_session,
|
|
"email": "token_test@example.com",
|
|
"password": "SecurePass123!",
|
|
"first_name": "Token",
|
|
"last_name": "Test",
|
|
"company_name": "Token Test Co",
|
|
},
|
|
)
|
|
|
|
# Setup payment
|
|
client.post(
|
|
"/api/v1/platform/signup/setup-payment",
|
|
json={"session_id": signup_session},
|
|
)
|
|
|
|
# Complete signup
|
|
response = client.post(
|
|
"/api/v1/platform/signup/complete",
|
|
json={
|
|
"session_id": signup_session,
|
|
"setup_intent_id": "seti_test_123",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# Verify access_token is returned
|
|
assert "access_token" in data
|
|
assert data["access_token"] is not None
|
|
assert len(data["access_token"]) > 50 # JWT tokens are long
|
|
|
|
def test_complete_signup_token_can_authenticate(
|
|
self, client, signup_session, mock_stripe_service, db
|
|
):
|
|
"""Test that the returned access token can be used to authenticate API calls."""
|
|
# Create account
|
|
client.post(
|
|
"/api/v1/platform/signup/create-account",
|
|
json={
|
|
"session_id": signup_session,
|
|
"email": "auth_test@example.com",
|
|
"password": "SecurePass123!",
|
|
"first_name": "Auth",
|
|
"last_name": "Test",
|
|
"company_name": "Auth Test Co",
|
|
},
|
|
)
|
|
|
|
# Setup payment
|
|
client.post(
|
|
"/api/v1/platform/signup/setup-payment",
|
|
json={"session_id": signup_session},
|
|
)
|
|
|
|
# Complete signup
|
|
complete_response = client.post(
|
|
"/api/v1/platform/signup/complete",
|
|
json={
|
|
"session_id": signup_session,
|
|
"setup_intent_id": "seti_test_123",
|
|
},
|
|
)
|
|
|
|
assert complete_response.status_code == 200
|
|
access_token = complete_response.json()["access_token"]
|
|
|
|
# Use the token to access a protected vendor endpoint
|
|
auth_response = client.get(
|
|
"/api/v1/vendor/onboarding/status",
|
|
headers={"Authorization": f"Bearer {access_token}"},
|
|
)
|
|
|
|
# Should be able to access the onboarding endpoint
|
|
assert auth_response.status_code == 200
|
|
|
|
def test_complete_signup_sets_vendor_token_cookie(
|
|
self, client, signup_session, mock_stripe_service, db
|
|
):
|
|
"""Test that completing signup sets the vendor_token HTTP-only cookie."""
|
|
# Create account
|
|
client.post(
|
|
"/api/v1/platform/signup/create-account",
|
|
json={
|
|
"session_id": signup_session,
|
|
"email": "cookie_test@example.com",
|
|
"password": "SecurePass123!",
|
|
"first_name": "Cookie",
|
|
"last_name": "Test",
|
|
"company_name": "Cookie Test Co",
|
|
},
|
|
)
|
|
|
|
# Setup payment
|
|
client.post(
|
|
"/api/v1/platform/signup/setup-payment",
|
|
json={"session_id": signup_session},
|
|
)
|
|
|
|
# Complete signup
|
|
response = client.post(
|
|
"/api/v1/platform/signup/complete",
|
|
json={
|
|
"session_id": signup_session,
|
|
"setup_intent_id": "seti_test_123",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
|
|
# Check that vendor_token cookie is set
|
|
cookies = response.cookies
|
|
assert "vendor_token" in cookies
|
|
|
|
def test_complete_signup_invalid_session(self, client, mock_stripe_service):
|
|
"""Test completing signup with invalid session."""
|
|
response = client.post(
|
|
"/api/v1/platform/signup/complete",
|
|
json={
|
|
"session_id": "invalid_session",
|
|
"setup_intent_id": "seti_test_123",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 404
|
|
|
|
def test_complete_signup_payment_not_succeeded(
|
|
self, client, signup_session, mock_stripe_service
|
|
):
|
|
"""Test completing signup when payment setup failed."""
|
|
# Create account
|
|
client.post(
|
|
"/api/v1/platform/signup/create-account",
|
|
json={
|
|
"session_id": signup_session,
|
|
"email": "fail@example.com",
|
|
"password": "SecurePass123!",
|
|
"first_name": "Fail",
|
|
"last_name": "User",
|
|
"company_name": "Fail Co",
|
|
},
|
|
)
|
|
|
|
# Setup payment
|
|
client.post(
|
|
"/api/v1/platform/signup/setup-payment",
|
|
json={"session_id": signup_session},
|
|
)
|
|
|
|
# Mock failed setup intent
|
|
mock_stripe_service.get_setup_intent.return_value = MagicMock(
|
|
id="seti_failed",
|
|
status="requires_payment_method",
|
|
payment_method=None,
|
|
)
|
|
|
|
# Complete signup
|
|
response = client.post(
|
|
"/api/v1/platform/signup/complete",
|
|
json={
|
|
"session_id": signup_session,
|
|
"setup_intent_id": "seti_failed",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 422 # ValidationException
|
|
data = response.json()
|
|
assert "not completed" in data["message"].lower()
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.api
|
|
@pytest.mark.platform
|
|
class TestGetSignupSessionAPI:
|
|
"""Test get signup session endpoint at /api/v1/platform/signup/session/{session_id}."""
|
|
|
|
def test_get_session_after_start(self, client, signup_session):
|
|
"""Test getting session after starting signup."""
|
|
response = client.get(f"/api/v1/platform/signup/session/{signup_session}")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["session_id"] == signup_session
|
|
assert data["step"] == "tier_selected"
|
|
assert data["tier_code"] == TierCode.PROFESSIONAL.value
|
|
assert "created_at" in data
|
|
|
|
def test_get_session_after_claim(self, client, signup_session):
|
|
"""Test getting session after claiming vendor."""
|
|
client.post(
|
|
"/api/v1/platform/signup/claim-vendor",
|
|
json={
|
|
"session_id": signup_session,
|
|
"letzshop_slug": "my-session-shop",
|
|
},
|
|
)
|
|
|
|
response = client.get(f"/api/v1/platform/signup/session/{signup_session}")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["step"] == "vendor_claimed"
|
|
assert data["letzshop_slug"] == "my-session-shop"
|
|
|
|
def test_get_session_invalid_id(self, client):
|
|
"""Test getting non-existent session."""
|
|
response = client.get("/api/v1/platform/signup/session/invalid_id")
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.api
|
|
@pytest.mark.platform
|
|
class TestSignupFullFlow:
|
|
"""Test complete signup flow end-to-end."""
|
|
|
|
def test_full_signup_flow(self, client, mock_stripe_service, db):
|
|
"""Test the complete signup flow from start to finish."""
|
|
# Step 1: Start signup
|
|
start_response = client.post(
|
|
"/api/v1/platform/signup/start",
|
|
json={"tier_code": TierCode.BUSINESS.value, "is_annual": True},
|
|
)
|
|
assert start_response.status_code == 200
|
|
session_id = start_response.json()["session_id"]
|
|
|
|
# Step 2: Claim Letzshop vendor (optional)
|
|
claim_response = client.post(
|
|
"/api/v1/platform/signup/claim-vendor",
|
|
json={
|
|
"session_id": session_id,
|
|
"letzshop_slug": "full-flow-shop",
|
|
},
|
|
)
|
|
assert claim_response.status_code == 200
|
|
|
|
# Step 3: Create account
|
|
account_response = client.post(
|
|
"/api/v1/platform/signup/create-account",
|
|
json={
|
|
"session_id": session_id,
|
|
"email": "fullflow@example.com",
|
|
"password": "SecurePass123!",
|
|
"first_name": "Full",
|
|
"last_name": "Flow",
|
|
"company_name": "Full Flow Company",
|
|
"phone": "+352 123 456",
|
|
},
|
|
)
|
|
assert account_response.status_code == 200
|
|
vendor_id = account_response.json()["vendor_id"]
|
|
|
|
# Step 4: Setup payment
|
|
payment_response = client.post(
|
|
"/api/v1/platform/signup/setup-payment",
|
|
json={"session_id": session_id},
|
|
)
|
|
assert payment_response.status_code == 200
|
|
assert "client_secret" in payment_response.json()
|
|
|
|
# Step 5: Complete signup
|
|
complete_response = client.post(
|
|
"/api/v1/platform/signup/complete",
|
|
json={
|
|
"session_id": session_id,
|
|
"setup_intent_id": "seti_test_123",
|
|
},
|
|
)
|
|
assert complete_response.status_code == 200
|
|
assert complete_response.json()["success"] is True
|
|
|
|
# Verify vendor was created with Letzshop link
|
|
vendor = db.query(Vendor).filter(Vendor.id == vendor_id).first()
|
|
assert vendor is not None
|
|
assert vendor.letzshop_vendor_slug == "full-flow-shop"
|
|
assert vendor.is_active is True
|
|
|
|
def test_signup_flow_without_letzshop_claim(self, client, mock_stripe_service, db):
|
|
"""Test signup flow skipping Letzshop claim step."""
|
|
# Step 1: Start signup
|
|
start_response = client.post(
|
|
"/api/v1/platform/signup/start",
|
|
json={"tier_code": TierCode.ESSENTIAL.value, "is_annual": False},
|
|
)
|
|
session_id = start_response.json()["session_id"]
|
|
|
|
# Skip Step 2, go directly to Step 3
|
|
account_response = client.post(
|
|
"/api/v1/platform/signup/create-account",
|
|
json={
|
|
"session_id": session_id,
|
|
"email": "noletzshop@example.com",
|
|
"password": "SecurePass123!",
|
|
"first_name": "No",
|
|
"last_name": "Letzshop",
|
|
"company_name": "Independent Shop",
|
|
},
|
|
)
|
|
assert account_response.status_code == 200
|
|
vendor_id = account_response.json()["vendor_id"]
|
|
|
|
# Step 4 & 5: Payment and complete
|
|
client.post(
|
|
"/api/v1/platform/signup/setup-payment",
|
|
json={"session_id": session_id},
|
|
)
|
|
|
|
complete_response = client.post(
|
|
"/api/v1/platform/signup/complete",
|
|
json={
|
|
"session_id": session_id,
|
|
"setup_intent_id": "seti_test_123",
|
|
},
|
|
)
|
|
assert complete_response.status_code == 200
|
|
|
|
# Verify vendor was created without Letzshop link
|
|
vendor = db.query(Vendor).filter(Vendor.id == vendor_id).first()
|
|
assert vendor is not None
|
|
assert vendor.letzshop_vendor_slug is None
|