package handlers import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "time" "github.com/gin-gonic/gin" "armature/store" ) // mockClusterStore implements store.ClusterStore for handler tests. type mockClusterStore struct { nodes []store.ClusterNode } func (m *mockClusterStore) Register(_ context.Context, _, _ string) error { return nil } func (m *mockClusterStore) Heartbeat(_ context.Context, _ string, _ json.RawMessage) (int64, error) { return 1, nil } func (m *mockClusterStore) SweepStale(_ context.Context, _ time.Duration) (int64, error) { return 0, nil } func (m *mockClusterStore) ListNodes(_ context.Context) ([]store.ClusterNode, error) { return m.nodes, nil } func (m *mockClusterStore) Deregister(_ context.Context, _ string) error { return nil } func TestClusterListNodes(t *testing.T) { gin.SetMode(gin.TestMode) mock := &mockClusterStore{ nodes: []store.ClusterNode{ { NodeID: "node-1", Endpoint: "http://node-1:8080", Seq: 1, RegisteredAt: time.Now(), Heartbeat: time.Now(), Stats: json.RawMessage(`{"ws_clients":3}`), }, { NodeID: "node-2", Endpoint: "http://node-2:8080", Seq: 2, RegisteredAt: time.Now(), Heartbeat: time.Now(), Stats: json.RawMessage(`{"ws_clients":7}`), }, }, } stores := store.Stores{Cluster: mock} h := NewClusterHandler(stores) r := gin.New() r.GET("/api/v1/admin/cluster", h.ListNodes) req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/cluster", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("status = %d, want %d", w.Code, http.StatusOK) } var resp struct { Data []store.ClusterNode `json:"data"` } if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("unmarshal response: %v", err) } if len(resp.Data) != 2 { t.Fatalf("data length = %d, want 2", len(resp.Data)) } if resp.Data[0].NodeID != "node-1" { t.Errorf("data[0].node_id = %q, want %q", resp.Data[0].NodeID, "node-1") } if resp.Data[1].NodeID != "node-2" { t.Errorf("data[1].node_id = %q, want %q", resp.Data[1].NodeID, "node-2") } } func TestClusterListNodesEmpty(t *testing.T) { gin.SetMode(gin.TestMode) mock := &mockClusterStore{nodes: nil} stores := store.Stores{Cluster: mock} h := NewClusterHandler(stores) r := gin.New() r.GET("/api/v1/admin/cluster", h.ListNodes) req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/cluster", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("status = %d, want %d", w.Code, http.StatusOK) } var resp struct { Data []json.RawMessage `json:"data"` } if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("unmarshal: %v", err) } // Should be empty array, not null if resp.Data == nil { t.Error("data should be [], not null") } }