Ctrl K
ComunidadGuías…

internal/server/dashboard_scope_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// SPDX-License-Identifier: Apache-2.0

package server

import (
	"context"
	"encoding/json"
	"html/template"
	"net/http"
	"net/http/httptest"
	"path/filepath"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/X4Applegate/caddyui/internal/auth"
	"github.com/X4Applegate/caddyui/internal/caddy"
	appdb "github.com/X4Applegate/caddyui/internal/db"
	"github.com/X4Applegate/caddyui/internal/models"
)

func TestOperationsTrafficAndCertificatesUseSelectedFleetServer(t *testing.T) {
	conn, err := appdb.Open(filepath.Join(t.TempDir(), "caddyui.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = conn.Close() })

	primaryID, err := models.CreateCaddyServer(conn, &models.CaddyServer{
		Name: "primary", AdminURL: "http://primary.invalid:2019",
	})
	if err != nil {
		t.Fatal(err)
	}
	selectedID, err := models.CreateCaddyServer(conn, &models.CaddyServer{
		Name: "selected", AdminURL: "http://selected.invalid:2019",
	})
	if err != nil {
		t.Fatal(err)
	}

	for _, serverID := range []int64{primaryID, selectedID} {
		if _, err := models.CreateProxyHost(conn, serverID, 0, &models.ProxyHost{
			Domains: "Shared.TEST", ForwardScheme: "http", ForwardHost: "backend", ForwardPort: 8080, Enabled: true,
		}); err != nil {
			t.Fatal(err)
		}
	}
	if _, err := models.CreateCertificate(conn, primaryID, 0, &models.Certificate{Name: "primary-cert"}); err != nil {
		t.Fatal(err)
	}
	for _, name := range []string{"selected-cert-a", "selected-cert-b"} {
		if _, err := models.CreateCertificate(conn, selectedID, 0, &models.Certificate{Name: name}); err != nil {
			t.Fatal(err)
		}
	}

	// Keep fixtures inside today's UTC bucket even when the test starts during
	// the first minute after midnight.
	now := time.Now().UTC().Truncate(24 * time.Hour).Add(time.Hour)
	events := []models.AccessEvent{
		{TS: now, ServerID: primaryID, ServerName: "primary", Host: "Shared.TEST", ClientIP: "192.0.2.1", BytesOut: 100},
		{TS: now, ServerID: selectedID, ServerName: "selected", Host: "Shared.TEST", ClientIP: "192.0.2.2", BytesOut: 20},
		{TS: now.Add(time.Second), ServerID: selectedID, ServerName: "selected", Host: "Shared.TEST", ClientIP: "192.0.2.3", BytesOut: 30},
	}
	for _, event := range events {
		if err := models.InsertAccessEvent(conn, event); err != nil {
			t.Fatal(err)
		}
	}

	dashboardTemplate := template.Must(template.New("dashboard.html").Parse(
		`{{define "layout"}}{{.TodayViews}}|{{.TodayVisitors}}|{{.TodayBandwidth}}|{{.CertCount}}{{end}}`,
	))
	s := &Server{
		DB: conn,
		Templates: map[string]*template.Template{
			"dashboard.html": dashboardTemplate,
		},
	}
	admin := &models.User{ID: 1, Email: "admin@example.com", IsAdmin: true, Role: models.RoleAdmin}
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	req.AddCookie(&http.Cookie{Name: serverCookie, Value: strconv.FormatInt(selectedID, 10)})
	req = req.WithContext(context.WithValue(req.Context(), auth.ContextUserKey, admin))
	rec := httptest.NewRecorder()
	s.dashboard(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("status = %d, body = %s", rec.Code, rec.Body.String())
	}
	if got := strings.TrimSpace(rec.Body.String()); got != "2|2|50|2" {
		t.Fatalf("selected dashboard totals = %q, want 2|2|50|2", got)
	}

	sparkReq := httptest.NewRequest(http.MethodGet, "/api/dashboard-sparklines", nil)
	sparkReq.AddCookie(&http.Cookie{Name: serverCookie, Value: strconv.FormatInt(selectedID, 10)})
	sparkReq = sparkReq.WithContext(context.WithValue(sparkReq.Context(), auth.ContextUserKey, admin))
	sparkRec := httptest.NewRecorder()
	s.apiDashboardSparklines(sparkRec, sparkReq)
	var spark struct {
		Days []struct {
			Views     int   `json:"views"`
			Visitors  int   `json:"visitors"`
			Bandwidth int64 `json:"bandwidth"`
		} `json:"days"`
	}
	if err := json.NewDecoder(sparkRec.Body).Decode(&spark); err != nil {
		t.Fatal(err)
	}
	if len(spark.Days) != 7 {
		t.Fatalf("sparkline days = %d, want 7", len(spark.Days))
	}
	today := spark.Days[len(spark.Days)-1]
	if today.Views != 2 || today.Visitors != 2 || today.Bandwidth != 50 {
		t.Fatalf("selected sparkline today = %#v, want 2 views, 2 visitors, 50 bytes", today)
	}
}

func TestOperationsLiveCaddyDataUsesSelectedServerAndCredentials(t *testing.T) {
	var primaryCalls int
	primary := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		primaryCalls++
		_ = json.NewEncoder(w).Encode(map[string]string{"version": "v2.9.0-primary"})
	}))
	defer primary.Close()

	var versionCalls, upstreamCalls int
	selected := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		user, password, ok := r.BasicAuth()
		if !ok || user != "monitor" || password != "secret" {
			http.Error(w, "missing selected-node credentials", http.StatusUnauthorized)
			return
		}
		switch r.URL.Path {
		case "/":
			versionCalls++
			_ = json.NewEncoder(w).Encode(map[string]string{"version": "v2.11.4-selected"})
		case "/reverse_proxy/upstreams":
			upstreamCalls++
			_ = json.NewEncoder(w).Encode([]map[string]any{
				{"address": "app-a:8080", "num_requests": 3, "fails": 0},
				{"address": "app-b:8080", "num_requests": 4, "fails": 1},
			})
		default:
			http.NotFound(w, r)
		}
	}))
	defer selected.Close()

	conn, err := appdb.Open(filepath.Join(t.TempDir(), "caddyui.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = conn.Close() })
	if _, err := models.CreateCaddyServer(conn, &models.CaddyServer{Name: "primary", AdminURL: primary.URL}); err != nil {
		t.Fatal(err)
	}
	selectedID, err := models.CreateCaddyServer(conn, &models.CaddyServer{
		Name: "selected", AdminURL: selected.URL, AdminUsername: "monitor", AdminPassword: "secret",
	})
	if err != nil {
		t.Fatal(err)
	}
	s := &Server{DB: conn, Caddy: caddy.New(primary.URL, "", "")}
	selectedCookie := &http.Cookie{Name: serverCookie, Value: strconv.FormatInt(selectedID, 10)}

	versionReq := httptest.NewRequest(http.MethodGet, "/api/caddy-version", nil)
	versionReq.AddCookie(selectedCookie)
	versionRec := httptest.NewRecorder()
	s.apiCaddyVersion(versionRec, versionReq)
	var versionBody map[string]any
	if err := json.NewDecoder(versionRec.Body).Decode(&versionBody); err != nil {
		t.Fatal(err)
	}
	if versionBody["version"] != "v2.11.4-selected" || int64(versionBody["server_id"].(float64)) != selectedID {
		t.Fatalf("selected version response = %#v", versionBody)
	}
	stored, err := models.GetCaddyServer(conn, selectedID)
	if err != nil || stored.Version != "v2.11.4-selected" {
		t.Fatalf("stored selected version = %#v, err=%v", stored, err)
	}

	statsReq := httptest.NewRequest(http.MethodGet, "/api/system-stats", nil)
	statsReq.AddCookie(selectedCookie)
	statsReq = statsReq.WithContext(context.WithValue(statsReq.Context(), auth.ContextUserKey, &models.User{
		ID: 1, Email: "admin@example.com", IsAdmin: true, Role: models.RoleAdmin,
	}))
	statsRec := httptest.NewRecorder()
	s.apiSystemStats(statsRec, statsReq)
	var stats map[string]any
	if err := json.NewDecoder(statsRec.Body).Decode(&stats); err != nil {
		t.Fatal(err)
	}
	if stats["selected_server_name"] != "selected" || stats["active_requests"] != float64(7) {
		t.Fatalf("selected system stats = %#v", stats)
	}
	if stats["healthy_upstreams"] != float64(1) || stats["total_upstreams"] != float64(2) {
		t.Fatalf("selected upstream stats = %#v", stats)
	}
	if stats["host_scope"] != "caddyui" {
		t.Fatalf("host scope = %#v", stats["host_scope"])
	}
	if primaryCalls != 0 || versionCalls != 1 || upstreamCalls != 1 {
		t.Fatalf("calls primary=%d version=%d upstream=%d", primaryCalls, versionCalls, upstreamCalls)
	}
}