mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-22 11:45:52 +00:00
280a44ef08
New white-box table tests over previously-untested pure functions in internal/ai (patrol run recency, patrol findings JSON validation), internal/ai/tools (read-only violation hints, VM config parsing, node target matching), internal/ai/qualification (compare predicate, percentile and model summary, runner helpers), internal/ai/chat (investigation run error, session compaction formatting) and internal/agentcapabilities (path parameter substitution, markdown helpers). Twelve files, tests only, no source changes. All gates green with go test, gofmt and go vet, plus an adversarial GLM review scoring twelve KEEP and zero reject.
188 lines
5.1 KiB
Go
188 lines
5.1 KiB
Go
package chat
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestFormatSessionCompactionToolCall exercises every branch of
|
|
// formatSessionCompactionToolCall: empty/nil input maps, the JSON marshal
|
|
// success and failure paths, secret redaction on both Input and Output, name
|
|
// trimming, output sanitization (including whitespace-only and emoji-stripped
|
|
// outputs), and the tool-output truncation boundary at
|
|
// sessionCompactionToolOutputMaxChars.
|
|
func TestFormatSessionCompactionToolCall(t *testing.T) {
|
|
const toolOutputBudget = sessionCompactionToolOutputMaxChars // 1200
|
|
longOutput := strings.Repeat("a", toolOutputBudget+100)
|
|
boundaryOutput := strings.Repeat("a", toolOutputBudget)
|
|
truncatedTail := "\n[truncated]"
|
|
|
|
cases := []struct {
|
|
name string
|
|
toolCall ToolCall
|
|
want string
|
|
}{
|
|
{
|
|
name: "zero value renders empty name and default input object literal",
|
|
toolCall: ToolCall{},
|
|
want: "Tool call: {}",
|
|
},
|
|
{
|
|
name: "name is trimmed and default input object literal is kept",
|
|
toolCall: ToolCall{
|
|
Name: " list_pods ",
|
|
},
|
|
want: "Tool call: list_pods {}",
|
|
},
|
|
{
|
|
name: "nil input leaves default object literal",
|
|
toolCall: ToolCall{
|
|
Name: "ping",
|
|
Input: nil,
|
|
},
|
|
want: "Tool call: ping {}",
|
|
},
|
|
{
|
|
name: "empty input map leaves default object literal",
|
|
toolCall: ToolCall{
|
|
Name: "ping",
|
|
Input: map[string]interface{}{},
|
|
},
|
|
want: "Tool call: ping {}",
|
|
},
|
|
{
|
|
name: "single key input is marshaled to compact json",
|
|
toolCall: ToolCall{
|
|
Name: "read_file",
|
|
Input: map[string]interface{}{"path": "/etc/hosts"},
|
|
},
|
|
want: `Tool call: read_file {"path":"/etc/hosts"}`,
|
|
},
|
|
{
|
|
name: "multiple input keys are emitted in sorted json key order",
|
|
toolCall: ToolCall{
|
|
Name: "provision",
|
|
Input: map[string]interface{}{
|
|
"zebra": "1",
|
|
"apple": "2",
|
|
"mango": "3",
|
|
},
|
|
},
|
|
want: `Tool call: provision {"apple":"2","mango":"3","zebra":"1"}`,
|
|
},
|
|
{
|
|
name: "secret bearing input value is redacted after marshal",
|
|
toolCall: ToolCall{
|
|
Name: "login",
|
|
Input: map[string]interface{}{
|
|
"api_key": "sk-live-secret-value",
|
|
},
|
|
},
|
|
want: `Tool call: login {"api_key":"[REDACTED]"}`,
|
|
},
|
|
{
|
|
name: "input that fails to marshal falls back to default object literal",
|
|
toolCall: ToolCall{
|
|
Name: "broken",
|
|
Input: map[string]interface{}{"bad": make(chan int)},
|
|
},
|
|
want: "Tool call: broken {}",
|
|
},
|
|
{
|
|
name: "output only is sanitized and rendered on its own line",
|
|
toolCall: ToolCall{
|
|
Output: "pod-1234 running",
|
|
},
|
|
want: "Tool call: {}\nTool output: pod-1234 running",
|
|
},
|
|
{
|
|
name: "whitespace only output produces no output line",
|
|
toolCall: ToolCall{
|
|
Name: "noop",
|
|
Output: " \n\t ",
|
|
},
|
|
want: "Tool call: noop {}",
|
|
},
|
|
{
|
|
name: "empty output produces no output line",
|
|
toolCall: ToolCall{
|
|
Name: "noop",
|
|
Output: "",
|
|
},
|
|
want: "Tool call: noop {}",
|
|
},
|
|
{
|
|
name: "secret bearing output value is redacted",
|
|
toolCall: ToolCall{
|
|
Name: "fetch",
|
|
Output: "api_key: sk-live-secret-value",
|
|
},
|
|
want: "Tool call: fetch {}\nTool output: api_key: [REDACTED]",
|
|
},
|
|
{
|
|
name: "decorative emoji prefix in output is stripped by sanitization",
|
|
toolCall: ToolCall{
|
|
Output: "🟢 All good",
|
|
},
|
|
want: "Tool call: {}\nTool output: All good",
|
|
},
|
|
{
|
|
name: "output surrounding whitespace is trimmed",
|
|
toolCall: ToolCall{
|
|
Output: "\n health: ok \n",
|
|
},
|
|
want: "Tool call: {}\nTool output: health: ok",
|
|
},
|
|
{
|
|
name: "output exceeding the char budget is truncated",
|
|
toolCall: ToolCall{
|
|
Output: longOutput,
|
|
},
|
|
want: "Tool call: {}\nTool output: " + boundaryOutput + truncatedTail,
|
|
},
|
|
{
|
|
name: "output exactly at the char budget is not truncated",
|
|
toolCall: ToolCall{
|
|
Output: boundaryOutput,
|
|
},
|
|
want: "Tool call: {}\nTool output: " + boundaryOutput,
|
|
},
|
|
{
|
|
name: "name input and output are all rendered together",
|
|
toolCall: ToolCall{
|
|
Name: "run_check",
|
|
Input: map[string]interface{}{"host": "vault.lan"},
|
|
Output: "ok",
|
|
},
|
|
want: `Tool call: run_check {"host":"vault.lan"}` + "\nTool output: ok",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := formatSessionCompactionToolCall(tc.toolCall)
|
|
require.Equal(t, tc.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFormatSessionCompactionToolCallDeterminism asserts that repeated calls
|
|
// with the same input produce byte-identical output (json.Marshal key sorting
|
|
// and redaction are stable).
|
|
func TestFormatSessionCompactionToolCallDeterminism(t *testing.T) {
|
|
toolCall := ToolCall{
|
|
Name: "deploy",
|
|
Input: map[string]interface{}{"image": "nginx:1.25", "replicas": 3},
|
|
Output: "deployments/apps Deployment created",
|
|
}
|
|
first := formatSessionCompactionToolCall(toolCall)
|
|
for i := 0; i < 10; i++ {
|
|
require.Equal(t, first, formatSessionCompactionToolCall(toolCall),
|
|
"iteration %d produced non-deterministic output", i)
|
|
}
|
|
want := `Tool call: deploy {"image":"nginx:1.25","replicas":3}` + "\nTool output: deployments/apps Deployment created"
|
|
require.Equal(t, want, first)
|
|
}
|