/** * Integration test for performance requirements * * This test verifies that the credential manager meets all performance requirements: * 1. Hot reload: <100ms for configuration file changes * 2. Signing operations: <50ms per signature * 3. Memory usage: <50MB total * 4. Concurrent operations: Support 50+ accounts * 5. Throughput: Handle high-frequency operations efficiently * * Tests MUST FAIL initially until implementation is provided. */ import { describe, test, expect, beforeEach, afterEach } from '@jest/globals'; import * as fs from 'fs/promises'; import * as path from 'path'; import * as os from 'os'; // Import types (this import will fail until types are implemented) import type { ICredentialManager, SignResult, LoadResult, Account, Platform, ConfigFile } from '@/specs/001-credential-manager/contracts/credential-manager'; describe('Performance Requirements Integration Tests', () => { let credentialManager: ICredentialManager; let tempDir: string; let configPath: string; // Performance test utilities const measureTime = async (operation: () => Promise): Promise<{ result: T; duration: number }> => { const startTime = Date.now(); const result = await operation(); const duration = Date.now() - startTime; return { result, duration }; }; const measureMemory = (): number => { if (process.memoryUsage) { return process.memoryUsage().heapUsed / (1024 * 1024); // MB } return 0; }; beforeEach(async () => { // This will fail until CredentialManager is implemented const { CredentialManager } = await import('@/core/credential-manager/CredentialManager'); credentialManager = new CredentialManager(); // Create temporary directory tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'performance-test-')); configPath = path.join(tempDir, 'performance-config.json'); }); afterEach(async () => { // Clean up credentialManager.stopWatching(); try { await fs.rm(tempDir, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors } }); describe('Hot Reload Performance (<100ms)', () => { test('should reload small configuration within 100ms', async () => { // Arrange const smallConfig: ConfigFile = { version: "1.0", accounts: [ { id: "small-test-account", platform: Platform.PACIFICA, name: "Small Test Account", credentials: { type: "ed25519", privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } } ] }; await fs.writeFile(configPath, JSON.stringify(smallConfig)); await credentialManager.loadConfig(configPath); const reloadPromise = new Promise<{ accounts: Account[]; reloadTime: number }>((resolve) => { const startTime = Date.now(); credentialManager.watchConfig(configPath, (accounts) => { const reloadTime = Date.now() - startTime; resolve({ accounts, reloadTime }); }); }); // Wait for watcher to initialize await new Promise(resolve => setTimeout(resolve, 50)); // Act - modify configuration const modifiedConfig: ConfigFile = { ...smallConfig, accounts: [ ...smallConfig.accounts, { id: "added-account", platform: Platform.BINANCE, name: "Added Account", credentials: { type: "hmac", apiKey: "test-key", secretKey: "test-secret" } } ] }; await fs.writeFile(configPath, JSON.stringify(modifiedConfig, null, 2)); // Assert const { accounts, reloadTime } = await Promise.race([ reloadPromise, new Promise((_, reject) => setTimeout(() => reject(new Error('Reload timeout')), 5000) ) ]); expect(reloadTime).toBeLessThan(100); // Core requirement expect(accounts).toHaveLength(2); }); test('should reload medium configuration (10 accounts) within 100ms', async () => { // Arrange - 10 accounts const mediumConfig: ConfigFile = { version: "1.0", accounts: Array(10).fill(null).map((_, index) => ({ id: `medium-account-${index}`, platform: Platform.PACIFICA, name: `Medium Account ${index}`, credentials: { type: "ed25519", privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } })) }; await fs.writeFile(configPath, JSON.stringify(mediumConfig)); // Act & Assert const { result, duration } = await measureTime(() => credentialManager.loadConfig(configPath) ); expect(result.success).toBe(true); expect(duration).toBeLessThan(100); expect(result.loadTime).toBeLessThan(100); }); test('should reload large configuration (50 accounts) within 100ms', async () => { // Arrange - 50 accounts (edge of requirement) const largeConfig: ConfigFile = { version: "1.0", accounts: Array(50).fill(null).map((_, index) => ({ id: `large-account-${index}`, platform: index % 3 === 0 ? Platform.PACIFICA : index % 3 === 1 ? Platform.ASTER : Platform.BINANCE, name: `Large Account ${index}`, credentials: index % 3 === 0 ? { type: "ed25519", privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } : index % 3 === 1 ? { type: "eip191", privateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" } : { type: "hmac", apiKey: `api-key-${index}`, secretKey: `secret-key-${index}` } })) }; await fs.writeFile(configPath, JSON.stringify(largeConfig)); // Act & Assert const { result, duration } = await measureTime(() => credentialManager.loadConfig(configPath) ); expect(result.success).toBe(true); expect(duration).toBeLessThan(100); // Critical requirement expect(result.loadTime).toBeLessThan(100); expect(result.accounts).toHaveLength(50); }); test('should handle incremental updates efficiently', async () => { // Arrange - start with base configuration const baseConfig: ConfigFile = { version: "1.0", accounts: Array(20).fill(null).map((_, index) => ({ id: `base-account-${index}`, platform: Platform.PACIFICA, name: `Base Account ${index}`, credentials: { type: "ed25519", privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } })) }; await fs.writeFile(configPath, JSON.stringify(baseConfig)); await credentialManager.loadConfig(configPath); const reloadTimes: number[] = []; // Setup reload measurement const reloadPromise = new Promise((resolve) => { let reloadCount = 0; credentialManager.watchConfig(configPath, () => { reloadTimes.push(Date.now()); reloadCount++; if (reloadCount === 5) resolve(); }); }); // Wait for watcher await new Promise(resolve => setTimeout(resolve, 50)); // Act - make incremental changes for (let i = 0; i < 5; i++) { const updatedConfig = { ...baseConfig, accounts: [ ...baseConfig.accounts, { id: `incremental-account-${i}`, platform: Platform.BINANCE, name: `Incremental Account ${i}`, credentials: { type: "hmac", apiKey: `key-${i}`, secretKey: `secret-${i}` } } ] }; const startTime = Date.now(); await fs.writeFile(configPath, JSON.stringify(updatedConfig)); // Wait for this reload to complete await new Promise(resolve => setTimeout(resolve, 120)); } // Assert - each reload should be fast await reloadPromise; expect(reloadTimes).toHaveLength(5); }); }); describe('Signing Performance (<50ms)', () => { test('should sign Pacifica messages within 50ms', async () => { // Arrange const config: ConfigFile = { version: "1.0", accounts: [ { id: "pacifica-perf-account", platform: Platform.PACIFICA, name: "Pacifica Performance Account", credentials: { type: "ed25519", privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } } ] }; await fs.writeFile(configPath, JSON.stringify(config)); await credentialManager.loadConfig(configPath); const message = new TextEncoder().encode("Performance test message"); // Act & Assert - multiple signing operations for (let i = 0; i < 10; i++) { const { result, duration } = await measureTime(() => credentialManager.sign("pacifica-perf-account", message) ); expect(result.success).toBe(true); expect(duration).toBeLessThan(50); // Core requirement } }); test('should sign Aster messages within 50ms', async () => { // Arrange const config: ConfigFile = { version: "1.0", accounts: [ { id: "aster-perf-account", platform: Platform.ASTER, name: "Aster Performance Account", credentials: { type: "eip191", privateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" } } ] }; await fs.writeFile(configPath, JSON.stringify(config)); await credentialManager.loadConfig(configPath); const message = new TextEncoder().encode("Aster performance test"); // Act & Assert for (let i = 0; i < 10; i++) { const { result, duration } = await measureTime(() => credentialManager.sign("aster-perf-account", message) ); expect(result.success).toBe(true); expect(duration).toBeLessThan(50); } }); test('should sign Binance messages within 50ms', async () => { // Arrange const config: ConfigFile = { version: "1.0", accounts: [ { id: "binance-perf-account", platform: Platform.BINANCE, name: "Binance Performance Account", credentials: { type: "hmac", apiKey: "performance-api-key", secretKey: "performance-secret-key" } } ] }; await fs.writeFile(configPath, JSON.stringify(config)); await credentialManager.loadConfig(configPath); const message = new TextEncoder().encode("symbol=BTCUSDT&side=BUY&type=MARKET&quantity=0.1"); // Act & Assert for (let i = 0; i < 10; i++) { const { result, duration } = await measureTime(() => credentialManager.sign("binance-perf-account", message) ); expect(result.success).toBe(true); expect(duration).toBeLessThan(50); } }); test('should handle concurrent signing requests efficiently', async () => { // Arrange - multiple accounts const config: ConfigFile = { version: "1.0", accounts: [ { id: "concurrent-pacifica", platform: Platform.PACIFICA, name: "Concurrent Pacifica", credentials: { type: "ed25519", privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } }, { id: "concurrent-aster", platform: Platform.ASTER, name: "Concurrent Aster", credentials: { type: "eip191", privateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" } }, { id: "concurrent-binance", platform: Platform.BINANCE, name: "Concurrent Binance", credentials: { type: "hmac", apiKey: "concurrent-api-key", secretKey: "concurrent-secret-key" } } ] }; await fs.writeFile(configPath, JSON.stringify(config)); await credentialManager.loadConfig(configPath); const message = new TextEncoder().encode("Concurrent test message"); // Act - create many concurrent signing requests const concurrentOperations = Array(20).fill(null).map((_, index) => { const accountId = index % 3 === 0 ? "concurrent-pacifica" : index % 3 === 1 ? "concurrent-aster" : "concurrent-binance"; return measureTime(() => credentialManager.sign(accountId, message)); }); const startTime = Date.now(); const results = await Promise.all(concurrentOperations); const totalDuration = Date.now() - startTime; // Assert - all should complete successfully and quickly results.forEach(({ result, duration }) => { expect(result.success).toBe(true); expect(duration).toBeLessThan(50); // Individual operation }); // Total time should be reasonable (not just sum of individual times) expect(totalDuration).toBeLessThan(500); // 20 operations in <500ms }); test('should maintain performance under sustained load', async () => { // Arrange const config: ConfigFile = { version: "1.0", accounts: [ { id: "sustained-load-account", platform: Platform.PACIFICA, name: "Sustained Load Account", credentials: { type: "ed25519", privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } } ] }; await fs.writeFile(configPath, JSON.stringify(config)); await credentialManager.loadConfig(configPath); const message = new TextEncoder().encode("Sustained load test"); const durations: number[] = []; // Act - sustained signing operations for (let i = 0; i < 100; i++) { const { result, duration } = await measureTime(() => credentialManager.sign("sustained-load-account", message) ); expect(result.success).toBe(true); durations.push(duration); // Small delay to simulate real usage await new Promise(resolve => setTimeout(resolve, 10)); } // Assert - performance should not degrade significantly const averageDuration = durations.reduce((a, b) => a + b, 0) / durations.length; const maxDuration = Math.max(...durations); expect(averageDuration).toBeLessThan(50); expect(maxDuration).toBeLessThan(100); // Allow some variance but not too much }); }); describe('Memory Usage (<50MB)', () => { test('should maintain memory usage under 50MB with many accounts', async () => { // Arrange - large configuration const largeConfig: ConfigFile = { version: "1.0", accounts: Array(100).fill(null).map((_, index) => ({ id: `memory-test-account-${index}`, platform: index % 3 === 0 ? Platform.PACIFICA : index % 3 === 1 ? Platform.ASTER : Platform.BINANCE, name: `Memory Test Account ${index}`, credentials: index % 3 === 0 ? { type: "ed25519", privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } : index % 3 === 1 ? { type: "eip191", privateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" } : { type: "hmac", apiKey: `memory-api-key-${index}`, secretKey: `memory-secret-key-${index}` } })) }; const initialMemory = measureMemory(); // Act await fs.writeFile(configPath, JSON.stringify(largeConfig)); await credentialManager.loadConfig(configPath); // Perform some operations to fully load everything for (let i = 0; i < 10; i++) { const accountId = `memory-test-account-${i * 10}`; const message = new TextEncoder().encode(`Memory test ${i}`); await credentialManager.sign(accountId, message); } const finalMemory = measureMemory(); const memoryIncrease = finalMemory - initialMemory; // Assert - memory increase should be reasonable expect(memoryIncrease).toBeLessThan(50); // Core requirement }); test('should not leak memory during repeated reloads', async () => { // Arrange const baseConfig: ConfigFile = { version: "1.0", accounts: Array(20).fill(null).map((_, index) => ({ id: `leak-test-account-${index}`, platform: Platform.PACIFICA, name: `Leak Test Account ${index}`, credentials: { type: "ed25519", privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } })) }; await fs.writeFile(configPath, JSON.stringify(baseConfig)); const initialMemory = measureMemory(); // Act - repeated reloads for (let i = 0; i < 10; i++) { const modifiedConfig = { ...baseConfig, accounts: baseConfig.accounts.map(account => ({ ...account, name: `${account.name} - Reload ${i}` })) }; await fs.writeFile(configPath, JSON.stringify(modifiedConfig)); await credentialManager.loadConfig(configPath); // Perform some operations const message = new TextEncoder().encode(`Reload test ${i}`); await credentialManager.sign("leak-test-account-0", message); } const finalMemory = measureMemory(); const memoryIncrease = finalMemory - initialMemory; // Assert - should not accumulate significant memory expect(memoryIncrease).toBeLessThan(10); // Should be minimal increase }); }); describe('Scalability (50+ Accounts)', () => { test('should support exactly 50 accounts efficiently', async () => { // Arrange - exactly 50 accounts const fiftyAccountConfig: ConfigFile = { version: "1.0", accounts: Array(50).fill(null).map((_, index) => ({ id: `scalability-account-${index}`, platform: index % 3 === 0 ? Platform.PACIFICA : index % 3 === 1 ? Platform.ASTER : Platform.BINANCE, name: `Scalability Account ${index}`, credentials: index % 3 === 0 ? { type: "ed25519", privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } : index % 3 === 1 ? { type: "eip191", privateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" } : { type: "hmac", apiKey: `scalability-api-key-${index}`, secretKey: `scalability-secret-key-${index}` } })) }; // Act & Assert await fs.writeFile(configPath, JSON.stringify(fiftyAccountConfig)); const { result, duration } = await measureTime(() => credentialManager.loadConfig(configPath) ); expect(result.success).toBe(true); expect(result.accounts).toHaveLength(50); expect(duration).toBeLessThan(100); // Should still meet reload requirement // Verify all accounts are accessible const allAccounts = credentialManager.listAccounts(); expect(allAccounts).toHaveLength(50); // Test random access performance for (let i = 0; i < 10; i++) { const randomIndex = Math.floor(Math.random() * 50); const accountId = `scalability-account-${randomIndex}`; const account = credentialManager.getAccount(accountId); expect(account).not.toBeNull(); } }); test('should handle concurrent operations across all 50 accounts', async () => { // Arrange - 50 accounts const manyAccountsConfig: ConfigFile = { version: "1.0", accounts: Array(50).fill(null).map((_, index) => ({ id: `concurrent-account-${index}`, platform: Platform.PACIFICA, // Use single platform for simplicity name: `Concurrent Account ${index}`, credentials: { type: "ed25519", privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } })) }; await fs.writeFile(configPath, JSON.stringify(manyAccountsConfig)); await credentialManager.loadConfig(configPath); // Act - concurrent operations across all accounts const message = new TextEncoder().encode("Concurrent scalability test"); const concurrentOperations = Array(50).fill(null).map((_, index) => measureTime(() => credentialManager.sign(`concurrent-account-${index}`, message)) ); const startTime = Date.now(); const results = await Promise.all(concurrentOperations); const totalDuration = Date.now() - startTime; // Assert - all operations should succeed results.forEach(({ result, duration }, index) => { expect(result.success).toBe(true); expect(duration).toBeLessThan(50); // Individual operation requirement }); // Total time should scale reasonably expect(totalDuration).toBeLessThan(1000); // 50 operations in <1 second }); test('should maintain performance with over 50 accounts', async () => { // Arrange - 75 accounts (beyond minimum requirement) const manyAccountsConfig: ConfigFile = { version: "1.0", accounts: Array(75).fill(null).map((_, index) => ({ id: `beyond-fifty-account-${index}`, platform: index % 3 === 0 ? Platform.PACIFICA : index % 3 === 1 ? Platform.ASTER : Platform.BINANCE, name: `Beyond Fifty Account ${index}`, credentials: index % 3 === 0 ? { type: "ed25519", privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } : index % 3 === 1 ? { type: "eip191", privateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" } : { type: "hmac", apiKey: `beyond-api-key-${index}`, secretKey: `beyond-secret-key-${index}` } })) }; // Act await fs.writeFile(configPath, JSON.stringify(manyAccountsConfig)); const { result, duration } = await measureTime(() => credentialManager.loadConfig(configPath) ); // Assert - should handle gracefully even beyond minimum expect(result.success).toBe(true); expect(result.accounts).toHaveLength(75); // Performance may degrade slightly but should still be reasonable expect(duration).toBeLessThan(200); // Allow slightly more time for 75 accounts }); }); describe('High-Frequency Operations', () => { test('should handle high-frequency signing requests', async () => { // Arrange const config: ConfigFile = { version: "1.0", accounts: [ { id: "high-freq-account", platform: Platform.PACIFICA, name: "High Frequency Account", credentials: { type: "ed25519", privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } } ] }; await fs.writeFile(configPath, JSON.stringify(config)); await credentialManager.loadConfig(configPath); // Act - rapid-fire signing requests const operations: Promise[] = []; const startTime = Date.now(); for (let i = 0; i < 100; i++) { const message = new TextEncoder().encode(`High freq message ${i}`); operations.push(credentialManager.sign("high-freq-account", message)); } const results = await Promise.all(operations); const totalDuration = Date.now() - startTime; // Assert results.forEach((result, index) => { expect(result.success).toBe(true); }); // Should handle 100 operations efficiently const avgTimePerOperation = totalDuration / 100; expect(avgTimePerOperation).toBeLessThan(50); }); test('should handle mixed operation types efficiently', async () => { // Arrange const config: ConfigFile = { version: "1.0", accounts: Array(10).fill(null).map((_, index) => ({ id: `mixed-ops-account-${index}`, platform: Platform.PACIFICA, name: `Mixed Ops Account ${index}`, credentials: { type: "ed25519", privateKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } })) }; await fs.writeFile(configPath, JSON.stringify(config)); await credentialManager.loadConfig(configPath); // Act - mix of operations: sign, verify, getAccount, listAccounts const operations: Promise[] = []; const message = new TextEncoder().encode("Mixed operations test"); for (let i = 0; i < 50; i++) { const accountId = `mixed-ops-account-${i % 10}`; if (i % 4 === 0) { // Sign operation operations.push(credentialManager.sign(accountId, message)); } else if (i % 4 === 1) { // Get account operation operations.push(Promise.resolve(credentialManager.getAccount(accountId))); } else if (i % 4 === 2) { // List accounts operation operations.push(Promise.resolve(credentialManager.listAccounts())); } else { // Verify operation (with dummy signature) operations.push(credentialManager.verify(accountId, message, "dummy-signature")); } } const startTime = Date.now(); const results = await Promise.all(operations); const totalDuration = Date.now() - startTime; // Assert - should handle mixed operations efficiently expect(results).toHaveLength(50); expect(totalDuration).toBeLessThan(1000); // 50 mixed operations in <1 second }); }); });