mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-29 03:50:18 +00:00
- HistoryChart: single metric visualization (CPU, memory, disk) - UnifiedHistoryChart: combined multi-metric view - Support for time range selection (1h to 90d) - Responsive charts with proper dark mode support - Fix corrupted tools_query_test.go from stash merge
314 lines
10 KiB
Go
314 lines
10 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/agentexec"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
|
)
|
|
|
|
func TestExecuteGetCapabilities(t *testing.T) {
|
|
executor := NewPulseToolExecutor(ExecutorConfig{
|
|
StateProvider: &mockStateProvider{},
|
|
AgentServer: &mockAgentServer{
|
|
agents: []agentexec.ConnectedAgent{
|
|
{Hostname: "host1", Version: "1.0", Platform: "linux"},
|
|
},
|
|
},
|
|
MetricsHistory: &mockMetricsHistoryProvider{},
|
|
BaselineProvider: &BaselineMCPAdapter{},
|
|
PatternProvider: &PatternMCPAdapter{},
|
|
AlertProvider: &mockAlertProvider{},
|
|
FindingsProvider: &mockFindingsProvider{},
|
|
ControlLevel: ControlLevelControlled,
|
|
ProtectedGuests: []string{"100"},
|
|
})
|
|
|
|
result, err := executor.executeGetCapabilities(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
var response CapabilitiesResponse
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &response); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if response.ControlLevel != string(ControlLevelControlled) || response.ConnectedAgents != 1 {
|
|
t.Fatalf("unexpected response: %+v", response)
|
|
}
|
|
if !response.Features.Control || !response.Features.MetricsHistory {
|
|
t.Fatalf("unexpected features: %+v", response.Features)
|
|
}
|
|
}
|
|
|
|
func TestExecuteGetURLContent(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("X-Test", "ok")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("hello"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
executor := NewPulseToolExecutor(ExecutorConfig{StateProvider: &mockStateProvider{}})
|
|
|
|
if result, _ := executor.executeGetURLContent(context.Background(), map[string]interface{}{}); !result.IsError {
|
|
t.Fatal("expected error when url missing")
|
|
}
|
|
|
|
result, err := executor.executeGetURLContent(context.Background(), map[string]interface{}{
|
|
"url": server.URL,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
var response URLFetchResponse
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &response); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if response.StatusCode != http.StatusOK || response.Headers["X-Test"] != "ok" {
|
|
t.Fatalf("unexpected response: %+v", response)
|
|
}
|
|
}
|
|
|
|
func TestExecuteListInfrastructureAndTopology(t *testing.T) {
|
|
state := models.StateSnapshot{
|
|
Nodes: []models.Node{{ID: "node1", Name: "node1", Status: "online"}},
|
|
VMs: []models.VM{
|
|
{Name: "vm1", VMID: 100, Status: "running", Node: "node1"},
|
|
},
|
|
Containers: []models.Container{
|
|
{Name: "ct1", VMID: 200, Status: "stopped", Node: "node1"},
|
|
},
|
|
DockerHosts: []models.DockerHost{
|
|
{
|
|
ID: "host1",
|
|
Hostname: "h1",
|
|
DisplayName: "Host 1",
|
|
Containers: []models.DockerContainer{
|
|
{ID: "c1", Name: "nginx", State: "running", Image: "nginx"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
executor := NewPulseToolExecutor(ExecutorConfig{
|
|
StateProvider: &mockStateProvider{state: state},
|
|
AgentServer: &mockAgentServer{
|
|
agents: []agentexec.ConnectedAgent{{Hostname: "node1"}},
|
|
},
|
|
ControlLevel: ControlLevelControlled,
|
|
})
|
|
|
|
result, err := executor.executeListInfrastructure(context.Background(), map[string]interface{}{
|
|
"type": "vms",
|
|
"status": "running",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
var infra InfrastructureResponse
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &infra); err != nil {
|
|
t.Fatalf("decode infra: %v", err)
|
|
}
|
|
if len(infra.VMs) != 1 || infra.VMs[0].Name != "vm1" {
|
|
t.Fatalf("unexpected infra response: %+v", infra)
|
|
}
|
|
|
|
// Topology includes derived node for VM reference if missing
|
|
state.Nodes = nil
|
|
executor.stateProvider = &mockStateProvider{state: state}
|
|
topologyResult, err := executor.executeGetTopology(context.Background(), map[string]interface{}{})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
var topology TopologyResponse
|
|
if err := json.Unmarshal([]byte(topologyResult.Content[0].Text), &topology); err != nil {
|
|
t.Fatalf("decode topology: %v", err)
|
|
}
|
|
if topology.Summary.TotalVMs != 1 || len(topology.Proxmox.Nodes) == 0 {
|
|
t.Fatalf("unexpected topology: %+v", topology)
|
|
}
|
|
}
|
|
|
|
func TestExecuteGetTopologySummaryOnly(t *testing.T) {
|
|
state := models.StateSnapshot{
|
|
Nodes: []models.Node{{ID: "node1", Name: "node1", Status: "online"}},
|
|
VMs: []models.VM{
|
|
{Name: "vm1", VMID: 100, Status: "running", Node: "node1"},
|
|
},
|
|
Containers: []models.Container{
|
|
{Name: "ct1", VMID: 200, Status: "stopped", Node: "node1"},
|
|
},
|
|
DockerHosts: []models.DockerHost{
|
|
{
|
|
Hostname: "host1",
|
|
Containers: []models.DockerContainer{
|
|
{ID: "c1", Name: "nginx", State: "running", Image: "nginx"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
executor := NewPulseToolExecutor(ExecutorConfig{
|
|
StateProvider: &mockStateProvider{state: state},
|
|
})
|
|
|
|
result, err := executor.executeGetTopology(context.Background(), map[string]interface{}{
|
|
"summary_only": true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
var topology TopologyResponse
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &topology); err != nil {
|
|
t.Fatalf("decode topology: %v", err)
|
|
}
|
|
if len(topology.Proxmox.Nodes) != 0 {
|
|
t.Fatalf("expected no proxmox nodes, got: %+v", topology.Proxmox.Nodes)
|
|
}
|
|
if len(topology.Docker.Hosts) != 0 {
|
|
t.Fatalf("expected no docker hosts, got: %+v", topology.Docker.Hosts)
|
|
}
|
|
if topology.Summary.TotalVMs != 1 || topology.Summary.TotalDockerHosts != 1 || topology.Summary.TotalDockerContainers != 1 {
|
|
t.Fatalf("unexpected summary: %+v", topology.Summary)
|
|
}
|
|
if topology.Summary.RunningVMs != 1 || topology.Summary.RunningDocker != 1 {
|
|
t.Fatalf("unexpected running summary: %+v", topology.Summary)
|
|
}
|
|
}
|
|
|
|
func TestExecuteSearchResources(t *testing.T) {
|
|
state := models.StateSnapshot{
|
|
Nodes: []models.Node{{ID: "node1", Name: "node1", Status: "online"}},
|
|
VMs: []models.VM{
|
|
{ID: "vm1", VMID: 100, Name: "web-vm", Status: "running", Node: "node1"},
|
|
},
|
|
Containers: []models.Container{
|
|
{ID: "ct1", VMID: 200, Name: "db-ct", Status: "stopped", Node: "node1"},
|
|
},
|
|
DockerHosts: []models.DockerHost{
|
|
{
|
|
ID: "host1",
|
|
Hostname: "dock1",
|
|
DisplayName: "Dock 1",
|
|
Status: "online",
|
|
Containers: []models.DockerContainer{
|
|
{ID: "c1", Name: "nginx", State: "running", Image: "nginx:latest"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
executor := NewPulseToolExecutor(ExecutorConfig{
|
|
StateProvider: &mockStateProvider{state: state},
|
|
})
|
|
|
|
result, err := executor.executeSearchResources(context.Background(), map[string]interface{}{
|
|
"query": "nginx",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
var response ResourceSearchResponse
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &response); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if len(response.Matches) != 1 || response.Matches[0].Type != "docker" || response.Matches[0].Name != "nginx" {
|
|
t.Fatalf("unexpected search response: %+v", response)
|
|
}
|
|
|
|
result, err = executor.executeSearchResources(context.Background(), map[string]interface{}{
|
|
"query": "web",
|
|
"type": "vm",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
response = ResourceSearchResponse{}
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &response); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if len(response.Matches) != 1 || response.Matches[0].Type != "vm" || response.Matches[0].Name != "web-vm" {
|
|
t.Fatalf("unexpected search response: %+v", response)
|
|
}
|
|
}
|
|
|
|
func TestExecuteSetResourceURLAndGetResource(t *testing.T) {
|
|
executor := NewPulseToolExecutor(ExecutorConfig{StateProvider: &mockStateProvider{}})
|
|
|
|
if result, _ := executor.executeSetResourceURL(context.Background(), map[string]interface{}{}); !result.IsError {
|
|
t.Fatal("expected error when resource_type missing")
|
|
}
|
|
|
|
updater := &fakeMetadataUpdater{}
|
|
executor.metadataUpdater = updater
|
|
result, err := executor.executeSetResourceURL(context.Background(), map[string]interface{}{
|
|
"resource_type": "guest",
|
|
"resource_id": "100",
|
|
"url": "http://example",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(updater.resourceArgs) != 3 || updater.resourceArgs[2] != "http://example" {
|
|
t.Fatalf("unexpected updater args: %+v", updater.resourceArgs)
|
|
}
|
|
var setResp map[string]interface{}
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &setResp); err != nil {
|
|
t.Fatalf("decode set response: %v", err)
|
|
}
|
|
if setResp["action"] != "set" {
|
|
t.Fatalf("unexpected set response: %+v", setResp)
|
|
}
|
|
|
|
state := models.StateSnapshot{
|
|
VMs: []models.VM{{ID: "vm1", VMID: 100, Name: "vm1", Status: "running", Node: "node1"}},
|
|
Containers: []models.Container{{ID: "ct1", VMID: 200, Name: "ct1", Status: "running", Node: "node1"}},
|
|
DockerHosts: []models.DockerHost{{
|
|
Hostname: "host",
|
|
Containers: []models.DockerContainer{{
|
|
ID: "abc123",
|
|
Name: "nginx",
|
|
State: "running",
|
|
Image: "nginx",
|
|
}},
|
|
}},
|
|
}
|
|
executor.stateProvider = &mockStateProvider{state: state}
|
|
|
|
resource, _ := executor.executeGetResource(context.Background(), map[string]interface{}{
|
|
"resource_type": "vm",
|
|
"resource_id": "100",
|
|
})
|
|
var res ResourceResponse
|
|
if err := json.Unmarshal([]byte(resource.Content[0].Text), &res); err != nil {
|
|
t.Fatalf("decode resource: %v", err)
|
|
}
|
|
if res.Type != "vm" || res.Name != "vm1" {
|
|
t.Fatalf("unexpected resource: %+v", res)
|
|
}
|
|
|
|
resource, _ = executor.executeGetResource(context.Background(), map[string]interface{}{
|
|
"resource_type": "docker",
|
|
"resource_id": "abc",
|
|
})
|
|
if err := json.Unmarshal([]byte(resource.Content[0].Text), &res); err != nil {
|
|
t.Fatalf("decode docker resource: %v", err)
|
|
}
|
|
if res.Type != "docker" || res.Name != "nginx" {
|
|
t.Fatalf("unexpected docker resource: %+v", res)
|
|
}
|
|
}
|
|
|
|
func TestIntArg(t *testing.T) {
|
|
if got := intArg(map[string]interface{}{}, "limit", 10); got != 10 {
|
|
t.Fatalf("unexpected default: %d", got)
|
|
}
|
|
if got := intArg(map[string]interface{}{"limit": float64(5)}, "limit", 10); got != 5 {
|
|
t.Fatalf("unexpected value: %d", got)
|
|
}
|
|
}
|