- 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>
277 lines
9.6 KiB
TypeScript
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);
|
|
});
|
|
});
|