Testing & Development
Best practices for developing and testing with Lumen
Local Development Setup
Environment Configuration
Create separate environments for development and production:
.env.local (Development)
NEXT_PUBLIC_LUMEN_PUBLISHABLE_KEY=pk_test_...
LUMEN_API_KEY=sk_test_...
LUMEN_WEBHOOK_SECRET=whsec_test_...
.env.production
NEXT_PUBLIC_LUMEN_PUBLISHABLE_KEY=pk_live_...
LUMEN_API_KEY=sk_live_...
LUMEN_WEBHOOK_SECRET=whsec_live_...
Test Mode
Lumen automatically detects test vs live mode based on your API keys:
- Test keys (
pk_test_
,sk_test_
): No real charges, test data only - Live keys (
pk_live_
,sk_live_
): Real charges, production data
Development Database
Keep test data separate from production:
// Use different customer IDs for testing
const userId = process.env.NODE_ENV === 'production'
? user.id
: `test_${user.id}`;
await isFeatureEntitled({
feature: "api-calls",
userId
});
Testing Payments
Stripe Test Mode
When using Stripe with Lumen, use Stripe's test card numbers:
Successful Payment:
- Card:
4242 4242 4242 4242
- Expiry: Any future date
- CVC: Any 3 digits
Declined Payment:
- Card:
4000 0000 0000 0002
Requires 3D Secure:
- Card:
4000 0025 0000 3155
Testing Scenarios
Test these common scenarios:
-
New Customer Signup
// Verify free plan assignment const customer = await getCustomer(userId); expect(customer.subscriptions[0].plan.name).toBe('Free');
-
Plan Upgrades
// Test upgrade flow await upgradePlan(userId, 'pro'); const entitled = await isFeatureEntitled({ feature: 'advanced-analytics', userId }); expect(entitled).toBe(true);
-
Usage Tracking
// Test usage events await sendEvent({ name: 'api-calls', userId, eventValue: 1 }); const usage = await getUsage({ feature: 'api-calls', userId }); expect(usage.total).toBe(1);
-
Credit System
// Test credit grants await createCreditGrant({ customerId: customer.id, creditDefinitionId: 'cd_welcome', initialAmount: 1000 }); const entitled = await isFeatureEntitled({ feature: 'bonus-credits', userId }); expect(entitled).toBe(true);
Unit Testing
Testing Entitlements
import { isFeatureEntitled } from '@getlumen/server';
// Mock Lumen for testing
jest.mock('@getlumen/server', () => ({
isFeatureEntitled: jest.fn(),
sendEvent: jest.fn()
}));
describe('Feature Access', () => {
test('allows feature access for pro users', async () => {
// Mock entitlement response
isFeatureEntitled.mockResolvedValue(true);
const result = await getAdvancedAnalytics(userId);
expect(isFeatureEntitled).toHaveBeenCalledWith({
feature: 'advanced-analytics',
userId
});
expect(result).toBeDefined();
});
test('denies feature access for free users', async () => {
isFeatureEntitled.mockResolvedValue(false);
await expect(getAdvancedAnalytics(userId))
.rejects.toThrow('Feature not available');
});
});
Testing React Components
import { render, screen } from '@testing-library/react';
import { useEntitlement } from '@getlumen/react';
import FeatureComponent from './FeatureComponent';
// Mock the hook
jest.mock('@getlumen/react', () => ({
useEntitlement: jest.fn()
}));
describe('FeatureComponent', () => {
test('shows feature when entitled', () => {
useEntitlement.mockReturnValue({
entitled: true,
loading: false
});
render(<FeatureComponent />);
expect(screen.getByText('Advanced Feature')).toBeInTheDocument();
});
test('shows upgrade prompt when not entitled', () => {
useEntitlement.mockReturnValue({
entitled: false,
loading: false
});
render(<FeatureComponent />);
expect(screen.getByText('Upgrade to Pro')).toBeInTheDocument();
});
});
Integration Testing
API Endpoint Testing
Test your API routes that use Lumen:
import { createMocks } from 'node-mocks-http';
import handler from '../pages/api/protected-feature';
describe('/api/protected-feature', () => {
test('allows access for entitled users', async () => {
const { req, res } = createMocks({
method: 'POST',
headers: { authorization: 'Bearer valid-token' }
});
// Mock user authentication
req.user = { id: 'user_pro_123' };
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
});
test('denies access for non-entitled users', async () => {
const { req, res } = createMocks({
method: 'POST',
headers: { authorization: 'Bearer valid-token' }
});
req.user = { id: 'user_free_123' };
await handler(req, res);
expect(res._getStatusCode()).toBe(403);
expect(JSON.parse(res._getData())).toEqual({
error: 'Feature not available on your plan'
});
});
});
Webhook Testing
Test webhook endpoints:
describe('Lumen Webhook', () => {
test('handles subscription created', async () => {
const payload = {
type: 'subscription.created',
data: {
customer_id: 'cust_123',
plan_id: 'plan_pro'
}
};
const { req, res } = createMocks({
method: 'POST',
body: payload,
headers: {
'lumen-signature': 'valid-signature'
}
});
await webhookHandler(req, res);
expect(res._getStatusCode()).toBe(200);
});
});
End-to-End Testing
Playwright/Cypress Tests
Test the complete user flow:
// Cypress test
describe('Billing Flow', () => {
it('upgrades user from free to pro', () => {
// Login as free user
cy.login('free-user@example.com');
// Try to access premium feature
cy.visit('/analytics');
cy.contains('Upgrade to Pro').should('be.visible');
// Go to pricing page
cy.contains('Upgrade to Pro').click();
cy.url().should('include', '/pricing');
// Subscribe to pro plan
cy.contains('Pro Plan').parent().contains('Subscribe').click();
// Fill payment form (use Stripe test card)
cy.fillStripeForm({
cardNumber: '4242424242424242',
expiry: '12/34',
cvc: '123'
});
// Verify upgrade
cy.visit('/analytics');
cy.contains('Advanced Analytics').should('be.visible');
});
});
Debugging
Common Issues
1. Entitlement Check Fails
// Debug entitlement response
const entitlement = await getEntitlement({
feature: 'api-calls',
userId
});
console.log('Entitlement:', {
entitled: entitlement.entitled,
source: entitlement.source,
usage: entitlement.usage,
limit: entitlement.limit,
credits: entitlement.creditInfo
});
2. Events Not Tracking
// Verify event sending
try {
await sendEvent({
name: 'api-calls',
userId,
eventValue: 1
});
console.log('Event sent successfully');
} catch (error) {
console.error('Event failed:', error);
}
3. Webhook Issues
// Debug webhook payload
export default function webhookHandler(req, res) {
console.log('Webhook received:', {
method: req.method,
headers: req.headers,
body: req.body
});
// Process webhook...
}
Logging
Add comprehensive logging:
import { sendEvent } from '@getlumen/server';
async function trackUsage(userId, feature, amount = 1) {
console.log(`Tracking usage: ${feature} for ${userId}`);
try {
await sendEvent({
name: feature,
userId,
eventValue: amount
});
console.log(`✓ Usage tracked: ${feature}=${amount}`);
} catch (error) {
console.error(`✗ Usage tracking failed:`, error);
throw error;
}
}
Development Tools
Lumen CLI (coming soon):
# Check customer status
lumen customer status user_123
# View usage
lumen usage list --customer=user_123 --feature=api-calls
# Test webhooks locally
lumen webhook listen --port=3000
Browser DevTools:
// Add to browser console for debugging
window.lumen = {
checkEntitlement: async (feature) => {
const response = await fetch(`/api/lumen/entitlements/${feature}`);
return response.json();
},
trackEvent: async (event, value = 1) => {
await fetch('/api/lumen/events', {
method: 'POST',
body: JSON.stringify({ name: event, eventValue: value })
});
}
};