Pulse/internal/agentcapabilities/tool_call_test.go
2026-07-14 15:35:48 +01:00

147 lines
8.6 KiB
Go

package agentcapabilities
import "testing"
func TestToolCallKindString(t *testing.T) {
tests := []struct {
kind ToolCallKind
want string
}{
{ToolCallKindResolve, "resolve"},
{ToolCallKindRead, "read"},
{ToolCallKindWrite, "write"},
{ToolCallKindUserInput, "user_input"},
{ToolCallKind(99), "unknown"},
}
for _, tt := range tests {
if got := tt.kind.String(); got != tt.want {
t.Fatalf("ToolCallKind(%d).String() = %q, want %q", tt.kind, got, tt.want)
}
}
}
func TestPulseIntelligenceToolNameConstants(t *testing.T) {
tests := map[string]string{
"PulseQueryToolName": PulseQueryToolName,
"PulseDiscoveryToolName": PulseDiscoveryToolName,
"PulseMetricsToolName": PulseMetricsToolName,
"PulseStorageToolName": PulseStorageToolName,
"PulseDockerToolName": PulseDockerToolName,
"PulseKubernetesToolName": PulseKubernetesToolName,
"PulseAlertsToolName": PulseAlertsToolName,
"PulseReadToolName": PulseReadToolName,
"PulseControlToolName": PulseControlToolName,
"PulseFileEditToolName": PulseFileEditToolName,
"PulseKnowledgeToolName": PulseKnowledgeToolName,
"PulsePMGToolName": PulsePMGToolName,
"PulseSummarizeToolName": PulseSummarizeToolName,
"PulseRunCommandToolName": PulseRunCommandToolName,
"PulseControlGuestToolName": PulseControlGuestToolName,
"PulseControlDockerToolName": PulseControlDockerToolName,
"PulseSearchResourcesToolName": PulseSearchResourcesToolName,
"PulseGetResourceToolName": PulseGetResourceToolName,
"PulseGetTopologyToolName": PulseGetTopologyToolName,
"PulseListInfrastructureToolName": PulseListInfrastructureToolName,
"PulseGetConnectionHealthToolName": PulseGetConnectionHealthToolName,
"PulseGetDockerLogsToolName": PulseGetDockerLogsToolName,
"PulseGetPerformanceMetricsToolName": PulseGetPerformanceMetricsToolName,
"PulseGetTemperaturesToolName": PulseGetTemperaturesToolName,
"PulseGetBaselinesToolName": PulseGetBaselinesToolName,
"PulseGetPatternsToolName": PulseGetPatternsToolName,
"PatrolGetFindingsToolName": PatrolGetFindingsToolName,
"PatrolAssessFindingToolName": PatrolAssessFindingToolName,
"PatrolReportFindingToolName": PatrolReportFindingToolName,
"PatrolResolveFindingToolName": PatrolResolveFindingToolName,
}
want := map[string]string{
"PulseQueryToolName": "pulse_query",
"PulseDiscoveryToolName": "pulse_discovery",
"PulseMetricsToolName": "pulse_metrics",
"PulseStorageToolName": "pulse_storage",
"PulseDockerToolName": "pulse_docker",
"PulseKubernetesToolName": "pulse_kubernetes",
"PulseAlertsToolName": "pulse_alerts",
"PulseReadToolName": "pulse_read",
"PulseControlToolName": "pulse_control",
"PulseFileEditToolName": "pulse_file_edit",
"PulseKnowledgeToolName": "pulse_knowledge",
"PulsePMGToolName": "pulse_pmg",
"PulseSummarizeToolName": "pulse_summarize",
"PulseRunCommandToolName": "pulse_run_command",
"PulseControlGuestToolName": "pulse_control_guest",
"PulseControlDockerToolName": "pulse_control_docker",
"PulseSearchResourcesToolName": "pulse_search_resources",
"PulseGetResourceToolName": "pulse_get_resource",
"PulseGetTopologyToolName": "pulse_get_topology",
"PulseListInfrastructureToolName": "pulse_list_infrastructure",
"PulseGetConnectionHealthToolName": "pulse_get_connection_health",
"PulseGetDockerLogsToolName": "pulse_get_docker_logs",
"PulseGetPerformanceMetricsToolName": "pulse_get_performance_metrics",
"PulseGetTemperaturesToolName": "pulse_get_temperatures",
"PulseGetBaselinesToolName": "pulse_get_baselines",
"PulseGetPatternsToolName": "pulse_get_patterns",
"PatrolGetFindingsToolName": "patrol_get_findings",
"PatrolAssessFindingToolName": "patrol_assess_finding",
"PatrolReportFindingToolName": "patrol_report_finding",
"PatrolResolveFindingToolName": "patrol_resolve_finding",
}
for name, got := range tests {
if got != want[name] {
t.Fatalf("%s = %q, want %q", name, got, want[name])
}
}
}
func TestClassifyToolCallUsesSharedSafetyClassification(t *testing.T) {
tests := []struct {
name string
toolName string
args map[string]interface{}
want ToolCallKind
}{
{name: "native question", toolName: PulseQuestionToolName, want: ToolCallKindUserInput},
{name: "query resolves", toolName: "pulse_query", want: ToolCallKindResolve},
{name: "discovery get resolves", toolName: "pulse_discovery", args: map[string]interface{}{"action": "get"}, want: ToolCallKindResolve},
{name: "discovery run resolves", toolName: "pulse_discovery", args: map[string]interface{}{"action": "run"}, want: ToolCallKindResolve},
{name: "discovery missing action fails closed", toolName: "pulse_discovery", want: ToolCallKindWrite},
{name: "metrics reads", toolName: "pulse_metrics", want: ToolCallKindRead},
{name: "summarize reads", toolName: PulseSummarizeToolName, want: ToolCallKindRead},
{name: "alert list reads", toolName: "pulse_alerts", args: map[string]interface{}{"action": "list"}, want: ToolCallKindRead},
{name: "alert resolve writes", toolName: "pulse_alerts", args: map[string]interface{}{"action": "resolve"}, want: ToolCallKindWrite},
{name: "read exec remains read", toolName: "pulse_read", args: map[string]interface{}{"action": "exec"}, want: ToolCallKindRead},
{name: "control writes", toolName: "pulse_control", args: map[string]interface{}{"type": "command"}, want: ToolCallKindWrite},
{name: "docker services reads", toolName: "pulse_docker", args: map[string]interface{}{"action": "services"}, want: ToolCallKindRead},
{name: "docker update writes", toolName: "pulse_docker", args: map[string]interface{}{"action": "update"}, want: ToolCallKindWrite},
// Kubernetes's real discriminator is `type`; the retired
// hard-coded classifier read `action` and therefore classified
// scale/restart/delete_pod/exec as read.
{name: "kubernetes pods reads", toolName: "pulse_kubernetes", args: map[string]interface{}{"type": "pods"}, want: ToolCallKindRead},
{name: "kubernetes scale writes", toolName: "pulse_kubernetes", args: map[string]interface{}{"type": "scale"}, want: ToolCallKindWrite},
{name: "kubernetes exec writes", toolName: "pulse_kubernetes", args: map[string]interface{}{"type": "exec"}, want: ToolCallKindWrite},
{name: "kubernetes wrong discriminator fails closed", toolName: "pulse_kubernetes", args: map[string]interface{}{"action": "pods"}, want: ToolCallKindWrite},
// pulse_file_edit is write-only; file reads route via pulse_read.
{name: "file read fails closed", toolName: "pulse_file_edit", args: map[string]interface{}{"action": "read"}, want: ToolCallKindWrite},
{name: "file append writes", toolName: "pulse_file_edit", args: map[string]interface{}{"action": "append"}, want: ToolCallKindWrite},
{name: "knowledge recall reads", toolName: "pulse_knowledge", args: map[string]interface{}{"action": "recall"}, want: ToolCallKindRead},
{name: "knowledge remember writes", toolName: "pulse_knowledge", args: map[string]interface{}{"action": "remember"}, want: ToolCallKindWrite},
{name: "legacy command writes", toolName: LegacyAssistantRunCommandToolName, want: ToolCallKindWrite},
{name: "legacy fetch url reads", toolName: LegacyAssistantFetchURLToolName, want: ToolCallKindRead},
{name: "legacy set url writes", toolName: LegacyAssistantSetResourceURLToolName, want: ToolCallKindWrite},
{name: "patrol findings read", toolName: "patrol_get_findings", want: ToolCallKindRead},
{name: "patrol finding assessment", toolName: "patrol_assess_finding", want: ToolCallKindWrite},
{name: "patrol report writes", toolName: "patrol_report_finding", want: ToolCallKindWrite},
{name: "unknown defaults write", toolName: "future_tool", want: ToolCallKindWrite},
{name: "generic action read", toolName: "future_tool", args: map[string]interface{}{"action": "inspect"}, want: ToolCallKindRead},
{name: "generic operation write", toolName: "future_tool", args: map[string]interface{}{"operation": "delete"}, want: ToolCallKindWrite},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ClassifyToolCall(tt.toolName, tt.args); got != tt.want {
t.Fatalf("ClassifyToolCall(%q, %#v) = %s, want %s", tt.toolName, tt.args, got, tt.want)
}
})
}
}