gui/pkg/crypt/crypt_test.go

520 lines
13 KiB
Go
Raw Permalink Normal View History

package crypt
import (
"bytes"
"testing"
"forge.lthn.ai/core/gui/pkg/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// --- Constructor Tests ---
func TestNew(t *testing.T) {
t.Run("creates service successfully", func(t *testing.T) {
service, err := New()
assert.NoError(t, err)
assert.NotNil(t, service)
})
t.Run("returns independent instances", func(t *testing.T) {
service1, err1 := New()
service2, err2 := New()
assert.NoError(t, err1)
assert.NoError(t, err2)
assert.NotSame(t, service1, service2)
})
}
func TestRegister(t *testing.T) {
t.Run("registers with core successfully", func(t *testing.T) {
coreInstance, err := core.New()
require.NoError(t, err)
service, err := Register(coreInstance)
require.NoError(t, err)
assert.NotNil(t, service)
})
t.Run("returns Service type with ServiceRuntime", func(t *testing.T) {
coreInstance, err := core.New()
require.NoError(t, err)
service, err := Register(coreInstance)
require.NoError(t, err)
cryptService, ok := service.(*Service)
assert.True(t, ok)
assert.NotNil(t, cryptService.ServiceRuntime)
})
}
// --- Hash Tests ---
func TestHash(t *testing.T) {
s, _ := New()
t.Run("LTHN hash", func(t *testing.T) {
hash := s.Hash(LTHN, "hello")
assert.NotEmpty(t, hash)
// LTHN hash should be consistent
hash2 := s.Hash(LTHN, "hello")
assert.Equal(t, hash, hash2)
})
t.Run("SHA512 hash", func(t *testing.T) {
hash := s.Hash(SHA512, "hello")
// Known SHA512 hash for "hello"
expected := "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
assert.Equal(t, expected, hash)
})
t.Run("SHA256 hash", func(t *testing.T) {
hash := s.Hash(SHA256, "hello")
// Known SHA256 hash for "hello"
expected := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
assert.Equal(t, expected, hash)
})
t.Run("SHA1 hash", func(t *testing.T) {
hash := s.Hash(SHA1, "hello")
// Known SHA1 hash for "hello"
expected := "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"
assert.Equal(t, expected, hash)
})
t.Run("MD5 hash", func(t *testing.T) {
hash := s.Hash(MD5, "hello")
// Known MD5 hash for "hello"
expected := "5d41402abc4b2a76b9719d911017c592"
assert.Equal(t, expected, hash)
})
t.Run("default falls back to SHA256", func(t *testing.T) {
hash := s.Hash("unknown", "hello")
sha256Hash := s.Hash(SHA256, "hello")
assert.Equal(t, sha256Hash, hash)
})
t.Run("empty string hash", func(t *testing.T) {
hash := s.Hash(SHA256, "")
// Known SHA256 hash for empty string
expected := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
assert.Equal(t, expected, hash)
})
t.Run("hash with special characters", func(t *testing.T) {
hash := s.Hash(SHA256, "hello!@#$%^&*()")
assert.NotEmpty(t, hash)
assert.Len(t, hash, 64) // SHA256 produces 64 hex chars
})
t.Run("hash with unicode", func(t *testing.T) {
hash := s.Hash(SHA256, "你好世界")
assert.NotEmpty(t, hash)
assert.Len(t, hash, 64)
})
t.Run("hash consistency", func(t *testing.T) {
payload := "test payload for consistency check"
for _, hashType := range []HashType{LTHN, SHA512, SHA256, SHA1, MD5} {
hash1 := s.Hash(hashType, payload)
hash2 := s.Hash(hashType, payload)
assert.Equal(t, hash1, hash2, "Hash should be consistent for %s", hashType)
}
})
}
// --- Luhn Tests ---
func TestLuhn(t *testing.T) {
s, _ := New()
t.Run("valid Luhn numbers", func(t *testing.T) {
validNumbers := []string{
"79927398713",
"4532015112830366", // Visa test number
"6011514433546201", // Discover test number
"371449635398431", // Amex test number
"30569309025904", // Diners Club test number
}
for _, num := range validNumbers {
assert.True(t, s.Luhn(num), "Expected %s to be valid", num)
}
})
t.Run("invalid Luhn numbers", func(t *testing.T) {
invalidNumbers := []string{
"79927398714",
"1234567890",
"1111111111",
"1234567891",
}
for _, num := range invalidNumbers {
assert.False(t, s.Luhn(num), "Expected %s to be invalid", num)
}
})
t.Run("all zeros is valid", func(t *testing.T) {
// All zeros: each digit contributes 0, sum=0, 0%10==0
assert.True(t, s.Luhn("0000000000"))
})
t.Run("handles spaces", func(t *testing.T) {
// Same number with and without spaces should give same result
assert.True(t, s.Luhn("7992 7398 713"))
assert.True(t, s.Luhn("4532 0151 1283 0366"))
})
t.Run("non-digit characters return false", func(t *testing.T) {
assert.False(t, s.Luhn("1234abcd5678"))
assert.False(t, s.Luhn("12-34-56-78"))
assert.False(t, s.Luhn("1234.5678"))
})
t.Run("empty string", func(t *testing.T) {
// Enchantrix treats empty string as invalid
assert.False(t, s.Luhn(""))
})
t.Run("single digit", func(t *testing.T) {
// Enchantrix requires minimum length for valid Luhn
assert.False(t, s.Luhn("0"))
assert.False(t, s.Luhn("1"))
})
}
// --- Fletcher Checksum Tests ---
func TestFletcher16(t *testing.T) {
s, _ := New()
t.Run("basic checksum", func(t *testing.T) {
checksum := s.Fletcher16("hello")
assert.NotZero(t, checksum)
})
t.Run("empty string", func(t *testing.T) {
checksum := s.Fletcher16("")
assert.Equal(t, uint16(0), checksum)
})
t.Run("consistency", func(t *testing.T) {
checksum1 := s.Fletcher16("test data")
checksum2 := s.Fletcher16("test data")
assert.Equal(t, checksum1, checksum2)
})
t.Run("different inputs produce different checksums", func(t *testing.T) {
checksum1 := s.Fletcher16("hello")
checksum2 := s.Fletcher16("world")
assert.NotEqual(t, checksum1, checksum2)
})
t.Run("known value", func(t *testing.T) {
// "abcde" has a known Fletcher-16 checksum
checksum := s.Fletcher16("abcde")
assert.NotZero(t, checksum)
})
}
func TestFletcher32(t *testing.T) {
s, _ := New()
t.Run("basic checksum", func(t *testing.T) {
checksum := s.Fletcher32("hello")
assert.NotZero(t, checksum)
})
t.Run("empty string", func(t *testing.T) {
checksum := s.Fletcher32("")
assert.Equal(t, uint32(0), checksum)
})
t.Run("consistency", func(t *testing.T) {
checksum1 := s.Fletcher32("test data")
checksum2 := s.Fletcher32("test data")
assert.Equal(t, checksum1, checksum2)
})
t.Run("different inputs produce different checksums", func(t *testing.T) {
checksum1 := s.Fletcher32("hello")
checksum2 := s.Fletcher32("world")
assert.NotEqual(t, checksum1, checksum2)
})
t.Run("handles odd-length input", func(t *testing.T) {
// Odd length input should be padded
checksum := s.Fletcher32("abc")
assert.NotZero(t, checksum)
})
t.Run("handles even-length input", func(t *testing.T) {
checksum := s.Fletcher32("abcd")
assert.NotZero(t, checksum)
})
}
func TestFletcher64(t *testing.T) {
s, _ := New()
t.Run("basic checksum", func(t *testing.T) {
checksum := s.Fletcher64("hello")
assert.NotZero(t, checksum)
})
t.Run("empty string", func(t *testing.T) {
checksum := s.Fletcher64("")
assert.Equal(t, uint64(0), checksum)
})
t.Run("consistency", func(t *testing.T) {
checksum1 := s.Fletcher64("test data")
checksum2 := s.Fletcher64("test data")
assert.Equal(t, checksum1, checksum2)
})
t.Run("different inputs produce different checksums", func(t *testing.T) {
checksum1 := s.Fletcher64("hello")
checksum2 := s.Fletcher64("world")
assert.NotEqual(t, checksum1, checksum2)
})
t.Run("handles various input lengths", func(t *testing.T) {
// Test padding for different lengths
for i := 1; i <= 8; i++ {
input := string(make([]byte, i))
checksum := s.Fletcher64(input)
// Just verify it doesn't panic
_ = checksum
}
})
t.Run("long input", func(t *testing.T) {
// Use actual text content, not null bytes
longInput := ""
for i := 0; i < 100; i++ {
longInput += "test data "
}
checksum := s.Fletcher64(longInput)
assert.NotZero(t, checksum)
})
}
// --- HashType Constants Tests ---
func TestHashTypeConstants(t *testing.T) {
t.Run("constants have expected values", func(t *testing.T) {
assert.Equal(t, HashType("lthn"), LTHN)
assert.Equal(t, HashType("sha512"), SHA512)
assert.Equal(t, HashType("sha256"), SHA256)
assert.Equal(t, HashType("sha1"), SHA1)
assert.Equal(t, HashType("md5"), MD5)
})
}
// --- IsHashAlgo Tests ---
func TestIsHashAlgo(t *testing.T) {
s, _ := New()
t.Run("valid hash algorithms", func(t *testing.T) {
assert.True(t, s.IsHashAlgo("sha256"))
assert.True(t, s.IsHashAlgo("sha512"))
assert.True(t, s.IsHashAlgo("sha1"))
assert.True(t, s.IsHashAlgo("md5"))
})
t.Run("invalid hash algorithm", func(t *testing.T) {
assert.False(t, s.IsHashAlgo("invalid"))
assert.False(t, s.IsHashAlgo(""))
})
}
// --- RSA Tests ---
func TestGenerateRSAKeyPair(t *testing.T) {
s, _ := New()
t.Run("generates valid key pair", func(t *testing.T) {
pubKey, privKey, err := s.GenerateRSAKeyPair(2048)
require.NoError(t, err)
assert.NotEmpty(t, pubKey)
assert.NotEmpty(t, privKey)
assert.Contains(t, pubKey, "PUBLIC KEY")
assert.Contains(t, privKey, "PRIVATE KEY")
})
}
func TestEncryptDecryptRSA(t *testing.T) {
s, _ := New()
t.Run("encrypt and decrypt roundtrip", func(t *testing.T) {
pubKey, privKey, err := s.GenerateRSAKeyPair(2048)
require.NoError(t, err)
plaintext := "hello RSA world"
ciphertext, err := s.EncryptRSA(pubKey, plaintext)
require.NoError(t, err)
assert.NotEmpty(t, ciphertext)
assert.NotEqual(t, plaintext, ciphertext)
decrypted, err := s.DecryptRSA(privKey, ciphertext)
require.NoError(t, err)
assert.Equal(t, plaintext, decrypted)
})
t.Run("encrypt with invalid key fails", func(t *testing.T) {
_, err := s.EncryptRSA("invalid key", "data")
assert.Error(t, err)
})
t.Run("decrypt with invalid key fails", func(t *testing.T) {
_, err := s.DecryptRSA("invalid key", "data")
assert.Error(t, err)
})
}
// --- PGP Tests ---
func TestGeneratePGPKeyPair(t *testing.T) {
s, _ := New()
t.Run("generates valid key pair", func(t *testing.T) {
pubKey, privKey, err := s.GeneratePGPKeyPair("Test User", "test@example.com", "test comment")
require.NoError(t, err)
assert.NotEmpty(t, pubKey)
assert.NotEmpty(t, privKey)
assert.Contains(t, pubKey, "PGP PUBLIC KEY")
assert.Contains(t, privKey, "PGP PRIVATE KEY")
})
}
func TestEncryptPGP(t *testing.T) {
s, _ := New()
t.Run("requires valid key", func(t *testing.T) {
var buf bytes.Buffer
err := s.EncryptPGP(&buf, "invalid key content", "test data")
assert.Error(t, err)
})
t.Run("encrypts with valid key", func(t *testing.T) {
pubKey, _, err := s.GeneratePGPKeyPair("Test", "test@test.com", "comment")
require.NoError(t, err)
var buf bytes.Buffer
err = s.EncryptPGP(&buf, pubKey, "test data")
require.NoError(t, err)
assert.NotEmpty(t, buf.String())
})
}
func TestEncryptPGPToString(t *testing.T) {
s, _ := New()
t.Run("encrypts to string", func(t *testing.T) {
pubKey, _, err := s.GeneratePGPKeyPair("Test", "test@test.com", "comment")
require.NoError(t, err)
ciphertext, err := s.EncryptPGPToString(pubKey, "test data")
require.NoError(t, err)
assert.NotEmpty(t, ciphertext)
})
t.Run("requires valid key", func(t *testing.T) {
_, err := s.EncryptPGPToString("invalid key", "data")
assert.Error(t, err)
})
}
func TestDecryptPGP(t *testing.T) {
s, _ := New()
t.Run("requires valid key", func(t *testing.T) {
_, err := s.DecryptPGP("invalid key content", "encrypted data")
assert.Error(t, err)
})
t.Run("decrypts with valid key", func(t *testing.T) {
pubKey, privKey, err := s.GeneratePGPKeyPair("Test", "test@test.com", "comment")
require.NoError(t, err)
plaintext := "secret message"
ciphertext, err := s.EncryptPGPToString(pubKey, plaintext)
require.NoError(t, err)
decrypted, err := s.DecryptPGP(privKey, ciphertext)
require.NoError(t, err)
assert.Equal(t, plaintext, decrypted)
})
}
func TestSignAndVerifyPGP(t *testing.T) {
s, _ := New()
t.Run("sign and verify roundtrip", func(t *testing.T) {
pubKey, privKey, err := s.GeneratePGPKeyPair("Test", "test@test.com", "comment")
require.NoError(t, err)
data := "data to sign"
signature, err := s.SignPGP(privKey, data)
require.NoError(t, err)
assert.NotEmpty(t, signature)
err = s.VerifyPGP(pubKey, data, signature)
assert.NoError(t, err)
})
t.Run("sign with invalid key fails", func(t *testing.T) {
_, err := s.SignPGP("invalid key", "data")
assert.Error(t, err)
})
t.Run("verify with invalid key fails", func(t *testing.T) {
err := s.VerifyPGP("invalid key", "data", "signature")
assert.Error(t, err)
})
}
func TestSymmetricallyEncryptPGP(t *testing.T) {
s, _ := New()
t.Run("encrypts with passphrase", func(t *testing.T) {
var buf bytes.Buffer
err := s.SymmetricallyEncryptPGP(&buf, "secret data", "my passphrase")
require.NoError(t, err)
assert.NotEmpty(t, buf.String())
})
}
// --- HandleIPCEvents Tests ---
func TestHandleIPCEvents(t *testing.T) {
t.Run("handles ActionServiceStartup", func(t *testing.T) {
coreInstance, err := core.New()
require.NoError(t, err)
serviceAny, err := Register(coreInstance)
require.NoError(t, err)
s := serviceAny.(*Service)
err = s.HandleIPCEvents(coreInstance, core.ActionServiceStartup{})
assert.NoError(t, err)
})
t.Run("handles unknown message type", func(t *testing.T) {
coreInstance, err := core.New()
require.NoError(t, err)
serviceAny, err := Register(coreInstance)
require.NoError(t, err)
s := serviceAny.(*Service)
// Pass an arbitrary type as unknown message
err = s.HandleIPCEvents(coreInstance, "unknown message")
assert.NoError(t, err)
})
}