fix: Add podcast-only demo mode readiness patches
- Patch pricing redirect to route to podcast-maker instead of onboarding - Allow all plan tiers in demo mode (remove alpha restriction) - Add Stripe mode warning in demo when key is missing - Add startup router mount assertions for subscription and podcast - Add smoke test script for demo mode validation
This commit is contained in:
@@ -571,10 +571,39 @@ async def startup_event():
|
|||||||
logger.warning("⚠️ WIX_API_KEY not found in environment - Wix publishing may fail")
|
logger.warning("⚠️ WIX_API_KEY not found in environment - Wix publishing may fail")
|
||||||
|
|
||||||
logger.info("ALwrity backend started successfully")
|
logger.info("ALwrity backend started successfully")
|
||||||
|
|
||||||
|
# Critical router mount assertions for podcast-only demo mode
|
||||||
|
_assert_router_mounted("subscription")
|
||||||
|
_assert_router_mounted("podcast")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error during startup: {e}")
|
logger.error(f"Error during startup: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def _assert_router_mounted(router_name: str) -> None:
|
||||||
|
"""Assert that a critical router is mounted. Fails startup if not found."""
|
||||||
|
from fastapi import routing
|
||||||
|
mounted_routes = [route.path for route in app.routes]
|
||||||
|
|
||||||
|
# Check for router-specific paths
|
||||||
|
router_path_indicators = {
|
||||||
|
"subscription": ["/api/subscription/plans", "/api/subscription/preflight"],
|
||||||
|
"podcast": ["/api/podcast/projects", "/api/podcast/"],
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_paths = router_path_indicators.get(router_name, [])
|
||||||
|
found = any(path in mounted_routes for path in expected_paths)
|
||||||
|
|
||||||
|
if found:
|
||||||
|
logger.info(f"✅ Critical router '{router_name}' is mounted")
|
||||||
|
else:
|
||||||
|
error_msg = f"❌ CRITICAL: Router '{router_name}' is NOT mounted! Expected paths: {expected_paths}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
if PODCAST_ONLY_DEMO_MODE:
|
||||||
|
# In demo mode, podcast router MUST be mounted
|
||||||
|
if router_name == "podcast":
|
||||||
|
raise RuntimeError(error_msg)
|
||||||
|
|
||||||
# Shutdown event
|
# Shutdown event
|
||||||
@app.on_event("shutdown")
|
@app.on_event("shutdown")
|
||||||
async def shutdown_event():
|
async def shutdown_event():
|
||||||
|
|||||||
173
backend/scripts/smoke_test_podcast_demo.py
Normal file
173
backend/scripts/smoke_test_podcast_demo.py
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Smoke test script for podcast-only demo mode.
|
||||||
|
Tests the subscription funnel, Stripe flow, and podcast runtime paths.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
BASE_URL = "http://localhost:8000"
|
||||||
|
|
||||||
|
|
||||||
|
def test_health() -> bool:
|
||||||
|
"""Test backend health endpoint."""
|
||||||
|
print("\n[TEST] Backend health check...")
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{BASE_URL}/health", timeout=10)
|
||||||
|
data = resp.json()
|
||||||
|
print(f" Status: {data.get('status')}")
|
||||||
|
print(f" Demo mode: {data.get('podcast_only_demo_mode')}")
|
||||||
|
return resp.status_code == 200
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ FAILED: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def test_router_status() -> bool:
|
||||||
|
"""Test router status endpoint."""
|
||||||
|
print("\n[TEST] Router status...")
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{BASE_URL}/api/routers/status", timeout=10)
|
||||||
|
data = resp.json()
|
||||||
|
|
||||||
|
# Check critical routers
|
||||||
|
podcast_mounted = data.get("podcast_only_demo_mode", False)
|
||||||
|
router_groups = data.get("router_groups", {})
|
||||||
|
|
||||||
|
print(f" Podcast router: {router_groups.get('podcast_maker', {}).get('mounted')}")
|
||||||
|
print(f" Assets serving: {router_groups.get('assets_serving', {}).get('mounted')}")
|
||||||
|
|
||||||
|
# Check podcast router is always mounted
|
||||||
|
podcast_ok = router_groups.get('podcast_maker', {}).get('mounted') == True
|
||||||
|
if not podcast_ok:
|
||||||
|
print(" ❌ Podcast router not mounted!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return resp.status_code == 200
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ FAILED: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def test_subscription_plans() -> bool:
|
||||||
|
"""Test subscription plans endpoint."""
|
||||||
|
print("\n[TEST] Subscription plans...")
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{BASE_URL}/api/subscription/plans", timeout=10)
|
||||||
|
data = resp.json()
|
||||||
|
|
||||||
|
if resp.status_code == 200:
|
||||||
|
plans = data.get("plans", [])
|
||||||
|
print(f" Plans returned: {len(plans)}")
|
||||||
|
for plan in plans[:3]:
|
||||||
|
print(f" - {plan.get('name')}: ${plan.get('price', {}).get('monthly', 'N/A')}/mo")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f" ❌ Status {resp.status_code}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ FAILED: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def test_podcast_routes() -> bool:
|
||||||
|
"""Test podcast router is accessible."""
|
||||||
|
print("\n[TEST] Podcast router endpoints...")
|
||||||
|
try:
|
||||||
|
# Test without auth (should return 401, not 404)
|
||||||
|
resp = requests.get(f"{BASE_URL}/api/podcast/projects", timeout=10)
|
||||||
|
|
||||||
|
if resp.status_code == 401:
|
||||||
|
print(" ✅ Podcast router mounted (auth required as expected)")
|
||||||
|
return True
|
||||||
|
elif resp.status_code == 404:
|
||||||
|
print(" ❌ Podcast router NOT mounted (404)")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print(f" Status: {resp.status_code}")
|
||||||
|
return resp.status_code in [200, 401]
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ FAILED: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def test_preflight() -> bool:
|
||||||
|
"""Test preflight cost estimation endpoint."""
|
||||||
|
print("\n[TEST] Preflight cost estimation...")
|
||||||
|
try:
|
||||||
|
resp = requests.post(
|
||||||
|
f"{BASE_URL}/api/subscription/preflight-check",
|
||||||
|
json={"operation": "podcast_analysis", "tier": "basic"},
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if resp.status_code in [200, 401]:
|
||||||
|
print(f" ✅ Preflight endpoint accessible (status: {resp.status_code})")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f" ❌ Status {resp.status_code}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ FAILED: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def test_onboarding_status() -> bool:
|
||||||
|
"""Test onboarding status endpoint."""
|
||||||
|
print("\n[TEST] Onboarding status...")
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{BASE_URL}/api/onboarding/status", timeout=10)
|
||||||
|
data = resp.json()
|
||||||
|
|
||||||
|
print(f" Status: {data.get('status')}")
|
||||||
|
print(f" Enabled: {data.get('enabled')}")
|
||||||
|
|
||||||
|
# In demo mode, should be disabled
|
||||||
|
if data.get('enabled') == False:
|
||||||
|
print(" ✅ Onboarding correctly disabled in demo mode")
|
||||||
|
return True
|
||||||
|
|
||||||
|
return resp.status_code == 200
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ FAILED: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run all smoke tests."""
|
||||||
|
print("=" * 60)
|
||||||
|
print("PODCAST-ONLY DEMO MODE SMOKE TESTS")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
results.append(("Health", test_health()))
|
||||||
|
results.append(("Router Status", test_router_status()))
|
||||||
|
results.append(("Subscription Plans", test_subscription_plans()))
|
||||||
|
results.append(("Podcast Routes", test_podcast_routes()))
|
||||||
|
results.append(("Preflight Check", test_preflight()))
|
||||||
|
results.append(("Onboarding Status", test_onboarding_status()))
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("SUMMARY")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
passed = sum(1 for _, r in results if r)
|
||||||
|
total = len(results)
|
||||||
|
|
||||||
|
for name, result in results:
|
||||||
|
status = "✅ PASS" if result else "❌ FAIL"
|
||||||
|
print(f" {status}: {name}")
|
||||||
|
|
||||||
|
print(f"\nTotal: {passed}/{total} tests passed")
|
||||||
|
|
||||||
|
return 0 if passed == total else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -145,7 +145,9 @@ const PricingPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For alpha testing, only allow Free and Basic plans (Pro features not ready)
|
// For alpha testing, only allow Free and Basic plans (Pro features not ready)
|
||||||
if (plan.tier !== 'free' && plan.tier !== 'basic') {
|
// Exception: In podcast-only demo mode, allow all plans
|
||||||
|
const demoModeEnabled = isPodcastOnlyDemoMode();
|
||||||
|
if (!demoModeEnabled && plan.tier !== 'free' && plan.tier !== 'basic') {
|
||||||
console.error('[PricingPage] Plan tier not available:', plan.tier);
|
console.error('[PricingPage] Plan tier not available:', plan.tier);
|
||||||
setError('This plan is not available for alpha testing');
|
setError('This plan is not available for alpha testing');
|
||||||
return;
|
return;
|
||||||
@@ -213,6 +215,12 @@ const PricingPage: React.FC = () => {
|
|||||||
window.location.href = response.data.url;
|
window.location.href = response.data.url;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Stripe not configured - warn in demo mode
|
||||||
|
if (isPodcastOnlyDemoMode()) {
|
||||||
|
console.warn('[PricingPage] ⚠️ DEMO MODE WARNING: Stripe is not configured. Using legacy subscription API.');
|
||||||
|
// In demo mode without Stripe, we may want to skip actual subscription for testing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[PricingPage] Making legacy subscription API call:', {
|
console.log('[PricingPage] Making legacy subscription API call:', {
|
||||||
|
|||||||
Reference in New Issue
Block a user