/** * Unit Tests for BinanceSigner * * Tests the Binance signing strategy implementation in isolation. */ import { BinanceSigner } from '@/core/credential-manager/signers/BinanceSigner' import { Platform } from '@/types/credential' describe('BinanceSigner Unit Tests', () => { let signer: BinanceSigner beforeEach(() => { signer = new BinanceSigner() }) describe('Platform Properties', () => { test('should have correct platform identifier', () => { expect(signer.platform).toBe(Platform.BINANCE) }) test('should have correct algorithm identifier', () => { expect(signer.algorithm).toBe('hmac-sha256') }) }) describe('HMAC Signing Operations', () => { test('should sign message with valid credentials', async () => { const message = new TextEncoder().encode('test message') const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const signature = await signer.sign(message, credentials) expect(signature).toBeDefined() expect(typeof signature).toBe('string') expect(signature.length).toBe(64) // SHA256 hash is 32 bytes = 64 hex chars }) test('should produce deterministic signatures', async () => { const message = new TextEncoder().encode('deterministic test') const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const signature1 = await signer.sign(message, credentials) const signature2 = await signer.sign(message, credentials) expect(signature1).toBe(signature2) }) test('should produce different signatures for different messages', async () => { const message1 = new TextEncoder().encode('message one') const message2 = new TextEncoder().encode('message two') const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const signature1 = await signer.sign(message1, credentials) const signature2 = await signer.sign(message2, credentials) expect(signature1).not.toBe(signature2) }) test('should produce different signatures for different secret keys', async () => { const message = new TextEncoder().encode('same message') const credentials1 = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'secret-key-one' } const credentials2 = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'secret-key-two' } const signature1 = await signer.sign(message, credentials1) const signature2 = await signer.sign(message, credentials2) expect(signature1).not.toBe(signature2) }) test('should handle empty messages', async () => { const emptyMessage = new Uint8Array(0) const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const signature = await signer.sign(emptyMessage, credentials) expect(signature).toBeDefined() expect(typeof signature).toBe('string') expect(signature.length).toBe(64) }) test('should handle large messages', async () => { const largeMessage = new Uint8Array(1024 * 1024) // 1MB largeMessage.fill(42) const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const signature = await signer.sign(largeMessage, credentials) expect(signature).toBeDefined() expect(typeof signature).toBe('string') expect(signature.length).toBe(64) }) test('should handle special characters in keys', async () => { const message = new TextEncoder().encode('test message') const credentials = { type: 'hmac' as const, apiKey: 'api-key-with-special-chars!@#$%^&*()', secretKey: 'secret-key-with-unicode-δΈ–η•Œ-and-emojis-πŸš€' } const signature = await signer.sign(message, credentials) expect(signature).toBeDefined() expect(typeof signature).toBe('string') expect(signature.length).toBe(64) }) test('should throw error for invalid credentials', async () => { const message = new TextEncoder().encode('test message') const invalidCredentials = [ { type: 'hmac' as const, apiKey: '', // Empty API key secretKey: 'test-secret-key' }, { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: '' // Empty secret key }, { type: 'hmac' as const, apiKey: 'test-api-key' // Missing secret key }, { type: 'wrong-type' as any, apiKey: 'test-api-key', secretKey: 'test-secret-key' } ] for (const invalidCred of invalidCredentials) { await expect(signer.sign(message, invalidCred)) .rejects.toThrow() } }) }) describe('Verification Operations', () => { test('should verify valid signature', async () => { const message = new TextEncoder().encode('verification test') const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const signature = await signer.sign(message, credentials) // For HMAC, verification is just re-computing and comparing const isValid = await signer.verify(message, signature, credentials.secretKey) expect(isValid).toBe(true) }) test('should reject invalid signature', async () => { const message = new TextEncoder().encode('verification test') const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const signature = await signer.sign(message, credentials) // Modify signature to make it invalid const invalidSignature = signature.slice(0, -2) + '00' const isValid = await signer.verify(message, invalidSignature, credentials.secretKey) expect(isValid).toBe(false) }) test('should reject signature with wrong secret key', async () => { const message = new TextEncoder().encode('verification test') const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'correct-secret-key' } const signature = await signer.sign(message, credentials) const isValid = await signer.verify(message, signature, 'wrong-secret-key') expect(isValid).toBe(false) }) test('should reject signature with wrong message', async () => { const message1 = new TextEncoder().encode('original message') const message2 = new TextEncoder().encode('different message') const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const signature = await signer.sign(message1, credentials) const isValid = await signer.verify(message2, signature, credentials.secretKey) expect(isValid).toBe(false) }) test('should handle malformed signature gracefully', async () => { const message = new TextEncoder().encode('test message') const secretKey = 'test-secret-key' const malformedSignatures = [ '', // Empty 'invalid', // Not hex '12345', // Too short 'g'.repeat(64) // Invalid hex characters ] for (const malformedSig of malformedSignatures) { const isValid = await signer.verify(message, malformedSig, secretKey) expect(isValid).toBe(false) } }) }) describe('Binance-Specific Query String Signing', () => { test('should sign query parameters correctly', async () => { const queryParams = 'symbol=LTCBTC&side=BUY&type=LIMIT&timeInForce=GTC&quantity=1&price=0.1&recvWindow=5000×tamp=1499827319559' const message = new TextEncoder().encode(queryParams) const credentials = { type: 'hmac' as const, apiKey: 'vmPUZE6mv9SD5VNHk4HlWFsOr6aKE2zvsw0MuIgwCIPy6utIco14y7Ju91duEh8A', secretKey: 'NhqPtmdSJYdKjVHjA7PZj4Mge3R5YNiP1e3UZjInClVN65XAbvqqM6A7H5fATj0j' } const signature = await signer.sign(message, credentials) // This should match Binance's expected signature format expect(signature).toBeDefined() expect(signature.length).toBe(64) expect(/^[0-9a-f]{64}$/i.test(signature)).toBe(true) }) test('should handle URL encoding properly', async () => { const queryWithEncoding = 'symbol=BTC%2FUSDT&side=BUY&quantity=1.0×tamp=1635724800000' const message = new TextEncoder().encode(queryWithEncoding) const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const signature = await signer.sign(message, credentials) expect(signature).toBeDefined() expect(signature.length).toBe(64) }) test('should handle timestamp parameter correctly', async () => { const timestamp = Date.now() const queryWithTimestamp = `symbol=BTCUSDT&side=BUY&type=MARKET&quantity=0.001×tamp=${timestamp}` const message = new TextEncoder().encode(queryWithTimestamp) const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const signature = await signer.sign(message, credentials) expect(signature).toBeDefined() expect(signature.length).toBe(64) // Should be reproducible with same timestamp const signature2 = await signer.sign(message, credentials) expect(signature).toBe(signature2) }) }) describe('Performance Requirements', () => { test('should sign within performance requirements', async () => { const message = new TextEncoder().encode('performance test') const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const startTime = performance.now() await signer.sign(message, credentials) const duration = performance.now() - startTime expect(duration).toBeLessThan(25) // HMAC should be very fast }) test('should verify within performance requirements', async () => { const message = new TextEncoder().encode('verification performance test') const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const signature = await signer.sign(message, credentials) const startTime = performance.now() await signer.verify(message, signature, credentials.secretKey) const duration = performance.now() - startTime expect(duration).toBeLessThan(25) // HMAC verification should be very fast }) test('should handle concurrent operations efficiently', async () => { const message = new TextEncoder().encode('concurrent test') const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const startTime = performance.now() const promises = Array.from({ length: 20 }, () => signer.sign(message, credentials)) const signatures = await Promise.all(promises) const duration = performance.now() - startTime expect(signatures).toHaveLength(20) expect(signatures.every(sig => sig === signatures[0])).toBe(true) // Deterministic expect(duration).toBeLessThan(100) // 20 concurrent operations should be very fast }) test('should handle high-frequency trading scenarios', async () => { const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } // Simulate rapid order signing const startTime = performance.now() const signatures: string[] = [] for (let i = 0; i < 100; i++) { const timestamp = Date.now() + i const queryString = `symbol=BTCUSDT&side=BUY&type=LIMIT&quantity=0.001&price=50000×tamp=${timestamp}` const message = new TextEncoder().encode(queryString) const signature = await signer.sign(message, credentials) signatures.push(signature) } const duration = performance.now() - startTime expect(signatures).toHaveLength(100) expect(new Set(signatures).size).toBe(100) // All should be unique due to timestamps expect(duration).toBeLessThan(200) // 100 signatures in 200ms for HFT }) }) describe('Credential Validation', () => { test('should accept valid HMAC credentials', () => { const validCredentials = [ { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' }, { type: 'hmac' as const, apiKey: 'API_KEY_WITH_UNDERSCORES', secretKey: 'SECRET_KEY_WITH_UNDERSCORES' }, { type: 'hmac' as const, apiKey: 'api-key-with-dashes', secretKey: 'secret-key-with-dashes' }, { type: 'hmac' as const, apiKey: '1234567890', secretKey: 'abcdefghij' } ] validCredentials.forEach(creds => { expect(() => signer.validateCredentials(creds)).not.toThrow() }) }) test('should reject invalid credential types', () => { const invalidCredentials = [ { type: 'ed25519' as any, apiKey: 'test-api-key', secretKey: 'test-secret-key' }, { type: 'secp256k1' as any, privateKey: '0x1234567890abcdef' }, { type: 'rsa' as any, apiKey: 'test-api-key', secretKey: 'test-secret-key' } ] invalidCredentials.forEach(creds => { expect(() => signer.validateCredentials(creds)).toThrow() }) }) test('should reject credentials with missing fields', () => { const incompleteCredentials = [ { type: 'hmac' as const, apiKey: 'test-api-key' // Missing secretKey }, { type: 'hmac' as const, secretKey: 'test-secret-key' // Missing apiKey }, { type: 'hmac' as const, apiKey: '', secretKey: 'test-secret-key' }, { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: '' } ] incompleteCredentials.forEach(creds => { expect(() => signer.validateCredentials(creds)).toThrow() }) }) }) describe('Edge Cases', () => { test('should handle null and undefined inputs gracefully', async () => { const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } await expect(signer.sign(null as any, credentials)).rejects.toThrow() await expect(signer.sign(undefined as any, credentials)).rejects.toThrow() const message = new TextEncoder().encode('test') await expect(signer.sign(message, null as any)).rejects.toThrow() await expect(signer.sign(message, undefined as any)).rejects.toThrow() }) test('should handle very large messages efficiently', async () => { const megaMessage = new Uint8Array(10 * 1024 * 1024) // 10MB megaMessage.fill(123) const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const startTime = performance.now() const signature = await signer.sign(megaMessage, credentials) const duration = performance.now() - startTime expect(signature).toBeDefined() expect(duration).toBeLessThan(500) // HMAC should handle large messages efficiently }) test('should maintain consistency across multiple instances', async () => { const signer1 = new BinanceSigner() const signer2 = new BinanceSigner() const message = new TextEncoder().encode('consistency test') const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const signature1 = await signer1.sign(message, credentials) const signature2 = await signer2.sign(message, credentials) expect(signature1).toBe(signature2) }) test('should handle binary data in messages', async () => { const binaryMessage = new Uint8Array([0, 1, 2, 3, 255, 254, 253, 252]) const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } const signature = await signer.sign(binaryMessage, credentials) expect(signature).toBeDefined() expect(signature.length).toBe(64) expect(/^[0-9a-f]{64}$/i.test(signature)).toBe(true) }) test('should handle keys with various encodings', async () => { const message = new TextEncoder().encode('encoding test') const credentials = [ { type: 'hmac' as const, apiKey: 'base64-style-key==', secretKey: 'base64-style-secret==' }, { type: 'hmac' as const, apiKey: 'hex-style-key-123abc', secretKey: 'hex-style-secret-456def' }, { type: 'hmac' as const, apiKey: 'unicode-key-δΈ–η•Œ', secretKey: 'unicode-secret-🌍' } ] for (const creds of credentials) { const signature = await signer.sign(message, creds) expect(signature).toBeDefined() expect(signature.length).toBe(64) } }) }) describe('Binance API Compatibility', () => { test('should produce signatures compatible with Binance API format', async () => { // Test with realistic Binance API parameters const params = { symbol: 'BTCUSDT', side: 'BUY', type: 'LIMIT', timeInForce: 'GTC', quantity: '0.001', price: '50000.00', timestamp: '1635724800000', recvWindow: '5000' } const queryString = Object.entries(params) .sort(([a], [b]) => a.localeCompare(b)) // Binance requires sorted params .map(([key, value]) => `${key}=${value}`) .join('&') const message = new TextEncoder().encode(queryString) const credentials = { type: 'hmac' as const, apiKey: 'test-binance-api-key', secretKey: 'test-binance-secret-key' } const signature = await signer.sign(message, credentials) expect(signature).toBeDefined() expect(signature.length).toBe(64) expect(/^[0-9a-f]{64}$/i.test(signature)).toBe(true) // Signature should be verifiable const isValid = await signer.verify(message, signature, credentials.secretKey) expect(isValid).toBe(true) }) test('should handle order parameter variations', async () => { const orderTypes = [ 'symbol=ETHUSDT&side=BUY&type=MARKET&quantity=0.1', 'symbol=ADAUSDT&side=SELL&type=LIMIT&quantity=100&price=1.5', 'symbol=BNBUSDT&side=BUY&type=STOP_LOSS_LIMIT&quantity=10&price=400&stopPrice=390&timeInForce=GTC' ] const credentials = { type: 'hmac' as const, apiKey: 'test-api-key', secretKey: 'test-secret-key' } for (const orderParams of orderTypes) { const fullParams = `${orderParams}×tamp=${Date.now()}` const message = new TextEncoder().encode(fullParams) const signature = await signer.sign(message, credentials) expect(signature).toBeDefined() expect(signature.length).toBe(64) // Should be verifiable const isValid = await signer.verify(message, signature, credentials.secretKey) expect(isValid).toBe(true) } }) }) })