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:

  1. New Customer Signup

    // Verify free plan assignment
    const customer = await getCustomer(userId);
    expect(customer.subscriptions[0].plan.name).toBe('Free');
  2. Plan Upgrades

    // Test upgrade flow
    await upgradePlan(userId, 'pro');
    
    const entitled = await isFeatureEntitled({
      feature: 'advanced-analytics',
      userId
    });
    expect(entitled).toBe(true);
  3. 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);
  4. 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 })
    });
  }
};