test: add platform API integration tests (67 tests)
Add comprehensive test coverage for platform marketing homepage API: - test_pricing.py: 17 tests for tiers, add-ons, pricing endpoints - test_letzshop_vendors.py: 22 tests for vendor lookup and claiming - test_signup.py: 28 tests for multi-step signup flow Fix signup.py to use correct password hashing from middleware/auth.py and properly create Company with owner_user_id. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2
tests/integration/api/v1/platform/__init__.py
Normal file
2
tests/integration/api/v1/platform/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# tests/integration/api/v1/platform/__init__.py
|
||||
"""Platform API integration tests."""
|
||||
324
tests/integration/api/v1/platform/test_letzshop_vendors.py
Normal file
324
tests/integration/api/v1/platform/test_letzshop_vendors.py
Normal file
@@ -0,0 +1,324 @@
|
||||
# tests/integration/api/v1/platform/test_letzshop_vendors.py
|
||||
"""Integration tests for platform Letzshop vendor lookup API endpoints.
|
||||
|
||||
Tests the /api/v1/platform/letzshop-vendors/* endpoints.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from models.database.vendor import Vendor
|
||||
from models.database.company import Company
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_owner_user(db, auth_manager):
|
||||
"""Create a test owner user for the company."""
|
||||
from models.database.user import User
|
||||
|
||||
user = User(
|
||||
email="owner@test.com",
|
||||
username="test_owner",
|
||||
hashed_password=auth_manager.hash_password("testpass123"),
|
||||
role="vendor",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(user)
|
||||
db.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_company(db, test_owner_user):
|
||||
"""Create a test company."""
|
||||
company = Company(
|
||||
name="Test Company",
|
||||
owner_user_id=test_owner_user.id,
|
||||
contact_email="test@company.com",
|
||||
)
|
||||
db.add(company)
|
||||
db.commit()
|
||||
return company
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def claimed_vendor(db, test_company):
|
||||
"""Create a vendor that has claimed a Letzshop shop."""
|
||||
vendor = Vendor(
|
||||
company_id=test_company.id,
|
||||
vendor_code="CLAIMED_VENDOR",
|
||||
subdomain="claimed-shop",
|
||||
name="Claimed Shop",
|
||||
contact_email="claimed@shop.lu",
|
||||
is_active=True,
|
||||
letzshop_vendor_slug="claimed-shop",
|
||||
letzshop_vendor_id="letz_123",
|
||||
)
|
||||
db.add(vendor)
|
||||
db.commit()
|
||||
return vendor
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.platform
|
||||
class TestLetzshopVendorLookupAPI:
|
||||
"""Test Letzshop vendor lookup endpoints at /api/v1/platform/letzshop-vendors/*."""
|
||||
|
||||
# =========================================================================
|
||||
# GET /api/v1/platform/letzshop-vendors
|
||||
# =========================================================================
|
||||
|
||||
def test_list_vendors_returns_empty_list(self, client):
|
||||
"""Test listing vendors returns empty list (placeholder)."""
|
||||
response = client.get("/api/v1/platform/letzshop-vendors")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "vendors" in data
|
||||
assert "total" in data
|
||||
assert "page" in data
|
||||
assert "limit" in data
|
||||
assert "has_more" in data
|
||||
assert isinstance(data["vendors"], list)
|
||||
|
||||
def test_list_vendors_with_pagination(self, client):
|
||||
"""Test listing vendors with pagination parameters."""
|
||||
response = client.get("/api/v1/platform/letzshop-vendors?page=2&limit=10")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["page"] == 2
|
||||
assert data["limit"] == 10
|
||||
|
||||
def test_list_vendors_with_search(self, client):
|
||||
"""Test listing vendors with search parameter."""
|
||||
response = client.get("/api/v1/platform/letzshop-vendors?search=my-shop")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data["vendors"], list)
|
||||
|
||||
def test_list_vendors_with_filters(self, client):
|
||||
"""Test listing vendors with category and city filters."""
|
||||
response = client.get(
|
||||
"/api/v1/platform/letzshop-vendors?category=fashion&city=luxembourg"
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data["vendors"], list)
|
||||
|
||||
def test_list_vendors_limit_validation(self, client):
|
||||
"""Test that limit parameter is validated."""
|
||||
# Maximum limit is 50
|
||||
response = client.get("/api/v1/platform/letzshop-vendors?limit=100")
|
||||
assert response.status_code == 422
|
||||
|
||||
# =========================================================================
|
||||
# POST /api/v1/platform/letzshop-vendors/lookup
|
||||
# =========================================================================
|
||||
|
||||
def test_lookup_vendor_by_full_url(self, client):
|
||||
"""Test looking up vendor by full Letzshop URL."""
|
||||
response = client.post(
|
||||
"/api/v1/platform/letzshop-vendors/lookup",
|
||||
json={"url": "https://letzshop.lu/vendors/my-test-shop"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["found"] is True
|
||||
assert data["vendor"]["slug"] == "my-test-shop"
|
||||
assert "letzshop.lu" in data["vendor"]["letzshop_url"]
|
||||
|
||||
def test_lookup_vendor_by_url_with_language(self, client):
|
||||
"""Test looking up vendor by URL with language prefix."""
|
||||
response = client.post(
|
||||
"/api/v1/platform/letzshop-vendors/lookup",
|
||||
json={"url": "https://letzshop.lu/en/vendors/my-shop"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["found"] is True
|
||||
assert data["vendor"]["slug"] == "my-shop"
|
||||
|
||||
def test_lookup_vendor_by_url_without_protocol(self, client):
|
||||
"""Test looking up vendor by URL without https://."""
|
||||
response = client.post(
|
||||
"/api/v1/platform/letzshop-vendors/lookup",
|
||||
json={"url": "letzshop.lu/vendors/test-shop"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["found"] is True
|
||||
assert data["vendor"]["slug"] == "test-shop"
|
||||
|
||||
def test_lookup_vendor_by_slug_only(self, client):
|
||||
"""Test looking up vendor by slug alone."""
|
||||
response = client.post(
|
||||
"/api/v1/platform/letzshop-vendors/lookup",
|
||||
json={"url": "my-shop-name"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["found"] is True
|
||||
assert data["vendor"]["slug"] == "my-shop-name"
|
||||
|
||||
def test_lookup_vendor_normalizes_slug(self, client):
|
||||
"""Test that slug is normalized to lowercase."""
|
||||
response = client.post(
|
||||
"/api/v1/platform/letzshop-vendors/lookup",
|
||||
json={"url": "https://letzshop.lu/vendors/MY-SHOP-NAME"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["vendor"]["slug"] == "my-shop-name"
|
||||
|
||||
def test_lookup_vendor_shows_claimed_status(self, client, claimed_vendor):
|
||||
"""Test that lookup shows if vendor is already claimed."""
|
||||
response = client.post(
|
||||
"/api/v1/platform/letzshop-vendors/lookup",
|
||||
json={"url": "claimed-shop"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["found"] is True
|
||||
assert data["vendor"]["is_claimed"] is True
|
||||
|
||||
def test_lookup_vendor_shows_unclaimed_status(self, client):
|
||||
"""Test that lookup shows if vendor is not claimed."""
|
||||
response = client.post(
|
||||
"/api/v1/platform/letzshop-vendors/lookup",
|
||||
json={"url": "unclaimed-new-shop"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["found"] is True
|
||||
assert data["vendor"]["is_claimed"] is False
|
||||
|
||||
def test_lookup_vendor_empty_url(self, client):
|
||||
"""Test lookup with empty URL."""
|
||||
response = client.post(
|
||||
"/api/v1/platform/letzshop-vendors/lookup",
|
||||
json={"url": ""},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["found"] is False
|
||||
assert data["error"] is not None
|
||||
|
||||
def test_lookup_vendor_response_has_expected_fields(self, client):
|
||||
"""Test that vendor lookup response has all expected fields."""
|
||||
response = client.post(
|
||||
"/api/v1/platform/letzshop-vendors/lookup",
|
||||
json={"url": "test-vendor"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
vendor = data["vendor"]
|
||||
assert "slug" in vendor
|
||||
assert "name" in vendor
|
||||
assert "letzshop_url" in vendor
|
||||
assert "is_claimed" in vendor
|
||||
|
||||
# =========================================================================
|
||||
# GET /api/v1/platform/letzshop-vendors/{slug}
|
||||
# =========================================================================
|
||||
|
||||
def test_get_vendor_by_slug(self, client):
|
||||
"""Test getting vendor by slug."""
|
||||
response = client.get("/api/v1/platform/letzshop-vendors/my-shop")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["slug"] == "my-shop"
|
||||
assert "name" in data
|
||||
assert "letzshop_url" in data
|
||||
assert "is_claimed" in data
|
||||
|
||||
def test_get_vendor_normalizes_slug(self, client):
|
||||
"""Test that get vendor normalizes slug to lowercase."""
|
||||
response = client.get("/api/v1/platform/letzshop-vendors/MY-SHOP")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["slug"] == "my-shop"
|
||||
|
||||
def test_get_claimed_vendor_shows_status(self, client, claimed_vendor):
|
||||
"""Test that get vendor shows claimed status correctly."""
|
||||
response = client.get("/api/v1/platform/letzshop-vendors/claimed-shop")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["is_claimed"] is True
|
||||
|
||||
def test_get_unclaimed_vendor_shows_status(self, client):
|
||||
"""Test that get vendor shows unclaimed status correctly."""
|
||||
response = client.get("/api/v1/platform/letzshop-vendors/new-unclaimed-shop")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["is_claimed"] is False
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.platform
|
||||
class TestLetzshopSlugExtraction:
|
||||
"""Test slug extraction from various URL formats."""
|
||||
|
||||
def test_extract_from_full_https_url(self, client):
|
||||
"""Test extraction from https://letzshop.lu/vendors/slug."""
|
||||
response = client.post(
|
||||
"/api/v1/platform/letzshop-vendors/lookup",
|
||||
json={"url": "https://letzshop.lu/vendors/cafe-luxembourg"},
|
||||
)
|
||||
assert response.json()["vendor"]["slug"] == "cafe-luxembourg"
|
||||
|
||||
def test_extract_from_http_url(self, client):
|
||||
"""Test extraction from http://letzshop.lu/vendors/slug."""
|
||||
response = client.post(
|
||||
"/api/v1/platform/letzshop-vendors/lookup",
|
||||
json={"url": "http://letzshop.lu/vendors/my-shop"},
|
||||
)
|
||||
assert response.json()["vendor"]["slug"] == "my-shop"
|
||||
|
||||
def test_extract_from_url_with_trailing_slash(self, client):
|
||||
"""Test extraction from URL with trailing slash."""
|
||||
response = client.post(
|
||||
"/api/v1/platform/letzshop-vendors/lookup",
|
||||
json={"url": "https://letzshop.lu/vendors/my-shop/"},
|
||||
)
|
||||
assert response.json()["vendor"]["slug"] == "my-shop"
|
||||
|
||||
def test_extract_from_url_with_query_params(self, client):
|
||||
"""Test extraction from URL with query parameters."""
|
||||
response = client.post(
|
||||
"/api/v1/platform/letzshop-vendors/lookup",
|
||||
json={"url": "https://letzshop.lu/vendors/my-shop?ref=google"},
|
||||
)
|
||||
assert response.json()["vendor"]["slug"] == "my-shop"
|
||||
|
||||
def test_extract_from_french_url(self, client):
|
||||
"""Test extraction from French language URL."""
|
||||
response = client.post(
|
||||
"/api/v1/platform/letzshop-vendors/lookup",
|
||||
json={"url": "https://letzshop.lu/fr/vendors/boulangerie-paul"},
|
||||
)
|
||||
assert response.json()["vendor"]["slug"] == "boulangerie-paul"
|
||||
|
||||
def test_extract_from_german_url(self, client):
|
||||
"""Test extraction from German language URL."""
|
||||
response = client.post(
|
||||
"/api/v1/platform/letzshop-vendors/lookup",
|
||||
json={"url": "https://letzshop.lu/de/vendors/backerei-muller"},
|
||||
)
|
||||
assert response.json()["vendor"]["slug"] == "backerei-muller"
|
||||
285
tests/integration/api/v1/platform/test_pricing.py
Normal file
285
tests/integration/api/v1/platform/test_pricing.py
Normal file
@@ -0,0 +1,285 @@
|
||||
# tests/integration/api/v1/platform/test_pricing.py
|
||||
"""Integration tests for platform pricing API endpoints.
|
||||
|
||||
Tests the /api/v1/platform/pricing/* endpoints.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from models.database.subscription import (
|
||||
AddOnProduct,
|
||||
SubscriptionTier,
|
||||
TierCode,
|
||||
TIER_LIMITS,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.platform
|
||||
class TestPlatformPricingAPI:
|
||||
"""Test platform pricing endpoints at /api/v1/platform/*."""
|
||||
|
||||
# =========================================================================
|
||||
# GET /api/v1/platform/tiers
|
||||
# =========================================================================
|
||||
|
||||
def test_get_tiers_returns_all_public_tiers(self, client):
|
||||
"""Test getting all subscription tiers."""
|
||||
response = client.get("/api/v1/platform/tiers")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data, list)
|
||||
assert len(data) >= 4 # Essential, Professional, Business, Enterprise
|
||||
|
||||
def test_get_tiers_has_expected_fields(self, client):
|
||||
"""Test that tier response has all expected fields."""
|
||||
response = client.get("/api/v1/platform/tiers")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) > 0
|
||||
|
||||
tier = data[0]
|
||||
assert "code" in tier
|
||||
assert "name" in tier
|
||||
assert "price_monthly" in tier
|
||||
assert "price_monthly_cents" in tier
|
||||
assert "orders_per_month" in tier
|
||||
assert "products_limit" in tier
|
||||
assert "team_members" in tier
|
||||
assert "features" in tier
|
||||
assert "is_popular" in tier
|
||||
assert "is_enterprise" in tier
|
||||
|
||||
def test_get_tiers_includes_essential(self, client):
|
||||
"""Test that Essential tier is included."""
|
||||
response = client.get("/api/v1/platform/tiers")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
tier_codes = [t["code"] for t in data]
|
||||
assert TierCode.ESSENTIAL.value in tier_codes
|
||||
|
||||
def test_get_tiers_includes_professional(self, client):
|
||||
"""Test that Professional tier is included and marked as popular."""
|
||||
response = client.get("/api/v1/platform/tiers")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
professional = next(
|
||||
(t for t in data if t["code"] == TierCode.PROFESSIONAL.value), None
|
||||
)
|
||||
assert professional is not None
|
||||
assert professional["is_popular"] is True
|
||||
|
||||
def test_get_tiers_includes_enterprise(self, client):
|
||||
"""Test that Enterprise tier is included and marked appropriately."""
|
||||
response = client.get("/api/v1/platform/tiers")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
enterprise = next(
|
||||
(t for t in data if t["code"] == TierCode.ENTERPRISE.value), None
|
||||
)
|
||||
assert enterprise is not None
|
||||
assert enterprise["is_enterprise"] is True
|
||||
|
||||
def test_get_tiers_from_database(self, client, db):
|
||||
"""Test getting tiers from database when available."""
|
||||
# Create a tier in the database
|
||||
tier = SubscriptionTier(
|
||||
code="test_tier",
|
||||
name="Test Tier",
|
||||
description="A test tier",
|
||||
price_monthly_cents=9900,
|
||||
price_annual_cents=99000,
|
||||
orders_per_month=1000,
|
||||
products_limit=500,
|
||||
team_members=5,
|
||||
features=["feature1", "feature2"],
|
||||
is_active=True,
|
||||
is_public=True,
|
||||
display_order=99,
|
||||
)
|
||||
db.add(tier)
|
||||
db.commit()
|
||||
|
||||
response = client.get("/api/v1/platform/tiers")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
tier_codes = [t["code"] for t in data]
|
||||
assert "test_tier" in tier_codes
|
||||
|
||||
# =========================================================================
|
||||
# GET /api/v1/platform/tiers/{tier_code}
|
||||
# =========================================================================
|
||||
|
||||
def test_get_tier_by_code_success(self, client):
|
||||
"""Test getting a specific tier by code."""
|
||||
response = client.get(f"/api/v1/platform/tiers/{TierCode.PROFESSIONAL.value}")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["code"] == TierCode.PROFESSIONAL.value
|
||||
assert data["name"] == TIER_LIMITS[TierCode.PROFESSIONAL]["name"]
|
||||
|
||||
def test_get_tier_by_code_essential(self, client):
|
||||
"""Test getting Essential tier details."""
|
||||
response = client.get(f"/api/v1/platform/tiers/{TierCode.ESSENTIAL.value}")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["code"] == TierCode.ESSENTIAL.value
|
||||
assert data["price_monthly"] == TIER_LIMITS[TierCode.ESSENTIAL]["price_monthly_cents"] / 100
|
||||
|
||||
def test_get_tier_by_code_not_found(self, client):
|
||||
"""Test getting a non-existent tier returns 404."""
|
||||
response = client.get("/api/v1/platform/tiers/nonexistent_tier")
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
assert "not found" in data["message"].lower()
|
||||
|
||||
# =========================================================================
|
||||
# GET /api/v1/platform/addons
|
||||
# =========================================================================
|
||||
|
||||
def test_get_addons_empty_when_none_configured(self, client):
|
||||
"""Test getting add-ons when none are configured."""
|
||||
response = client.get("/api/v1/platform/addons")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data, list)
|
||||
|
||||
def test_get_addons_returns_configured_addons(self, client, db):
|
||||
"""Test getting add-ons when configured in database."""
|
||||
# Create test add-on
|
||||
addon = AddOnProduct(
|
||||
code="test_domain",
|
||||
name="Custom Domain",
|
||||
description="Use your own domain",
|
||||
category="domain",
|
||||
price_cents=1500,
|
||||
billing_period="annual",
|
||||
is_active=True,
|
||||
display_order=1,
|
||||
)
|
||||
db.add(addon)
|
||||
db.commit()
|
||||
|
||||
response = client.get("/api/v1/platform/addons")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) >= 1
|
||||
|
||||
addon_codes = [a["code"] for a in data]
|
||||
assert "test_domain" in addon_codes
|
||||
|
||||
def test_get_addons_has_expected_fields(self, client, db):
|
||||
"""Test that addon response has all expected fields."""
|
||||
addon = AddOnProduct(
|
||||
code="test_ssl",
|
||||
name="Premium SSL",
|
||||
description="EV certificate",
|
||||
category="security",
|
||||
price_cents=4900,
|
||||
billing_period="annual",
|
||||
is_active=True,
|
||||
display_order=1,
|
||||
)
|
||||
db.add(addon)
|
||||
db.commit()
|
||||
|
||||
response = client.get("/api/v1/platform/addons")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) > 0
|
||||
|
||||
addon_response = data[0]
|
||||
assert "code" in addon_response
|
||||
assert "name" in addon_response
|
||||
assert "description" in addon_response
|
||||
assert "category" in addon_response
|
||||
assert "price" in addon_response
|
||||
assert "price_cents" in addon_response
|
||||
assert "billing_period" in addon_response
|
||||
|
||||
def test_get_addons_excludes_inactive(self, client, db):
|
||||
"""Test that inactive add-ons are excluded."""
|
||||
# Create active and inactive add-ons
|
||||
active_addon = AddOnProduct(
|
||||
code="active_addon",
|
||||
name="Active Addon",
|
||||
category="test",
|
||||
price_cents=1000,
|
||||
billing_period="monthly",
|
||||
is_active=True,
|
||||
display_order=1,
|
||||
)
|
||||
inactive_addon = AddOnProduct(
|
||||
code="inactive_addon",
|
||||
name="Inactive Addon",
|
||||
category="test",
|
||||
price_cents=1000,
|
||||
billing_period="monthly",
|
||||
is_active=False,
|
||||
display_order=2,
|
||||
)
|
||||
db.add_all([active_addon, inactive_addon])
|
||||
db.commit()
|
||||
|
||||
response = client.get("/api/v1/platform/addons")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
addon_codes = [a["code"] for a in data]
|
||||
assert "active_addon" in addon_codes
|
||||
assert "inactive_addon" not in addon_codes
|
||||
|
||||
# =========================================================================
|
||||
# GET /api/v1/platform/pricing
|
||||
# =========================================================================
|
||||
|
||||
def test_get_pricing_returns_complete_info(self, client):
|
||||
"""Test getting complete pricing information."""
|
||||
response = client.get("/api/v1/platform/pricing")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
assert "tiers" in data
|
||||
assert "addons" in data
|
||||
assert "trial_days" in data
|
||||
assert "annual_discount_months" in data
|
||||
|
||||
def test_get_pricing_includes_trial_days(self, client):
|
||||
"""Test that pricing includes correct trial period."""
|
||||
response = client.get("/api/v1/platform/pricing")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["trial_days"] == 30 # Updated from 14 to 30
|
||||
|
||||
def test_get_pricing_includes_annual_discount(self, client):
|
||||
"""Test that pricing includes annual discount info."""
|
||||
response = client.get("/api/v1/platform/pricing")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["annual_discount_months"] == 2 # 2 months free
|
||||
|
||||
def test_get_pricing_tiers_not_empty(self, client):
|
||||
"""Test that pricing always includes tiers."""
|
||||
response = client.get("/api/v1/platform/pricing")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["tiers"]) >= 4
|
||||
672
tests/integration/api/v1/platform/test_signup.py
Normal file
672
tests/integration/api/v1/platform/test_signup.py
Normal file
@@ -0,0 +1,672 @@
|
||||
# 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.api.v1.platform.signup.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 == 400
|
||||
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 == 400
|
||||
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 == 400
|
||||
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 == 400
|
||||
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_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 == 400
|
||||
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
|
||||
Reference in New Issue
Block a user