Mining/pkg/mining/service_test.go
Claude 3673757d2a
ax(batch): replace prose comments with usage examples across all packages
Converts comments that restate function signatures into concrete usage
examples per AX Principle 2. Affected files: database_race_test.go,
interface_test.go, errors_test.go, xmrig_test.go, service_test.go,
manager_test.go.

Co-Authored-By: Charon <charon@lethean.io>
2026-04-02 18:16:56 +01:00

267 lines
9.2 KiB
Go

package mining
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/gin-gonic/gin"
)
// mock := &MockMiner{GetNameFunc: func() string { return "test" }}
// mock.GetName() == "test"
type MockMiner struct {
InstallFunc func() error
UninstallFunc func() error
StartFunc func(config *Config) error
StopFunc func() error
GetStatsFunc func(ctx context.Context) (*PerformanceMetrics, error)
GetTypeFunc func() string
GetNameFunc func() string
GetPathFunc func() string
GetBinaryPathFunc func() string
CheckInstallationFunc func() (*InstallationDetails, error)
GetLatestVersionFunc func() (string, error)
GetHashrateHistoryFunc func() []HashratePoint
AddHashratePointFunc func(point HashratePoint)
ReduceHashrateHistoryFunc func(now time.Time)
GetLogsFunc func() []string
WriteStdinFunc func(input string) error
}
func (m *MockMiner) Install() error { return m.InstallFunc() }
func (m *MockMiner) Uninstall() error { return m.UninstallFunc() }
func (m *MockMiner) Start(config *Config) error { return m.StartFunc(config) }
func (m *MockMiner) Stop() error { return m.StopFunc() }
func (m *MockMiner) GetStats(ctx context.Context) (*PerformanceMetrics, error) {
return m.GetStatsFunc(ctx)
}
func (m *MockMiner) GetType() string {
if m.GetTypeFunc != nil {
return m.GetTypeFunc()
}
return "mock"
}
func (m *MockMiner) GetName() string { return m.GetNameFunc() }
func (m *MockMiner) GetPath() string { return m.GetPathFunc() }
func (m *MockMiner) GetBinaryPath() string { return m.GetBinaryPathFunc() }
func (m *MockMiner) CheckInstallation() (*InstallationDetails, error) {
return m.CheckInstallationFunc()
}
func (m *MockMiner) GetLatestVersion() (string, error) { return m.GetLatestVersionFunc() }
func (m *MockMiner) GetHashrateHistory() []HashratePoint { return m.GetHashrateHistoryFunc() }
func (m *MockMiner) AddHashratePoint(point HashratePoint) { m.AddHashratePointFunc(point) }
func (m *MockMiner) ReduceHashrateHistory(now time.Time) { m.ReduceHashrateHistoryFunc(now) }
func (m *MockMiner) GetLogs() []string { return m.GetLogsFunc() }
func (m *MockMiner) WriteStdin(input string) error { return m.WriteStdinFunc(input) }
// mock := &MockManager{ListMinersFunc: func() []Miner { return nil }}
// mock.ListMiners()
type MockManager struct {
ListMinersFunc func() []Miner
ListAvailableMinersFunc func() []AvailableMiner
StartMinerFunc func(ctx context.Context, minerType string, config *Config) (Miner, error)
StopMinerFunc func(ctx context.Context, minerName string) error
GetMinerFunc func(minerName string) (Miner, error)
GetMinerHashrateHistoryFunc func(minerName string) ([]HashratePoint, error)
UninstallMinerFunc func(ctx context.Context, minerType string) error
StopFunc func()
}
func (m *MockManager) ListMiners() []Miner { return m.ListMinersFunc() }
func (m *MockManager) ListAvailableMiners() []AvailableMiner { return m.ListAvailableMinersFunc() }
func (m *MockManager) StartMiner(ctx context.Context, minerType string, config *Config) (Miner, error) {
return m.StartMinerFunc(ctx, minerType, config)
}
func (m *MockManager) StopMiner(ctx context.Context, minerName string) error {
return m.StopMinerFunc(ctx, minerName)
}
func (m *MockManager) GetMiner(minerName string) (Miner, error) {
return m.GetMinerFunc(minerName)
}
func (m *MockManager) GetMinerHashrateHistory(minerName string) ([]HashratePoint, error) {
return m.GetMinerHashrateHistoryFunc(minerName)
}
func (m *MockManager) UninstallMiner(ctx context.Context, minerType string) error {
return m.UninstallMinerFunc(ctx, minerType)
}
func (m *MockManager) Stop() { m.StopFunc() }
var _ ManagerInterface = (*MockManager)(nil)
func setupTestRouter() (*gin.Engine, *MockManager) {
gin.SetMode(gin.TestMode)
router := gin.Default()
mockManager := &MockManager{
ListMinersFunc: func() []Miner { return []Miner{} },
ListAvailableMinersFunc: func() []AvailableMiner { return []AvailableMiner{} },
StartMinerFunc: func(ctx context.Context, minerType string, config *Config) (Miner, error) {
return nil, nil
},
StopMinerFunc: func(ctx context.Context, minerName string) error { return nil },
GetMinerFunc: func(minerName string) (Miner, error) { return nil, nil },
GetMinerHashrateHistoryFunc: func(minerName string) ([]HashratePoint, error) {
return nil, nil
},
UninstallMinerFunc: func(ctx context.Context, minerType string) error { return nil },
StopFunc: func() {},
}
service := &Service{
Manager: mockManager,
Router: router,
APIBasePath: "/",
SwaggerUIPath: "/swagger",
}
service.SetupRoutes()
return router, mockManager
}
func TestService_HandleListMiners_Good(t *testing.T) {
router, mockManager := setupTestRouter()
mockManager.ListMinersFunc = func() []Miner {
return []Miner{&XMRigMiner{BaseMiner: BaseMiner{Name: "test-miner"}}}
}
req, _ := http.NewRequest("GET", "/miners", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status %d, got %d", http.StatusOK, w.Code)
}
}
func TestService_HandleGetInfo_Good(t *testing.T) {
router, _ := setupTestRouter()
// Case 1: Successful response
req, _ := http.NewRequest("GET", "/info", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status %d, got %d", http.StatusOK, w.Code)
}
}
func TestService_HandleDoctor_Good(t *testing.T) {
router, mockManager := setupTestRouter()
mockManager.ListAvailableMinersFunc = func() []AvailableMiner {
return []AvailableMiner{{Name: "xmrig"}}
}
// Case 1: Successful response
req, _ := http.NewRequest("POST", "/doctor", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status %d, got %d", http.StatusOK, w.Code)
}
}
func TestService_HandleInstallMiner_Good(t *testing.T) {
router, _ := setupTestRouter()
// Test installing a miner
req, _ := http.NewRequest("POST", "/miners/xmrig/install", nil)
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Installation endpoint should be accessible
if w.Code != http.StatusOK && w.Code != http.StatusInternalServerError {
t.Errorf("expected status 200 or 500, got %d", w.Code)
}
}
func TestService_HandleStopMiner_Good(t *testing.T) {
router, mockManager := setupTestRouter()
mockManager.StopMinerFunc = func(ctx context.Context, minerName string) error {
return nil
}
req, _ := http.NewRequest("DELETE", "/miners/test-miner", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status %d, got %d", http.StatusOK, w.Code)
}
}
func TestService_HandleGetMinerStats_Good(t *testing.T) {
router, mockManager := setupTestRouter()
mockManager.GetMinerFunc = func(minerName string) (Miner, error) {
return &MockMiner{
GetStatsFunc: func(ctx context.Context) (*PerformanceMetrics, error) {
return &PerformanceMetrics{Hashrate: 100}, nil
},
GetLogsFunc: func() []string { return []string{} },
}, nil
}
req, _ := http.NewRequest("GET", "/miners/test-miner/stats", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status %d, got %d", http.StatusOK, w.Code)
}
}
func TestService_HandleGetMinerHashrateHistory_Good(t *testing.T) {
router, mockManager := setupTestRouter()
mockManager.GetMinerHashrateHistoryFunc = func(minerName string) ([]HashratePoint, error) {
return []HashratePoint{{Timestamp: time.Now(), Hashrate: 100}}, nil
}
req, _ := http.NewRequest("GET", "/miners/test-miner/hashrate-history", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected status %d, got %d", http.StatusOK, w.Code)
}
}
func TestGenerateRequestID_FormatAndUniqueness(t *testing.T) {
firstRequestID := generateRequestID()
secondRequestID := generateRequestID()
if firstRequestID == secondRequestID {
t.Fatalf("expected unique request IDs, got %q twice", firstRequestID)
}
for _, requestID := range []string{firstRequestID, secondRequestID} {
parts := strings.Split(requestID, "-")
if len(parts) != 2 {
t.Fatalf("expected request ID format timestamp-randomhex, got %q", requestID)
}
if len(parts[1]) != 16 {
t.Fatalf("expected 16 hex characters, got %q", parts[1])
}
}
}
func TestRequestIDMiddleware_PreservesProvidedHeader(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
router.Use(requestIDMiddleware())
router.GET("/health", func(context *gin.Context) {
context.Status(http.StatusOK)
})
request, _ := http.NewRequest(http.MethodGet, "/health", nil)
request.Header.Set("X-Request-ID", "user-supplied-request-id")
responseRecorder := httptest.NewRecorder()
router.ServeHTTP(responseRecorder, request)
if responseRecorder.Header().Get("X-Request-ID") != "user-supplied-request-id" {
t.Fatalf("expected middleware to preserve request ID header, got %q", responseRecorder.Header().Get("X-Request-ID"))
}
}