go-p2p/node/worker_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

513 lines
13 KiB
Go

package node
import (
"os"
"path/filepath"
"testing"
"time"
)
// setupTestEnv sets up a temporary environment for testing and returns cleanup function
func setupTestEnv(t *testing.T) func() {
tmpDir := t.TempDir()
os.Setenv("XDG_CONFIG_HOME", filepath.Join(tmpDir, "config"))
os.Setenv("XDG_DATA_HOME", filepath.Join(tmpDir, "data"))
return func() {
os.Unsetenv("XDG_CONFIG_HOME")
os.Unsetenv("XDG_DATA_HOME")
}
}
func TestNewWorker(t *testing.T) {
cleanup := setupTestEnv(t)
defer cleanup()
nm, err := NewNodeManager()
if err != nil {
t.Fatalf("failed to create node manager: %v", err)
}
if err := nm.GenerateIdentity("test-worker", RoleWorker); err != nil {
t.Fatalf("failed to generate identity: %v", err)
}
pr, err := NewPeerRegistryWithPath(t.TempDir() + "/peers.json")
if err != nil {
t.Fatalf("failed to create peer registry: %v", err)
}
transport := NewTransport(nm, pr, DefaultTransportConfig())
worker := NewWorker(nm, transport)
if worker == nil {
t.Fatal("NewWorker returned nil")
}
if worker.node != nm {
t.Error("worker.node not set correctly")
}
if worker.transport != transport {
t.Error("worker.transport not set correctly")
}
}
func TestWorker_SetMinerManager(t *testing.T) {
cleanup := setupTestEnv(t)
defer cleanup()
nm, err := NewNodeManager()
if err != nil {
t.Fatalf("failed to create node manager: %v", err)
}
if err := nm.GenerateIdentity("test-worker", RoleWorker); err != nil {
t.Fatalf("failed to generate identity: %v", err)
}
pr, err := NewPeerRegistryWithPath(t.TempDir() + "/peers.json")
if err != nil {
t.Fatalf("failed to create peer registry: %v", err)
}
transport := NewTransport(nm, pr, DefaultTransportConfig())
worker := NewWorker(nm, transport)
mockManager := &mockMinerManager{}
worker.SetMinerManager(mockManager)
if worker.minerManager != mockManager {
t.Error("minerManager not set correctly")
}
}
func TestWorker_SetProfileManager(t *testing.T) {
cleanup := setupTestEnv(t)
defer cleanup()
nm, err := NewNodeManager()
if err != nil {
t.Fatalf("failed to create node manager: %v", err)
}
if err := nm.GenerateIdentity("test-worker", RoleWorker); err != nil {
t.Fatalf("failed to generate identity: %v", err)
}
pr, err := NewPeerRegistryWithPath(t.TempDir() + "/peers.json")
if err != nil {
t.Fatalf("failed to create peer registry: %v", err)
}
transport := NewTransport(nm, pr, DefaultTransportConfig())
worker := NewWorker(nm, transport)
mockProfile := &mockProfileManager{}
worker.SetProfileManager(mockProfile)
if worker.profileManager != mockProfile {
t.Error("profileManager not set correctly")
}
}
func TestWorker_HandlePing(t *testing.T) {
cleanup := setupTestEnv(t)
defer cleanup()
nm, err := NewNodeManager()
if err != nil {
t.Fatalf("failed to create node manager: %v", err)
}
if err := nm.GenerateIdentity("test-worker", RoleWorker); err != nil {
t.Fatalf("failed to generate identity: %v", err)
}
pr, err := NewPeerRegistryWithPath(t.TempDir() + "/peers.json")
if err != nil {
t.Fatalf("failed to create peer registry: %v", err)
}
transport := NewTransport(nm, pr, DefaultTransportConfig())
worker := NewWorker(nm, transport)
// Create a ping message
identity := nm.GetIdentity()
if identity == nil {
t.Fatal("expected identity to be generated")
}
pingPayload := PingPayload{SentAt: time.Now().UnixMilli()}
pingMsg, err := NewMessage(MsgPing, "sender-id", identity.ID, pingPayload)
if err != nil {
t.Fatalf("failed to create ping message: %v", err)
}
// Call handlePing directly
response, err := worker.handlePing(pingMsg)
if err != nil {
t.Fatalf("handlePing returned error: %v", err)
}
if response == nil {
t.Fatal("handlePing returned nil response")
}
if response.Type != MsgPong {
t.Errorf("expected response type %s, got %s", MsgPong, response.Type)
}
var pong PongPayload
if err := response.ParsePayload(&pong); err != nil {
t.Fatalf("failed to parse pong payload: %v", err)
}
if pong.SentAt != pingPayload.SentAt {
t.Errorf("pong SentAt mismatch: expected %d, got %d", pingPayload.SentAt, pong.SentAt)
}
if pong.ReceivedAt == 0 {
t.Error("pong ReceivedAt not set")
}
}
func TestWorker_HandleGetStats(t *testing.T) {
cleanup := setupTestEnv(t)
defer cleanup()
nm, err := NewNodeManager()
if err != nil {
t.Fatalf("failed to create node manager: %v", err)
}
if err := nm.GenerateIdentity("test-worker", RoleWorker); err != nil {
t.Fatalf("failed to generate identity: %v", err)
}
pr, err := NewPeerRegistryWithPath(t.TempDir() + "/peers.json")
if err != nil {
t.Fatalf("failed to create peer registry: %v", err)
}
transport := NewTransport(nm, pr, DefaultTransportConfig())
worker := NewWorker(nm, transport)
// Create a get_stats message
identity := nm.GetIdentity()
if identity == nil {
t.Fatal("expected identity to be generated")
}
msg, err := NewMessage(MsgGetStats, "sender-id", identity.ID, nil)
if err != nil {
t.Fatalf("failed to create get_stats message: %v", err)
}
// Call handleGetStats directly (without miner manager)
response, err := worker.handleGetStats(msg)
if err != nil {
t.Fatalf("handleGetStats returned error: %v", err)
}
if response == nil {
t.Fatal("handleGetStats returned nil response")
}
if response.Type != MsgStats {
t.Errorf("expected response type %s, got %s", MsgStats, response.Type)
}
var stats StatsPayload
if err := response.ParsePayload(&stats); err != nil {
t.Fatalf("failed to parse stats payload: %v", err)
}
if stats.NodeID != identity.ID {
t.Errorf("stats NodeID mismatch: expected %s, got %s", identity.ID, stats.NodeID)
}
if stats.NodeName != identity.Name {
t.Errorf("stats NodeName mismatch: expected %s, got %s", identity.Name, stats.NodeName)
}
}
func TestWorker_HandleStartMiner_NoManager(t *testing.T) {
cleanup := setupTestEnv(t)
defer cleanup()
nm, err := NewNodeManager()
if err != nil {
t.Fatalf("failed to create node manager: %v", err)
}
if err := nm.GenerateIdentity("test-worker", RoleWorker); err != nil {
t.Fatalf("failed to generate identity: %v", err)
}
pr, err := NewPeerRegistryWithPath(t.TempDir() + "/peers.json")
if err != nil {
t.Fatalf("failed to create peer registry: %v", err)
}
transport := NewTransport(nm, pr, DefaultTransportConfig())
worker := NewWorker(nm, transport)
// Create a start_miner message
identity := nm.GetIdentity()
if identity == nil {
t.Fatal("expected identity to be generated")
}
payload := StartMinerPayload{MinerType: "xmrig", ProfileID: "test-profile"}
msg, err := NewMessage(MsgStartMiner, "sender-id", identity.ID, payload)
if err != nil {
t.Fatalf("failed to create start_miner message: %v", err)
}
// Without miner manager, should return error
_, err = worker.handleStartMiner(msg)
if err == nil {
t.Error("expected error when miner manager is nil")
}
}
func TestWorker_HandleStopMiner_NoManager(t *testing.T) {
cleanup := setupTestEnv(t)
defer cleanup()
nm, err := NewNodeManager()
if err != nil {
t.Fatalf("failed to create node manager: %v", err)
}
if err := nm.GenerateIdentity("test-worker", RoleWorker); err != nil {
t.Fatalf("failed to generate identity: %v", err)
}
pr, err := NewPeerRegistryWithPath(t.TempDir() + "/peers.json")
if err != nil {
t.Fatalf("failed to create peer registry: %v", err)
}
transport := NewTransport(nm, pr, DefaultTransportConfig())
worker := NewWorker(nm, transport)
// Create a stop_miner message
identity := nm.GetIdentity()
if identity == nil {
t.Fatal("expected identity to be generated")
}
payload := StopMinerPayload{MinerName: "test-miner"}
msg, err := NewMessage(MsgStopMiner, "sender-id", identity.ID, payload)
if err != nil {
t.Fatalf("failed to create stop_miner message: %v", err)
}
// Without miner manager, should return error
_, err = worker.handleStopMiner(msg)
if err == nil {
t.Error("expected error when miner manager is nil")
}
}
func TestWorker_HandleGetLogs_NoManager(t *testing.T) {
cleanup := setupTestEnv(t)
defer cleanup()
nm, err := NewNodeManager()
if err != nil {
t.Fatalf("failed to create node manager: %v", err)
}
if err := nm.GenerateIdentity("test-worker", RoleWorker); err != nil {
t.Fatalf("failed to generate identity: %v", err)
}
pr, err := NewPeerRegistryWithPath(t.TempDir() + "/peers.json")
if err != nil {
t.Fatalf("failed to create peer registry: %v", err)
}
transport := NewTransport(nm, pr, DefaultTransportConfig())
worker := NewWorker(nm, transport)
// Create a get_logs message
identity := nm.GetIdentity()
if identity == nil {
t.Fatal("expected identity to be generated")
}
payload := GetLogsPayload{MinerName: "test-miner", Lines: 100}
msg, err := NewMessage(MsgGetLogs, "sender-id", identity.ID, payload)
if err != nil {
t.Fatalf("failed to create get_logs message: %v", err)
}
// Without miner manager, should return error
_, err = worker.handleGetLogs(msg)
if err == nil {
t.Error("expected error when miner manager is nil")
}
}
func TestWorker_HandleDeploy_Profile(t *testing.T) {
cleanup := setupTestEnv(t)
defer cleanup()
nm, err := NewNodeManager()
if err != nil {
t.Fatalf("failed to create node manager: %v", err)
}
if err := nm.GenerateIdentity("test-worker", RoleWorker); err != nil {
t.Fatalf("failed to generate identity: %v", err)
}
pr, err := NewPeerRegistryWithPath(t.TempDir() + "/peers.json")
if err != nil {
t.Fatalf("failed to create peer registry: %v", err)
}
transport := NewTransport(nm, pr, DefaultTransportConfig())
worker := NewWorker(nm, transport)
// Create a deploy message for profile
identity := nm.GetIdentity()
if identity == nil {
t.Fatal("expected identity to be generated")
}
payload := DeployPayload{
BundleType: "profile",
Data: []byte(`{"id": "test", "name": "Test Profile"}`),
Name: "test-profile",
}
msg, err := NewMessage(MsgDeploy, "sender-id", identity.ID, payload)
if err != nil {
t.Fatalf("failed to create deploy message: %v", err)
}
// Without profile manager, should return error
_, err = worker.handleDeploy(nil, msg)
if err == nil {
t.Error("expected error when profile manager is nil")
}
}
func TestWorker_HandleDeploy_UnknownType(t *testing.T) {
cleanup := setupTestEnv(t)
defer cleanup()
nm, err := NewNodeManager()
if err != nil {
t.Fatalf("failed to create node manager: %v", err)
}
if err := nm.GenerateIdentity("test-worker", RoleWorker); err != nil {
t.Fatalf("failed to generate identity: %v", err)
}
pr, err := NewPeerRegistryWithPath(t.TempDir() + "/peers.json")
if err != nil {
t.Fatalf("failed to create peer registry: %v", err)
}
transport := NewTransport(nm, pr, DefaultTransportConfig())
worker := NewWorker(nm, transport)
// Create a deploy message with unknown type
identity := nm.GetIdentity()
if identity == nil {
t.Fatal("expected identity to be generated")
}
payload := DeployPayload{
BundleType: "unknown",
Data: []byte(`{}`),
Name: "test",
}
msg, err := NewMessage(MsgDeploy, "sender-id", identity.ID, payload)
if err != nil {
t.Fatalf("failed to create deploy message: %v", err)
}
_, err = worker.handleDeploy(nil, msg)
if err == nil {
t.Error("expected error for unknown bundle type")
}
}
func TestConvertMinerStats(t *testing.T) {
tests := []struct {
name string
rawStats interface{}
wantHash float64
}{
{
name: "MapWithHashrate",
rawStats: map[string]interface{}{
"hashrate": 100.5,
"shares": 10,
"rejected": 2,
"uptime": 3600,
"pool": "test-pool",
"algorithm": "rx/0",
},
wantHash: 100.5,
},
{
name: "EmptyMap",
rawStats: map[string]interface{}{},
wantHash: 0,
},
{
name: "NonMap",
rawStats: "not a map",
wantHash: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mock := &mockMinerInstance{name: "test", minerType: "xmrig"}
result := convertMinerStats(mock, tt.rawStats)
if result.Name != "test" {
t.Errorf("expected name 'test', got '%s'", result.Name)
}
if result.Hashrate != tt.wantHash {
t.Errorf("expected hashrate %f, got %f", tt.wantHash, result.Hashrate)
}
})
}
}
// Mock implementations for testing
type mockMinerManager struct {
miners []MinerInstance
}
func (m *mockMinerManager) StartMiner(minerType string, config interface{}) (MinerInstance, error) {
return nil, nil
}
func (m *mockMinerManager) StopMiner(name string) error {
return nil
}
func (m *mockMinerManager) ListMiners() []MinerInstance {
return m.miners
}
func (m *mockMinerManager) GetMiner(name string) (MinerInstance, error) {
for _, miner := range m.miners {
if miner.GetName() == name {
return miner, nil
}
}
return nil, nil
}
type mockMinerInstance struct {
name string
minerType string
stats interface{}
}
func (m *mockMinerInstance) GetName() string { return m.name }
func (m *mockMinerInstance) GetType() string { return m.minerType }
func (m *mockMinerInstance) GetStats() (interface{}, error) { return m.stats, nil }
func (m *mockMinerInstance) GetConsoleHistory(lines int) []string { return []string{} }
type mockProfileManager struct{}
func (m *mockProfileManager) GetProfile(id string) (interface{}, error) {
return nil, nil
}
func (m *mockProfileManager) SaveProfile(profile interface{}) error {
return nil
}