255 lines
7.7 KiB
Go
255 lines
7.7 KiB
Go
package status
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/smallnest/pigo/internal/agentcore"
|
|
"github.com/smallnest/pigo/internal/cli"
|
|
"github.com/smallnest/pigo/internal/compaction"
|
|
"github.com/smallnest/pigo/internal/runtime"
|
|
)
|
|
|
|
func TestRunStatus(t *testing.T) {
|
|
host := newFakeHost()
|
|
host.live.Model = "test-model"
|
|
host.live.ProviderName = "test-provider"
|
|
host.live.BaseURL = "https://api.example.com"
|
|
host.live.Protocol = "anthropic"
|
|
host.live.ContextWindow = 128000
|
|
|
|
host.agentCtx.Messages = append(host.agentCtx.Messages,
|
|
agentcore.UserMessage{RoleField: agentcore.RoleUser, Content: agentcore.ContentList{agentcore.NewTextContent("hello")}},
|
|
)
|
|
|
|
var buf bytes.Buffer
|
|
RunStatus(&buf, host)
|
|
output := buf.String()
|
|
|
|
if !strings.Contains(output, "runtime config:") {
|
|
t.Error("expected output to contain 'runtime config:'")
|
|
}
|
|
if !strings.Contains(output, "model: test-model") {
|
|
t.Error("expected output to contain 'model: test-model'")
|
|
}
|
|
if !strings.Contains(output, "provider: test-provider") {
|
|
t.Error("expected output to contain 'provider: test-provider'")
|
|
}
|
|
if !strings.Contains(output, "base URL: https://api.example.com") {
|
|
t.Error("expected output to contain 'base URL: https://api.example.com'")
|
|
}
|
|
if !strings.Contains(output, "protocol: anthropic") {
|
|
t.Error("expected output to contain 'protocol: anthropic'")
|
|
}
|
|
if !strings.Contains(output, "context window: 128000 tokens") {
|
|
t.Error("expected output to contain 'context window: 128000 tokens'")
|
|
}
|
|
|
|
if !strings.Contains(output, "context:") {
|
|
t.Error("expected output to contain 'context:'")
|
|
}
|
|
if !strings.Contains(output, "current:") {
|
|
t.Error("expected output to contain 'current:'")
|
|
}
|
|
if !strings.Contains(output, "utilization:") {
|
|
t.Error("expected output to contain 'utilization:'")
|
|
}
|
|
if !strings.Contains(output, "compactions: 0") {
|
|
t.Error("expected output to contain 'compactions: 0'")
|
|
}
|
|
if !strings.Contains(output, "before compact:") {
|
|
t.Error("expected output to contain 'before compact:'")
|
|
}
|
|
}
|
|
|
|
func TestRunStatusWithCompaction(t *testing.T) {
|
|
host := newFakeHost()
|
|
host.live.ContextWindow = 128000
|
|
|
|
host.agentCtx.Messages = append(host.agentCtx.Messages,
|
|
agentcore.UserMessage{RoleField: agentcore.RoleUser, Content: agentcore.ContentList{agentcore.NewTextContent("hello")}},
|
|
agentcore.CompactionMessage{Summary: "compacted history"},
|
|
agentcore.UserMessage{RoleField: agentcore.RoleUser, Content: agentcore.ContentList{agentcore.NewTextContent("more")}},
|
|
)
|
|
|
|
var buf bytes.Buffer
|
|
RunStatus(&buf, host)
|
|
output := buf.String()
|
|
|
|
if !strings.Contains(output, "compactions: 1") {
|
|
t.Error("expected output to contain 'compactions: 1'")
|
|
}
|
|
}
|
|
|
|
func TestRunStatusUnknownContextWindow(t *testing.T) {
|
|
host := newFakeHost()
|
|
host.live.ContextWindow = 0 // unknown
|
|
|
|
var buf bytes.Buffer
|
|
RunStatus(&buf, host)
|
|
output := buf.String()
|
|
|
|
if !strings.Contains(output, "context window: unknown") {
|
|
t.Error("expected output to contain 'context window: unknown'")
|
|
}
|
|
if !strings.Contains(output, "auto-compaction disabled") {
|
|
t.Error("expected output to contain 'auto-compaction disabled'")
|
|
}
|
|
}
|
|
|
|
func TestBeforeCompactCalculation(t *testing.T) {
|
|
reserve := compaction.DefaultCompactionSettings.ReserveTokens
|
|
if reserve != 16384 {
|
|
t.Errorf("expected reserve tokens to be 16384, got %d", reserve)
|
|
}
|
|
|
|
contextWindow := 128000
|
|
threshold := contextWindow - reserve
|
|
if threshold != 128000-16384 {
|
|
t.Errorf("expected threshold to be 128000-16384=%d, got %d", 128000-16384, threshold)
|
|
}
|
|
}
|
|
|
|
func TestRunStatusEnvAndCreds(t *testing.T) {
|
|
host := newFakeHost()
|
|
host.cwd = "/tmp/test-cwd"
|
|
host.live.ProviderName = "test-provider"
|
|
host.live.BaseURL = "https://api.example.com"
|
|
|
|
host.slash.AddSkill(runtime.SlashCommand{Name: "my-skill", Expand: func(string) string { return "" }})
|
|
host.slash.AddPlugin(runtime.SlashCommand{Name: "my-plugin", Run: func(string) (string, string) { return "", "" }})
|
|
|
|
host.creds.SetOverride("test-provider", "sk-secretkey-wxyz")
|
|
|
|
var buf bytes.Buffer
|
|
RunStatus(&buf, host)
|
|
output := buf.String()
|
|
|
|
if !strings.Contains(output, "project & environment:") {
|
|
t.Error("expected 'project & environment:' section")
|
|
}
|
|
if !strings.Contains(output, "cwd: /tmp/test-cwd") {
|
|
t.Error("expected 'cwd: /tmp/test-cwd'")
|
|
}
|
|
if !strings.Contains(output, "trust: disabled") {
|
|
t.Error("expected 'trust: disabled' when trust manager is nil")
|
|
}
|
|
if !strings.Contains(output, "skills: 1 (my-skill)") {
|
|
t.Error("expected 'skills: 1 (my-skill)'")
|
|
}
|
|
if !strings.Contains(output, "plugins: 1 (my-plugin)") {
|
|
t.Error("expected 'plugins: 1 (my-plugin)'")
|
|
}
|
|
|
|
if !strings.Contains(output, "credentials & connectivity:") {
|
|
t.Error("expected 'credentials & connectivity:' section")
|
|
}
|
|
if !strings.Contains(output, "api key: set") {
|
|
t.Error("expected 'api key: set'")
|
|
}
|
|
if !strings.Contains(output, "••••wxyz") {
|
|
t.Error("expected masked key '••••wxyz'")
|
|
}
|
|
if strings.Contains(output, "sk-secretkey-wxyz") {
|
|
t.Error("full API key leaked into /status output")
|
|
}
|
|
if !strings.Contains(output, "endpoint: https://api.example.com") {
|
|
t.Error("expected 'endpoint: https://api.example.com'")
|
|
}
|
|
}
|
|
|
|
func TestRunStatusTelemetryNoData(t *testing.T) {
|
|
host := newFakeHost()
|
|
// host.telemetry is nil.
|
|
|
|
var buf bytes.Buffer
|
|
RunStatus(&buf, host)
|
|
output := buf.String()
|
|
|
|
if !strings.Contains(output, "telemetry:") {
|
|
t.Error("expected 'telemetry:' section")
|
|
}
|
|
if n := strings.Count(output, "no telemetry yet"); n != 2 {
|
|
t.Errorf("expected 2 'no telemetry yet' (cumulative + last run), got %d", n)
|
|
}
|
|
}
|
|
|
|
func TestRunStatusTelemetryPopulated(t *testing.T) {
|
|
host := newFakeHost()
|
|
host.live.ContextWindow = 128000
|
|
|
|
holder := cli.NewTelemetryHolder()
|
|
holder.Fold(agentcore.TelemetryEvent{
|
|
Turns: 3,
|
|
TruncationCount: 1,
|
|
CompactionCount: 0,
|
|
ContextUtilization: 0.42,
|
|
ContextTokens: 53760,
|
|
ContextWindow: 128000,
|
|
ToolDurationsMs: map[string]agentcore.ToolTiming{
|
|
"bash": {Count: 2, TotalMs: 150},
|
|
"read": {Count: 4, TotalMs: 80},
|
|
},
|
|
})
|
|
host.telemetry = holder
|
|
|
|
var buf bytes.Buffer
|
|
RunStatus(&buf, host)
|
|
output := buf.String()
|
|
|
|
if strings.Contains(output, "no telemetry yet") {
|
|
t.Error("did not expect 'no telemetry yet' when telemetry is populated")
|
|
}
|
|
if !strings.Contains(output, "since session start:") {
|
|
t.Error("expected 'since session start:' cumulative block")
|
|
}
|
|
if !strings.Contains(output, "last run:") {
|
|
t.Error("expected 'last run:' block")
|
|
}
|
|
if !strings.Contains(output, "turns: 3") {
|
|
t.Error("expected 'turns: 3' (last run == cumulative after one run)")
|
|
}
|
|
if !strings.Contains(output, "bash") || !strings.Contains(output, "2 calls") || !strings.Contains(output, "150ms") {
|
|
t.Error("expected bash tool row with '2 calls' / '150ms'")
|
|
}
|
|
if !strings.Contains(output, "utilization: 42%") {
|
|
t.Error("expected 'utilization: 42%'")
|
|
}
|
|
|
|
holder.Fold(agentcore.TelemetryEvent{
|
|
Turns: 2,
|
|
TruncationCount: 0,
|
|
CompactionCount: 1,
|
|
ContextUtilization: 0.5,
|
|
ContextTokens: 64000,
|
|
ContextWindow: 128000,
|
|
ToolDurationsMs: map[string]agentcore.ToolTiming{
|
|
"bash": {Count: 1, TotalMs: 40},
|
|
},
|
|
})
|
|
buf.Reset()
|
|
RunStatus(&buf, host)
|
|
output = buf.String()
|
|
if !strings.Contains(output, "turns: 5") {
|
|
t.Error("expected cumulative 'turns: 5' after two runs")
|
|
}
|
|
if !strings.Contains(output, "turns: 2") {
|
|
t.Error("expected last-run 'turns: 2' after two runs")
|
|
}
|
|
}
|
|
|
|
func TestMaskKey(t *testing.T) {
|
|
cases := []struct{ in, want string }{
|
|
{"sk-secretkey-wxyz", "••••wxyz"},
|
|
{"abcd", "••••"}, // exactly 4 -> masked entirely
|
|
{"ab", "••"},
|
|
{"", ""},
|
|
}
|
|
for _, c := range cases {
|
|
if got := maskKey(c.in); got != c.want {
|
|
t.Errorf("maskKey(%q) = %q, want %q", c.in, got, c.want)
|
|
}
|
|
}
|
|
}
|