package crypt import ( "bytes" "testing" "github.com/host-uk/core/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) }) }