mirror of
https://github.com/Noooste/garage-ui.git
synced 2026-08-11 05:56:55 +00:00
dd275d2e78
Signed-off-by: Noooste <83548733+Noooste@users.noreply.github.com>
216 lines
6.7 KiB
Go
216 lines
6.7 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"Noooste/garage-ui/internal/models"
|
|
"Noooste/garage-ui/internal/services/mocks"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
)
|
|
|
|
func newMonitoringTestApp(t *testing.T) (*fiber.App, *mocks.AdminMock) {
|
|
t.Helper()
|
|
admin := &mocks.AdminMock{}
|
|
h := NewMonitoringHandler(admin, nil) // s3Service unused by this handler
|
|
app := fiber.New()
|
|
app.Get("/monitoring/metrics", h.GetMetrics)
|
|
app.Get("/monitoring/admin-health", h.CheckAdminHealth)
|
|
app.Get("/monitoring/dashboard", h.GetDashboardMetrics)
|
|
return app, admin
|
|
}
|
|
|
|
func TestMonitoring_GetMetrics_PassesThroughAsPlainText(t *testing.T) {
|
|
app, admin := newMonitoringTestApp(t)
|
|
admin.GetMetricsFn = func(_ context.Context) (string, error) {
|
|
return "# HELP foo\nfoo 1\n", nil
|
|
}
|
|
req := httptest.NewRequest(http.MethodGet, "/monitoring/metrics", nil)
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatalf("app.Test: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200", resp.StatusCode)
|
|
}
|
|
if ct := resp.Header.Get("Content-Type"); ct != "text/plain; charset=utf-8" {
|
|
t.Errorf("Content-Type = %q", ct)
|
|
}
|
|
b, _ := io.ReadAll(resp.Body)
|
|
if string(b) != "# HELP foo\nfoo 1\n" {
|
|
t.Errorf("body = %q", b)
|
|
}
|
|
}
|
|
|
|
func TestMonitoring_GetMetrics_ServiceErrorReturns500JSON(t *testing.T) {
|
|
app, admin := newMonitoringTestApp(t)
|
|
admin.GetMetricsFn = func(_ context.Context) (string, error) {
|
|
return "", errors.New("scrape failed")
|
|
}
|
|
req := httptest.NewRequest(http.MethodGet, "/monitoring/metrics", nil)
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatalf("app.Test: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusInternalServerError {
|
|
t.Fatalf("status = %d, want 500", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestMonitoring_CheckAdminHealth_Healthy(t *testing.T) {
|
|
app, admin := newMonitoringTestApp(t)
|
|
admin.HealthCheckFn = func(_ context.Context) error { return nil }
|
|
req := httptest.NewRequest(http.MethodGet, "/monitoring/admin-health", nil)
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatalf("app.Test: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200", resp.StatusCode)
|
|
}
|
|
var body struct {
|
|
Success bool `json:"success"`
|
|
Data struct {
|
|
Status string `json:"status"`
|
|
Message string `json:"message"`
|
|
} `json:"data"`
|
|
}
|
|
_ = json.NewDecoder(resp.Body).Decode(&body)
|
|
if !body.Success || body.Data.Status != "healthy" {
|
|
t.Errorf("body = %+v", body)
|
|
}
|
|
}
|
|
|
|
func TestMonitoring_CheckAdminHealth_Unhealthy503(t *testing.T) {
|
|
app, admin := newMonitoringTestApp(t)
|
|
admin.HealthCheckFn = func(_ context.Context) error { return errors.New("down") }
|
|
req := httptest.NewRequest(http.MethodGet, "/monitoring/admin-health", nil)
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatalf("app.Test: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusServiceUnavailable {
|
|
t.Fatalf("status = %d, want 503", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestMonitoring_GetDashboardMetrics_AggregatesSizesAndPercentages(t *testing.T) {
|
|
app, admin := newMonitoringTestApp(t)
|
|
admin.ListBucketsFn = func(_ context.Context) ([]models.ListBucketsResponseItem, error) {
|
|
return []models.ListBucketsResponseItem{
|
|
{ID: "b1", Created: time.Unix(0, 0), GlobalAliases: []string{"alpha"}},
|
|
{ID: "b2", Created: time.Unix(0, 0), GlobalAliases: []string{"beta"}},
|
|
}, nil
|
|
}
|
|
admin.GetBucketInfoFn = func(_ context.Context, id string) (*models.GarageBucketInfo, error) {
|
|
switch id {
|
|
case "b1":
|
|
return &models.GarageBucketInfo{ID: "b1", Bytes: 300, Objects: 3}, nil
|
|
case "b2":
|
|
return &models.GarageBucketInfo{ID: "b2", Bytes: 100, Objects: 1}, nil
|
|
}
|
|
return nil, errors.New("unexpected id: " + id)
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/monitoring/dashboard", nil)
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatalf("app.Test: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200", resp.StatusCode)
|
|
}
|
|
|
|
var body struct {
|
|
Data models.DashboardMetrics `json:"data"`
|
|
}
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
|
|
if body.Data.TotalSize != 400 {
|
|
t.Errorf("TotalSize = %d, want 400", body.Data.TotalSize)
|
|
}
|
|
if body.Data.ObjectCount != 4 {
|
|
t.Errorf("ObjectCount = %d, want 4", body.Data.ObjectCount)
|
|
}
|
|
if body.Data.BucketCount != 2 {
|
|
t.Errorf("BucketCount = %d, want 2", body.Data.BucketCount)
|
|
}
|
|
if len(body.Data.UsageByBucket) != 2 {
|
|
t.Fatalf("UsageByBucket len = %d, want 2", len(body.Data.UsageByBucket))
|
|
}
|
|
// Percentages: 300/400 = 75, 100/400 = 25.
|
|
for _, u := range body.Data.UsageByBucket {
|
|
if u.BucketName == "alpha" && u.Percentage != 75 {
|
|
t.Errorf("alpha pct = %v, want 75", u.Percentage)
|
|
}
|
|
if u.BucketName == "beta" && u.Percentage != 25 {
|
|
t.Errorf("beta pct = %v, want 25", u.Percentage)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMonitoring_GetDashboardMetrics_SkipsInaccessibleBuckets(t *testing.T) {
|
|
app, admin := newMonitoringTestApp(t)
|
|
admin.ListBucketsFn = func(_ context.Context) ([]models.ListBucketsResponseItem, error) {
|
|
return []models.ListBucketsResponseItem{
|
|
{ID: "good", GlobalAliases: []string{"good"}},
|
|
{ID: "bad", GlobalAliases: []string{"bad"}},
|
|
}, nil
|
|
}
|
|
admin.GetBucketInfoFn = func(_ context.Context, id string) (*models.GarageBucketInfo, error) {
|
|
if id == "bad" {
|
|
return nil, errors.New("access denied")
|
|
}
|
|
return &models.GarageBucketInfo{ID: "good", Bytes: 10, Objects: 1}, nil
|
|
}
|
|
req := httptest.NewRequest(http.MethodGet, "/monitoring/dashboard", nil)
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatalf("app.Test: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200", resp.StatusCode)
|
|
}
|
|
var body struct {
|
|
Data models.DashboardMetrics `json:"data"`
|
|
}
|
|
_ = json.NewDecoder(resp.Body).Decode(&body)
|
|
if body.Data.BucketCount != 2 {
|
|
t.Errorf("BucketCount = %d, want 2 (count includes inaccessible)", body.Data.BucketCount)
|
|
}
|
|
if len(body.Data.UsageByBucket) != 1 {
|
|
t.Errorf("UsageByBucket = %d, want 1 (inaccessible skipped)", len(body.Data.UsageByBucket))
|
|
}
|
|
}
|
|
|
|
func TestMonitoring_GetDashboardMetrics_ListBucketsErrorReturns500(t *testing.T) {
|
|
app, admin := newMonitoringTestApp(t)
|
|
admin.ListBucketsFn = func(_ context.Context) ([]models.ListBucketsResponseItem, error) {
|
|
return nil, errors.New("upstream")
|
|
}
|
|
req := httptest.NewRequest(http.MethodGet, "/monitoring/dashboard", nil)
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatalf("app.Test: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusInternalServerError {
|
|
t.Fatalf("status = %d, want 500", resp.StatusCode)
|
|
}
|
|
}
|