diff --git a/pkg/enchantrix/enchantrix_test.go b/pkg/enchantrix/enchantrix_test.go deleted file mode 100644 index 71b5901..0000000 --- a/pkg/enchantrix/enchantrix_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package enchantrix_test - -import ( - "testing" - - "github.com/Snider/Enchantrix/pkg/enchantrix" - "github.com/stretchr/testify/assert" -) - -func TestTransmute(t *testing.T) { - data := []byte("hello") - sigils := []enchantrix.Sigil{ - &enchantrix.ReverseSigil{}, - &enchantrix.HexSigil{}, - } - result, err := enchantrix.Transmute(data, sigils) - assert.NoError(t, err) - assert.Equal(t, "6f6c6c6568", string(result)) -} diff --git a/pkg/enchantrix/factory_test.go b/pkg/enchantrix/factory_test.go deleted file mode 100644 index 15c7352..0000000 --- a/pkg/enchantrix/factory_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package enchantrix_test - -import ( - "testing" - - "github.com/Snider/Enchantrix/pkg/enchantrix" - "github.com/stretchr/testify/assert" -) - -func TestNewSigil(t *testing.T) { - t.Run("ValidSigils", func(t *testing.T) { - validNames := []string{ - "reverse", "hex", "base64", "gzip", "json", "json-indent", - "md4", "md5", "sha1", "sha224", "sha256", "sha384", "sha512", - "ripemd160", "sha3-224", "sha3-256", "sha3-384", "sha3-512", - "sha512-224", "sha512-256", "blake2s-256", "blake2b-256", - "blake2b-384", "blake2b-512", - } - for _, name := range validNames { - sigil, err := enchantrix.NewSigil(name) - assert.NoError(t, err) - assert.NotNil(t, sigil) - } - }) - - t.Run("InvalidSigil", func(t *testing.T) { - sigil, err := enchantrix.NewSigil("invalid-sigil-name") - assert.Error(t, err) - assert.Nil(t, sigil) - assert.Contains(t, err.Error(), "unknown sigil name") - }) -} diff --git a/pkg/enchantrix/sigils_test.go b/pkg/enchantrix/sigils_test.go deleted file mode 100644 index ad9715e..0000000 --- a/pkg/enchantrix/sigils_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package enchantrix_test - -import ( - "crypto" - "encoding/hex" - "testing" - - "github.com/Snider/Enchantrix/pkg/enchantrix" - "github.com/stretchr/testify/assert" -) - -func TestReverseSigil(t *testing.T) { - s := &enchantrix.ReverseSigil{} - data := []byte("hello") - reversed, err := s.In(data) - assert.NoError(t, err) - assert.Equal(t, "olleh", string(reversed)) - original, err := s.Out(reversed) - assert.NoError(t, err) - assert.Equal(t, "hello", string(original)) -} - -func TestHexSigil(t *testing.T) { - s := &enchantrix.HexSigil{} - data := []byte("hello") - encoded, err := s.In(data) - assert.NoError(t, err) - assert.Equal(t, "68656c6c6f", string(encoded)) - decoded, err := s.Out(encoded) - assert.NoError(t, err) - assert.Equal(t, "hello", string(decoded)) -} - -func TestBase64Sigil(t *testing.T) { - s := &enchantrix.Base64Sigil{} - data := []byte("hello") - encoded, err := s.In(data) - assert.NoError(t, err) - assert.Equal(t, "aGVsbG8=", string(encoded)) - decoded, err := s.Out(encoded) - assert.NoError(t, err) - assert.Equal(t, "hello", string(decoded)) -} - -func TestGzipSigil(t *testing.T) { - s := &enchantrix.GzipSigil{} - data := []byte("hello") - compressed, err := s.In(data) - assert.NoError(t, err) - assert.NotEqual(t, data, compressed) - decompressed, err := s.Out(compressed) - assert.NoError(t, err) - assert.Equal(t, "hello", string(decompressed)) -} - -func TestJSONSigil(t *testing.T) { - s := &enchantrix.JSONSigil{Indent: true} - data := []byte(`{"hello":"world"}`) - indented, err := s.In(data) - assert.NoError(t, err) - assert.Equal(t, "{\n \"hello\": \"world\"\n}", string(indented)) - s.Indent = false - compacted, err := s.In(indented) - assert.NoError(t, err) - assert.Equal(t, `{"hello":"world"}`, string(compacted)) -} - -func TestHashSigil(t *testing.T) { - s := enchantrix.NewHashSigil(crypto.SHA256) - data := []byte("hello") - hashed, err := s.In(data) - assert.NoError(t, err) - expectedHash := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" - assert.Equal(t, expectedHash, hex.EncodeToString(hashed)) - unhashed, err := s.Out(hashed) - assert.NoError(t, err) - assert.Equal(t, hashed, unhashed) -} diff --git a/pkg/trix/fuzz_test.go b/pkg/trix/fuzz_test.go deleted file mode 100644 index 28565d7..0000000 --- a/pkg/trix/fuzz_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package trix - -import ( - "testing" -) - -func FuzzDecode(f *testing.F) { - // Seed with a valid encoded Trix object - validTrix := &Trix{ - Header: map[string]interface{}{"content_type": "text/plain"}, - Payload: []byte("hello world"), - } - validEncoded, _ := Encode(validTrix, "FUZZ") - f.Add(validEncoded) - - // Seed with the corrupted header length from the ugly test - var buf []byte - buf = append(buf, []byte("UGLY")...) - buf = append(buf, byte(Version)) - buf = append(buf, []byte{0, 0, 3, 232}...) // BigEndian representation of 1000 - buf = append(buf, []byte("{}")...) - buf = append(buf, []byte("payload")...) - f.Add(buf) - - // Seed with a short, invalid input - f.Add([]byte("short")) - - f.Fuzz(func(t *testing.T, data []byte) { - // The fuzzer will generate random data here. - // We just need to call our function and make sure it doesn't panic. - // The fuzzer will report any crashes as failures. - _, _ = Decode(data, "FUZZ") - }) -} diff --git a/tdd/crypt_test.go b/tdd/crypt/crypt_test.go similarity index 100% rename from tdd/crypt_test.go rename to tdd/crypt/crypt_test.go diff --git a/tdd/enchantrix/enchantrix_test.go b/tdd/enchantrix/enchantrix_test.go new file mode 100644 index 0000000..79e6330 --- /dev/null +++ b/tdd/enchantrix/enchantrix_test.go @@ -0,0 +1,159 @@ +package enchantrix_test + +import ( + "crypto" + "encoding/hex" + "errors" + "testing" + + "github.com/Snider/Enchantrix/pkg/enchantrix" + "github.com/stretchr/testify/assert" +) + +// --- Transmute Tests --- + +func TestTransmute_Good(t *testing.T) { + data := []byte("hello") + sigils := []enchantrix.Sigil{ + &enchantrix.ReverseSigil{}, + &enchantrix.HexSigil{}, + } + result, err := enchantrix.Transmute(data, sigils) + assert.NoError(t, err) + assert.Equal(t, "6f6c6c6568", string(result)) +} + +type errorSigil struct{} + +func (s *errorSigil) In(data []byte) ([]byte, error) { + return nil, errors.New("sigil error") +} +func (s *errorSigil) Out(data []byte) ([]byte, error) { + return nil, errors.New("sigil error") +} + +func TestTransmute_Bad(t *testing.T) { + data := []byte("hello") + sigils := []enchantrix.Sigil{ + &enchantrix.ReverseSigil{}, + &errorSigil{}, + } + _, err := enchantrix.Transmute(data, sigils) + assert.Error(t, err) +} + +// --- Factory Tests --- + +func TestNewSigil_Good(t *testing.T) { + validNames := []string{ + "reverse", "hex", "base64", "gzip", "json", "json-indent", + "md4", "md5", "sha1", "sha224", "sha256", "sha384", "sha512", + "ripemd160", "sha3-224", "sha3-256", "sha3-384", "sha3-512", + "sha512-224", "sha512-256", "blake2s-256", "blake2b-256", + "blake2b-384", "blake2b-512", + } + for _, name := range validNames { + sigil, err := enchantrix.NewSigil(name) + assert.NoError(t, err, "Failed to create sigil: %s", name) + assert.NotNil(t, sigil, "Sigil should not be nil for name: %s", name) + } +} + +func TestNewSigil_Bad(t *testing.T) { + sigil, err := enchantrix.NewSigil("invalid-sigil-name") + assert.Error(t, err) + assert.Nil(t, sigil) + assert.Contains(t, err.Error(), "unknown sigil name") +} + +// --- Sigil Tests --- + +func TestReverseSigil(t *testing.T) { + s := &enchantrix.ReverseSigil{} + data := []byte("hello") + reversed, err := s.In(data) + assert.NoError(t, err) + assert.Equal(t, "olleh", string(reversed)) + original, err := s.Out(reversed) + assert.NoError(t, err) + assert.Equal(t, "hello", string(original)) + + // Ugly - empty string + empty := []byte("") + reversedEmpty, err := s.In(empty) + assert.NoError(t, err) + assert.Equal(t, "", string(reversedEmpty)) +} + +func TestHexSigil(t *testing.T) { + s := &enchantrix.HexSigil{} + data := []byte("hello") + encoded, err := s.In(data) + assert.NoError(t, err) + assert.Equal(t, "68656c6c6f", string(encoded)) + decoded, err := s.Out(encoded) + assert.NoError(t, err) + assert.Equal(t, "hello", string(decoded)) + + // Bad - invalid hex string + _, err = s.Out([]byte("not hex")) + assert.Error(t, err) +} + +func TestBase64Sigil(t *testing.T) { + s := &enchantrix.Base64Sigil{} + data := []byte("hello") + encoded, err := s.In(data) + assert.NoError(t, err) + assert.Equal(t, "aGVsbG8=", string(encoded)) + decoded, err := s.Out(encoded) + assert.NoError(t, err) + assert.Equal(t, "hello", string(decoded)) + + // Bad - invalid base64 string + _, err = s.Out([]byte("not base64")) + assert.Error(t, err) +} + +func TestGzipSigil(t *testing.T) { + s := &enchantrix.GzipSigil{} + data := []byte("hello") + compressed, err := s.In(data) + assert.NoError(t, err) + assert.NotEqual(t, data, compressed) + decompressed, err := s.Out(compressed) + assert.NoError(t, err) + assert.Equal(t, "hello", string(decompressed)) + + // Bad - invalid gzip data + _, err = s.Out([]byte("not gzip")) + assert.Error(t, err) +} + +func TestJSONSigil(t *testing.T) { + s := &enchantrix.JSONSigil{Indent: true} + data := []byte(`{"hello":"world"}`) + indented, err := s.In(data) + assert.NoError(t, err) + assert.Equal(t, "{\n \"hello\": \"world\"\n}", string(indented)) + s.Indent = false + compacted, err := s.In(indented) + assert.NoError(t, err) + assert.Equal(t, `{"hello":"world"}`, string(compacted)) + + // Bad - invalid json + _, err = s.In([]byte("not json")) + assert.Error(t, err) +} + +func TestHashSigil(t *testing.T) { + s := enchantrix.NewHashSigil(crypto.SHA256) + data := []byte("hello") + hashed, err := s.In(data) + assert.NoError(t, err) + expectedHash := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + assert.Equal(t, expectedHash, hex.EncodeToString(hashed)) + unhashed, err := s.Out(hashed) + assert.NoError(t, err) + assert.Equal(t, hashed, unhashed) // Out is a no-op +} diff --git a/pkg/trix/trix_test.go b/tdd/trix/trix_test.go similarity index 50% rename from pkg/trix/trix_test.go rename to tdd/trix/trix_test.go index 5a3cd32..ee2c4d2 100644 --- a/pkg/trix/trix_test.go +++ b/tdd/trix/trix_test.go @@ -1,4 +1,4 @@ -package trix +package trix_test import ( "io" @@ -6,6 +6,7 @@ import ( "testing" "github.com/Snider/Enchantrix/pkg/crypt" + "github.com/Snider/Enchantrix/pkg/trix" "github.com/stretchr/testify/assert" ) @@ -18,37 +19,37 @@ func TestTrixEncodeDecode_Good(t *testing.T) { "created_at": "2025-10-30T12:00:00Z", } payload := []byte("This is a secret message.") - trix := &Trix{Header: header, Payload: payload} + trixOb := &trix.Trix{Header: header, Payload: payload} magicNumber := "TRIX" - encoded, err := Encode(trix, magicNumber) + encoded, err := trix.Encode(trixOb, magicNumber) assert.NoError(t, err) - decoded, err := Decode(encoded, magicNumber) + decoded, err := trix.Decode(encoded, magicNumber) assert.NoError(t, err) - assert.True(t, reflect.DeepEqual(trix.Header, decoded.Header)) - assert.Equal(t, trix.Payload, decoded.Payload) + assert.True(t, reflect.DeepEqual(trixOb.Header, decoded.Header)) + assert.Equal(t, trixOb.Payload, decoded.Payload) } // TestTrixEncodeDecode_Bad tests expected failure scenarios with well-formed but invalid inputs. func TestTrixEncodeDecode_Bad(t *testing.T) { t.Run("MismatchedMagicNumber", func(t *testing.T) { - trix := &Trix{Header: map[string]interface{}{}, Payload: []byte("payload")} - encoded, err := Encode(trix, "GOOD") + trixOb := &trix.Trix{Header: map[string]interface{}{}, Payload: []byte("payload")} + encoded, err := trix.Encode(trixOb, "GOOD") assert.NoError(t, err) - _, err = Decode(encoded, "BAD!") + _, err = trix.Decode(encoded, "BAD!") assert.Error(t, err) assert.Contains(t, err.Error(), "invalid magic number") }) t.Run("InvalidMagicNumberLength", func(t *testing.T) { - trix := &Trix{Header: map[string]interface{}{}, Payload: []byte("payload")} - _, err := Encode(trix, "TOOLONG") + trixOb := &trix.Trix{Header: map[string]interface{}{}, Payload: []byte("payload")} + _, err := trix.Encode(trixOb, "TOOLONG") assert.EqualError(t, err, "trix: magic number must be 4 bytes long") - _, err = Decode([]byte{}, "SHORT") + _, err = trix.Decode([]byte{}, "SHORT") assert.EqualError(t, err, "trix: magic number must be 4 bytes long") }) @@ -57,11 +58,21 @@ func TestTrixEncodeDecode_Bad(t *testing.T) { header := map[string]interface{}{ "unsupported": make(chan int), // Channels cannot be JSON-encoded } - trix := &Trix{Header: header, Payload: []byte("payload")} - _, err := Encode(trix, "TRIX") + trixOb := &trix.Trix{Header: header, Payload: []byte("payload")} + _, err := trix.Encode(trixOb, "TRIX") assert.Error(t, err) assert.Contains(t, err.Error(), "json: unsupported type") }) + + t.Run("HeaderTooLarge", func(t *testing.T) { + data := make([]byte, trix.MaxHeaderSize+10) + trixOb := &trix.Trix{ + Header: map[string]interface{}{"large": string(data)}, + Payload: []byte("payload"), + } + _, err := trix.Encode(trixOb, "TRIX") + assert.NoError(t, err) + }) } // TestTrixEncodeDecode_Ugly tests malicious or malformed inputs designed to cause crashes or panics. @@ -71,43 +82,40 @@ func TestTrixEncodeDecode_Ugly(t *testing.T) { t.Run("CorruptedHeaderLength", func(t *testing.T) { // Manually construct a byte slice where the header length is larger than the actual data. var buf []byte - buf = append(buf, []byte(magicNumber)...) // Magic Number - buf = append(buf, byte(Version)) // Version - // Header length of 1000, but the header is only 2 bytes long. - buf = append(buf, []byte{0, 0, 3, 232}...) // BigEndian representation of 1000 - buf = append(buf, []byte("{}")...) // A minimal valid JSON header + buf = append(buf, []byte(magicNumber)...) // Magic Number + buf = append(buf, byte(trix.Version)) // Version + buf = append(buf, []byte{0, 0, 3, 232}...) // BigEndian representation of 1000 + buf = append(buf, []byte("{}")...) // A minimal valid JSON header buf = append(buf, []byte("payload")...) - _, err := Decode(buf, magicNumber) + _, err := trix.Decode(buf, magicNumber) assert.Error(t, err) assert.Equal(t, err, io.ErrUnexpectedEOF) }) t.Run("DataTooShort", func(t *testing.T) { - // Data is too short to contain even the magic number. data := []byte("BAD") - _, err := Decode(data, magicNumber) + _, err := trix.Decode(data, magicNumber) assert.Error(t, err) }) t.Run("EmptyPayload", func(t *testing.T) { data := []byte{} - _, err := Decode(data, magicNumber) + _, err := trix.Decode(data, magicNumber) assert.Error(t, err) }) t.Run("FuzzedJSON", func(t *testing.T) { - // A header that is technically valid but contains unexpected types. header := map[string]interface{}{ "payload": map[string]interface{}{"nested": 123}, } payload := []byte("some data") - trix := &Trix{Header: header, Payload: payload} + trixOb := &trix.Trix{Header: header, Payload: payload} - encoded, err := Encode(trix, magicNumber) + encoded, err := trix.Encode(trixOb, magicNumber) assert.NoError(t, err) - decoded, err := Decode(encoded, magicNumber) + decoded, err := trix.Decode(encoded, magicNumber) assert.NoError(t, err) assert.NotNil(t, decoded) }) @@ -117,86 +125,126 @@ func TestTrixEncodeDecode_Ugly(t *testing.T) { func TestPackUnpack_Good(t *testing.T) { originalPayload := []byte("hello world") - trix := &Trix{ - Header: map[string]interface{}{}, - Payload: originalPayload, + trixOb := &trix.Trix{ + Header: map[string]interface{}{}, + Payload: originalPayload, InSigils: []string{"reverse", "reverse"}, // Double reverse should be original } - err := trix.Pack() + err := trixOb.Pack() assert.NoError(t, err) - assert.Equal(t, originalPayload, trix.Payload) // Should be back to the original + assert.Equal(t, originalPayload, trixOb.Payload) - err = trix.Unpack() + err = trixOb.Unpack() assert.NoError(t, err) - assert.Equal(t, originalPayload, trix.Payload) // Should be back to the original again + assert.Equal(t, originalPayload, trixOb.Payload) } func TestPackUnpack_Bad(t *testing.T) { - trix := &Trix{ - Header: map[string]interface{}{}, - Payload: []byte("some data"), + trixOb := &trix.Trix{ + Header: map[string]interface{}{}, + Payload: []byte("some data"), InSigils: []string{"reverse", "invalid-sigil-name"}, } - err := trix.Pack() + err := trixOb.Pack() assert.Error(t, err) assert.Contains(t, err.Error(), "unknown sigil name") + + trixOb.InSigils = []string{"hex"} + trixOb.Payload = []byte("not hex") + err = trixOb.Unpack() + assert.Error(t, err) +} + +func TestPackUnpack_Ugly(t *testing.T) { + trixOb := &trix.Trix{ + Header: map[string]interface{}{}, + Payload: nil, // Nil payload + InSigils: []string{"reverse"}, + } + err := trixOb.Pack() + assert.NoError(t, err) // Should handle nil payload gracefully + + err = trixOb.Unpack() + assert.NoError(t, err) } // --- Checksum Tests --- func TestChecksum_Good(t *testing.T) { - trix := &Trix{ + trixOb := &trix.Trix{ Header: map[string]interface{}{}, Payload: []byte("hello world"), ChecksumAlgo: crypt.SHA256, } - encoded, err := Encode(trix, "CHCK") + encoded, err := trix.Encode(trixOb, "CHCK") assert.NoError(t, err) - decoded, err := Decode(encoded, "CHCK") + decoded, err := trix.Decode(encoded, "CHCK") assert.NoError(t, err) - assert.Equal(t, trix.Payload, decoded.Payload) + assert.Equal(t, trixOb.Payload, decoded.Payload) } func TestChecksum_Bad(t *testing.T) { - trix := &Trix{ + trixOb := &trix.Trix{ Header: map[string]interface{}{}, Payload: []byte("hello world"), ChecksumAlgo: crypt.SHA256, } - encoded, err := Encode(trix, "CHCK") + encoded, err := trix.Encode(trixOb, "CHCK") assert.NoError(t, err) - // Tamper with the payload - encoded[len(encoded)-1] = 0 + encoded[len(encoded)-1] = 0 // Tamper with the payload - _, err = Decode(encoded, "CHCK") + _, err = trix.Decode(encoded, "CHCK") assert.Error(t, err) - assert.Equal(t, ErrChecksumMismatch, err) + assert.Equal(t, trix.ErrChecksumMismatch, err) } func TestChecksum_Ugly(t *testing.T) { t.Run("MissingAlgoInHeader", func(t *testing.T) { - trix := &Trix{ + trixOb := &trix.Trix{ Header: map[string]interface{}{}, Payload: []byte("hello world"), ChecksumAlgo: crypt.SHA256, } - encoded, err := Encode(trix, "UGLY") + encoded, err := trix.Encode(trixOb, "UGLY") assert.NoError(t, err) - // Manually decode to tamper with the header - decoded, err := Decode(encoded, "UGLY") + decoded, err := trix.Decode(encoded, "UGLY") assert.NoError(t, err) delete(decoded.Header, "checksum_algo") - // Re-encode with the tampered header - tamperedEncoded, err := Encode(decoded, "UGLY") + tamperedEncoded, err := trix.Encode(decoded, "UGLY") assert.NoError(t, err) - _, err = Decode(tamperedEncoded, "UGLY") + _, err = trix.Decode(tamperedEncoded, "UGLY") assert.Error(t, err) }) } + +// --- Fuzz Tests --- + +func FuzzDecode(f *testing.F) { + validTrix := &trix.Trix{ + Header: map[string]interface{}{"content_type": "text/plain"}, + Payload: []byte("hello world"), + } + validEncoded, _ := trix.Encode(validTrix, "FUZZ") + f.Add(validEncoded) + + var buf []byte + buf = append(buf, []byte("UGLY")...) + buf = append(buf, byte(trix.Version)) + buf = append(buf, []byte{0, 0, 3, 232}...) + buf = append(buf, []byte("{}")...) + buf = append(buf, []byte("payload")...) + f.Add(buf) + + f.Add([]byte("short")) + + f.Fuzz(func(t *testing.T, data []byte) { + _, _ = trix.Decode(data, "FUZZ") + }) +}