fix: use custom exceptions in onboarding and add tests
- Create onboarding-specific exceptions (OnboardingNotFoundException, etc.) - Remove HTTPException usage from API endpoints per architecture rules - Let exceptions propagate to global handler - Add 12 integration tests for onboarding API endpoints 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
368
tests/integration/api/v1/vendor/test_onboarding.py
vendored
Normal file
368
tests/integration/api/v1/vendor/test_onboarding.py
vendored
Normal file
@@ -0,0 +1,368 @@
|
||||
# tests/integration/api/v1/vendor/test_onboarding.py
|
||||
"""
|
||||
Integration tests for vendor onboarding API endpoints.
|
||||
|
||||
Tests cover:
|
||||
1. Onboarding status retrieval
|
||||
2. Step 1: Company profile setup
|
||||
3. Step 2: Letzshop API configuration
|
||||
4. Step 3: Product import configuration
|
||||
5. Step 4: Order sync (mocked)
|
||||
6. Step order validation (can't skip ahead)
|
||||
7. Onboarding completion flow
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from models.database.onboarding import OnboardingStatus, OnboardingStep, VendorOnboarding
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.vendor
|
||||
class TestVendorOnboardingStatusAPI:
|
||||
"""Test onboarding status endpoint"""
|
||||
|
||||
def test_get_status_creates_onboarding_if_missing(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user, db
|
||||
):
|
||||
"""Test that getting status creates onboarding record if none exists"""
|
||||
# First ensure no onboarding exists
|
||||
existing = (
|
||||
db.query(VendorOnboarding)
|
||||
.filter(VendorOnboarding.vendor_id == test_vendor_with_vendor_user.id)
|
||||
.first()
|
||||
)
|
||||
if existing:
|
||||
db.delete(existing)
|
||||
db.commit()
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/vendor/onboarding/status", headers=vendor_user_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
assert data["vendor_id"] == test_vendor_with_vendor_user.id
|
||||
assert data["status"] == OnboardingStatus.NOT_STARTED.value
|
||||
assert data["current_step"] == OnboardingStep.COMPANY_PROFILE.value
|
||||
assert data["completed_steps_count"] == 0
|
||||
assert data["total_steps"] == 4
|
||||
assert data["is_completed"] is False
|
||||
|
||||
def test_get_status_structure(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user, db
|
||||
):
|
||||
"""Test status response has correct structure"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/onboarding/status", headers=vendor_user_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Verify structure
|
||||
assert "id" in data
|
||||
assert "vendor_id" in data
|
||||
assert "status" in data
|
||||
assert "current_step" in data
|
||||
assert "company_profile" in data
|
||||
assert "letzshop_api" in data
|
||||
assert "product_import" in data
|
||||
assert "order_sync" in data
|
||||
assert "completion_percentage" in data
|
||||
assert "completed_steps_count" in data
|
||||
assert "total_steps" in data
|
||||
assert "is_completed" in data
|
||||
|
||||
def test_get_status_requires_auth(self, client):
|
||||
"""Test that onboarding status requires authentication"""
|
||||
response = client.get("/api/v1/vendor/onboarding/status")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.vendor
|
||||
class TestVendorOnboardingStep1API:
|
||||
"""Test Step 1: Company Profile endpoints"""
|
||||
|
||||
def test_get_company_profile_data(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user, db
|
||||
):
|
||||
"""Test getting company profile data"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/onboarding/step/company-profile",
|
||||
headers=vendor_user_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Should return pre-filled data
|
||||
assert "brand_name" in data
|
||||
assert "contact_email" in data
|
||||
assert "default_language" in data
|
||||
|
||||
def test_save_company_profile_success(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user, db
|
||||
):
|
||||
"""Test saving company profile completes step 1"""
|
||||
# First ensure clean state
|
||||
existing = (
|
||||
db.query(VendorOnboarding)
|
||||
.filter(VendorOnboarding.vendor_id == test_vendor_with_vendor_user.id)
|
||||
.first()
|
||||
)
|
||||
if existing:
|
||||
db.delete(existing)
|
||||
db.commit()
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/vendor/onboarding/step/company-profile",
|
||||
headers=vendor_user_headers,
|
||||
json={
|
||||
"company_name": "Test Company Ltd",
|
||||
"brand_name": "Test Brand",
|
||||
"description": "A test company for testing",
|
||||
"contact_email": "test@example.com",
|
||||
"contact_phone": "+352123456789",
|
||||
"website": "https://test.example.com",
|
||||
"business_address": "123 Test Street, Luxembourg",
|
||||
"tax_number": "LU12345678",
|
||||
"default_language": "fr",
|
||||
"dashboard_language": "en",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
assert data["success"] is True
|
||||
assert data["step_completed"] is True
|
||||
assert data["next_step"] == OnboardingStep.LETZSHOP_API.value
|
||||
|
||||
# Verify onboarding was updated
|
||||
onboarding = (
|
||||
db.query(VendorOnboarding)
|
||||
.filter(VendorOnboarding.vendor_id == test_vendor_with_vendor_user.id)
|
||||
.first()
|
||||
)
|
||||
db.refresh(onboarding)
|
||||
assert onboarding.step_company_profile_completed is True
|
||||
assert onboarding.status == OnboardingStatus.IN_PROGRESS.value
|
||||
|
||||
def test_save_company_profile_with_minimal_data(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user, db
|
||||
):
|
||||
"""Test saving company profile with minimal data"""
|
||||
# Clear existing onboarding
|
||||
existing = (
|
||||
db.query(VendorOnboarding)
|
||||
.filter(VendorOnboarding.vendor_id == test_vendor_with_vendor_user.id)
|
||||
.first()
|
||||
)
|
||||
if existing:
|
||||
db.delete(existing)
|
||||
db.commit()
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/vendor/onboarding/step/company-profile",
|
||||
headers=vendor_user_headers,
|
||||
json={
|
||||
"default_language": "fr",
|
||||
"dashboard_language": "fr",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["success"] is True
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.vendor
|
||||
class TestVendorOnboardingStep2API:
|
||||
"""Test Step 2: Letzshop API Configuration endpoints"""
|
||||
|
||||
def test_letzshop_api_test_endpoint(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user
|
||||
):
|
||||
"""Test the Letzshop API test endpoint"""
|
||||
response = client.post(
|
||||
"/api/v1/vendor/onboarding/step/letzshop-api/test",
|
||||
headers=vendor_user_headers,
|
||||
json={
|
||||
"api_key": "test_invalid_key_12345",
|
||||
"shop_slug": "test-shop",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Response should indicate success or failure
|
||||
assert "success" in data
|
||||
assert "message" in data
|
||||
|
||||
def test_letzshop_api_requires_step1_complete(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user, db
|
||||
):
|
||||
"""Test that step 2 requires step 1 to be completed"""
|
||||
# Ensure fresh state with no steps completed
|
||||
existing = (
|
||||
db.query(VendorOnboarding)
|
||||
.filter(VendorOnboarding.vendor_id == test_vendor_with_vendor_user.id)
|
||||
.first()
|
||||
)
|
||||
if existing:
|
||||
db.delete(existing)
|
||||
db.commit()
|
||||
|
||||
# Try to save step 2 directly
|
||||
response = client.post(
|
||||
"/api/v1/vendor/onboarding/step/letzshop-api",
|
||||
headers=vendor_user_headers,
|
||||
json={
|
||||
"api_key": "test_api_key_12345678901234567890",
|
||||
"shop_slug": "test-shop",
|
||||
},
|
||||
)
|
||||
|
||||
# Should fail because step 1 is not complete
|
||||
assert response.status_code == 422 # Validation error
|
||||
data = response.json()
|
||||
assert "step" in str(data).lower() or "complete" in str(data).lower()
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.vendor
|
||||
class TestVendorOnboardingStep3API:
|
||||
"""Test Step 3: Product Import Configuration endpoints"""
|
||||
|
||||
def test_get_product_import_config(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user
|
||||
):
|
||||
"""Test getting product import configuration"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/onboarding/step/product-import",
|
||||
headers=vendor_user_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
assert "csv_url_fr" in data
|
||||
assert "csv_url_en" in data
|
||||
assert "csv_url_de" in data
|
||||
assert "default_tax_rate" in data
|
||||
assert "delivery_method" in data
|
||||
|
||||
def test_product_import_requires_csv_url(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user, db
|
||||
):
|
||||
"""Test that product import requires at least one CSV URL"""
|
||||
# Set up: complete steps 1 and 2 first
|
||||
onboarding = (
|
||||
db.query(VendorOnboarding)
|
||||
.filter(VendorOnboarding.vendor_id == test_vendor_with_vendor_user.id)
|
||||
.first()
|
||||
)
|
||||
if not onboarding:
|
||||
onboarding = VendorOnboarding(
|
||||
vendor_id=test_vendor_with_vendor_user.id,
|
||||
status=OnboardingStatus.IN_PROGRESS.value,
|
||||
current_step=OnboardingStep.PRODUCT_IMPORT.value,
|
||||
)
|
||||
db.add(onboarding)
|
||||
|
||||
onboarding.step_company_profile_completed = True
|
||||
onboarding.step_letzshop_api_completed = True
|
||||
onboarding.current_step = OnboardingStep.PRODUCT_IMPORT.value
|
||||
db.commit()
|
||||
|
||||
# Try to save without any CSV URL
|
||||
response = client.post(
|
||||
"/api/v1/vendor/onboarding/step/product-import",
|
||||
headers=vendor_user_headers,
|
||||
json={
|
||||
"default_tax_rate": 17,
|
||||
"delivery_method": "package_delivery",
|
||||
"preorder_days": 1,
|
||||
},
|
||||
)
|
||||
|
||||
# Should fail with validation error
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.vendor
|
||||
class TestVendorOnboardingStep4API:
|
||||
"""Test Step 4: Order Sync endpoints"""
|
||||
|
||||
def test_order_sync_progress_endpoint(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user
|
||||
):
|
||||
"""Test getting order sync progress for non-existent job"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/onboarding/step/order-sync/progress/99999",
|
||||
headers=vendor_user_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Should return not_found status for non-existent job
|
||||
assert data["status"] == "not_found"
|
||||
assert data["job_id"] == 99999
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.vendor
|
||||
class TestVendorOnboardingFlowAPI:
|
||||
"""Test complete onboarding flow"""
|
||||
|
||||
def test_completion_percentage_updates(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user, db
|
||||
):
|
||||
"""Test that completion percentage updates as steps are completed"""
|
||||
# Clear existing onboarding
|
||||
existing = (
|
||||
db.query(VendorOnboarding)
|
||||
.filter(VendorOnboarding.vendor_id == test_vendor_with_vendor_user.id)
|
||||
.first()
|
||||
)
|
||||
if existing:
|
||||
db.delete(existing)
|
||||
db.commit()
|
||||
|
||||
# Check initial state
|
||||
response = client.get(
|
||||
"/api/v1/vendor/onboarding/status", headers=vendor_user_headers
|
||||
)
|
||||
data = response.json()
|
||||
assert data["completion_percentage"] == 0
|
||||
|
||||
# Complete step 1
|
||||
client.post(
|
||||
"/api/v1/vendor/onboarding/step/company-profile",
|
||||
headers=vendor_user_headers,
|
||||
json={
|
||||
"default_language": "fr",
|
||||
"dashboard_language": "fr",
|
||||
},
|
||||
)
|
||||
|
||||
# Check progress
|
||||
response = client.get(
|
||||
"/api/v1/vendor/onboarding/status", headers=vendor_user_headers
|
||||
)
|
||||
data = response.json()
|
||||
assert data["completion_percentage"] == 25 # 1/4 = 25%
|
||||
assert data["completed_steps_count"] == 1
|
||||
Reference in New Issue
Block a user