Add OnboardingProviderProtocol so modules declare their own post-signup onboarding steps. The core OnboardingAggregator discovers enabled providers and exposes a dashboard API (GET /dashboard/onboarding). A session-scoped banner on the store dashboard shows a checklist that guides merchants through setup without blocking signup. Signup is simplified from 4 steps to 3 (Plan → Account → Payment): store creation is merged into account creation, store language is captured from the user's browsing language, and platform-specific template branching is removed. Includes 47 unit and integration tests covering all new providers, the aggregator, the API endpoint, and the signup service changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
126 lines
3.8 KiB
Python
126 lines
3.8 KiB
Python
# app/modules/loyalty/tests/unit/test_loyalty_onboarding.py
|
|
"""Unit tests for LoyaltyOnboardingProvider."""
|
|
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
from app.modules.loyalty.models.loyalty_program import LoyaltyProgram, LoyaltyType
|
|
from app.modules.loyalty.services.loyalty_onboarding import LoyaltyOnboardingProvider
|
|
from app.modules.tenancy.models import Merchant, Store, User
|
|
|
|
|
|
@pytest.fixture
|
|
def loy_owner(db):
|
|
"""Create a store owner for loyalty onboarding tests."""
|
|
from middleware.auth import AuthManager
|
|
|
|
auth = AuthManager()
|
|
user = User(
|
|
email=f"loyonb_{uuid.uuid4().hex[:8]}@test.com",
|
|
username=f"loyonb_{uuid.uuid4().hex[:8]}",
|
|
hashed_password=auth.hash_password("pass123"),
|
|
role="merchant_owner",
|
|
is_active=True,
|
|
)
|
|
db.add(user)
|
|
db.commit()
|
|
db.refresh(user)
|
|
return user
|
|
|
|
|
|
@pytest.fixture
|
|
def loy_merchant(db, loy_owner):
|
|
"""Create a merchant for loyalty onboarding tests."""
|
|
merchant = Merchant(
|
|
name="Loyalty Onboarding Merchant",
|
|
owner_user_id=loy_owner.id,
|
|
contact_email=loy_owner.email,
|
|
is_active=True,
|
|
is_verified=True,
|
|
)
|
|
db.add(merchant)
|
|
db.commit()
|
|
db.refresh(merchant)
|
|
return merchant
|
|
|
|
|
|
@pytest.fixture
|
|
def loy_store(db, loy_merchant):
|
|
"""Create a store (no loyalty program) for loyalty onboarding tests."""
|
|
uid = uuid.uuid4().hex[:8]
|
|
store = Store(
|
|
merchant_id=loy_merchant.id,
|
|
store_code=f"LOYONB_{uid.upper()}",
|
|
subdomain=f"loyonb{uid.lower()}",
|
|
name="Loyalty Onboarding Store",
|
|
is_active=True,
|
|
is_verified=True,
|
|
)
|
|
db.add(store)
|
|
db.commit()
|
|
db.refresh(store)
|
|
return store
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.loyalty
|
|
class TestLoyaltyOnboardingProvider:
|
|
"""Tests for LoyaltyOnboardingProvider."""
|
|
|
|
def setup_method(self):
|
|
self.provider = LoyaltyOnboardingProvider()
|
|
|
|
def test_category(self):
|
|
"""Returns 'loyalty' as the onboarding category."""
|
|
assert self.provider.onboarding_category == "loyalty"
|
|
|
|
def test_get_onboarding_steps_returns_one_step(self):
|
|
"""Returns exactly one step: create_program."""
|
|
steps = self.provider.get_onboarding_steps()
|
|
assert len(steps) == 1
|
|
assert steps[0].key == "loyalty.create_program"
|
|
assert steps[0].order == 300
|
|
|
|
def test_incomplete_without_program(self, db, loy_store):
|
|
"""Step is incomplete when merchant has no loyalty programs."""
|
|
result = self.provider.is_step_completed(
|
|
db, loy_store.id, "loyalty.create_program"
|
|
)
|
|
assert result is False
|
|
|
|
def test_complete_with_program(self, db, loy_store, loy_merchant):
|
|
"""Step is complete when merchant has at least one loyalty program."""
|
|
program = LoyaltyProgram(
|
|
merchant_id=loy_merchant.id,
|
|
loyalty_type=LoyaltyType.STAMPS.value,
|
|
stamps_target=10,
|
|
cooldown_minutes=0,
|
|
max_daily_stamps=10,
|
|
require_staff_pin=False,
|
|
card_name="Test Card",
|
|
card_color="#FF0000",
|
|
is_active=True,
|
|
)
|
|
db.add(program)
|
|
db.commit()
|
|
|
|
result = self.provider.is_step_completed(
|
|
db, loy_store.id, "loyalty.create_program"
|
|
)
|
|
assert result is True
|
|
|
|
def test_nonexistent_store_returns_false(self, db):
|
|
"""Returns False for a store ID that doesn't exist."""
|
|
result = self.provider.is_step_completed(
|
|
db, 999999, "loyalty.create_program"
|
|
)
|
|
assert result is False
|
|
|
|
def test_unknown_step_key_returns_false(self, db, loy_store):
|
|
"""Returns False for an unrecognized step key."""
|
|
result = self.provider.is_step_completed(
|
|
db, loy_store.id, "loyalty.unknown_step"
|
|
)
|
|
assert result is False
|