Pulse/internal/resources/populate_test.go
rcourtman 1e3fdb6f63 feat(ai): Enhanced AI patrol system with alert triggers and history persistence
- Add alert-triggered AI analysis for real-time incident response
- Implement patrol history persistence across restarts
- Add patrol schedule configuration UI in AI Settings
- Enhance AIChat with patrol status and manual trigger controls
- Add resource store improvements for AI context building
- Expand Alerts page with AI-powered analysis integration
- Add Vite proxy config for AI API endpoints
- Support both Anthropic and OpenAI providers with streaming
2025-12-10 21:08:22 +00:00

188 lines
4.9 KiB
Go

package resources
import (
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
)
func TestStorePopulateFromSnapshot(t *testing.T) {
store := NewStore()
// Create a minimal snapshot with test data
snapshot := models.StateSnapshot{
Nodes: []models.Node{
{
ID: "node-1",
Name: "pve-node-1",
Instance: "https://pve1.local:8006",
Status: "online",
CPU: 0.455, // Proxmox API returns 0-1 ratio
Memory: models.Memory{
Total: 16000000000,
Used: 8000000000,
Free: 8000000000,
Usage: 50.0,
},
Uptime: 86400,
LastSeen: time.Now(),
},
},
VMs: []models.VM{
{
ID: "vm-100",
VMID: 100,
Name: "test-vm",
Node: "pve-node-1",
Instance: "https://pve1.local:8006",
Status: "running",
CPU: 0.25, // Proxmox API returns 0-1 ratio
Memory: models.Memory{
Total: 4000000000,
Used: 2000000000,
Free: 2000000000,
Usage: 50.0,
},
},
},
Containers: []models.Container{
{
ID: "ct-200",
VMID: 200,
Name: "test-container",
Node: "pve-node-1",
Instance: "https://pve1.local:8006",
Status: "running",
},
},
Hosts: []models.Host{
{
ID: "host-1",
Hostname: "my-host",
Status: "online",
},
},
}
// Populate the store
store.PopulateFromSnapshot(snapshot)
// Verify resources were created
all := store.GetAll()
if len(all) == 0 {
t.Fatal("Expected resources to be populated, got 0")
}
t.Logf("Populated %d resources", len(all))
// Check for each type
nodes := store.Query().OfType(ResourceTypeNode).Execute()
if len(nodes) != 1 {
t.Errorf("Expected 1 node, got %d", len(nodes))
} else {
t.Logf("Node: id=%s, name=%s, cpu=%.1f%%", nodes[0].ID, nodes[0].Name, nodes[0].CPU.Current)
}
vms := store.Query().OfType(ResourceTypeVM).Execute()
if len(vms) != 1 {
t.Errorf("Expected 1 VM, got %d", len(vms))
} else {
t.Logf("VM: id=%s, name=%s, status=%s", vms[0].ID, vms[0].Name, vms[0].Status)
}
containers := store.Query().OfType(ResourceTypeContainer).Execute()
if len(containers) != 1 {
t.Errorf("Expected 1 container, got %d", len(containers))
} else {
t.Logf("Container: id=%s, name=%s", containers[0].ID, containers[0].Name)
}
hosts := store.Query().OfType(ResourceTypeHost).Execute()
if len(hosts) != 1 {
t.Errorf("Expected 1 host, got %d", len(hosts))
} else {
t.Logf("Host: id=%s, hostname=%s", hosts[0].ID, hosts[0].Identity.Hostname)
}
// Test summary
t.Logf("SUCCESS: PopulateFromSnapshot works correctly!")
t.Logf("Total resources: %d (1 node + 1 VM + 1 container + 1 host)", len(all))
}
// TestPopulateFromSnapshotRemovesStaleResources verifies that resources not present
// in subsequent snapshots are removed from the store (fixing the "ghost LXCs" bug).
func TestPopulateFromSnapshotRemovesStaleResources(t *testing.T) {
store := NewStore()
// First snapshot with 2 containers
snapshot1 := models.StateSnapshot{
Containers: []models.Container{
{
ID: "ct-100",
VMID: 100,
Name: "container-to-keep",
Node: "pve-node-1",
Instance: "pve1",
Status: "running",
},
{
ID: "ct-200",
VMID: 200,
Name: "container-to-remove",
Node: "pve-node-1",
Instance: "pve1",
Status: "running",
},
},
}
// Populate with first snapshot
store.PopulateFromSnapshot(snapshot1)
// Verify both containers exist
containers := store.Query().OfType(ResourceTypeContainer).Execute()
if len(containers) != 2 {
t.Fatalf("Expected 2 containers after first snapshot, got %d", len(containers))
}
t.Logf("After first snapshot: %d containers", len(containers))
// Second snapshot with only 1 container (the other was "deleted" from Proxmox)
snapshot2 := models.StateSnapshot{
Containers: []models.Container{
{
ID: "ct-100",
VMID: 100,
Name: "container-to-keep",
Node: "pve-node-1",
Instance: "pve1",
Status: "running",
},
// container-to-remove is NOT included - simulating it was deleted
},
}
// Populate with second snapshot
store.PopulateFromSnapshot(snapshot2)
// Verify only 1 container remains
containers = store.Query().OfType(ResourceTypeContainer).Execute()
if len(containers) != 1 {
t.Fatalf("Expected 1 container after second snapshot (removed container should be gone), got %d", len(containers))
}
// Verify the correct container remains
if containers[0].ID != "ct-100" {
t.Errorf("Expected container-to-keep (ct-100) to remain, got %s", containers[0].ID)
}
// Verify the removed container is gone
_, found := store.Get("ct-200")
if found {
t.Error("container-to-remove (ct-200) should have been removed from the store")
}
t.Logf("SUCCESS: Removed resources are correctly cleaned up!")
t.Logf("After second snapshot: %d container(s) - 'container-to-remove' was properly removed", len(containers))
}