WellNuo/services/__tests__/performance.test.ts
Sergei dd5bc7f95a Add performance optimizations for app startup and BLE operations
- Add 2-second timeout to profile fetch in getStoredUser() to ensure
  app startup < 3 seconds even with slow network. Falls back to cached
  user data on timeout.

- Implement early scan termination in BLEManager when devices found.
  Scan now exits after 3 seconds once minimum devices are detected,
  instead of always waiting full 10 seconds.

- Add PerformanceService for tracking app startup time, API response
  times, and BLE operation durations with threshold checking.

- Integrate performance tracking in app/_layout.tsx to measure and
  log startup duration in dev mode.

- Add comprehensive test suite for performance service and BLE
  scan optimizations.

Performance targets:
- App startup: < 3 seconds
- BLE operations: < 10 seconds

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-01 11:45:10 -08:00

277 lines
9.6 KiB
TypeScript

import { performanceService, PERFORMANCE_THRESHOLDS } from '../performance';
describe('PerformanceService', () => {
beforeEach(() => {
performanceService.clear();
});
describe('PERFORMANCE_THRESHOLDS', () => {
it('should have correct threshold values', () => {
expect(PERFORMANCE_THRESHOLDS.appStartup).toBe(3000);
expect(PERFORMANCE_THRESHOLDS.bleOperation).toBe(10000);
expect(PERFORMANCE_THRESHOLDS.apiCall).toBe(2000);
});
});
describe('App Startup Tracking', () => {
it('should track app startup duration', () => {
performanceService.markAppStart();
// Simulate some delay
const startTime = Date.now();
while (Date.now() - startTime < 50) {
// Wait 50ms
}
const duration = performanceService.markAppReady();
expect(duration).toBeGreaterThanOrEqual(50);
expect(duration).toBeLessThan(200);
});
it('should return startup duration via getAppStartupDuration', () => {
performanceService.markAppStart();
performanceService.markAppReady();
const duration = performanceService.getAppStartupDuration();
expect(duration).not.toBeNull();
expect(duration).toBeGreaterThanOrEqual(0);
});
it('should return null if app not started', () => {
expect(performanceService.getAppStartupDuration()).toBeNull();
});
it('should record startup in metrics', () => {
performanceService.markAppStart();
performanceService.markAppReady();
const metrics = performanceService.getMetricsByName('app_startup');
expect(metrics).toHaveLength(1);
expect(metrics[0].name).toBe('app_startup');
});
it('should mark startup as success when within threshold', () => {
performanceService.markAppStart();
const duration = performanceService.markAppReady();
const metrics = performanceService.getMetricsByName('app_startup');
// Test starts almost immediately, so should be within threshold
expect(metrics[0].success).toBe(true);
expect(metrics[0].metadata?.withinTarget).toBe(true);
});
});
describe('Timer Operations', () => {
it('should track operation duration with timer', () => {
performanceService.startTimer('test_operation');
const startTime = Date.now();
while (Date.now() - startTime < 50) {
// Wait 50ms
}
const duration = performanceService.endTimer('test_operation');
expect(duration).toBeGreaterThanOrEqual(50);
expect(duration).toBeLessThan(200);
});
it('should return 0 for non-existent timer', () => {
const duration = performanceService.endTimer('non_existent');
expect(duration).toBe(0);
});
it('should record timer metrics', () => {
performanceService.startTimer('my_op');
performanceService.endTimer('my_op', true, { custom: 'data' });
const metrics = performanceService.getMetrics();
expect(metrics).toHaveLength(1);
expect(metrics[0].name).toBe('my_op');
expect(metrics[0].success).toBe(true);
expect(metrics[0].metadata?.custom).toBe('data');
});
it('should record failed operations', () => {
performanceService.startTimer('failed_op');
performanceService.endTimer('failed_op', false);
const metrics = performanceService.getMetrics();
expect(metrics[0].success).toBe(false);
});
});
describe('BLE Operation Tracking', () => {
it('should track BLE operations', () => {
performanceService.trackBleOperation('scan', 5000, true);
performanceService.trackBleOperation('connect', 2000, true);
performanceService.trackBleOperation('command', 500, true);
const metrics = performanceService.getMetricsByName(/^ble_/);
expect(metrics).toHaveLength(3);
});
it('should mark BLE operation as within target when under 10s', () => {
performanceService.trackBleOperation('scan', 5000, true);
const metrics = performanceService.getMetricsByName('ble_scan');
expect(metrics[0].metadata?.withinTarget).toBe(true);
});
it('should mark BLE operation as over target when over 10s', () => {
performanceService.trackBleOperation('wifi_config', 12000, true);
const metrics = performanceService.getMetricsByName('ble_wifi_config');
expect(metrics[0].metadata?.withinTarget).toBe(false);
});
it('should track failed BLE operations', () => {
performanceService.trackBleOperation('connect', 5000, false, { deviceId: 'test' });
const metrics = performanceService.getMetricsByName('ble_connect');
expect(metrics[0].success).toBe(false);
expect(metrics[0].metadata?.deviceId).toBe('test');
});
});
describe('API Call Tracking', () => {
it('should track API calls', () => {
performanceService.trackApiCall('auth/me', 500, true);
performanceService.trackApiCall('beneficiaries', 800, true);
const metrics = performanceService.getMetricsByName(/^api_/);
expect(metrics).toHaveLength(2);
});
it('should mark API call as within target when under 2s', () => {
performanceService.trackApiCall('auth/me', 500, true);
const metrics = performanceService.getMetricsByName('api_auth/me');
expect(metrics[0].metadata?.withinTarget).toBe(true);
});
it('should mark API call as over target when over 2s', () => {
performanceService.trackApiCall('slow_endpoint', 3000, true);
const metrics = performanceService.getMetricsByName('api_slow_endpoint');
expect(metrics[0].metadata?.withinTarget).toBe(false);
});
});
describe('Metrics Aggregation', () => {
it('should calculate average duration', () => {
performanceService.trackBleOperation('connect', 1000, true);
performanceService.trackBleOperation('connect', 2000, true);
performanceService.trackBleOperation('connect', 3000, true);
const avg = performanceService.getAverageDuration('ble_connect');
expect(avg).toBe(2000);
});
it('should return null for non-existent metrics', () => {
const avg = performanceService.getAverageDuration('non_existent');
expect(avg).toBeNull();
});
it('should get summary stats', () => {
performanceService.markAppStart();
performanceService.markAppReady();
performanceService.trackBleOperation('scan', 5000, true);
performanceService.trackBleOperation('connect', 2000, false);
performanceService.trackApiCall('auth/me', 500, true);
const summary = performanceService.getSummary();
expect(summary.appStartup.duration).toBeGreaterThanOrEqual(0);
expect(summary.appStartup.withinTarget).toBe(true);
expect(summary.bleOperations.count).toBe(2);
expect(summary.bleOperations.avgDuration).toBe(3500);
expect(summary.bleOperations.successRate).toBe(0.5);
expect(summary.apiCalls.count).toBe(1);
expect(summary.apiCalls.avgDuration).toBe(500);
expect(summary.apiCalls.successRate).toBe(1);
});
});
describe('Memory Management', () => {
it('should keep only last 100 metrics', () => {
for (let i = 0; i < 150; i++) {
performanceService.trackApiCall(`endpoint_${i}`, 100, true);
}
const metrics = performanceService.getMetrics();
expect(metrics).toHaveLength(100);
// Should have the most recent metrics
expect(metrics[99].name).toBe('api_endpoint_149');
});
it('should clear all metrics', () => {
performanceService.markAppStart();
performanceService.markAppReady();
performanceService.trackBleOperation('scan', 5000, true);
performanceService.clear();
expect(performanceService.getMetrics()).toHaveLength(0);
expect(performanceService.getAppStartupDuration()).toBeNull();
});
});
describe('Metrics Filtering', () => {
it('should filter by string pattern', () => {
performanceService.trackBleOperation('scan', 5000, true);
performanceService.trackBleOperation('connect', 2000, true);
performanceService.trackApiCall('auth/me', 500, true);
const bleMetrics = performanceService.getMetricsByName('ble_');
expect(bleMetrics).toHaveLength(2);
});
it('should filter by regex pattern', () => {
performanceService.trackBleOperation('scan', 5000, true);
performanceService.trackBleOperation('connect', 2000, true);
performanceService.trackApiCall('auth/me', 500, true);
const bleMetrics = performanceService.getMetricsByName(/^ble_/);
expect(bleMetrics).toHaveLength(2);
const apiMetrics = performanceService.getMetricsByName(/^api_/);
expect(apiMetrics).toHaveLength(1);
});
});
});
describe('Performance Targets', () => {
beforeEach(() => {
performanceService.clear();
});
it('should verify app startup target is 3 seconds', () => {
expect(PERFORMANCE_THRESHOLDS.appStartup).toBe(3000);
});
it('should verify BLE operation target is 10 seconds', () => {
expect(PERFORMANCE_THRESHOLDS.bleOperation).toBe(10000);
});
it('should correctly identify performance violations', () => {
// App startup violation
performanceService.markAppStart();
// Simulate slow startup (for testing, we just track the metric directly)
// In real scenario, markAppReady would measure actual time
// BLE operation within target
performanceService.trackBleOperation('scan', 8000, true);
const scanMetrics = performanceService.getMetricsByName('ble_scan');
expect(scanMetrics[0].metadata?.withinTarget).toBe(true);
// BLE operation over target
performanceService.trackBleOperation('bulk_wifi', 15000, true);
const bulkMetrics = performanceService.getMetricsByName('ble_bulk_wifi');
expect(bulkMetrics[0].metadata?.withinTarget).toBe(false);
});
});