// Copyright 2026 Arctel.net // SPDX-License-Identifier: Apache-2.0 package dashboard import ( "context" "testing" "time" "github.com/Rain-kl/Wavelet/internal/db" "github.com/Rain-kl/Wavelet/internal/model" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" ) func setupDashboardTestDB(t *testing.T) func() { sqliteDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ DisableForeignKeyConstraintWhenMigrating: true, }) require.NoError(t, err) require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareNode{})) db.SetDB(sqliteDB) resetAccessLogStore := model.SetAccessLogStoreForTest(model.NewMemoryAccessLogStore()) resetObservabilityStore := model.SetObservabilityStoreForTest(model.NewMemoryObservabilityStore()) return func() { resetObservabilityStore() resetAccessLogStore() db.SetDB(nil) } } func TestGetOverviewStructure(t *testing.T) { cleanup := setupDashboardTestDB(t) defer cleanup() ctx := context.Background() now := time.Now().UTC() lastSeen := now.Add(-time.Minute) require.NoError(t, db.DB(ctx).Create(&model.OpenFlareNode{ NodeID: "node-dashboard-1", Name: "Edge 1", IP: "10.0.0.1", Status: "online", OpenrestyStatus: "healthy", CurrentVersion: "v1.0.0", LastSeenAt: &lastSeen, }).Error) require.NoError(t, db.DB(ctx).Create(&model.OpenFlareNode{ NodeID: "node-dashboard-2", Name: "Edge 2", IP: "10.0.0.2", Status: "pending", OpenrestyStatus: "unknown", }).Error) // Seed older + newer snapshots per node; health must use latest-per-node, not a global raw limit. require.NoError(t, model.InsertOpenFlareMetricSnapshot(ctx, &model.OpenFlareMetricSnapshot{ NodeID: "node-dashboard-1", CapturedAt: now.Add(-2 * time.Hour), CPUUsagePercent: 10, MemoryUsedBytes: 1, MemoryTotalBytes: 10, })) require.NoError(t, model.InsertOpenFlareMetricSnapshot(ctx, &model.OpenFlareMetricSnapshot{ NodeID: "node-dashboard-1", CapturedAt: now.Add(-time.Minute), CPUUsagePercent: 55, MemoryUsedBytes: 5, MemoryTotalBytes: 10, StorageUsedBytes: 2, StorageTotalBytes: 10, })) require.NoError(t, model.InsertOpenFlareRequestReport(ctx, &model.OpenFlareRequestReport{ NodeID: "node-dashboard-1", WindowStartedAt: now.Add(-2 * time.Minute), WindowEndedAt: now.Add(-time.Minute), RequestCount: 12, ErrorCount: 1, UniqueVisitorCount: 4, })) overview, err := GetOverview(ctx) require.NoError(t, err) require.NotNil(t, overview) assert.False(t, overview.GeneratedAt.(time.Time).IsZero()) assert.Equal(t, 2, overview.Summary.TotalNodes) assert.Equal(t, 1, overview.Summary.OnlineNodes) assert.Equal(t, 1, overview.Summary.PendingNodes) assert.Equal(t, 0, overview.Summary.OfflineNodes) assert.Equal(t, 0, overview.Summary.UnhealthyNodes) assert.Equal(t, int64(12), overview.Traffic.RequestCount) assert.Equal(t, int64(4), overview.Traffic.UniqueVisitors) assert.Equal(t, int64(1), overview.Traffic.ErrorCount) assert.InDelta(t, 0.2, overview.Traffic.EstimatedQPS, 0.0001) assert.Equal(t, 1, overview.Traffic.ReportedNodes) assert.Equal(t, 55.0, overview.Capacity.AverageCPUUsagePercent) assert.Equal(t, 50.0, overview.Capacity.AverageMemoryUsagePercent) assert.Equal(t, 0, overview.Capacity.HighCPUNodes) assert.Equal(t, 0, overview.Capacity.HighMemoryNodes) assert.Equal(t, 0, overview.Capacity.HighStorageNodes) require.NotNil(t, overview.Distributions.StatusCodes) require.NotNil(t, overview.Distributions.TopDomains) require.NotNil(t, overview.Distributions.SourceCountries) assert.Empty(t, overview.Distributions.StatusCodes) assert.Empty(t, overview.Distributions.TopDomains) assert.Empty(t, overview.Distributions.SourceCountries) require.Len(t, overview.Trends.Traffic24h, 24) require.Len(t, overview.Trends.Capacity24h, 24) require.Len(t, overview.Trends.Network24h, 24) require.Len(t, overview.Trends.DiskIO24h, 24) for _, row := range overview.Trends.Traffic24h { require.Len(t, row, 4) } for _, row := range overview.Trends.Capacity24h { require.Len(t, row, 4) } for _, row := range overview.Trends.Network24h { require.Len(t, row, 6) } for _, row := range overview.Trends.DiskIO24h { require.Len(t, row, 4) } require.Len(t, overview.Nodes, 2) for _, row := range overview.Nodes { require.Len(t, row, 17) } nodeByID := make(map[string][]any, len(overview.Nodes)) for _, row := range overview.Nodes { nodeByID[row[1].(string)] = row } onlineNode := nodeByID["node-dashboard-1"] require.NotNil(t, onlineNode) assert.Equal(t, "Edge 1", onlineNode[2]) assert.Equal(t, "online", onlineNode[6]) assert.Equal(t, "healthy", onlineNode[7]) // Latest-per-node health fields (indexes match compressDashboardNodes). assert.Equal(t, 55.0, onlineNode[11]) // cpu_usage_percent from latest snapshot assert.Equal(t, 50.0, onlineNode[12]) // memory_usage_percent assert.Equal(t, int64(12), onlineNode[14]) assert.Equal(t, int64(1), onlineNode[15]) assert.Equal(t, int64(4), onlineNode[16]) pendingNode := nodeByID["node-dashboard-2"] require.NotNil(t, pendingNode) assert.Equal(t, "Edge 2", pendingNode[2]) assert.Equal(t, "pending", pendingNode[6]) assert.Equal(t, "unknown", pendingNode[7]) assert.Equal(t, 55.0, overview.Capacity.AverageCPUUsagePercent) assert.Equal(t, 1, overview.Traffic.ReportedNodes) assert.Equal(t, int64(4), overview.Traffic.UniqueVisitors) }