/** * Unit Tests for UnifiedSigner * * Tests the UnifiedSigner service implementation in isolation. */ import { UnifiedSigner, BatchSignRequest } from '@/core/credential-manager/UnifiedSigner' import { PacificaSigner } from '@/core/credential-manager/signers/PacificaSigner' import { AsterSigner } from '@/core/credential-manager/signers/AsterSigner' import { BinanceSigner } from '@/core/credential-manager/signers/BinanceSigner' import { Platform } from '@/types/credential' describe('UnifiedSigner Unit Tests', () => { let unifiedSigner: UnifiedSigner let mockGetAccount: jest.Mock beforeEach(() => { unifiedSigner = new UnifiedSigner() mockGetAccount = jest.fn() unifiedSigner.setAccountGetter(mockGetAccount) }) afterEach(() => { jest.clearAllMocks() }) describe('Initialization and Configuration', () => { test('should initialize with default strategies', () => { const supportedPlatforms = unifiedSigner.getSupportedPlatforms() expect(supportedPlatforms).toContain(Platform.PACIFICA) expect(supportedPlatforms).toContain(Platform.ASTER) expect(supportedPlatforms).toContain(Platform.BINANCE) expect(supportedPlatforms).toHaveLength(3) }) test('should allow registering custom strategies', () => { const customSigner = new PacificaSigner() const customPlatform = 'CUSTOM' as Platform // Mock the platform property for testing Object.defineProperty(customSigner, 'platform', { value: customPlatform, writable: false }) unifiedSigner.registerStrategy(customPlatform, customSigner) const supportedPlatforms = unifiedSigner.getSupportedPlatforms() expect(supportedPlatforms).toContain(customPlatform) }) test('should throw error for mismatched platform in strategy registration', () => { const pacificaSigner = new PacificaSigner() expect(() => { unifiedSigner.registerStrategy(Platform.ASTER, pacificaSigner) }).toThrow('Strategy platform mismatch') }) test('should throw error for invalid strategy registration parameters', () => { expect(() => { unifiedSigner.registerStrategy(null as any, null as any) }).toThrow('Platform and strategy are required') }) test('should require account getter to be set', async () => { const signerWithoutGetter = new UnifiedSigner() const message = new TextEncoder().encode('test') await expect(signerWithoutGetter.sign('test-account', message)) .rejects.toThrow('Account getter function not configured') }) }) describe('Signing Operations', () => { test('should sign with Pacifica account', async () => { const pacificaAccount = { id: 'pac-account', platform: Platform.PACIFICA, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } mockGetAccount.mockResolvedValue(pacificaAccount) const message = new TextEncoder().encode('test message') const result = await unifiedSigner.sign('pac-account', message) expect(result.success).toBe(true) expect(result.algorithm).toBe('ed25519') expect(result.metadata?.platform).toBe(Platform.PACIFICA) expect(mockGetAccount).toHaveBeenCalledWith('pac-account') }) test('should sign with Aster account', async () => { const asterAccount = { id: 'ast-account', platform: Platform.ASTER, credentials: { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } } mockGetAccount.mockResolvedValue(asterAccount) const message = new TextEncoder().encode('test message') const result = await unifiedSigner.sign('ast-account', message) expect(result.success).toBe(true) expect(result.algorithm).toBe('eip191') expect(result.metadata?.platform).toBe(Platform.ASTER) }) test('should sign with Binance account', async () => { const binanceAccount = { id: 'bnb-account', platform: Platform.BINANCE, credentials: { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } } mockGetAccount.mockResolvedValue(binanceAccount) const message = new TextEncoder().encode('test message') const result = await unifiedSigner.sign('bnb-account', message) expect(result.success).toBe(true) expect(result.algorithm).toBe('hmac-sha256') expect(result.metadata?.platform).toBe(Platform.BINANCE) }) test('should handle account not found error', async () => { mockGetAccount.mockResolvedValue(null) const message = new TextEncoder().encode('test message') const result = await unifiedSigner.sign('non-existent', message) expect(result.success).toBe(false) expect(result.error).toContain('Account not found') }) test('should handle platform detection failure', async () => { const accountWithoutPlatform = { id: 'invalid-account', credentials: { type: 'unknown' as any } } mockGetAccount.mockResolvedValue(accountWithoutPlatform) const message = new TextEncoder().encode('test message') const result = await unifiedSigner.sign('invalid-account', message) expect(result.success).toBe(false) expect(result.error).toContain('Cannot determine platform') }) test('should handle unsupported platform', async () => { const accountWithUnsupportedPlatform = { id: 'unsupported-account', platform: 'UNSUPPORTED' as Platform, credentials: { type: 'unknown' as any } } mockGetAccount.mockResolvedValue(accountWithUnsupportedPlatform) const message = new TextEncoder().encode('test message') const result = await unifiedSigner.sign('unsupported-account', message) expect(result.success).toBe(false) expect(result.error).toContain('No strategy available') }) test('should respect force platform option', async () => { const asterAccount = { id: 'ast-account', platform: Platform.ASTER, credentials: { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } } mockGetAccount.mockResolvedValue(asterAccount) const message = new TextEncoder().encode('test message') const result = await unifiedSigner.sign('ast-account', message, { forcePlatform: Platform.ASTER }) expect(result.success).toBe(true) expect(result.metadata?.platform).toBe(Platform.ASTER) }) }) describe('Verification Operations', () => { test('should verify Pacifica signatures', async () => { const pacificaAccount = { id: 'pac-account', platform: Platform.PACIFICA, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } mockGetAccount.mockResolvedValue(pacificaAccount) const message = new TextEncoder().encode('verification test') // First sign the message const signResult = await unifiedSigner.sign('pac-account', message) expect(signResult.success).toBe(true) // Then verify it const isValid = await unifiedSigner.verify( 'pac-account', message, signResult.signature! ) expect(isValid).toBe(true) }) test('should reject invalid signatures during verification', async () => { const pacificaAccount = { id: 'pac-account', platform: Platform.PACIFICA, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } mockGetAccount.mockResolvedValue(pacificaAccount) const message = new TextEncoder().encode('verification test') const invalidSignature = 'invalid-signature' const isValid = await unifiedSigner.verify('pac-account', message, invalidSignature) expect(isValid).toBe(false) }) test('should handle verification errors gracefully', async () => { mockGetAccount.mockResolvedValue(null) const message = new TextEncoder().encode('test') const signature = 'some-signature' const isValid = await unifiedSigner.verify('non-existent', message, signature) expect(isValid).toBe(false) }) }) describe('Batch Operations', () => { test('should handle batch signing requests', async () => { const pacificaAccount = { id: 'pac-account', platform: Platform.PACIFICA, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } const asterAccount = { id: 'ast-account', platform: Platform.ASTER, credentials: { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } } mockGetAccount .mockResolvedValueOnce(pacificaAccount) .mockResolvedValueOnce(asterAccount) const batchRequests: BatchSignRequest[] = [ { accountId: 'pac-account', message: new TextEncoder().encode('pacifica message') }, { accountId: 'ast-account', message: new TextEncoder().encode('aster message') } ] const results = await unifiedSigner.signBatch(batchRequests) expect(results).toHaveLength(2) expect(results[0].success).toBe(true) expect(results[0].accountId).toBe('pac-account') expect(results[1].success).toBe(true) expect(results[1].accountId).toBe('ast-account') }) test('should handle batch requests with failures', async () => { const validAccount = { id: 'valid-account', platform: Platform.PACIFICA, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } mockGetAccount .mockResolvedValueOnce(validAccount) .mockResolvedValueOnce(null) // Account not found const batchRequests: BatchSignRequest[] = [ { accountId: 'valid-account', message: new TextEncoder().encode('valid message') }, { accountId: 'invalid-account', message: new TextEncoder().encode('invalid message') } ] const results = await unifiedSigner.signBatch(batchRequests) expect(results).toHaveLength(2) expect(results[0].success).toBe(true) expect(results[0].accountId).toBe('valid-account') expect(results[1].success).toBe(false) expect(results[1].accountId).toBe('invalid-account') }) test('should maintain order in batch results', async () => { const account = { id: 'test-account', platform: Platform.PACIFICA, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } mockGetAccount.mockResolvedValue(account) const batchRequests: BatchSignRequest[] = [ { accountId: 'test-account', message: new TextEncoder().encode('message 1') }, { accountId: 'test-account', message: new TextEncoder().encode('message 2') }, { accountId: 'test-account', message: new TextEncoder().encode('message 3') } ] const results = await unifiedSigner.signBatch(batchRequests) expect(results).toHaveLength(3) expect(results[0].batchIndex).toBe(0) expect(results[1].batchIndex).toBe(1) expect(results[2].batchIndex).toBe(2) }) test('should group requests by platform for optimal processing', async () => { const pacificaAccount = { id: 'pac-account', platform: Platform.PACIFICA, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } mockGetAccount.mockResolvedValue(pacificaAccount) const batchRequests: BatchSignRequest[] = [ { accountId: 'pac-account', message: new TextEncoder().encode('message 1'), options: { forcePlatform: Platform.PACIFICA } }, { accountId: 'pac-account', message: new TextEncoder().encode('message 2'), options: { forcePlatform: Platform.PACIFICA } } ] const results = await unifiedSigner.signBatch(batchRequests) expect(results).toHaveLength(2) expect(results.every(r => r.success)).toBe(true) }) }) describe('Performance Metrics', () => { test('should collect and provide metrics', async () => { const account = { id: 'test-account', platform: Platform.PACIFICA, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } mockGetAccount.mockResolvedValue(account) // Reset metrics unifiedSigner.resetMetrics() const message = new TextEncoder().encode('metrics test') // Perform some operations await unifiedSigner.sign('test-account', message, { collectMetrics: true }) await unifiedSigner.sign('test-account', message, { collectMetrics: true }) const metrics = unifiedSigner.getMetrics() expect(metrics.totalOperations).toBe(2) expect(metrics.successfulOperations).toBe(2) expect(metrics.failedOperations).toBe(0) expect(metrics.averageSigningTime).toBeGreaterThan(0) expect(metrics.platformBreakdown[Platform.PACIFICA].operations).toBe(2) expect(metrics.platformBreakdown[Platform.PACIFICA].successRate).toBe(1) }) test('should track failures in metrics', async () => { mockGetAccount.mockResolvedValue(null) // Always fail unifiedSigner.resetMetrics() const message = new TextEncoder().encode('failure test') // Perform failing operations await unifiedSigner.sign('non-existent', message, { collectMetrics: true }) await unifiedSigner.sign('non-existent', message, { collectMetrics: true }) const metrics = unifiedSigner.getMetrics() expect(metrics.totalOperations).toBe(2) expect(metrics.successfulOperations).toBe(0) expect(metrics.failedOperations).toBe(2) }) test('should reset metrics correctly', () => { unifiedSigner.resetMetrics() const metrics = unifiedSigner.getMetrics() expect(metrics.totalOperations).toBe(0) expect(metrics.successfulOperations).toBe(0) expect(metrics.failedOperations).toBe(0) expect(metrics.averageSigningTime).toBe(0) expect(metrics.lastResetAt).toBeInstanceOf(Date) }) test('should calculate platform-specific metrics correctly', async () => { const pacificaAccount = { id: 'pac-account', platform: Platform.PACIFICA, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } const asterAccount = { id: 'ast-account', platform: Platform.ASTER, credentials: { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } } mockGetAccount .mockResolvedValueOnce(pacificaAccount) .mockResolvedValueOnce(asterAccount) .mockResolvedValueOnce(null) // Failure unifiedSigner.resetMetrics() const message = new TextEncoder().encode('platform metrics test') await unifiedSigner.sign('pac-account', message, { collectMetrics: true }) await unifiedSigner.sign('ast-account', message, { collectMetrics: true }) await unifiedSigner.sign('non-existent', message, { collectMetrics: true }) const metrics = unifiedSigner.getMetrics() expect(metrics.platformBreakdown[Platform.PACIFICA].operations).toBe(1) expect(metrics.platformBreakdown[Platform.PACIFICA].successRate).toBe(1) expect(metrics.platformBreakdown[Platform.ASTER].operations).toBe(1) expect(metrics.platformBreakdown[Platform.ASTER].successRate).toBe(1) }) }) describe('Timeout Handling', () => { test('should respect custom timeout settings', async () => { const slowAccount = { id: 'slow-account', platform: Platform.PACIFICA, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } // Mock a slow account getter mockGetAccount.mockImplementation(() => new Promise(resolve => setTimeout(() => resolve(slowAccount), 100)) ) const message = new TextEncoder().encode('timeout test') // Test with very short timeout const result = await unifiedSigner.sign('slow-account', message, { timeout: 50 // 50ms timeout, but getter takes 100ms }) expect(result.success).toBe(false) expect(result.error).toContain('timed out') }) test('should use default timeout when not specified', async () => { const account = { id: 'normal-account', platform: Platform.PACIFICA, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } mockGetAccount.mockResolvedValue(account) const message = new TextEncoder().encode('default timeout test') const result = await unifiedSigner.sign('normal-account', message) expect(result.success).toBe(true) // Should succeed with default timeout }) }) describe('Error Handling', () => { test('should handle account getter errors gracefully', async () => { mockGetAccount.mockRejectedValue(new Error('Database connection failed')) const message = new TextEncoder().encode('error test') const result = await unifiedSigner.sign('test-account', message) expect(result.success).toBe(false) expect(result.error).toContain('Database connection failed') }) test('should handle signing errors gracefully', async () => { const invalidAccount = { id: 'invalid-account', platform: Platform.PACIFICA, credentials: { type: 'ed25519' as const, privateKey: 'invalid-key' // This will cause signing to fail } } mockGetAccount.mockResolvedValue(invalidAccount) const message = new TextEncoder().encode('signing error test') const result = await unifiedSigner.sign('invalid-account', message) expect(result.success).toBe(false) expect(result.error).toBeDefined() }) test('should provide meaningful error messages', async () => { const testCases = [ { mockReturn: null, expectedError: 'Account not found' }, { mockReturn: { id: 'test', credentials: {} }, expectedError: 'Cannot determine platform' } ] for (const testCase of testCases) { mockGetAccount.mockResolvedValueOnce(testCase.mockReturn) const message = new TextEncoder().encode('error message test') const result = await unifiedSigner.sign('test-account', message) expect(result.success).toBe(false) expect(result.error).toContain(testCase.expectedError) } }) }) describe('Integration with Platform Detectors', () => { test('should use platform detector when no platform specified', async () => { const pacificaAccount = { id: 'auto-detect-account', credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } mockGetAccount.mockResolvedValue(pacificaAccount) const message = new TextEncoder().encode('auto-detect test') const result = await unifiedSigner.sign('auto-detect-account', message) expect(result.success).toBe(true) expect(result.metadata?.platform).toBe(Platform.PACIFICA) }) test('should handle platform detection failures', async () => { const unknownAccount = { id: 'unknown-account', credentials: { type: 'unknown-type' as any, privateKey: 'some-key' } } mockGetAccount.mockResolvedValue(unknownAccount) const message = new TextEncoder().encode('detection failure test') const result = await unifiedSigner.sign('unknown-account', message) expect(result.success).toBe(false) expect(result.error).toContain('Cannot determine platform') }) }) })