go-p2p/node/message_test.go
Claude 8f94639ec9
feat: extract P2P networking and UEPS protocol from Mining repo
P2P node layer (peer discovery, WebSocket transport, message protocol,
worker pool, identity management) and Unified Ethical Protocol Stack
(TLV packet builder with HMAC-signed frames).

Ported from github.com/Snider/Mining/pkg/{node,ueps,logging}

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 15:47:10 +00:00

284 lines
6.4 KiB
Go

package node
import (
"encoding/json"
"testing"
"time"
)
func TestNewMessage(t *testing.T) {
t.Run("BasicMessage", func(t *testing.T) {
msg, err := NewMessage(MsgPing, "sender-id", "receiver-id", nil)
if err != nil {
t.Fatalf("failed to create message: %v", err)
}
if msg.Type != MsgPing {
t.Errorf("expected type MsgPing, got %s", msg.Type)
}
if msg.From != "sender-id" {
t.Errorf("expected from 'sender-id', got '%s'", msg.From)
}
if msg.To != "receiver-id" {
t.Errorf("expected to 'receiver-id', got '%s'", msg.To)
}
if msg.ID == "" {
t.Error("message ID should not be empty")
}
if msg.Timestamp.IsZero() {
t.Error("timestamp should be set")
}
})
t.Run("MessageWithPayload", func(t *testing.T) {
payload := PingPayload{
SentAt: time.Now().UnixMilli(),
}
msg, err := NewMessage(MsgPing, "sender", "receiver", payload)
if err != nil {
t.Fatalf("failed to create message: %v", err)
}
if msg.Payload == nil {
t.Error("payload should not be nil")
}
var parsed PingPayload
err = msg.ParsePayload(&parsed)
if err != nil {
t.Fatalf("failed to parse payload: %v", err)
}
if parsed.SentAt != payload.SentAt {
t.Errorf("expected SentAt %d, got %d", payload.SentAt, parsed.SentAt)
}
})
}
func TestMessageReply(t *testing.T) {
original, _ := NewMessage(MsgPing, "sender", "receiver", PingPayload{SentAt: 12345})
reply, err := original.Reply(MsgPong, PongPayload{
SentAt: 12345,
ReceivedAt: 12350,
})
if err != nil {
t.Fatalf("failed to create reply: %v", err)
}
if reply.ReplyTo != original.ID {
t.Errorf("reply should reference original message ID")
}
if reply.From != original.To {
t.Error("reply From should be original To")
}
if reply.To != original.From {
t.Error("reply To should be original From")
}
if reply.Type != MsgPong {
t.Errorf("expected type MsgPong, got %s", reply.Type)
}
}
func TestParsePayload(t *testing.T) {
t.Run("ValidPayload", func(t *testing.T) {
payload := StartMinerPayload{
MinerType: "xmrig",
ProfileID: "test-profile",
}
msg, _ := NewMessage(MsgStartMiner, "ctrl", "worker", payload)
var parsed StartMinerPayload
err := msg.ParsePayload(&parsed)
if err != nil {
t.Fatalf("failed to parse payload: %v", err)
}
if parsed.ProfileID != "test-profile" {
t.Errorf("expected ProfileID 'test-profile', got '%s'", parsed.ProfileID)
}
})
t.Run("NilPayload", func(t *testing.T) {
msg, _ := NewMessage(MsgGetStats, "ctrl", "worker", nil)
var parsed StatsPayload
err := msg.ParsePayload(&parsed)
if err != nil {
t.Errorf("parsing nil payload should not error: %v", err)
}
})
t.Run("ComplexPayload", func(t *testing.T) {
stats := StatsPayload{
NodeID: "node-123",
NodeName: "Test Node",
Miners: []MinerStatsItem{
{
Name: "xmrig-1",
Type: "xmrig",
Hashrate: 1234.56,
Shares: 100,
Rejected: 2,
Uptime: 3600,
Pool: "pool.example.com:3333",
Algorithm: "RandomX",
},
},
Uptime: 86400,
}
msg, _ := NewMessage(MsgStats, "worker", "ctrl", stats)
var parsed StatsPayload
err := msg.ParsePayload(&parsed)
if err != nil {
t.Fatalf("failed to parse stats payload: %v", err)
}
if parsed.NodeID != "node-123" {
t.Errorf("expected NodeID 'node-123', got '%s'", parsed.NodeID)
}
if len(parsed.Miners) != 1 {
t.Fatalf("expected 1 miner, got %d", len(parsed.Miners))
}
if parsed.Miners[0].Hashrate != 1234.56 {
t.Errorf("expected hashrate 1234.56, got %f", parsed.Miners[0].Hashrate)
}
})
}
func TestNewErrorMessage(t *testing.T) {
errMsg, err := NewErrorMessage("sender", "receiver", ErrCodeOperationFailed, "something went wrong", "original-msg-id")
if err != nil {
t.Fatalf("failed to create error message: %v", err)
}
if errMsg.Type != MsgError {
t.Errorf("expected type MsgError, got %s", errMsg.Type)
}
if errMsg.ReplyTo != "original-msg-id" {
t.Errorf("expected ReplyTo 'original-msg-id', got '%s'", errMsg.ReplyTo)
}
var errPayload ErrorPayload
err = errMsg.ParsePayload(&errPayload)
if err != nil {
t.Fatalf("failed to parse error payload: %v", err)
}
if errPayload.Code != ErrCodeOperationFailed {
t.Errorf("expected code %d, got %d", ErrCodeOperationFailed, errPayload.Code)
}
if errPayload.Message != "something went wrong" {
t.Errorf("expected message 'something went wrong', got '%s'", errPayload.Message)
}
}
func TestMessageSerialization(t *testing.T) {
original, _ := NewMessage(MsgStartMiner, "ctrl", "worker", StartMinerPayload{
MinerType: "xmrig",
ProfileID: "my-profile",
})
// Serialize
data, err := json.Marshal(original)
if err != nil {
t.Fatalf("failed to serialize message: %v", err)
}
// Deserialize
var restored Message
err = json.Unmarshal(data, &restored)
if err != nil {
t.Fatalf("failed to deserialize message: %v", err)
}
if restored.ID != original.ID {
t.Error("ID mismatch after serialization")
}
if restored.Type != original.Type {
t.Error("Type mismatch after serialization")
}
if restored.From != original.From {
t.Error("From mismatch after serialization")
}
var payload StartMinerPayload
err = restored.ParsePayload(&payload)
if err != nil {
t.Fatalf("failed to parse restored payload: %v", err)
}
if payload.ProfileID != "my-profile" {
t.Errorf("expected ProfileID 'my-profile', got '%s'", payload.ProfileID)
}
}
func TestMessageTypes(t *testing.T) {
types := []MessageType{
MsgHandshake,
MsgHandshakeAck,
MsgPing,
MsgPong,
MsgDisconnect,
MsgGetStats,
MsgStats,
MsgStartMiner,
MsgStopMiner,
MsgMinerAck,
MsgDeploy,
MsgDeployAck,
MsgGetLogs,
MsgLogs,
MsgError,
}
for _, msgType := range types {
t.Run(string(msgType), func(t *testing.T) {
msg, err := NewMessage(msgType, "from", "to", nil)
if err != nil {
t.Fatalf("failed to create message of type %s: %v", msgType, err)
}
if msg.Type != msgType {
t.Errorf("expected type %s, got %s", msgType, msg.Type)
}
})
}
}
func TestErrorCodes(t *testing.T) {
codes := map[int]string{
ErrCodeUnknown: "Unknown",
ErrCodeInvalidMessage: "InvalidMessage",
ErrCodeUnauthorized: "Unauthorized",
ErrCodeNotFound: "NotFound",
ErrCodeOperationFailed: "OperationFailed",
ErrCodeTimeout: "Timeout",
}
for code, name := range codes {
t.Run(name, func(t *testing.T) {
if code < 1000 || code > 1999 {
t.Errorf("error code %d should be in 1000-1999 range", code)
}
})
}
}