123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633 |
- /**
- * 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)
- }
- })
- })
- })
|