PRIV ProtocolPRIV Docs
Guides

Testing

Set up test environments, sandbox mode, and testing strategies for your PRIV integration.

Overview

PRIV provides comprehensive testing tools to ensure your integration works correctly before going to production.

Test API Keys

Use pk_test_ and sk_test_ keys for development without affecting production data.

Sandbox Mode

Test blockchain interactions on Base Sepolia testnet.


Test Environment Setup

Test API Keys

Create test keys from your dashboard:

  1. Go to Settings > API Keys
  2. Click Create Test Key
  3. Select permissions
  4. Use pk_test_ keys in your test environment
// Development environment
const priv = new Priv({
  apiKey: process.env.NODE_ENV === 'production'
    ? process.env.NEXT_PUBLIC_PRIV_KEY!    // pk_live_xxx
    : process.env.NEXT_PUBLIC_PRIV_TEST_KEY! // pk_test_xxx
})

Environment Variables

# .env.local
NEXT_PUBLIC_PRIV_KEY=pk_live_xxxxxxxxxxxxx
NEXT_PUBLIC_PRIV_TEST_KEY=pk_test_xxxxxxxxxxxxx
PRIV_SECRET_KEY=sk_live_xxxxxxxxxxxxx
PRIV_SECRET_TEST_KEY=sk_test_xxxxxxxxxxxxx

Test vs Production Data

AspectTest KeysProduction Keys
Data storageSeparate test databaseProduction database
Rate limitsRelaxed (10x higher)Standard limits
BlockchainBase Sepolia testnetBase mainnet
WebhooksTest endpoints onlyAll endpoints
BillingNo chargesActive billing

Sandbox Mode

For testing smart contract interactions, use sandbox mode with Base Sepolia:

Configure Sandbox

import { Priv } from '@priv/sdk'

const priv = new Priv({
  apiKey: process.env.NEXT_PUBLIC_PRIV_TEST_KEY!,
  sandbox: true, // Enables testnet interactions
})

Testnet Contracts

PRIV contracts are deployed on Base Sepolia for testing:

Base Sepolia0x1234...5678
ContractTestnet Address
PRIVToken0x... (Sepolia)
PRIVStaking0x... (Sepolia)
DataXchange0x... (Sepolia)

Getting Testnet Tokens

  1. Get Sepolia ETH from a faucet
  2. Bridge to Base Sepolia
  3. Claim test PRIV tokens:
// Claim test tokens (testnet only)
await priv.claimTestTokens({
  amount: '1000', // 1000 test PRIV
})

Or use the faucet: testnet.priv.io/faucet


Unit Testing

Testing Event Tracking

import { describe, it, expect, vi, beforeEach } from 'vitest'
import { Priv } from '@priv/sdk'

describe('Event Tracking', () => {
  let priv: Priv
  let mockFetch: ReturnType<typeof vi.fn>

  beforeEach(() => {
    mockFetch = vi.fn().mockResolvedValue({
      ok: true,
      json: () => Promise.resolve({ success: true }),
    })
    global.fetch = mockFetch

    priv = new Priv({
      apiKey: 'pk_test_xxx',
      disabled: false,
    })
  })

  it('should track events with correct payload', async () => {
    await priv.track('button_click', { button_id: 'signup' })
    await priv.flush()

    expect(mockFetch).toHaveBeenCalledWith(
      expect.stringContaining('/api/v1/events'),
      expect.objectContaining({
        method: 'POST',
        body: expect.stringContaining('button_click'),
      })
    )
  })

  it('should respect disabled mode', async () => {
    const disabledPriv = new Priv({
      apiKey: 'pk_test_xxx',
      disabled: true,
    })

    await disabledPriv.track('test_event')
    await disabledPriv.flush()

    expect(mockFetch).not.toHaveBeenCalled()
  })

  it('should batch events correctly', async () => {
    priv = new Priv({
      apiKey: 'pk_test_xxx',
      batchSize: 3,
    })

    await priv.track('event_1')
    await priv.track('event_2')
    await priv.track('event_3')

    // Should auto-flush at batch size
    expect(mockFetch).toHaveBeenCalledTimes(1)

    const body = JSON.parse(mockFetch.mock.calls[0][1].body)
    expect(body.events).toHaveLength(3)
  })
})
import { describe, it, expect, beforeEach } from 'vitest'
import { Priv, ConsentStatus } from '@priv/sdk'

