285 lines
6.4 KiB
Go
285 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)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|