Files
pulse/internal/api/resources_state_integration_test.go
T
2026-08-21 18:36:21 +01:00

118 lines
4.3 KiB
Go

package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
unified "github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
authpkg "github.com/rcourtman/pulse-go-rewrite/pkg/auth"
)
type stateSupplementalRecordsProvider struct {
records []unified.IngestRecord
}
func (p stateSupplementalRecordsProvider) GetCurrentRecords() []unified.IngestRecord {
return append([]unified.IngestRecord(nil), p.records...)
}
func (p stateSupplementalRecordsProvider) SupplementalRecords(*monitoring.Monitor, string) []unified.IngestRecord {
return p.GetCurrentRecords()
}
func TestStateEndpointDerivesProxmoxWorkloadParentFromSupplementalRecords(t *testing.T) {
now := time.Date(2026, 5, 14, 10, 0, 0, 0, time.UTC)
hashedPassword, err := authpkg.HashPassword("password")
if err != nil {
t.Fatalf("hash password: %v", err)
}
dataPath := t.TempDir()
InitSessionStore(dataPath)
InitCSRFStore(dataPath)
cfg := &config.Config{DataPath: dataPath, AuthUser: "admin", AuthPass: hashedPassword}
monitor, err := monitoring.New(cfg)
if err != nil {
t.Fatalf("new monitor: %v", err)
}
t.Cleanup(func() { monitor.Stop() })
monitor.SetResourceStore(unified.NewMonitorAdapter(nil))
monitor.SetSupplementalRecordsProvider(unified.SourceProxmox, stateSupplementalRecordsProvider{records: []unified.IngestRecord{
{
SourceID: "homelab-delly",
Resource: unified.Resource{
Type: unified.ResourceTypeAgent, Name: "delly", Status: unified.StatusOnline, LastSeen: now,
Proxmox: &unified.ProxmoxData{SourceID: "homelab-delly", NodeName: "delly", ClusterName: "homelab", Instance: "delly"},
},
Identity: unified.ResourceIdentity{MachineID: "machine-delly", Hostnames: []string{"delly"}},
},
{
SourceID: "delly:delly:104",
Resource: unified.Resource{
Type: unified.ResourceTypeSystemContainer, Name: "cloudflared", Status: unified.StatusOnline, LastSeen: now,
Proxmox: &unified.ProxmoxData{SourceID: "delly:delly:104", NodeName: "delly", ClusterName: "homelab", Instance: "delly", VMID: 104},
},
Identity: unified.ResourceIdentity{Hostnames: []string{"cloudflared"}},
},
}})
router := &Router{config: cfg, monitor: monitor}
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/api/state", nil)
req.SetBasicAuth("admin", "password")
router.handleState(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("/api/state status = %d, body=%s", rec.Code, rec.Body.String())
}
var state models.StateFrontend
if err := json.NewDecoder(rec.Body).Decode(&state); err != nil {
t.Fatalf("decode /api/state: %v", err)
}
dellyCount, dellyID, cloudflaredParentID := 0, "", ""
for _, resource := range state.Resources {
switch {
case resource.Type == string(unified.ResourceTypeAgent) && resource.Name == "delly":
dellyCount++
dellyID = resource.ID
case resource.Type == string(unified.ResourceTypeSystemContainer) && resource.Name == "cloudflared":
cloudflaredParentID = resource.ParentID
}
}
if dellyCount != 1 || dellyID == "" || cloudflaredParentID != dellyID {
t.Fatalf("unexpected /api/state resource projection: dellyCount=%d dellyID=%q cloudflaredParentID=%q resources=%#v", dellyCount, dellyID, cloudflaredParentID, state.Resources)
}
}
// Closing must be safe to repeat and safe on a nil compatibility facade: the
// shutdown path runs before every dependency is guaranteed to be wired.
func TestResourceHandlers_CloseIsIdempotentAndNilSafe(t *testing.T) {
var nilHandlers *ResourceHandlers
if err := nilHandlers.CloseStores(); err != nil {
t.Fatalf("nil handler CloseStores: %v", err)
}
if err := nilHandlers.CloseTenantStore("client-a"); err != nil {
t.Fatalf("nil handler CloseTenantStore: %v", err)
}
handlers := NewResourceHandlers(&config.Config{DataPath: t.TempDir()})
if _, err := handlers.getStore("client-a"); err != nil {
t.Fatalf("getStore: %v", err)
}
if err := handlers.CloseStores(); err != nil {
t.Fatalf("first CloseStores: %v", err)
}
if err := handlers.CloseStores(); err != nil {
t.Fatalf("second CloseStores must be a no-op: %v", err)
}
if err := handlers.CloseTenantStore("client-a"); err != nil {
t.Fatalf("CloseTenantStore on an evicted org must be a no-op: %v", err)
}
}