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:
- Go to Settings > API Keys
- Click Create Test Key
- Select permissions
- 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_xxxxxxxxxxxxxTest vs Production Data
| Aspect | Test Keys | Production Keys |
|---|---|---|
| Data storage | Separate test database | Production database |
| Rate limits | Relaxed (10x higher) | Standard limits |
| Blockchain | Base Sepolia testnet | Base mainnet |
| Webhooks | Test endpoints only | All endpoints |
| Billing | No charges | Active 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:
| Contract | Testnet Address |
|---|---|
| PRIVToken | 0x... (Sepolia) |
| PRIVStaking | 0x... (Sepolia) |
| DataXchange | 0x... (Sepolia) |
Getting Testnet Tokens
- Get Sepolia ETH from a faucet
- Bridge to Base Sepolia
- 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)
})
})Testing Consent Management
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:e2eDebugging 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}`,
},
})
}
})