๐งช Testing & Sandbox
Learn how to test your integration in a safe sandbox environment.
Development Mode
The rPPG API backend includes a development mode that bypasses authentication for easier local testing.
Enabling Dev Mode
Set the DEV_MODE environment variable on the backend:
# Backend .env file
DEV_MODE=true
When dev mode is enabled:
- โ Any API key value is accepted (authentication bypassed)
- โ Rate limiting is relaxed
- โ Useful for local development and testing
โ ๏ธ Warning: Never enable
DEV_MODEin production!
Using Dev Mode
import { RPPGClient } from 'rppg-api-client';
// Any API key works in dev mode
const client = new RPPGClient({
apiKey: 'dev-key',
baseUrl: 'http://localhost/api'
});
const vitals = await client.analyzeVideo(videoFile);
Local Testing Setup
Option 1: Docker Compose (Recommended)
The quickest way to get a complete local testing environment:
# Clone the repository
git clone https://github.com/mediverusapi/rppg-model.git
cd rppg-model
# Start all services
make start-bg
# Or without make:
docker-compose up -d
Services will be available at:
- Frontend:
http://localhost(for visual testing) - Backend API:
http://localhost/api - API docs: Check this documentation
Option 2: Manual Setup
# Terminal 1: Model service
cd model
python -m venv venv
source venv/bin/activate
pip install -e .
python -m uvicorn src.server:app --port 8001
# Terminal 2: Backend API
cd backend
python -m venv venv
source venv/bin/activate
pip install -e .
export DEV_MODE=true
python -m uvicorn src.main:app --port 8000
# Terminal 3: Frontend (optional, for visual testing)
cd frontend
npm install
npm run dev
Test Video Files
Creating Test Videos
For consistent testing, create test videos with these characteristics:
Good test video:
- Duration: 10-15 seconds
- Face centered and well-lit
- Minimal movement
- Frontal view
- Resolution: 720p or higher
Sample recording script:
// Record test video from webcam
async function recordTestVideo(duration = 10000) {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'user', width: 1280, height: 720 }
});
const mediaRecorder = new MediaRecorder(stream, {
mimeType: 'video/webm;codecs=vp8,opus'
});
const chunks = [];
mediaRecorder.ondataavailable = (e) => chunks.push(e.data);
return new Promise((resolve) => {
mediaRecorder.onstop = () => {
const blob = new Blob(chunks, { type: 'video/webm' });
resolve(blob);
};
mediaRecorder.start();
setTimeout(() => {
mediaRecorder.stop();
stream.getTracks().forEach(track => track.stop());
}, duration);
});
}
Example Test Videos
You can find sample test videos in the repository:
# Download test videos
cd rppg-model/test-data
# Contains various test scenarios:
# - good-lighting-frontal.mp4
# - low-lighting.mp4
# - side-angle.mp4
# - motion-artifacts.mp4
Testing Checklist
Basic Functionality Tests
-
[ ] Authentication
- [ ] Valid API key works
- [ ] Invalid API key returns 401
- [ ] Missing API key returns 401
-
[ ] Session Creation
- [ ] Can create a new session
- [ ] Session ID is returned
- [ ] Session ID format is valid (
sess_*)
-
[ ] Video Upload
- [ ] Can upload valid video
- [ ] Large files (>100MB) are rejected
- [ ] Invalid file types are rejected
- [ ] Empty files are rejected
-
[ ] Results Retrieval
- [ ] Can poll for results
- [ ] Results eventually return
completedstatus - [ ] Vitals data is present and valid
- [ ] Confidence scores are between 0-1
-
[ ] Error Handling
- [ ] Network errors are caught
- [ ] Timeout scenarios are handled
- [ ] Invalid session IDs return 404
Edge Cases
-
[ ] Video Quality
- [ ] Test with good lighting
- [ ] Test with poor lighting
- [ ] Test with motion artifacts
- [ ] Test with occluded face
- [ ] Test with no face present
-
[ ] File Formats
- [ ] MP4 files work
- [ ] WebM files work
- [ ] MOV files work
- [ ] Other formats are rejected gracefully
-
[ ] Concurrency
- [ ] Multiple sessions can run simultaneously
- [ ] Session IDs don't collide
- [ ] Results are correctly associated with sessions
Test Code Examples
Unit Test Example (Jest)
import { RPPGClient } from 'rppg-api-client';
describe('RPPGClient', () => {
let client;
beforeAll(() => {
client = new RPPGClient({
apiKey: 'test-key',
baseUrl: 'http://localhost/api'
});
});
test('creates a session', async () => {
const session = await client.createSession();
expect(session).toHaveProperty('session_id');
expect(session.session_id).toMatch(/^sess_/);
});
test('rejects files over 100MB', async () => {
const largeFile = new File(
[new ArrayBuffer(101 * 1024 * 1024)],
'large.mp4',
{ type: 'video/mp4' }
);
await expect(
client.analyzeVideo(largeFile)
).rejects.toThrow('file size');
});
test('analyzes valid video', async () => {
const videoFile = await loadTestVideo('good-lighting-frontal.mp4');
const vitals = await client.analyzeVideo(videoFile);
expect(vitals).toHaveProperty('heart_rate');
expect(vitals.heart_rate).toBeGreaterThan(40);
expect(vitals.heart_rate).toBeLessThan(200);
}, 60000); // 60 second timeout
});
Integration Test Example
describe('Full Analysis Flow', () => {
test('complete video analysis', async () => {
const client = new RPPGClient({
apiKey: 'test-key',
baseUrl: 'http://localhost/api'
});
// Step 1: Create session
const session = await client.createSession();
expect(session.session_id).toBeDefined();
// Step 2: Upload video
const videoFile = await loadTestVideo('test-video.mp4');
const uploadResult = await client.uploadVideo(
session.session_id,
videoFile
);
expect(uploadResult.status).toBe('processing');
// Step 3: Wait for results
const vitals = await client.waitForVitals(session.session_id, {
maxPollingTime: 120000
});
// Step 4: Verify results
expect(vitals).toMatchObject({
heart_rate: expect.any(Number),
respiratory_rate: expect.any(Number),
confidence: expect.any(Number)
});
expect(vitals.confidence).toBeGreaterThan(0);
expect(vitals.confidence).toBeLessThanOrEqual(1);
}, 180000); // 3 minute timeout
});
Load Testing Example (Artillery)
# artillery.yml
config:
target: "http://localhost/api"
phases:
- duration: 60
arrivalRate: 5
variables:
apiKey: "test-key"
scenarios:
- name: "Video Analysis"
flow:
- post:
url: "/v1/scan-session"
headers:
Authorization: "Bearer {{ apiKey }}"
capture:
- json: "$.session_id"
as: "sessionId"
- post:
url: "/v1/scan-session/{{ sessionId }}/video"
headers:
Authorization: "Bearer {{ apiKey }}"
beforeRequest: "loadVideoFile"
- loop:
- get:
url: "/v1/scan-session/{{ sessionId }}/vitals"
headers:
Authorization: "Bearer {{ apiKey }}"
count: 40
delay: 3
Debugging Tips
Enable Verbose Logging
// Set environment variable
process.env.DEBUG = 'rppg:*';
// Or use console.log in SDK
const client = new RPPGClient({
apiKey: 'test-key',
baseUrl: 'http://localhost/api',
debug: true // If supported
});
Check Backend Logs
# Docker logs
docker-compose logs -f backend
docker-compose logs -f model
# Or with make
make logs-backend
make logs-model
Inspect Network Requests
Use browser DevTools or tools like:
- Chrome DevTools โ Network tab
- Postman โ For API testing
- cURL โ Command-line testing
Example cURL commands:
# Create session
curl -v -X POST http://localhost/api/v1/scan-session \
-H "Authorization: Bearer test-key"
# Check health
curl http://localhost/api/health
# Get vitals
curl http://localhost/api/v1/scan-session/sess_xxx/vitals \
-H "Authorization: Bearer test-key"
Common Issues
Issue: "Connection refused"
- Solution: Ensure backend is running on the correct port
Issue: "404 Not Found"
- Solution: Check API base URL includes
/apiprefix
Issue: "CORS errors"
- Solution: Use backend proxy or enable CORS in dev mode
Issue: "Video processing never completes"
- Solution: Check model service logs for errors
Issue: "Heart rate returns 0"
- Solution: Ensure face is clearly visible in video, check lighting
Performance Benchmarks
Expected performance metrics for testing:
| Metric | Value | |--------|-------| | Session creation | < 100ms | | Video upload (10s, 720p) | < 2s | | Processing time | 30-40s | | Result retrieval | < 50ms | | Total end-to-end | 35-45s |
Mock Data
For frontend development without a backend:
// Mock client for testing
class MockRPPGClient {
async createSession() {
await this.delay(100);
return {
session_id: 'sess_mock_123',
created_at: new Date().toISOString(),
owner: 'mock-user'
};
}
async uploadVideo(sessionId, file) {
await this.delay(500);
return {
session_id: sessionId,
status: 'processing'
};
}
async waitForVitals(sessionId, options = {}) {
// Simulate processing time
await this.delay(5000);
return {
session_id: sessionId,
heart_rate: 72 + Math.random() * 10,
respiratory_rate: 16 + Math.random() * 4,
hrv: 45 + Math.random() * 10,
blood_pressure: null,
confidence: 0.85 + Math.random() * 0.1,
timestamp: new Date().toISOString(),
processing_time_ms: 5000
};
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Use in development
const client = process.env.USE_MOCK
? new MockRPPGClient()
: new RPPGClient({ apiKey: 'key', baseUrl: '/api' });
Test Environment URLs
Local Development
- Backend:
http://localhost/api - Frontend:
http://localhost
Staging (if available)
- Backend:
https://staging-api.yourdomain.com/api
Production
- Backend:
https://api.yourdomain.com/api
Next Steps
- Rate Limits โ - Understand API quotas
- Code Examples โ - More implementation patterns
- API Reference โ - Full endpoint documentation