2025-08-22 17:04:06 +03:00
|
|
|
package protocol
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"io"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
2025-08-23 08:04:38 +03:00
|
|
|
"hash-of-wisdom/internal/pow/challenge"
|
|
|
|
|
"hash-of-wisdom/internal/quotes"
|
|
|
|
|
|
2025-08-22 17:04:06 +03:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
)
|
|
|
|
|
|
2025-08-23 08:04:38 +03:00
|
|
|
func TestCodec_Decode(t *testing.T) {
|
2025-08-22 17:04:06 +03:00
|
|
|
codec := NewCodec()
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
data []byte
|
|
|
|
|
wantErr string
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "empty data",
|
|
|
|
|
data: []byte{},
|
|
|
|
|
wantErr: "EOF",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "invalid message type",
|
|
|
|
|
data: []byte{0xFF, 0x00, 0x00, 0x00, 0x00},
|
|
|
|
|
wantErr: "invalid message type",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "incomplete header",
|
|
|
|
|
data: []byte{0x01, 0x00, 0x00},
|
|
|
|
|
wantErr: "failed to read payload length",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "payload too large",
|
|
|
|
|
data: append([]byte{0x01}, encodeBigEndianUint32(MaxPayloadSize+1)...),
|
|
|
|
|
wantErr: "payload length",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "incomplete payload",
|
|
|
|
|
data: []byte{0x01, 0x00, 0x00, 0x00, 0x05, 0x01, 0x02},
|
|
|
|
|
wantErr: "failed to read payload",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "invalid UTF-8 in payload",
|
|
|
|
|
data: []byte{0x01, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0xFD},
|
|
|
|
|
wantErr: "invalid UTF-8",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
buf := bytes.NewBuffer(tt.data)
|
|
|
|
|
_, err := codec.Decode(buf)
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), tt.wantErr)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-23 08:04:38 +03:00
|
|
|
func TestCodec_ReadError_Handling(t *testing.T) {
|
2025-08-22 17:04:06 +03:00
|
|
|
codec := NewCodec()
|
|
|
|
|
|
2025-08-23 08:04:38 +03:00
|
|
|
// Create a reader that fails after reading header
|
|
|
|
|
reader := &failingReader{
|
|
|
|
|
data: []byte{0x01, 0x00, 0x00, 0x00, 0x05},
|
|
|
|
|
failAfter: 5,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err := codec.Decode(reader)
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "failed to read payload")
|
2025-08-22 17:04:06 +03:00
|
|
|
}
|
|
|
|
|
|
2025-08-23 08:04:38 +03:00
|
|
|
func TestChallengeResponse_Encode(t *testing.T) {
|
|
|
|
|
challenge := &challenge.Challenge{
|
|
|
|
|
Timestamp: time.Now().Unix(),
|
|
|
|
|
Difficulty: 4,
|
|
|
|
|
Resource: "quotes",
|
|
|
|
|
Random: []byte("random123"),
|
|
|
|
|
HMAC: []byte("hmac_signature"),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response := &ChallengeResponse{Challenge: challenge}
|
|
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
err := response.Encode(&buf)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Verify the encoded message can be decoded
|
2025-08-22 17:04:06 +03:00
|
|
|
codec := NewCodec()
|
2025-08-23 08:04:38 +03:00
|
|
|
decoded, err := codec.Decode(&buf)
|
|
|
|
|
require.NoError(t, err)
|
2025-08-22 17:04:06 +03:00
|
|
|
|
2025-08-23 08:04:38 +03:00
|
|
|
assert.Equal(t, ChallengeResponseType, decoded.Type)
|
|
|
|
|
assert.Contains(t, string(decoded.Payload), "quotes")
|
|
|
|
|
assert.Contains(t, string(decoded.Payload), "cmFuZG9tMTIz") // "random123" base64 encoded
|
|
|
|
|
}
|
2025-08-22 17:04:06 +03:00
|
|
|
|
2025-08-23 08:04:38 +03:00
|
|
|
func TestSolutionResponse_Encode(t *testing.T) {
|
|
|
|
|
quote := "es.Quote{
|
|
|
|
|
Text: "Test quote",
|
|
|
|
|
Author: "Test author",
|
2025-08-22 17:04:06 +03:00
|
|
|
}
|
|
|
|
|
|
2025-08-23 08:04:38 +03:00
|
|
|
response := &SolutionResponse{Quote: quote}
|
|
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
err := response.Encode(&buf)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Verify the encoded message can be decoded
|
|
|
|
|
codec := NewCodec()
|
|
|
|
|
decoded, err := codec.Decode(&buf)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, QuoteResponseType, decoded.Type)
|
|
|
|
|
assert.Contains(t, string(decoded.Payload), "Test quote")
|
|
|
|
|
assert.Contains(t, string(decoded.Payload), "Test author")
|
2025-08-22 17:04:06 +03:00
|
|
|
}
|
|
|
|
|
|
2025-08-23 08:04:38 +03:00
|
|
|
func TestErrorResponse_Encode(t *testing.T) {
|
|
|
|
|
errorResp := &ErrorResponse{
|
|
|
|
|
Code: "INVALID_SOLUTION",
|
|
|
|
|
Message: "The provided PoW solution is incorrect",
|
|
|
|
|
RetryAfter: 30,
|
|
|
|
|
Details: map[string]string{"attempt": "1"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
err := errorResp.Encode(&buf)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Verify the encoded message can be decoded
|
2025-08-22 17:04:06 +03:00
|
|
|
codec := NewCodec()
|
2025-08-23 08:04:38 +03:00
|
|
|
decoded, err := codec.Decode(&buf)
|
|
|
|
|
require.NoError(t, err)
|
2025-08-22 17:04:06 +03:00
|
|
|
|
2025-08-23 08:04:38 +03:00
|
|
|
assert.Equal(t, ErrorResponseType, decoded.Type)
|
|
|
|
|
assert.Contains(t, string(decoded.Payload), "INVALID_SOLUTION")
|
|
|
|
|
assert.Contains(t, string(decoded.Payload), "The provided PoW solution is incorrect")
|
|
|
|
|
assert.Contains(t, string(decoded.Payload), "30")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestResponse_WriteError_Handling(t *testing.T) {
|
|
|
|
|
response := &ErrorResponse{
|
|
|
|
|
Code: "TEST_ERROR",
|
|
|
|
|
Message: "Test message",
|
2025-08-22 17:04:06 +03:00
|
|
|
}
|
|
|
|
|
|
2025-08-23 08:04:38 +03:00
|
|
|
// Create a writer that fails immediately
|
|
|
|
|
writer := &failingWriter{failAfter: 1}
|
|
|
|
|
|
|
|
|
|
err := response.Encode(writer)
|
2025-08-22 17:04:06 +03:00
|
|
|
assert.Error(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper functions and types for testing
|
|
|
|
|
|
|
|
|
|
func encodeBigEndianUint32(val uint32) []byte {
|
|
|
|
|
return []byte{
|
|
|
|
|
byte(val >> 24),
|
|
|
|
|
byte(val >> 16),
|
|
|
|
|
byte(val >> 8),
|
|
|
|
|
byte(val),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type failingWriter struct {
|
|
|
|
|
written int
|
|
|
|
|
failAfter int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w *failingWriter) Write(data []byte) (int, error) {
|
|
|
|
|
if w.written >= w.failAfter {
|
|
|
|
|
return 0, io.ErrShortWrite
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
remaining := w.failAfter - w.written
|
|
|
|
|
if len(data) <= remaining {
|
|
|
|
|
w.written += len(data)
|
|
|
|
|
return len(data), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w.written = w.failAfter
|
|
|
|
|
return remaining, io.ErrShortWrite
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type failingReader struct {
|
|
|
|
|
data []byte
|
|
|
|
|
pos int
|
|
|
|
|
failAfter int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *failingReader) Read(buf []byte) (int, error) {
|
|
|
|
|
if r.pos >= r.failAfter {
|
|
|
|
|
return 0, io.ErrUnexpectedEOF
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.pos >= len(r.data) {
|
|
|
|
|
return 0, io.EOF
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
n := copy(buf, r.data[r.pos:])
|
|
|
|
|
r.pos += n
|
|
|
|
|
|
|
|
|
|
if r.pos >= r.failAfter {
|
|
|
|
|
return n, io.ErrUnexpectedEOF
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return n, nil
|
|
|
|
|
}
|