Pulse/internal/api/router_version_tenant_metrics_test.go
rcourtman bfa648ddd5 Test: expand api feature test coverage
Add tests for AI intelligence, Docker/K8s agents, log redaction, and general router helper functions.
2026-02-02 22:02:22 +00:00

234 lines
6.8 KiB
Go

package api
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
"github.com/rcourtman/pulse-go-rewrite/internal/updates"
"github.com/rcourtman/pulse-go-rewrite/pkg/metrics"
)
func TestGetTenantMonitor_Default(t *testing.T) {
defaultMonitor, _, _ := newTestMonitor(t)
router := &Router{monitor: defaultMonitor}
if got := router.getTenantMonitor(context.Background()); got != defaultMonitor {
t.Fatalf("expected default monitor to be returned")
}
}
func TestGetTenantMonitor_WithTenant(t *testing.T) {
defaultMonitor, _, _ := newTestMonitor(t)
tenantMonitor, _, _ := newTestMonitor(t)
mtm := &monitoring.MultiTenantMonitor{}
setUnexportedField(t, mtm, "monitors", map[string]*monitoring.Monitor{
"tenant-1": tenantMonitor,
})
router := &Router{monitor: defaultMonitor, mtMonitor: mtm}
ctx := context.WithValue(context.Background(), OrgIDContextKey, "tenant-1")
if got := router.getTenantMonitor(ctx); got != tenantMonitor {
t.Fatalf("expected tenant monitor to be returned")
}
}
func TestGetTenantMonitor_FallbackOnError(t *testing.T) {
defaultMonitor, _, _ := newTestMonitor(t)
mtp := config.NewMultiTenantPersistence(t.TempDir())
mtm := monitoring.NewMultiTenantMonitor(&config.Config{}, mtp, nil)
defer mtm.Stop()
router := &Router{monitor: defaultMonitor, mtMonitor: mtm}
ctx := context.WithValue(context.Background(), OrgIDContextKey, "../bad")
if got := router.getTenantMonitor(ctx); got != defaultMonitor {
t.Fatalf("expected fallback to default monitor")
}
}
func TestHandleVersion_MethodNotAllowed(t *testing.T) {
router := &Router{updateManager: updates.NewManager(&config.Config{})}
req := httptest.NewRequest(http.MethodPost, "/api/version", nil)
rec := httptest.NewRecorder()
router.handleVersion(rec, req)
if rec.Code != http.StatusMethodNotAllowed {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusMethodNotAllowed)
}
}
func TestHandleVersion_Success(t *testing.T) {
router := &Router{updateManager: updates.NewManager(&config.Config{})}
req := httptest.NewRequest(http.MethodGet, "/api/version", nil)
rec := httptest.NewRecorder()
router.handleVersion(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
var payload map[string]interface{}
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if payload["version"] == "" {
t.Fatalf("expected version in response, got %#v", payload)
}
}
func TestHandleMetricsHistory_MethodNotAllowed(t *testing.T) {
router := &Router{}
req := httptest.NewRequest(http.MethodPost, "/api/metrics/history", nil)
rec := httptest.NewRecorder()
router.handleMetricsHistory(rec, req)
if rec.Code != http.StatusMethodNotAllowed {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusMethodNotAllowed)
}
}
func TestHandleMetricsHistory_MissingParams(t *testing.T) {
monitor, _, _ := newTestMonitor(t)
router := &Router{monitor: monitor}
req := httptest.NewRequest(http.MethodGet, "/api/metrics/history", nil)
rec := httptest.NewRecorder()
router.handleMetricsHistory(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusBadRequest)
}
}
func TestHandleMetricsHistory_LicenseRequired(t *testing.T) {
monitor, _, _ := newTestMonitor(t)
mtp := config.NewMultiTenantPersistence(t.TempDir())
if _, err := mtp.GetPersistence("default"); err != nil {
t.Fatalf("failed to init persistence: %v", err)
}
router := &Router{
monitor: monitor,
licenseHandlers: NewLicenseHandlers(mtp),
}
req := httptest.NewRequest(http.MethodGet, "/api/metrics-store/history?resourceType=vm&resourceId=vm-1&range=30d", nil)
rec := httptest.NewRecorder()
router.handleMetricsHistory(rec, req)
if rec.Code != http.StatusPaymentRequired {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusPaymentRequired)
}
}
func TestHandleMetricsHistory_UsesStore(t *testing.T) {
monitor, _, _ := newTestMonitor(t)
store, err := metrics.NewStore(metrics.DefaultConfig(t.TempDir()))
if err != nil {
t.Fatalf("metrics.NewStore error: %v", err)
}
defer store.Close()
store.WriteBatchSync([]metrics.WriteMetric{{
ResourceType: "vm",
ResourceID: "vm-1",
MetricType: "cpu",
Value: 42.0,
Timestamp: time.Now(),
Tier: metrics.TierRaw,
}})
setUnexportedField(t, monitor, "metricsStore", store)
router := &Router{monitor: monitor}
req := httptest.NewRequest(http.MethodGet, "/api/metrics-store/history?resourceType=vm&resourceId=vm-1&metric=cpu&range=1h", nil)
rec := httptest.NewRecorder()
router.handleMetricsHistory(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
var payload map[string]interface{}
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if payload["source"] != "store" {
t.Fatalf("expected source store, got %#v", payload["source"])
}
}
func TestHandleMetricsHistory_UsesStoreAllMetrics(t *testing.T) {
monitor, _, _ := newTestMonitor(t)
store, err := metrics.NewStore(metrics.DefaultConfig(t.TempDir()))
if err != nil {
t.Fatalf("metrics.NewStore error: %v", err)
}
defer store.Close()
now := time.Now()
store.WriteBatchSync([]metrics.WriteMetric{
{
ResourceType: "vm",
ResourceID: "vm-1",
MetricType: "cpu",
Value: 50.0,
Timestamp: now,
Tier: metrics.TierRaw,
},
{
ResourceType: "vm",
ResourceID: "vm-1",
MetricType: "memory",
Value: 70.0,
Timestamp: now,
Tier: metrics.TierRaw,
},
})
setUnexportedField(t, monitor, "metricsStore", store)
router := &Router{monitor: monitor}
req := httptest.NewRequest(http.MethodGet, "/api/metrics-store/history?resourceType=vm&resourceId=vm-1&range=1h", nil)
rec := httptest.NewRecorder()
router.handleMetricsHistory(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
var payload map[string]interface{}
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if payload["source"] != "store" {
t.Fatalf("expected source store, got %#v", payload["source"])
}
metricsMap, ok := payload["metrics"].(map[string]interface{})
if !ok || metricsMap["cpu"] == nil {
t.Fatalf("expected cpu metrics in response, got %#v", payload["metrics"])
}
}
func TestHandleCharts_MethodNotAllowed(t *testing.T) {
router := &Router{}
req := httptest.NewRequest(http.MethodPost, "/api/charts", nil)
rec := httptest.NewRecorder()
router.handleCharts(rec, req)
if rec.Code != http.StatusMethodNotAllowed {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusMethodNotAllowed)
}
}