mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-22 11:45:52 +00:00
668 lines
21 KiB
Go
668 lines
21 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
type stubDiscoveryProvider struct {
|
|
lastGetResourceType string
|
|
lastGetTargetID string
|
|
lastGetResourceID string
|
|
getResp *ResourceDiscoveryInfo
|
|
getErr error
|
|
|
|
lastListType string
|
|
lastListTargetID string
|
|
listResp []*ResourceDiscoveryInfo
|
|
listErr error
|
|
|
|
lastTriggerResourceType string
|
|
lastTriggerTargetID string
|
|
lastTriggerResourceID string
|
|
lastTriggerForce bool
|
|
triggerResp *ResourceDiscoveryInfo
|
|
triggerErr error
|
|
}
|
|
|
|
func (s *stubDiscoveryProvider) GetDiscovery(_ string) (*ResourceDiscoveryInfo, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *stubDiscoveryProvider) GetDiscoveryByResource(resourceType, targetID, resourceID string) (*ResourceDiscoveryInfo, error) {
|
|
s.lastGetResourceType = resourceType
|
|
s.lastGetTargetID = targetID
|
|
s.lastGetResourceID = resourceID
|
|
return s.getResp, s.getErr
|
|
}
|
|
|
|
func (s *stubDiscoveryProvider) ListDiscoveries() ([]*ResourceDiscoveryInfo, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *stubDiscoveryProvider) ListDiscoveriesByType(resourceType string) ([]*ResourceDiscoveryInfo, error) {
|
|
s.lastListType = resourceType
|
|
return s.listResp, s.listErr
|
|
}
|
|
|
|
func (s *stubDiscoveryProvider) ListDiscoveriesByTarget(targetID string) ([]*ResourceDiscoveryInfo, error) {
|
|
s.lastListTargetID = targetID
|
|
return s.listResp, s.listErr
|
|
}
|
|
|
|
func (s *stubDiscoveryProvider) FormatForAIContext(_ []*ResourceDiscoveryInfo) string {
|
|
return ""
|
|
}
|
|
|
|
func (s *stubDiscoveryProvider) TriggerDiscovery(_ context.Context, resourceType, targetID, resourceID string, force bool) (*ResourceDiscoveryInfo, error) {
|
|
s.lastTriggerResourceType = resourceType
|
|
s.lastTriggerTargetID = targetID
|
|
s.lastTriggerResourceID = resourceID
|
|
s.lastTriggerForce = force
|
|
return s.triggerResp, s.triggerErr
|
|
}
|
|
|
|
func TestIsTransientError(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
err error
|
|
expected bool
|
|
}{
|
|
// Transient errors — should return true
|
|
{"nil error", nil, false},
|
|
{"rate limit 429", errors.New("API returned status 429"), true},
|
|
{"503 service unavailable", errors.New("HTTP 503 Service Unavailable"), true},
|
|
{"rate_limit underscore", errors.New("rate_limit: too many requests"), true},
|
|
{"rate limit space", errors.New("rate limit exceeded"), true},
|
|
{"ratelimit single word", errors.New("ratelimit error from provider"), true},
|
|
{"too many requests", errors.New("too many requests, slow down"), true},
|
|
{"timeout", errors.New("request timeout after 30s"), true},
|
|
{"context deadline", errors.New("context deadline exceeded"), true},
|
|
{"failed after retries", errors.New("failed after 3 retries"), true},
|
|
{"temporarily unavailable", errors.New("service temporarily unavailable"), true},
|
|
{"server overloaded", errors.New("server overloaded, try later"), true},
|
|
{"service unavailable text", errors.New("the service is service unavailable"), true},
|
|
{"connection refused", errors.New("dial tcp: connection refused"), true},
|
|
{"connection reset", errors.New("connection reset by peer"), true},
|
|
{"broken pipe", errors.New("write: broken pipe"), true},
|
|
{"i/o timeout", errors.New("i/o timeout"), true},
|
|
{"network unreachable", errors.New("network unreachable"), true},
|
|
|
|
// Anthropic-style rate limit
|
|
{"anthropic rate limit", errors.New("Error: 429 {\"type\":\"error\",\"error\":{\"type\":\"rate_limit_error\"}}"), true},
|
|
// OpenAI-style
|
|
{"openai rate limit", errors.New("Rate limit reached for gpt-4"), true},
|
|
// Gemini-style
|
|
{"gemini quota", errors.New("429 Too Many Requests"), true},
|
|
|
|
// Non-transient errors — should return false
|
|
{"resource not found", errors.New("resource not found"), false},
|
|
{"permission denied", errors.New("permission denied"), false},
|
|
{"invalid argument", errors.New("invalid resource_type: foo"), false},
|
|
{"generic error", errors.New("something went wrong"), false},
|
|
{"empty error", errors.New(""), false},
|
|
{"auth error", errors.New("authentication failed"), false},
|
|
{"not found", errors.New("404 not found"), false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := isTransientError(tt.err)
|
|
assert.Equal(t, tt.expected, result, "error: %v", tt.err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExecuteGetDiscovery_UsesTargetID(t *testing.T) {
|
|
provider := &stubDiscoveryProvider{
|
|
getResp: &ResourceDiscoveryInfo{
|
|
ID: "vm:node1:101",
|
|
ResourceType: "vm",
|
|
ResourceID: "101",
|
|
TargetID: "node1",
|
|
Hostname: "vm-101",
|
|
},
|
|
}
|
|
exec := NewPulseToolExecutor(ExecutorConfig{DiscoveryProvider: provider})
|
|
|
|
result, err := exec.executeGetDiscovery(context.Background(), map[string]interface{}{
|
|
"resource_type": "vm",
|
|
"resource_id": "101",
|
|
"target_id": "node1",
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.False(t, result.IsError)
|
|
assert.Equal(t, "vm", provider.lastGetResourceType)
|
|
assert.Equal(t, "node1", provider.lastGetTargetID)
|
|
assert.Equal(t, "101", provider.lastGetResourceID)
|
|
|
|
var payload map[string]interface{}
|
|
assert.NoError(t, json.Unmarshal([]byte(result.Content[0].Text), &payload))
|
|
assert.Equal(t, "node1", payload["target_id"])
|
|
assert.NotContains(t, payload, "host_id")
|
|
}
|
|
|
|
func TestPulseDiscoveryToolSchemaIncludesRunAction(t *testing.T) {
|
|
exec := NewPulseToolExecutor(ExecutorConfig{DiscoveryProvider: &stubDiscoveryProvider{}})
|
|
tools := exec.ListTools()
|
|
|
|
var discoveryTool *Tool
|
|
for i := range tools {
|
|
if tools[i].Name == "pulse_discovery" {
|
|
discoveryTool = &tools[i]
|
|
break
|
|
}
|
|
}
|
|
if discoveryTool == nil {
|
|
t.Fatalf("expected pulse_discovery tool")
|
|
}
|
|
|
|
actionSchema := discoveryTool.InputSchema.Properties["action"]
|
|
assert.Contains(t, actionSchema.Enum, "run")
|
|
assert.Contains(t, discoveryTool.Description, `action="run" forces a fresh discovery run`)
|
|
assert.Contains(t, discoveryTool.InputSchema.Properties["resource_id"].Description, "canonical resource ID")
|
|
}
|
|
|
|
func TestCanonicalDiscoveryResourceType(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
want string
|
|
}{
|
|
{in: "docker", want: "app-container"},
|
|
{in: "app-container", want: "app-container"},
|
|
{in: "k8s-cluster", want: "k8s-cluster"},
|
|
{in: "k8s-pod", want: "k8s-pod"},
|
|
{in: "docker-host", want: "docker-host"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
assert.Equal(t, tc.want, CanonicalDiscoveryResourceType(tc.in))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDiscoveryProviderResourceType(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
want string
|
|
}{
|
|
{in: "app-container", want: "docker"},
|
|
{in: "docker", want: "docker"},
|
|
{in: "vm", want: "vm"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
assert.Equal(t, tc.want, DiscoveryProviderResourceType(tc.in))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCanonicalDiscoveryTargetID(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in *ResourceDiscoveryInfo
|
|
fallback string
|
|
want string
|
|
}{
|
|
{
|
|
name: "uses discovery target",
|
|
in: &ResourceDiscoveryInfo{
|
|
TargetID: "node1",
|
|
},
|
|
fallback: "fallback",
|
|
want: "node1",
|
|
},
|
|
{
|
|
name: "falls back when target missing",
|
|
in: &ResourceDiscoveryInfo{
|
|
TargetID: " ",
|
|
},
|
|
fallback: "fallback",
|
|
want: "fallback",
|
|
},
|
|
{
|
|
name: "nil discovery falls back",
|
|
in: nil,
|
|
fallback: "fallback",
|
|
want: "fallback",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert.Equal(t, tc.want, CanonicalDiscoveryTargetID(tc.in, tc.fallback))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExecuteGetDiscovery_TargetIDRequired(t *testing.T) {
|
|
exec := NewPulseToolExecutor(ExecutorConfig{DiscoveryProvider: &stubDiscoveryProvider{}})
|
|
|
|
result, err := exec.executeGetDiscovery(context.Background(), map[string]interface{}{
|
|
"resource_type": "vm",
|
|
"resource_id": "101",
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.True(t, result.IsError)
|
|
assert.Contains(t, result.Content[0].Text, "target_id is required")
|
|
}
|
|
|
|
func TestIsUnsupportedDiscoveryLegacyResourceTypeToken(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want bool
|
|
}{
|
|
{name: "host rejected", in: "host", want: true},
|
|
{name: "mixed case host rejected", in: " HoSt ", want: true},
|
|
{name: "docker rejected", in: "docker", want: true},
|
|
{name: "docker container rejected", in: "docker-container", want: true},
|
|
{name: "lxc deferred to supported-type check", in: "lxc", want: false},
|
|
{name: "agent allowed", in: "agent", want: false},
|
|
{name: "vm allowed", in: "vm", want: false},
|
|
{name: "app container allowed", in: "app-container", want: false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.want, isUnsupportedDiscoveryLegacyResourceTypeToken(tt.in))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExecuteGetDiscovery_RejectsLegacyResourceTypeAlias(t *testing.T) {
|
|
provider := &stubDiscoveryProvider{}
|
|
exec := NewPulseToolExecutor(ExecutorConfig{DiscoveryProvider: provider})
|
|
|
|
for _, alias := range []string{"host", "lxc"} {
|
|
t.Run(alias, func(t *testing.T) {
|
|
result, err := exec.executeGetDiscovery(context.Background(), map[string]interface{}{
|
|
"resource_type": alias,
|
|
"resource_id": "101",
|
|
"target_id": "node1",
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.True(t, result.IsError)
|
|
assert.Contains(t, result.Content[0].Text, "unsupported resource_type")
|
|
assert.Equal(t, "", provider.lastGetResourceType)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExecuteGetDiscovery_RejectsLegacyUnderscoreAppContainerAlias(t *testing.T) {
|
|
provider := &stubDiscoveryProvider{}
|
|
exec := NewPulseToolExecutor(ExecutorConfig{DiscoveryProvider: provider})
|
|
|
|
result, err := exec.executeGetDiscovery(context.Background(), map[string]interface{}{
|
|
"resource_type": "docker_container",
|
|
"resource_id": "abc123",
|
|
"target_id": "agent-1",
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.True(t, result.IsError)
|
|
assert.Contains(t, result.Content[0].Text, "unsupported resource_type")
|
|
assert.Equal(t, "", provider.lastGetResourceType)
|
|
}
|
|
|
|
func TestExecuteGetDiscovery_RejectsLegacyDockerResourceTypeAliases(t *testing.T) {
|
|
for _, alias := range []string{"docker", "docker-container"} {
|
|
t.Run(alias, func(t *testing.T) {
|
|
provider := &stubDiscoveryProvider{}
|
|
exec := NewPulseToolExecutor(ExecutorConfig{DiscoveryProvider: provider})
|
|
|
|
result, err := exec.executeGetDiscovery(context.Background(), map[string]interface{}{
|
|
"resource_type": alias,
|
|
"resource_id": "abc123",
|
|
"target_id": "agent-1",
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.True(t, result.IsError)
|
|
assert.Contains(t, result.Content[0].Text, "unsupported resource_type")
|
|
assert.Equal(t, "", provider.lastGetResourceType)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExecuteGetDiscovery_CanonicalAppContainerUsesDockerProviderType(t *testing.T) {
|
|
provider := &stubDiscoveryProvider{
|
|
getResp: &ResourceDiscoveryInfo{
|
|
ID: "docker:agent-1:abc123",
|
|
ResourceType: "docker",
|
|
ResourceID: "abc123",
|
|
TargetID: "agent-1",
|
|
Hostname: "docker-host-1",
|
|
SuggestedURL: "http://192.0.2.10:8080",
|
|
},
|
|
}
|
|
exec := NewPulseToolExecutor(ExecutorConfig{DiscoveryProvider: provider})
|
|
|
|
result, err := exec.executeGetDiscovery(context.Background(), map[string]interface{}{
|
|
"resource_type": "app-container",
|
|
"resource_id": "abc123",
|
|
"target_id": "agent-1",
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.False(t, result.IsError)
|
|
assert.Equal(t, "docker", provider.lastGetResourceType)
|
|
assert.Equal(t, "agent-1", provider.lastGetTargetID)
|
|
assert.Equal(t, "abc123", provider.lastGetResourceID)
|
|
|
|
var payload map[string]interface{}
|
|
assert.NoError(t, json.Unmarshal([]byte(result.Content[0].Text), &payload))
|
|
assert.Equal(t, "app-container", payload["resource_type"])
|
|
assert.Equal(t, "http://192.0.2.10:8080", payload["suggested_url"])
|
|
}
|
|
|
|
func TestExecuteGetDiscovery_ResolvesCanonicalAppContainerIDForProvider(t *testing.T) {
|
|
const canonicalID = "app-container-e5593f5074d6cd7f"
|
|
providerID := strings.Repeat("3", 64)
|
|
discovery := &stubDiscoveryProvider{getResp: &ResourceDiscoveryInfo{
|
|
ID: "docker:agent-1:" + providerID,
|
|
ResourceType: "docker",
|
|
ResourceID: providerID,
|
|
TargetID: "agent-1",
|
|
Hostname: "docker-host-1",
|
|
}}
|
|
exec := NewPulseToolExecutor(ExecutorConfig{
|
|
DiscoveryProvider: discovery,
|
|
UnifiedResourceProvider: &stubUnifiedResourceProvider{resources: []unifiedresources.Resource{{
|
|
ID: canonicalID,
|
|
Type: unifiedresources.ResourceTypeAppContainer,
|
|
Technology: "docker",
|
|
Name: "worker",
|
|
Status: unifiedresources.StatusOffline,
|
|
ParentName: "docker-host-1",
|
|
Docker: &unifiedresources.DockerData{
|
|
ContainerID: providerID,
|
|
ContainerState: "exited",
|
|
},
|
|
}}},
|
|
})
|
|
|
|
result, err := exec.executeGetDiscovery(context.Background(), map[string]interface{}{
|
|
"resource_type": "app-container",
|
|
"resource_id": canonicalID,
|
|
"target_id": "agent-1",
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.False(t, result.IsError)
|
|
assert.Equal(t, "docker", discovery.lastGetResourceType)
|
|
assert.Equal(t, "agent-1", discovery.lastGetTargetID)
|
|
assert.Equal(t, providerID, discovery.lastGetResourceID)
|
|
}
|
|
|
|
func TestBuildDiscoveryToolResponse_IncludesBindMounts(t *testing.T) {
|
|
req := discoveryResourceRequest{
|
|
resourceType: "app-container",
|
|
resourceID: "abc123",
|
|
targetID: "agent-1",
|
|
}
|
|
discovery := &ResourceDiscoveryInfo{
|
|
ID: "docker:agent-1:abc123",
|
|
ResourceType: "docker",
|
|
ResourceID: "abc123",
|
|
TargetID: "agent-1",
|
|
Hostname: "docker-host-1",
|
|
ServiceType: "homeassistant",
|
|
BindMounts: []DiscoveryMount{
|
|
{
|
|
ContainerName: "homeassistant",
|
|
Source: "/opt/appdata/homeassistant",
|
|
Destination: "/config",
|
|
Type: "bind",
|
|
ReadOnly: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
resp := buildDiscoveryToolResponse(req, discovery)
|
|
|
|
rawMounts, ok := resp["bind_mounts"].([]map[string]interface{})
|
|
if !ok {
|
|
t.Fatalf("expected bind_mounts to be []map[string]interface{}, got %T", resp["bind_mounts"])
|
|
}
|
|
if assert.Len(t, rawMounts, 1) {
|
|
mount := rawMounts[0]
|
|
assert.Equal(t, "/opt/appdata/homeassistant", mount["source"])
|
|
assert.Equal(t, "/config", mount["destination"])
|
|
assert.Equal(t, "homeassistant", mount["container_name"])
|
|
assert.Equal(t, "bind", mount["type"])
|
|
assert.Equal(t, true, mount["read_only"])
|
|
}
|
|
}
|
|
|
|
func TestBuildDiscoveryToolResponse_OmitsBindMountsWhenEmpty(t *testing.T) {
|
|
req := discoveryResourceRequest{
|
|
resourceType: "vm",
|
|
resourceID: "101",
|
|
targetID: "node1",
|
|
}
|
|
discovery := &ResourceDiscoveryInfo{
|
|
ID: "vm:node1:101",
|
|
ResourceType: "vm",
|
|
ResourceID: "101",
|
|
TargetID: "node1",
|
|
Hostname: "vm-101",
|
|
}
|
|
|
|
resp := buildDiscoveryToolResponse(req, discovery)
|
|
assert.NotContains(t, resp, "bind_mounts")
|
|
}
|
|
|
|
func TestBuildDiscoveryToolResponse_OmitsOptionalMountFields(t *testing.T) {
|
|
req := discoveryResourceRequest{
|
|
resourceType: "app-container",
|
|
resourceID: "abc123",
|
|
targetID: "agent-1",
|
|
}
|
|
discovery := &ResourceDiscoveryInfo{
|
|
ID: "docker:agent-1:abc123",
|
|
ResourceType: "docker",
|
|
ResourceID: "abc123",
|
|
TargetID: "agent-1",
|
|
BindMounts: []DiscoveryMount{
|
|
{
|
|
Source: "/srv/data",
|
|
Destination: "/data",
|
|
},
|
|
},
|
|
}
|
|
|
|
resp := buildDiscoveryToolResponse(req, discovery)
|
|
rawMounts, ok := resp["bind_mounts"].([]map[string]interface{})
|
|
if !ok {
|
|
t.Fatalf("expected bind_mounts to be []map[string]interface{}, got %T", resp["bind_mounts"])
|
|
}
|
|
if assert.Len(t, rawMounts, 1) {
|
|
mount := rawMounts[0]
|
|
assert.Equal(t, "/srv/data", mount["source"])
|
|
assert.Equal(t, "/data", mount["destination"])
|
|
assert.NotContains(t, mount, "container_name")
|
|
assert.NotContains(t, mount, "type")
|
|
assert.NotContains(t, mount, "read_only")
|
|
}
|
|
}
|
|
|
|
func TestBuildDiscoveryToolResponse_IncludesFactProvenance(t *testing.T) {
|
|
req := discoveryResourceRequest{
|
|
resourceType: "system-container",
|
|
resourceID: "101",
|
|
targetID: "node1",
|
|
}
|
|
discovery := &ResourceDiscoveryInfo{
|
|
ID: "system-container:node1:101",
|
|
ResourceType: "system-container",
|
|
ResourceID: "101",
|
|
TargetID: "node1",
|
|
Facts: []DiscoveryFact{
|
|
{Category: "version", Key: "os", Value: "Debian 12", Source: "/etc/os-release", Confidence: 0.95},
|
|
{Category: "service", Key: "unit", Value: "home-assistant.service"}, // no source/confidence
|
|
},
|
|
}
|
|
|
|
resp := buildDiscoveryToolResponse(req, discovery)
|
|
|
|
rawFacts, ok := resp["facts"].([]map[string]interface{})
|
|
if !ok {
|
|
t.Fatalf("expected facts to be []map[string]interface{}, got %T", resp["facts"])
|
|
}
|
|
if assert.Len(t, rawFacts, 2) {
|
|
// A fact with provenance carries source + confidence so the model can
|
|
// attribute and weight it.
|
|
assert.Equal(t, "Debian 12", rawFacts[0]["value"])
|
|
assert.Equal(t, "/etc/os-release", rawFacts[0]["source"])
|
|
assert.Equal(t, 0.95, rawFacts[0]["confidence"])
|
|
// A fact without provenance omits the optional fields cleanly.
|
|
assert.Equal(t, "home-assistant.service", rawFacts[1]["value"])
|
|
assert.NotContains(t, rawFacts[1], "source")
|
|
assert.NotContains(t, rawFacts[1], "confidence")
|
|
}
|
|
}
|
|
|
|
func TestExecuteRunDiscovery_ForcesFreshDiscovery(t *testing.T) {
|
|
provider := &stubDiscoveryProvider{
|
|
triggerResp: &ResourceDiscoveryInfo{
|
|
ID: "vm:node1:101",
|
|
ResourceType: "vm",
|
|
ResourceID: "101",
|
|
TargetID: "node1",
|
|
Hostname: "vm-101",
|
|
ServiceName: "PostgreSQL",
|
|
},
|
|
}
|
|
exec := NewPulseToolExecutor(ExecutorConfig{DiscoveryProvider: provider})
|
|
|
|
result, err := exec.executeDiscovery(context.Background(), map[string]interface{}{
|
|
"action": "run",
|
|
"resource_type": "vm",
|
|
"resource_id": "101",
|
|
"target_id": "node1",
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.False(t, result.IsError)
|
|
assert.Equal(t, "vm", provider.lastTriggerResourceType)
|
|
assert.Equal(t, "node1", provider.lastTriggerTargetID)
|
|
assert.Equal(t, "101", provider.lastTriggerResourceID)
|
|
assert.True(t, provider.lastTriggerForce)
|
|
|
|
var payload map[string]interface{}
|
|
assert.NoError(t, json.Unmarshal([]byte(result.Content[0].Text), &payload))
|
|
assert.Equal(t, true, payload["refreshed"])
|
|
assert.Equal(t, "PostgreSQL", payload["service_name"])
|
|
}
|
|
|
|
func TestExecuteListDiscoveries_FiltersByTargetID(t *testing.T) {
|
|
provider := &stubDiscoveryProvider{
|
|
listResp: []*ResourceDiscoveryInfo{
|
|
{
|
|
ID: "vm:node2:102",
|
|
ResourceType: "vm",
|
|
ResourceID: "102",
|
|
TargetID: "node2",
|
|
Hostname: "vm-102",
|
|
},
|
|
},
|
|
}
|
|
exec := NewPulseToolExecutor(ExecutorConfig{DiscoveryProvider: provider})
|
|
|
|
result, err := exec.executeListDiscoveries(context.Background(), map[string]interface{}{
|
|
"target_id": "node2",
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.False(t, result.IsError)
|
|
assert.Equal(t, "node2", provider.lastListTargetID)
|
|
|
|
var payload map[string]interface{}
|
|
assert.NoError(t, json.Unmarshal([]byte(result.Content[0].Text), &payload))
|
|
assert.Equal(t, "node2", payload["filter_target_id"])
|
|
discoveries, ok := payload["discoveries"].([]interface{})
|
|
assert.True(t, ok)
|
|
if assert.NotEmpty(t, discoveries) {
|
|
first, castOK := discoveries[0].(map[string]interface{})
|
|
assert.True(t, castOK)
|
|
assert.NotContains(t, first, "host_id")
|
|
}
|
|
}
|
|
|
|
func TestExecuteListDiscoveries_RejectsLegacyTypeAlias(t *testing.T) {
|
|
exec := NewPulseToolExecutor(ExecutorConfig{DiscoveryProvider: &stubDiscoveryProvider{}})
|
|
|
|
for _, alias := range []string{"host", "lxc"} {
|
|
t.Run(alias, func(t *testing.T) {
|
|
result, err := exec.executeListDiscoveries(context.Background(), map[string]interface{}{
|
|
"type": alias,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.True(t, result.IsError)
|
|
assert.Contains(t, result.Content[0].Text, "unsupported type")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExecuteListDiscoveries_RejectsLegacyUnderscoreAppContainerAlias(t *testing.T) {
|
|
exec := NewPulseToolExecutor(ExecutorConfig{DiscoveryProvider: &stubDiscoveryProvider{}})
|
|
|
|
result, err := exec.executeListDiscoveries(context.Background(), map[string]interface{}{
|
|
"type": "app_container",
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.True(t, result.IsError)
|
|
assert.Contains(t, result.Content[0].Text, "unsupported type")
|
|
}
|
|
|
|
func TestExecuteListDiscoveries_RejectsLegacyDockerTypeAliases(t *testing.T) {
|
|
for _, alias := range []string{"docker", "docker-container"} {
|
|
t.Run(alias, func(t *testing.T) {
|
|
exec := NewPulseToolExecutor(ExecutorConfig{DiscoveryProvider: &stubDiscoveryProvider{}})
|
|
|
|
result, err := exec.executeListDiscoveries(context.Background(), map[string]interface{}{
|
|
"type": alias,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.True(t, result.IsError)
|
|
assert.Contains(t, result.Content[0].Text, "unsupported type")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExecuteListDiscoveries_CanonicalAppContainerUsesDockerProviderType(t *testing.T) {
|
|
provider := &stubDiscoveryProvider{
|
|
listResp: []*ResourceDiscoveryInfo{
|
|
{
|
|
ID: "docker:agent-1:abc123",
|
|
ResourceType: "docker",
|
|
ResourceID: "abc123",
|
|
TargetID: "agent-1",
|
|
Hostname: "docker-host-1",
|
|
},
|
|
},
|
|
}
|
|
exec := NewPulseToolExecutor(ExecutorConfig{DiscoveryProvider: provider})
|
|
|
|
result, err := exec.executeListDiscoveries(context.Background(), map[string]interface{}{
|
|
"type": "app-container",
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.False(t, result.IsError)
|
|
assert.Equal(t, "docker", provider.lastListType)
|
|
|
|
var payload map[string]interface{}
|
|
assert.NoError(t, json.Unmarshal([]byte(result.Content[0].Text), &payload))
|
|
assert.Equal(t, "app-container", payload["filter_type"])
|
|
discoveries, ok := payload["discoveries"].([]interface{})
|
|
assert.True(t, ok)
|
|
if assert.Len(t, discoveries, 1) {
|
|
first, castOK := discoveries[0].(map[string]interface{})
|
|
assert.True(t, castOK)
|
|
assert.Equal(t, "app-container", first["resource_type"])
|
|
}
|
|
}
|