Pulse/internal/api/resource_handlers_more_test.go
2026-02-02 23:01:29 +00:00

332 lines
10 KiB
Go

package api
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
"github.com/rcourtman/pulse-go-rewrite/internal/resources"
)
type stubResourceStateProvider struct {
snapshot models.StateSnapshot
}
func (s stubResourceStateProvider) GetState() models.StateSnapshot {
return s.snapshot
}
type stubTenantStateProvider struct {
snapshot models.StateSnapshot
}
func (s stubTenantStateProvider) GetStateForTenant(_ string) models.StateSnapshot {
return s.snapshot
}
func decodeResourcesResponse(t *testing.T, rec *httptest.ResponseRecorder) ResourcesResponse {
t.Helper()
var resp ResourcesResponse
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
t.Fatalf("decode response: %v", err)
}
return resp
}
func TestResourceHandlers_GetStoreStats(t *testing.T) {
handlers := NewResourceHandlers()
handlers.Store().Upsert(resources.Resource{ID: "vm-1", Type: resources.ResourceTypeVM})
handlers.getStoreForTenant("tenant-a").Upsert(resources.Resource{ID: "node-1", Type: resources.ResourceTypeNode})
stats := handlers.GetStoreStats()
if stats["default"].TotalResources != 1 {
t.Fatalf("expected default total 1, got %d", stats["default"].TotalResources)
}
if stats["tenant-a"].TotalResources != 1 {
t.Fatalf("expected tenant total 1, got %d", stats["tenant-a"].TotalResources)
}
}
func TestHandleGetResources_FiltersAndModes(t *testing.T) {
handlers := NewResourceHandlers()
handlers.Store().Upsert(resources.Resource{
ID: "vm-1",
Type: resources.ResourceTypeVM,
Status: resources.StatusRunning,
PlatformType: resources.PlatformProxmoxPVE,
ParentID: "node-1",
Alerts: []resources.ResourceAlert{{ID: "alert-1"}},
})
handlers.Store().Upsert(resources.Resource{
ID: "node-1",
Type: resources.ResourceTypeNode,
Status: resources.StatusOnline,
PlatformType: resources.PlatformProxmoxPVE,
})
handlers.Store().Upsert(resources.Resource{
ID: "ct-1",
Type: resources.ResourceTypeContainer,
Status: resources.StatusStopped,
PlatformType: resources.PlatformProxmoxPVE,
})
handlers.Store().Upsert(resources.Resource{
ID: "dh-1",
Type: resources.ResourceTypeDockerHost,
Status: resources.StatusOnline,
PlatformType: resources.PlatformDocker,
})
req := httptest.NewRequest(http.MethodGet, "/api/resources", nil)
rec := httptest.NewRecorder()
handlers.HandleGetResources(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
}
resp := decodeResourcesResponse(t, rec)
if resp.Count != 4 {
t.Fatalf("expected count 4, got %d", resp.Count)
}
req = httptest.NewRequest(http.MethodGet, "/api/resources?type=vm,container", nil)
rec = httptest.NewRecorder()
handlers.HandleGetResources(rec, req)
resp = decodeResourcesResponse(t, rec)
if resp.Count != 2 {
t.Fatalf("expected type count 2, got %d", resp.Count)
}
req = httptest.NewRequest(http.MethodGet, "/api/resources?platform=docker", nil)
rec = httptest.NewRecorder()
handlers.HandleGetResources(rec, req)
resp = decodeResourcesResponse(t, rec)
if resp.Count != 1 || resp.Resources[0].ID != "dh-1" {
t.Fatalf("expected docker host, got %#v", resp.Resources)
}
req = httptest.NewRequest(http.MethodGet, "/api/resources?status=running", nil)
rec = httptest.NewRecorder()
handlers.HandleGetResources(rec, req)
resp = decodeResourcesResponse(t, rec)
if resp.Count != 1 || resp.Resources[0].ID != "vm-1" {
t.Fatalf("expected running vm, got %#v", resp.Resources)
}
req = httptest.NewRequest(http.MethodGet, "/api/resources?parent=node-1", nil)
rec = httptest.NewRecorder()
handlers.HandleGetResources(rec, req)
resp = decodeResourcesResponse(t, rec)
if resp.Count != 1 || resp.Resources[0].ID != "vm-1" {
t.Fatalf("expected parent filter vm, got %#v", resp.Resources)
}
req = httptest.NewRequest(http.MethodGet, "/api/resources?alerts=true", nil)
rec = httptest.NewRecorder()
handlers.HandleGetResources(rec, req)
resp = decodeResourcesResponse(t, rec)
if resp.Count != 1 || resp.Resources[0].ID != "vm-1" {
t.Fatalf("expected alerts filter vm, got %#v", resp.Resources)
}
req = httptest.NewRequest(http.MethodGet, "/api/resources?infrastructure=true", nil)
rec = httptest.NewRecorder()
handlers.HandleGetResources(rec, req)
resp = decodeResourcesResponse(t, rec)
if resp.Count != 2 {
t.Fatalf("expected infrastructure count 2, got %d", resp.Count)
}
req = httptest.NewRequest(http.MethodGet, "/api/resources?workloads=true", nil)
rec = httptest.NewRecorder()
handlers.HandleGetResources(rec, req)
resp = decodeResourcesResponse(t, rec)
if resp.Count != 2 {
t.Fatalf("expected workloads count 2, got %d", resp.Count)
}
}
func TestHandleGetResources_MethodNotAllowed(t *testing.T) {
handlers := NewResourceHandlers()
req := httptest.NewRequest(http.MethodPost, "/api/resources", nil)
rec := httptest.NewRecorder()
handlers.HandleGetResources(rec, req)
if rec.Code != http.StatusMethodNotAllowed {
t.Fatalf("expected status %d, got %d", http.StatusMethodNotAllowed, rec.Code)
}
}
func TestHandleGetResource_MethodNotAllowedAndMissingID(t *testing.T) {
handlers := NewResourceHandlers()
req := httptest.NewRequest(http.MethodPost, "/api/resources/vm-1", nil)
rec := httptest.NewRecorder()
handlers.HandleGetResource(rec, req)
if rec.Code != http.StatusMethodNotAllowed {
t.Fatalf("expected status %d, got %d", http.StatusMethodNotAllowed, rec.Code)
}
req = httptest.NewRequest(http.MethodGet, "/api/resources/", nil)
rec = httptest.NewRecorder()
handlers.HandleGetResource(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("expected status %d, got %d", http.StatusBadRequest, rec.Code)
}
}
func TestHandleGetResourceStats_MethodNotAllowed(t *testing.T) {
handlers := NewResourceHandlers()
req := httptest.NewRequest(http.MethodPost, "/api/resources/stats", nil)
rec := httptest.NewRecorder()
handlers.HandleGetResourceStats(rec, req)
if rec.Code != http.StatusMethodNotAllowed {
t.Fatalf("expected status %d, got %d", http.StatusMethodNotAllowed, rec.Code)
}
}
func TestHandleGetResources_StateProviderAndTenantProvider(t *testing.T) {
handlers := NewResourceHandlers()
snapshot := models.StateSnapshot{
Nodes: []models.Node{{ID: "node-1", Name: "node-1"}},
}
handlers.SetStateProvider(stubResourceStateProvider{snapshot: snapshot})
req := httptest.NewRequest(http.MethodGet, "/api/resources", nil)
rec := httptest.NewRecorder()
handlers.HandleGetResources(rec, req)
resp := decodeResourcesResponse(t, rec)
if resp.Count != 1 {
t.Fatalf("expected count 1 from state provider, got %d", resp.Count)
}
tenantSnapshot := models.StateSnapshot{
Nodes: []models.Node{{ID: "node-tenant", Name: "node-tenant"}},
}
handlers.SetTenantStateProvider(stubTenantStateProvider{snapshot: tenantSnapshot})
req = httptest.NewRequest(http.MethodGet, "/api/resources", nil)
ctx := context.WithValue(req.Context(), OrgIDContextKey, "tenant-1")
req = req.WithContext(ctx)
rec = httptest.NewRecorder()
handlers.HandleGetResources(rec, req)
resp = decodeResourcesResponse(t, rec)
if resp.Count != 1 || resp.Resources[0].ID != "node-tenant" {
t.Fatalf("expected tenant resource, got %#v", resp.Resources)
}
}
func TestPopulateFromSnapshotForTenant(t *testing.T) {
handlers := NewResourceHandlers()
now := time.Now()
snapshot := models.StateSnapshot{
Nodes: []models.Node{{
ID: "node-1",
Name: "node-1",
CPU: 0.2,
Memory: models.Memory{Total: 100, Used: 50, Free: 50, Usage: 50},
Disk: models.Disk{Total: 100, Used: 10, Free: 90, Usage: 10},
}},
VMs: []models.VM{{
ID: "vm-1",
Name: "vm-1",
Status: "running",
Memory: models.Memory{Total: 100, Used: 10, Free: 90, Usage: 10},
Disk: models.Disk{Total: 100, Used: 10, Free: 90, Usage: 10},
}},
Containers: []models.Container{{
ID: "ct-1",
Name: "ct-1",
Status: "running",
Memory: models.Memory{Total: 100, Used: 10, Free: 90, Usage: 10},
Disk: models.Disk{Total: 100, Used: 10, Free: 90, Usage: 10},
}},
Hosts: []models.Host{{
ID: "host-1",
Hostname: "host-1",
Status: "online",
Memory: models.Memory{Total: 100, Used: 20, Free: 80, Usage: 20},
}},
DockerHosts: []models.DockerHost{{
ID: "dh-1",
Hostname: "dh-1",
Status: "online",
CPUUsage: 5,
Memory: models.Memory{Total: 100, Used: 20, Free: 80, Usage: 20},
TotalMemoryBytes: 100,
LastSeen: now,
IntervalSeconds: 60,
Containers: []models.DockerContainer{{
ID: "dc-1",
Name: "dc-1",
State: "running",
Status: "running",
CPUPercent: 1,
MemoryLimit: 100,
MemoryUsage: 20,
MemoryPercent: 20,
UptimeSeconds: 10,
CreatedAt: now,
}},
}},
PBSInstances: []models.PBSInstance{{
ID: "pbs-1",
Name: "pbs-1",
Host: "pbs-host",
Status: "online",
CPU: 2,
Memory: 10,
MemoryUsed: 10,
MemoryTotal: 100,
Uptime: 10,
ConnectionHealth: "ok",
LastSeen: now,
}},
Storage: []models.Storage{{
ID: "storage-1",
Name: "storage-1",
Instance: "inst-1",
Node: "node-1",
Status: "online",
Total: 100,
Used: 50,
Free: 50,
Usage: 50,
Content: "images",
Enabled: true,
Active: true,
}},
}
handlers.PopulateFromSnapshotForTenant("tenant-1", snapshot)
store := handlers.getStoreForTenant("tenant-1")
stats := store.GetStats()
if stats.TotalResources != 8 {
t.Fatalf("expected 8 resources, got %d", stats.TotalResources)
}
}
func TestParsePlatformTypesAndStatuses(t *testing.T) {
platforms := parsePlatformTypes(" docker , proxmox-pve , ")
if len(platforms) != 2 {
t.Fatalf("expected 2 platforms, got %d", len(platforms))
}
if platforms[0] != resources.PlatformDocker || platforms[1] != resources.PlatformProxmoxPVE {
t.Fatalf("unexpected platforms: %#v", platforms)
}
statuses := parseStatuses("running, stopped ,")
if len(statuses) != 2 {
t.Fatalf("expected 2 statuses, got %d", len(statuses))
}
if statuses[0] != resources.StatusRunning || statuses[1] != resources.StatusStopped {
t.Fatalf("unexpected statuses: %#v", statuses)
}
}