[PHASE-7] Add more protocol tests
This commit is contained in:
parent
8aa5b91f24
commit
65945d34c0
157
internal/protocol/encode_decode_test.go
Normal file
157
internal/protocol/encode_decode_test.go
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"hash-of-wisdom/internal/pow/challenge"
|
||||||
|
"hash-of-wisdom/internal/quotes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSymmetricEncoding_ChallengeRequest(t *testing.T) {
|
||||||
|
// Create a challenge request
|
||||||
|
req := &ChallengeRequest{}
|
||||||
|
|
||||||
|
// Encode it
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := req.Encode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Decode it back
|
||||||
|
decoder := NewMessageDecoder()
|
||||||
|
msg, err := decoder.Decode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, ChallengeRequestType, msg.Type)
|
||||||
|
assert.Equal(t, uint32(0), msg.PayloadLength)
|
||||||
|
|
||||||
|
// Decode the request payload
|
||||||
|
decodedReq := &ChallengeRequest{}
|
||||||
|
err = decodedReq.Decode(msg.PayloadStream)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSymmetricEncoding_SolutionRequest(t *testing.T) {
|
||||||
|
// Create a solution request
|
||||||
|
req := &SolutionRequest{
|
||||||
|
Challenge: challenge.Challenge{
|
||||||
|
Timestamp: 1640995200,
|
||||||
|
Difficulty: 4,
|
||||||
|
Resource: "quotes",
|
||||||
|
Random: []byte("test"),
|
||||||
|
HMAC: []byte("test"),
|
||||||
|
},
|
||||||
|
Nonce: 12345,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode it
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := req.Encode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Decode it back
|
||||||
|
decoder := NewMessageDecoder()
|
||||||
|
msg, err := decoder.Decode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, SolutionRequestType, msg.Type)
|
||||||
|
assert.Greater(t, msg.PayloadLength, uint32(0))
|
||||||
|
|
||||||
|
// Decode the request payload
|
||||||
|
decodedReq := &SolutionRequest{}
|
||||||
|
err = decodedReq.Decode(msg.PayloadStream)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, req.Nonce, decodedReq.Nonce)
|
||||||
|
assert.Equal(t, req.Challenge.Timestamp, decodedReq.Challenge.Timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSymmetricEncoding_ChallengeResponse(t *testing.T) {
|
||||||
|
// Create a challenge response
|
||||||
|
resp := &ChallengeResponse{
|
||||||
|
Challenge: &challenge.Challenge{
|
||||||
|
Timestamp: 1640995200,
|
||||||
|
Difficulty: 4,
|
||||||
|
Resource: "quotes",
|
||||||
|
Random: []byte("test"),
|
||||||
|
HMAC: []byte("test"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode it
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := resp.Encode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Decode it back
|
||||||
|
decoder := NewMessageDecoder()
|
||||||
|
msg, err := decoder.Decode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, ChallengeResponseType, msg.Type)
|
||||||
|
assert.Greater(t, msg.PayloadLength, uint32(0))
|
||||||
|
|
||||||
|
// Decode the response payload
|
||||||
|
decodedResp := &ChallengeResponse{}
|
||||||
|
err = decodedResp.Decode(msg.PayloadStream)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, resp.Challenge.Timestamp, decodedResp.Challenge.Timestamp)
|
||||||
|
assert.Equal(t, resp.Challenge.Difficulty, decodedResp.Challenge.Difficulty)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSymmetricEncoding_SolutionResponse(t *testing.T) {
|
||||||
|
// Create a solution response
|
||||||
|
resp := &SolutionResponse{
|
||||||
|
Quote: "es.Quote{
|
||||||
|
Text: "Test quote",
|
||||||
|
Author: "Test Author",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode it
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := resp.Encode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Decode it back
|
||||||
|
decoder := NewMessageDecoder()
|
||||||
|
msg, err := decoder.Decode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, QuoteResponseType, msg.Type)
|
||||||
|
assert.Greater(t, msg.PayloadLength, uint32(0))
|
||||||
|
|
||||||
|
// Decode the response payload
|
||||||
|
decodedResp := &SolutionResponse{}
|
||||||
|
err = decodedResp.Decode(msg.PayloadStream)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, resp.Quote.Text, decodedResp.Quote.Text)
|
||||||
|
assert.Equal(t, resp.Quote.Author, decodedResp.Quote.Author)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSymmetricEncoding_ErrorResponse(t *testing.T) {
|
||||||
|
// Create an error response
|
||||||
|
resp := &ErrorResponse{
|
||||||
|
Code: "TEST_ERROR",
|
||||||
|
Message: "Test error message",
|
||||||
|
Details: map[string]string{"key": "value"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode it
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := resp.Encode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Decode it back
|
||||||
|
decoder := NewMessageDecoder()
|
||||||
|
msg, err := decoder.Decode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, ErrorResponseType, msg.Type)
|
||||||
|
assert.Greater(t, msg.PayloadLength, uint32(0))
|
||||||
|
|
||||||
|
// Decode the response payload
|
||||||
|
decodedResp := &ErrorResponse{}
|
||||||
|
err = decodedResp.Decode(msg.PayloadStream)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, resp.Code, decodedResp.Code)
|
||||||
|
assert.Equal(t, resp.Message, decodedResp.Message)
|
||||||
|
assert.Equal(t, resp.Details, decodedResp.Details)
|
||||||
|
}
|
||||||
|
|
@ -113,16 +113,6 @@ func TestChallengeRequest_EmptyPayload(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMessageDecoder_RejectsResponseTypes(t *testing.T) {
|
|
||||||
decoder := NewMessageDecoder()
|
|
||||||
|
|
||||||
data := []byte{byte(ErrorResponseType), 0x00, 0x00, 0x00, 0x05, 'h', 'e', 'l', 'l', 'o'}
|
|
||||||
buf := bytes.NewBuffer(data)
|
|
||||||
|
|
||||||
_, err := decoder.Decode(buf)
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Contains(t, err.Error(), "invalid message type")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPayloadStream_LimitedRead(t *testing.T) {
|
func TestPayloadStream_LimitedRead(t *testing.T) {
|
||||||
decoder := NewMessageDecoder()
|
decoder := NewMessageDecoder()
|
||||||
|
|
|
||||||
177
internal/protocol/spec_compliance_test.go
Normal file
177
internal/protocol/spec_compliance_test.go
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"hash-of-wisdom/internal/pow/challenge"
|
||||||
|
"hash-of-wisdom/internal/quotes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestSpecCompliance_ChallengeResponse verifies challenge response matches PROTOCOL.md format
|
||||||
|
func TestSpecCompliance_ChallengeResponse(t *testing.T) {
|
||||||
|
resp := &ChallengeResponse{
|
||||||
|
Challenge: &challenge.Challenge{
|
||||||
|
Timestamp: 1640995200,
|
||||||
|
Difficulty: 4,
|
||||||
|
Resource: "quotes",
|
||||||
|
Random: []byte{0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf6},
|
||||||
|
HMAC: []byte("base64url_encoded_signature"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := resp.Encode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Skip header (5 bytes) and get payload
|
||||||
|
header := buf.Bytes()[:5]
|
||||||
|
payload := buf.Bytes()[5:]
|
||||||
|
|
||||||
|
// Verify header format
|
||||||
|
assert.Equal(t, byte(ChallengeResponseType), header[0])
|
||||||
|
assert.Equal(t, uint32(len(payload)), uint32(header[1])<<24|uint32(header[2])<<16|uint32(header[3])<<8|uint32(header[4]))
|
||||||
|
|
||||||
|
// Verify JSON payload matches spec format
|
||||||
|
var decoded map[string]interface{}
|
||||||
|
err = json.Unmarshal(payload, &decoded)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check required fields from spec
|
||||||
|
assert.Contains(t, decoded, "timestamp")
|
||||||
|
assert.Contains(t, decoded, "difficulty")
|
||||||
|
assert.Contains(t, decoded, "resource")
|
||||||
|
assert.Contains(t, decoded, "random")
|
||||||
|
assert.Contains(t, decoded, "hmac")
|
||||||
|
|
||||||
|
assert.Equal(t, float64(1640995200), decoded["timestamp"])
|
||||||
|
assert.Equal(t, float64(4), decoded["difficulty"])
|
||||||
|
assert.Equal(t, "quotes", decoded["resource"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSpecCompliance_SolutionRequest verifies solution request matches PROTOCOL.md format
|
||||||
|
func TestSpecCompliance_SolutionRequest(t *testing.T) {
|
||||||
|
req := &SolutionRequest{
|
||||||
|
Challenge: challenge.Challenge{
|
||||||
|
Timestamp: 1640995200,
|
||||||
|
Difficulty: 4,
|
||||||
|
Resource: "quotes",
|
||||||
|
Random: []byte{0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf6},
|
||||||
|
HMAC: []byte("base64url_encoded_signature"),
|
||||||
|
},
|
||||||
|
Nonce: 12345,
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := req.Encode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Skip header and get payload
|
||||||
|
payload := buf.Bytes()[5:]
|
||||||
|
|
||||||
|
// Verify JSON payload matches spec format
|
||||||
|
var decoded map[string]interface{}
|
||||||
|
err = json.Unmarshal(payload, &decoded)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check required top-level fields
|
||||||
|
assert.Contains(t, decoded, "challenge")
|
||||||
|
assert.Contains(t, decoded, "nonce")
|
||||||
|
assert.Equal(t, float64(12345), decoded["nonce"])
|
||||||
|
|
||||||
|
// Check challenge structure
|
||||||
|
challenge := decoded["challenge"].(map[string]interface{})
|
||||||
|
assert.Contains(t, challenge, "timestamp")
|
||||||
|
assert.Contains(t, challenge, "difficulty")
|
||||||
|
assert.Contains(t, challenge, "resource")
|
||||||
|
assert.Contains(t, challenge, "random")
|
||||||
|
assert.Contains(t, challenge, "hmac")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSpecCompliance_QuoteResponse verifies quote response matches PROTOCOL.md format
|
||||||
|
func TestSpecCompliance_QuoteResponse(t *testing.T) {
|
||||||
|
resp := &SolutionResponse{
|
||||||
|
Quote: "es.Quote{
|
||||||
|
Text: "The only way to do great work is to love what you do.",
|
||||||
|
Author: "Steve Jobs",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := resp.Encode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Skip header and get payload
|
||||||
|
payload := buf.Bytes()[5:]
|
||||||
|
|
||||||
|
// Verify JSON payload matches spec format
|
||||||
|
var decoded map[string]interface{}
|
||||||
|
err = json.Unmarshal(payload, &decoded)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check required fields from spec
|
||||||
|
assert.Contains(t, decoded, "text")
|
||||||
|
assert.Contains(t, decoded, "author")
|
||||||
|
assert.Equal(t, "The only way to do great work is to love what you do.", decoded["text"])
|
||||||
|
assert.Equal(t, "Steve Jobs", decoded["author"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSpecCompliance_ErrorResponse verifies error response matches PROTOCOL.md format
|
||||||
|
func TestSpecCompliance_ErrorResponse(t *testing.T) {
|
||||||
|
resp := &ErrorResponse{
|
||||||
|
Code: "INVALID_SOLUTION",
|
||||||
|
Message: "The provided PoW solution is incorrect",
|
||||||
|
RetryAfter: 30,
|
||||||
|
Details: map[string]string{"reason": "hash verification failed"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := resp.Encode(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Skip header and get payload
|
||||||
|
payload := buf.Bytes()[5:]
|
||||||
|
|
||||||
|
// Verify JSON payload matches spec format
|
||||||
|
var decoded map[string]interface{}
|
||||||
|
err = json.Unmarshal(payload, &decoded)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check required fields from spec
|
||||||
|
assert.Contains(t, decoded, "code")
|
||||||
|
assert.Contains(t, decoded, "message")
|
||||||
|
assert.Equal(t, "INVALID_SOLUTION", decoded["code"])
|
||||||
|
assert.Equal(t, "The provided PoW solution is incorrect", decoded["message"])
|
||||||
|
|
||||||
|
// Check optional fields
|
||||||
|
assert.Contains(t, decoded, "retry_after")
|
||||||
|
assert.Contains(t, decoded, "details")
|
||||||
|
assert.Equal(t, float64(30), decoded["retry_after"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSpecCompliance_MessageSizeLimits verifies 8KB payload limit
|
||||||
|
func TestSpecCompliance_MessageSizeLimits(t *testing.T) {
|
||||||
|
decoder := NewMessageDecoder()
|
||||||
|
|
||||||
|
// Create a message that exceeds 8KB payload limit
|
||||||
|
largePayload := make([]byte, MaxPayloadSize+1)
|
||||||
|
for i := range largePayload {
|
||||||
|
largePayload[i] = 'A'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create message with oversized payload
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteByte(byte(ChallengeRequestType))
|
||||||
|
buf.Write([]byte{0x00, 0x00, 0x20, 0x01}) // 8193 bytes (8KB + 1)
|
||||||
|
buf.Write(largePayload)
|
||||||
|
|
||||||
|
// Should reject oversized payload
|
||||||
|
_, err := decoder.Decode(&buf)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "payload length")
|
||||||
|
assert.Contains(t, err.Error(), "exceeds maximum")
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue