package chain import ( "encoding/hex" "testing" tocrypto "github.com/tolelom/tolchain/crypto" ) // testEncKey returns a valid 32-byte AES-256 key for testing. func testEncKey() []byte { key := make([]byte, 32) for i := range key { key[i] = byte(i) } return key } // newTestService creates a minimal Service with only the encryption key set. // No DB, Redis, or chain client — only suitable for testing pure crypto functions. func newTestService() *Service { return &Service{ encKeyBytes: testEncKey(), } } func TestEncryptDecryptRoundTrip(t *testing.T) { svc := newTestService() // Generate a real ed25519 private key privKey, _, err := tocrypto.GenerateKeyPair() if err != nil { t.Fatalf("failed to generate key pair: %v", err) } // Encrypt cipherHex, nonceHex, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey)) if err != nil { t.Fatalf("encryptPrivKey failed: %v", err) } if cipherHex == "" || nonceHex == "" { t.Fatal("encryptPrivKey returned empty strings") } // Verify ciphertext is valid hex if _, err := hex.DecodeString(cipherHex); err != nil { t.Errorf("cipherHex is not valid hex: %v", err) } if _, err := hex.DecodeString(nonceHex); err != nil { t.Errorf("nonceHex is not valid hex: %v", err) } // Decrypt decrypted, err := svc.decryptPrivKey(cipherHex, nonceHex) if err != nil { t.Fatalf("decryptPrivKey failed: %v", err) } // Compare if hex.EncodeToString(decrypted) != hex.EncodeToString(privKey) { t.Error("decrypted key does not match original") } } func TestEncryptDecrypt_DifferentKeysProduceDifferentCiphertext(t *testing.T) { svc := newTestService() privKey1, _, err := tocrypto.GenerateKeyPair() if err != nil { t.Fatalf("failed to generate key pair 1: %v", err) } privKey2, _, err := tocrypto.GenerateKeyPair() if err != nil { t.Fatalf("failed to generate key pair 2: %v", err) } cipher1, _, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey1)) if err != nil { t.Fatalf("encryptPrivKey 1 failed: %v", err) } cipher2, _, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey2)) if err != nil { t.Fatalf("encryptPrivKey 2 failed: %v", err) } if cipher1 == cipher2 { t.Error("different private keys should produce different ciphertexts") } } func TestEncryptSameKey_DifferentNonces(t *testing.T) { svc := newTestService() privKey, _, err := tocrypto.GenerateKeyPair() if err != nil { t.Fatalf("failed to generate key pair: %v", err) } cipher1, nonce1, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey)) if err != nil { t.Fatalf("encryptPrivKey 1 failed: %v", err) } cipher2, nonce2, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey)) if err != nil { t.Fatalf("encryptPrivKey 2 failed: %v", err) } // Each encryption should use a different random nonce if nonce1 == nonce2 { t.Error("encrypting the same key twice should use different nonces") } // So ciphertext should also differ (AES-GCM is nonce-dependent) if cipher1 == cipher2 { t.Error("encrypting the same key with different nonces should produce different ciphertexts") } } func TestDecryptWithWrongKey(t *testing.T) { svc := newTestService() privKey, _, err := tocrypto.GenerateKeyPair() if err != nil { t.Fatalf("failed to generate key pair: %v", err) } cipherHex, nonceHex, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey)) if err != nil { t.Fatalf("encryptPrivKey failed: %v", err) } // Create a service with a different encryption key wrongKey := make([]byte, 32) for i := range wrongKey { wrongKey[i] = byte(255 - i) } wrongSvc := &Service{encKeyBytes: wrongKey} _, err = wrongSvc.decryptPrivKey(cipherHex, nonceHex) if err == nil { t.Error("decryptPrivKey with wrong key should fail") } } func TestDecryptWithInvalidHex(t *testing.T) { svc := newTestService() _, err := svc.decryptPrivKey("not-hex", "also-not-hex") if err == nil { t.Error("decryptPrivKey with invalid hex should fail") } } func TestDecryptWithTamperedCiphertext(t *testing.T) { svc := newTestService() privKey, _, err := tocrypto.GenerateKeyPair() if err != nil { t.Fatalf("failed to generate key pair: %v", err) } cipherHex, nonceHex, err := svc.encryptPrivKey(tocrypto.PrivateKey(privKey)) if err != nil { t.Fatalf("encryptPrivKey failed: %v", err) } // Tamper with the ciphertext by flipping a byte cipherBytes, _ := hex.DecodeString(cipherHex) cipherBytes[0] ^= 0xFF tamperedHex := hex.EncodeToString(cipherBytes) _, err = svc.decryptPrivKey(tamperedHex, nonceHex) if err == nil { t.Error("decryptPrivKey with tampered ciphertext should fail") } } func TestNewService_InvalidEncryptionKey(t *testing.T) { tests := []struct { name string encKey string }{ {"too short", "aabbccdd"}, {"not hex", "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"}, {"empty", ""}, {"odd length", "aabbccddeeff00112233445566778899aabbccddeeff0011223344556677889"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := NewService(nil, nil, "test-chain", "", tt.encKey) if err == nil { t.Error("NewService should fail with invalid encryption key") } }) } } func TestNewService_ValidEncryptionKey(t *testing.T) { // 64 hex chars = 32 bytes validKey := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" svc, err := NewService(nil, nil, "test-chain", "", validKey) if err != nil { t.Fatalf("NewService with valid key should succeed: %v", err) } if svc == nil { t.Fatal("NewService returned nil service") } if svc.chainID != "test-chain" { t.Errorf("chainID = %q, want %q", svc.chainID, "test-chain") } // No operator key provided, so operatorWallet should be nil if svc.operatorWallet != nil { t.Error("operatorWallet should be nil when no operator key is provided") } } func TestEnsureOperator_NilWallet(t *testing.T) { svc := newTestService() err := svc.ensureOperator() if err == nil { t.Error("ensureOperator should fail when operatorWallet is nil") } } func TestResolveUsername_NoResolver(t *testing.T) { svc := newTestService() _, err := svc.resolveUsername("testuser") if err == nil { t.Error("resolveUsername should fail when userResolver is nil") } } func TestClassifyTxError(t *testing.T) { tests := []struct { chainMsg string want string }{ {"insufficient balance: have 0 need 100: insufficient balance", "잔액이 부족합니다"}, {"only the asset owner can list it: unauthorized", "권한이 없습니다"}, {"session \"abc\" already exists: already exists", "이미 존재합니다"}, {"asset \"xyz\" not found: not found", "리소스를 찾을 수 없습니다"}, {"asset is not tradeable", "거래할 수 없는 아이템입니다"}, {"asset \"a\" is equipped; unequip it before listing", "장착 중인 아이템입니다"}, {"asset \"a\" is already listed (listing x): already exists", "이미 마켓에 등록된 아이템입니다"}, {"listing \"x\" is not active", "활성 상태가 아닌 매물입니다"}, {"session \"x\" is not open (status=closed)", "진행 중이 아닌 세션입니다"}, {"invalid nonce: expected 5 got 3: invalid nonce", "트랜잭션 처리 중 오류가 발생했습니다. 다시 시도해주세요"}, {"some unknown error", "블록체인 트랜잭션이 실패했습니다"}, } for _, tt := range tests { t.Run(tt.chainMsg, func(t *testing.T) { got := classifyTxError(tt.chainMsg) if got != tt.want { t.Errorf("classifyTxError(%q) = %q, want %q", tt.chainMsg, got, tt.want) } }) } } func TestTxError_Error(t *testing.T) { err := &TxError{TxID: "abc123", Message: "insufficient balance"} got := err.Error() want := "transaction abc123 failed: insufficient balance" if got != want { t.Errorf("TxError.Error() = %q, want %q", got, want) } }