|
|
@@ -0,0 +1,776 @@
|
|
|
+/**
|
|
|
+ * 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 <T>(operation: () => Promise<T>): 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<never>((_, 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<void>((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<SignResult>[] = [];
|
|
|
+ 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<any>[] = [];
|
|
|
+ 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
|
|
|
+ });
|
|
|
+ });
|
|
|
+});
|