describe('Consent Management', () => {
  let priv: Priv

  beforeEach(() => {
    localStorage.clear()
    priv = new Priv({
      apiKey: 'pk_test_xxx',
      autoTrack: false,
    })
  })

  it('should start with pending consent', () => {
    expect(priv.getConsentStatus()).toBe(ConsentStatus.PENDING)
  })

  it('should update consent status', () => {
    priv.setConsent({
      analytics: true,
      marketing: false,
    })

    expect(priv.getConsentStatus()).toBe(ConsentStatus.GRANTED)
    expect(priv.getConsent()).toEqual({
      analytics: true,
      marketing: false,
    })
  })

  it('should persist consent across instances', () => {
    priv.setConsent({ analytics: true })

    const newPriv = new Priv({
      apiKey: 'pk_test_xxx',
      autoTrack: false,
    })

    expect(newPriv.getConsentStatus()).toBe(ConsentStatus.GRANTED)
  })

  it('should not track without consent', async () => {
    const mockFetch = vi.fn()
    global.fetch = mockFetch

    await priv.track('test_event')

    expect(mockFetch).not.toHaveBeenCalled()
  })
})

Testing React Components

import { describe, it, expect, vi } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import { PrivProvider, usePriv } from '@priv/sdk/react'

function TestComponent() {
  const { track } = usePriv()

  return (
    <button onClick={() => track('button_click', { id: 'test' })}>
      Track Event
    </button>
  )
}

describe('React Integration', () => {
  it('should provide tracking context', () => {
    render(
      <PrivProvider apiKey="pk_test_xxx">
        <TestComponent />
      </PrivProvider>
    )

    const button = screen.getByText('Track Event')
    expect(button).toBeInTheDocument()
  })

  it('should track events on interaction', async () => {
    const mockFetch = vi.fn().mockResolvedValue({
      ok: true,
      json: () => Promise.resolve({}),
    })
    global.fetch = mockFetch

    render(
      <PrivProvider apiKey="pk_test_xxx">
        <TestComponent />
      </PrivProvider>
    )

    fireEvent.click(screen.getByText('Track Event'))

    // Wait for batch flush
    await new Promise(resolve => setTimeout(resolve, 100))

    expect(mockFetch).toHaveBeenCalled()
  })
})

Integration Testing

Testing API Endpoints

import { describe, it, expect } from 'vitest'

describe('API Integration', () => {
  const API_URL = process.env.PRIV_API_URL || 'https://api.priv.io'
  const TEST_KEY = process.env.PRIV_SECRET_TEST_KEY!

  it('should accept valid events', async () => {
    const response = await fetch(`${API_URL}/v1/events`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${TEST_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        events: [
          {
            type: 'test_event',
            timestamp: new Date().toISOString(),
            properties: { test: true },
          },
        ],
      }),
    })

    expect(response.status).toBe(200)
    const data = await response.json()
    expect(data.success).toBe(true)
  })

  it('should reject invalid API key', async () => {
    const response = await fetch(`${API_URL}/v1/events`, {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer invalid_key',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        events: [{ type: 'test_event' }],
      }),
    })

    expect(response.status).toBe(401)
  })

  it('should validate event schema', async () => {
    const response = await fetch(`${API_URL}/v1/events`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${TEST_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        events: [
          {
            // Missing required 'type' field
            properties: { test: true },
          },
        ],
      }),
    })

    expect(response.status).toBe(400)
    const data = await response.json()
    expect(data.error).toContain('type')
  })
})

Testing Webhooks

import { describe, it, expect } from 'vitest'
import crypto from 'crypto'

