Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
635 lines
19 KiB
Go
635 lines
19 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"gcy_hpc_server/internal/service"
|
|
"gcy_hpc_server/internal/slurm"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
"go.uber.org/zap/zaptest/observer"
|
|
)
|
|
|
|
func setupClusterHandler(slurmHandler http.HandlerFunc) (*httptest.Server, *ClusterHandler) {
|
|
srv := httptest.NewServer(slurmHandler)
|
|
client, _ := slurm.NewClient(srv.URL, srv.Client())
|
|
clusterSvc := service.NewClusterService(client, zap.NewNop())
|
|
return srv, NewClusterHandler(clusterSvc, zap.NewNop())
|
|
}
|
|
|
|
func setupClusterHandlerWithObserver(slurmHandler http.HandlerFunc) (*httptest.Server, *ClusterHandler, *observer.ObservedLogs) {
|
|
core, recorded := observer.New(zapcore.DebugLevel)
|
|
l := zap.New(core)
|
|
srv := httptest.NewServer(slurmHandler)
|
|
client, _ := slurm.NewClient(srv.URL, srv.Client())
|
|
clusterSvc := service.NewClusterService(client, l)
|
|
return srv, NewClusterHandler(clusterSvc, l), recorded
|
|
}
|
|
|
|
func setupClusterRouter(h *ClusterHandler) *gin.Engine {
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
v1 := r.Group("/api/v1")
|
|
v1.GET("/nodes", h.GetNodes)
|
|
v1.GET("/nodes/:name", h.GetNode)
|
|
v1.GET("/partitions", h.GetPartitions)
|
|
v1.GET("/partitions/:name", h.GetPartition)
|
|
v1.GET("/diag", h.GetDiag)
|
|
return r
|
|
}
|
|
|
|
func TestGetNodes_Success(t *testing.T) {
|
|
srv, h := setupClusterHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"nodes": []map[string]interface{}{
|
|
{"name": "node1", "state": []string{"IDLE"}, "cpus": 64, "real_memory": 128000},
|
|
},
|
|
"last_update": map[string]interface{}{},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/nodes", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp map[string]interface{}
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if resp["success"] != true {
|
|
t.Fatal("expected success=true")
|
|
}
|
|
}
|
|
|
|
func TestGetNode_Success(t *testing.T) {
|
|
srv, h := setupClusterHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"nodes": []map[string]interface{}{
|
|
{"name": "node1", "state": []string{"IDLE"}, "cpus": 64, "real_memory": 128000},
|
|
},
|
|
"last_update": map[string]interface{}{},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/nodes/node1", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp map[string]interface{}
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if resp["success"] != true {
|
|
t.Fatal("expected success=true")
|
|
}
|
|
}
|
|
|
|
func TestGetNode_NotFound(t *testing.T) {
|
|
srv, h := setupClusterHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"nodes": []map[string]interface{}{},
|
|
"last_update": map[string]interface{}{},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/nodes/nonexistent", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Fatalf("expected 404, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestGetPartitions_Success(t *testing.T) {
|
|
srv, h := setupClusterHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"partitions": []map[string]interface{}{
|
|
{
|
|
"name": "normal",
|
|
"partition": map[string]interface{}{
|
|
"state": []string{"UP"},
|
|
},
|
|
"nodes": map[string]interface{}{
|
|
"configured": "node[1-10]",
|
|
"total": int32(10),
|
|
},
|
|
"cpus": map[string]interface{}{
|
|
"total": int32(640),
|
|
},
|
|
"maximums": map[string]interface{}{
|
|
"time": map[string]interface{}{"number": int64(60)},
|
|
},
|
|
},
|
|
},
|
|
"last_update": map[string]interface{}{},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/partitions", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp map[string]interface{}
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if resp["success"] != true {
|
|
t.Fatal("expected success=true")
|
|
}
|
|
}
|
|
|
|
func TestGetPartition_Success(t *testing.T) {
|
|
srv, h := setupClusterHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"partitions": []map[string]interface{}{
|
|
{
|
|
"name": "normal",
|
|
"partition": map[string]interface{}{
|
|
"state": []string{"UP"},
|
|
},
|
|
"nodes": map[string]interface{}{
|
|
"configured": "node[1-10]",
|
|
"total": int32(10),
|
|
},
|
|
"cpus": map[string]interface{}{
|
|
"total": int32(640),
|
|
},
|
|
"maximums": map[string]interface{}{
|
|
"time": map[string]interface{}{"number": int64(60)},
|
|
},
|
|
},
|
|
},
|
|
"last_update": map[string]interface{}{},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/partitions/normal", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp map[string]interface{}
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if resp["success"] != true {
|
|
t.Fatal("expected success=true")
|
|
}
|
|
}
|
|
|
|
func TestGetPartition_NotFound(t *testing.T) {
|
|
srv, h := setupClusterHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"partitions": []map[string]interface{}{},
|
|
"last_update": map[string]interface{}{},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/partitions/nonexistent", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Fatalf("expected 404, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestGetDiag_Success(t *testing.T) {
|
|
srv, h := setupClusterHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"statistics": map[string]interface{}{
|
|
"server_thread_count": 3,
|
|
"agent_queue_size": 0,
|
|
"jobs_submitted": 100,
|
|
"jobs_started": 90,
|
|
"jobs_completed": 85,
|
|
"schedule_cycle_last": 10,
|
|
"schedule_cycle_total": 500,
|
|
},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/diag", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp map[string]interface{}
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if resp["success"] != true {
|
|
t.Fatal("expected success=true")
|
|
}
|
|
}
|
|
|
|
// --- Logging tests ---
|
|
|
|
func TestClusterHandler_GetNodes_InternalError_LogsError(t *testing.T) {
|
|
srv, h, recorded := setupClusterHandlerWithObserver(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
fmt.Fprint(w, `{"errors":[{"error":"internal"}]}`)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/nodes", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusInternalServerError {
|
|
t.Fatalf("expected 500, got %d", w.Code)
|
|
}
|
|
|
|
handlerLogs := recorded.FilterMessage("handler error")
|
|
if handlerLogs.Len() != 1 {
|
|
t.Fatalf("expected 1 handler error log, got %d", handlerLogs.Len())
|
|
}
|
|
entry := handlerLogs.All()[0]
|
|
if entry.Level != zapcore.ErrorLevel {
|
|
t.Fatalf("expected Error level, got %v", entry.Level)
|
|
}
|
|
assertField(t, entry.Context, "method", "GetNodes")
|
|
}
|
|
|
|
func TestClusterHandler_GetNodes_Success_NoLogs(t *testing.T) {
|
|
srv, h, recorded := setupClusterHandlerWithObserver(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"nodes": []map[string]interface{}{
|
|
{"name": "node1"},
|
|
},
|
|
"last_update": map[string]interface{}{},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/nodes", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
|
|
if recorded.Len() != 2 {
|
|
t.Fatalf("expected 2 log entries on success, got %d", recorded.Len())
|
|
}
|
|
}
|
|
|
|
func TestClusterHandler_GetNode_InternalError_LogsError(t *testing.T) {
|
|
srv, h, recorded := setupClusterHandlerWithObserver(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
fmt.Fprint(w, `{"errors":[{"error":"internal"}]}`)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/nodes/node1", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusInternalServerError {
|
|
t.Fatalf("expected 500, got %d", w.Code)
|
|
}
|
|
|
|
handlerLogs := recorded.FilterMessage("handler error")
|
|
if handlerLogs.Len() != 1 {
|
|
t.Fatalf("expected 1 handler error log, got %d", handlerLogs.Len())
|
|
}
|
|
entry := handlerLogs.All()[0]
|
|
if entry.Level != zapcore.ErrorLevel {
|
|
t.Fatalf("expected Error level, got %v", entry.Level)
|
|
}
|
|
assertField(t, entry.Context, "method", "GetNode")
|
|
}
|
|
|
|
func TestClusterHandler_GetNode_NotFound_LogsWarn(t *testing.T) {
|
|
srv, h, recorded := setupClusterHandlerWithObserver(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"nodes": []map[string]interface{}{},
|
|
"last_update": map[string]interface{}{},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/nodes/nonexistent", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Fatalf("expected 404, got %d", w.Code)
|
|
}
|
|
|
|
if recorded.Len() != 3 {
|
|
t.Fatalf("expected 3 log entries, got %d", recorded.Len())
|
|
}
|
|
entry := recorded.All()[2]
|
|
if entry.Level != zapcore.WarnLevel {
|
|
t.Fatalf("expected Warn level, got %v", entry.Level)
|
|
}
|
|
if entry.Message != "not found" {
|
|
t.Fatalf("expected message 'not found', got %q", entry.Message)
|
|
}
|
|
assertField(t, entry.Context, "method", "GetNode")
|
|
assertField(t, entry.Context, "name", "nonexistent")
|
|
}
|
|
|
|
func TestClusterHandler_GetNode_Success_NoLogs(t *testing.T) {
|
|
srv, h, recorded := setupClusterHandlerWithObserver(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"nodes": []map[string]interface{}{
|
|
{"name": "node1", "state": []string{"IDLE"}, "cpus": 64, "real_memory": 128000},
|
|
},
|
|
"last_update": map[string]interface{}{},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/nodes/node1", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
|
|
if recorded.Len() != 2 {
|
|
t.Fatalf("expected 2 log entries on success, got %d", recorded.Len())
|
|
}
|
|
}
|
|
|
|
func TestClusterHandler_GetPartitions_InternalError_LogsError(t *testing.T) {
|
|
srv, h, recorded := setupClusterHandlerWithObserver(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
fmt.Fprint(w, `{"errors":[{"error":"internal"}]}`)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/partitions", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusInternalServerError {
|
|
t.Fatalf("expected 500, got %d", w.Code)
|
|
}
|
|
|
|
handlerLogs := recorded.FilterMessage("handler error")
|
|
if handlerLogs.Len() != 1 {
|
|
t.Fatalf("expected 1 handler error log, got %d", handlerLogs.Len())
|
|
}
|
|
entry := handlerLogs.All()[0]
|
|
if entry.Level != zapcore.ErrorLevel {
|
|
t.Fatalf("expected Error level, got %v", entry.Level)
|
|
}
|
|
assertField(t, entry.Context, "method", "GetPartitions")
|
|
}
|
|
|
|
func TestClusterHandler_GetPartitions_Success_NoLogs(t *testing.T) {
|
|
srv, h, recorded := setupClusterHandlerWithObserver(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"partitions": []map[string]interface{}{
|
|
{"name": "normal"},
|
|
},
|
|
"last_update": map[string]interface{}{},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/partitions", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
|
|
if recorded.Len() != 2 {
|
|
t.Fatalf("expected 2 log entries on success, got %d", recorded.Len())
|
|
}
|
|
}
|
|
|
|
func TestClusterHandler_GetPartition_InternalError_LogsError(t *testing.T) {
|
|
srv, h, recorded := setupClusterHandlerWithObserver(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
fmt.Fprint(w, `{"errors":[{"error":"internal"}]}`)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/partitions/normal", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusInternalServerError {
|
|
t.Fatalf("expected 500, got %d", w.Code)
|
|
}
|
|
|
|
handlerLogs := recorded.FilterMessage("handler error")
|
|
if handlerLogs.Len() != 1 {
|
|
t.Fatalf("expected 1 handler error log, got %d", handlerLogs.Len())
|
|
}
|
|
entry := handlerLogs.All()[0]
|
|
if entry.Level != zapcore.ErrorLevel {
|
|
t.Fatalf("expected Error level, got %v", entry.Level)
|
|
}
|
|
assertField(t, entry.Context, "method", "GetPartition")
|
|
}
|
|
|
|
func TestClusterHandler_GetPartition_NotFound_LogsWarn(t *testing.T) {
|
|
srv, h, recorded := setupClusterHandlerWithObserver(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"partitions": []map[string]interface{}{},
|
|
"last_update": map[string]interface{}{},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/partitions/nonexistent", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Fatalf("expected 404, got %d", w.Code)
|
|
}
|
|
|
|
if recorded.Len() != 3 {
|
|
t.Fatalf("expected 3 log entries, got %d", recorded.Len())
|
|
}
|
|
entry := recorded.All()[2]
|
|
if entry.Level != zapcore.WarnLevel {
|
|
t.Fatalf("expected Warn level, got %v", entry.Level)
|
|
}
|
|
if entry.Message != "not found" {
|
|
t.Fatalf("expected message 'not found', got %q", entry.Message)
|
|
}
|
|
assertField(t, entry.Context, "method", "GetPartition")
|
|
assertField(t, entry.Context, "name", "nonexistent")
|
|
}
|
|
|
|
func TestClusterHandler_GetPartition_Success_NoLogs(t *testing.T) {
|
|
srv, h, recorded := setupClusterHandlerWithObserver(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"partitions": []map[string]interface{}{
|
|
{
|
|
"name": "normal",
|
|
"partition": map[string]interface{}{
|
|
"state": []string{"UP"},
|
|
},
|
|
"nodes": map[string]interface{}{
|
|
"configured": "node[1-10]",
|
|
"total": int32(10),
|
|
},
|
|
"cpus": map[string]interface{}{
|
|
"total": int32(640),
|
|
},
|
|
"maximums": map[string]interface{}{
|
|
"time": map[string]interface{}{"number": int64(60)},
|
|
},
|
|
},
|
|
},
|
|
"last_update": map[string]interface{}{},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/partitions/normal", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
|
|
if recorded.Len() != 2 {
|
|
t.Fatalf("expected 2 log entries on success, got %d", recorded.Len())
|
|
}
|
|
}
|
|
|
|
func TestClusterHandler_GetDiag_InternalError_LogsError(t *testing.T) {
|
|
srv, h, recorded := setupClusterHandlerWithObserver(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
fmt.Fprint(w, `{"errors":[{"error":"internal"}]}`)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/diag", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusInternalServerError {
|
|
t.Fatalf("expected 500, got %d", w.Code)
|
|
}
|
|
|
|
handlerLogs := recorded.FilterMessage("handler error")
|
|
if handlerLogs.Len() != 1 {
|
|
t.Fatalf("expected 1 handler error log, got %d", handlerLogs.Len())
|
|
}
|
|
entry := handlerLogs.All()[0]
|
|
if entry.Level != zapcore.ErrorLevel {
|
|
t.Fatalf("expected Error level, got %v", entry.Level)
|
|
}
|
|
assertField(t, entry.Context, "method", "GetDiag")
|
|
}
|
|
|
|
func TestClusterHandler_GetDiag_Success_NoLogs(t *testing.T) {
|
|
srv, h, recorded := setupClusterHandlerWithObserver(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"statistics": map[string]interface{}{
|
|
"server_thread_count": 3,
|
|
"agent_queue_size": 0,
|
|
"jobs_submitted": 100,
|
|
"jobs_started": 90,
|
|
"jobs_completed": 85,
|
|
"schedule_cycle_last": 10,
|
|
"schedule_cycle_total": 500,
|
|
},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
router := setupClusterRouter(h)
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/diag", nil)
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
|
|
if recorded.Len() != 2 {
|
|
t.Fatalf("expected 2 log entries on success, got %d", recorded.Len())
|
|
}
|
|
}
|
|
|
|
// assertField checks that a zap Field slice contains a string field with the given key and value.
|
|
func assertField(t *testing.T, fields []zapcore.Field, key, value string) {
|
|
t.Helper()
|
|
for _, f := range fields {
|
|
if f.Key == key && f.String == value {
|
|
return
|
|
}
|
|
}
|
|
t.Fatalf("expected field %q=%q in context, got %v", key, value, fields)
|
|
}
|