describe('Webhook Handling', () => {
  const WEBHOOK_SECRET = process.env.PRIV_WEBHOOK_SECRET!

  function createSignature(payload: string): string {
    return crypto
      .createHmac('sha256', WEBHOOK_SECRET)
      .update(payload)
      .digest('hex')
  }

  it('should verify valid signature', async () => {
    const payload = JSON.stringify({
      id: 'evt_test',
      type: 'user.created',
      data: { user_id: 'test' },
    })
    const signature = `sha256=${createSignature(payload)}`

    const response = await fetch('http://localhost:3000/api/webhooks/priv', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-priv-signature': signature,
      },
      body: payload,
    })

    expect(response.status).toBe(200)
  })

  it('should reject invalid signature', async () => {
    const payload = JSON.stringify({
      id: 'evt_test',
      type: 'user.created',
      data: { user_id: 'test' },
    })

    const response = await fetch('http://localhost:3000/api/webhooks/priv', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-priv-signature': 'sha256=invalid',
      },
      body: payload,
    })

    expect(response.status).toBe(401)
  })
})

End-to-End Testing

Playwright Setup

// playwright.config.ts
import { defineConfig } from '@playwright/test'

export default defineConfig({
  testDir: './tests/e2e',
  use: {
    baseURL: 'http://localhost:3000',
  },
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
})

E2E Test Example

// tests/e2e/tracking.spec.ts
import { test, expect } from '@playwright/test'

test.describe('Event Tracking', () => {
  test('should track page views', async ({ page }) => {
    // Listen for network requests
    const eventRequests: string[] = []
    page.on('request', request => {
      if (request.url().includes('/api/v1/events')) {
        eventRequests.push(request.postData() || '')
      }
    })

    await page.goto('/')

    // Wait for tracking to fire
    await page.waitForTimeout(6000) // Wait for flush interval

    expect(eventRequests.length).toBeGreaterThan(0)

    const body = JSON.parse(eventRequests[0])
    expect(body.events.some((e: any) => e.type === 'page_view')).toBe(true)
  })

  test('should track custom events', async ({ page }) => {
    const eventRequests: string[] = []
    page.on('request', request => {
      if (request.url().includes('/api/v1/events')) {
        eventRequests.push(request.postData() || '')
      }
    })

    await page.goto('/')

    // Click a tracked button
    await page.click('[data-track="signup"]')

    await page.waitForTimeout(6000)

    const allEvents = eventRequests.flatMap(r => JSON.parse(r).events)
    expect(allEvents.some((e: any) => e.type === 'signup_click')).toBe(true)
  })

  test('should respect consent', async ({ page }) => {
    // Deny consent in localStorage before navigating
    await page.addInitScript(() => {
      localStorage.setItem('priv_consent', JSON.stringify({
        status: 'denied',
        analytics: false,
      }))
    })

    const eventRequests: string[] = []
    page.on('request', request => {
      if (request.url().includes('/api/v1/events')) {
        eventRequests.push(request.postData() || '')
      }
    })

    await page.goto('/')
    await page.waitForTimeout(6000)

    // Should not have sent any events
    expect(eventRequests.length).toBe(0)
  })
})

CI/CD Integration

GitHub Actions Example

# .github/workflows/test.yml
name: Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  NEXT_PUBLIC_PRIV_TEST_KEY: ${{ secrets.PRIV_TEST_KEY }}
  PRIV_SECRET_TEST_KEY: ${{ secrets.PRIV_SECRET_TEST_KEY }}

jobs:
  unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm run test:unit

  integration:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm run test:integration

  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npm run test:e2e

Debugging Tests

Debug Mode

Enable debug output for troubleshooting:

const priv = new Priv({
  apiKey: 'pk_test_xxx',
  debug: true, // Logs all SDK activity
})

Network Debugging

// Mock with detailed logging
const mockFetch = vi.fn().mockImplementation((...args) => {
  console.log('Fetch called with:', args)
  return Promise.resolve({
    ok: true,
    json: () => Promise.resolve({ success: true }),
  })
})

Test Data Cleanup

// Clean up test data after tests
afterAll(async () => {
  if (process.env.NODE_ENV === 'test') {
    await fetch(`${API_URL}/v1/test/cleanup`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${TEST_KEY}`,
      },
    })
  }
})

Next Steps