Files
clawgo/pkg/agent/loop_run_control_test.go
2026-02-19 15:23:25 +08:00

193 lines
4.3 KiB
Go

package agent
import (
"context"
"testing"
"time"
"clawgo/pkg/bus"
)
func TestDetectRunControlIntent(t *testing.T) {
t.Parallel()
intent, ok := detectRunControlIntent("请等待 run-123-7 120 秒后告诉我状态")
if !ok {
t.Fatalf("expected run control intent")
}
if intent.runID != "run-123-7" {
t.Fatalf("unexpected run id: %s", intent.runID)
}
if !intent.wait {
t.Fatalf("expected wait=true")
}
if intent.timeout != 120*time.Second {
t.Fatalf("unexpected timeout: %s", intent.timeout)
}
}
func TestDetectRunControlIntentLatest(t *testing.T) {
t.Parallel()
intent, ok := detectRunControlIntent("latest run status")
if !ok {
t.Fatalf("expected latest run status intent")
}
if !intent.latest {
t.Fatalf("expected latest=true")
}
if intent.runID != "" {
t.Fatalf("expected empty run id")
}
}
func TestParseRunWaitTimeout_MinClamp(t *testing.T) {
t.Parallel()
got := parseRunWaitTimeout("wait run-1-1 1 s")
if got != minRunWaitTimeout {
t.Fatalf("expected min timeout %s, got %s", minRunWaitTimeout, got)
}
}
func TestParseRunWaitTimeout_MinuteUnit(t *testing.T) {
t.Parallel()
got := parseRunWaitTimeout("等待 run-1-1 2 分钟")
if got != 2*time.Minute {
t.Fatalf("expected 2m, got %s", got)
}
}
func TestDetectRunControlIntentIgnoresNonControlText(t *testing.T) {
t.Parallel()
if _, ok := detectRunControlIntent("帮我写一个README"); ok {
t.Fatalf("did not expect run control intent")
}
}
func TestDetectRunControlIntentWithCustomLexicon(t *testing.T) {
t.Parallel()
lex := runControlLexicon{
latestKeywords: []string{"newest"},
waitKeywords: []string{"block"},
statusKeywords: []string{"health"},
runMentionKeywords: []string{"job"},
minuteUnits: map[string]struct{}{"mins": {}},
}
intent, ok := detectRunControlIntentWithLexicon("block run-55-1 for 2 mins and show health", lex)
if !ok {
t.Fatalf("expected intent with custom lexicon")
}
if !intent.wait {
t.Fatalf("expected wait=true")
}
if intent.timeout != 2*time.Minute {
t.Fatalf("unexpected timeout: %s", intent.timeout)
}
}
func TestLatestRunStateBySession(t *testing.T) {
t.Parallel()
now := time.Now()
al := &AgentLoop{
runStates: map[string]*runState{
"run-1-1": {
runID: "run-1-1",
sessionKey: "s1",
startedAt: now.Add(-2 * time.Minute),
},
"run-1-2": {
runID: "run-1-2",
sessionKey: "s1",
startedAt: now.Add(-time.Minute),
},
"run-2-1": {
runID: "run-2-1",
sessionKey: "s2",
startedAt: now,
},
},
}
rs, ok := al.latestRunState("s1")
if !ok {
t.Fatalf("expected state for s1")
}
if rs.runID != "run-1-2" {
t.Fatalf("unexpected run id: %s", rs.runID)
}
}
func TestHandleSlashCommand_StatusRunLatest(t *testing.T) {
t.Parallel()
al := &AgentLoop{
runStates: map[string]*runState{
"run-100-1": {
runID: "run-100-1",
sessionKey: "s1",
status: runStatusOK,
acceptedAt: time.Now().Add(-time.Minute),
startedAt: time.Now().Add(-time.Minute),
endedAt: time.Now().Add(-30 * time.Second),
done: closedChan(),
},
},
}
handled, out, err := al.handleSlashCommand(context.Background(), bus.InboundMessage{
Content: "/status run latest",
SessionKey: "s1",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !handled {
t.Fatalf("expected command handled")
}
if out == "" || !containsAnySubstring(out, "run-100-1", "Run ID: run-100-1") {
t.Fatalf("unexpected output: %s", out)
}
}
func TestHandleSlashCommand_StatusWaitDoneRun(t *testing.T) {
t.Parallel()
al := &AgentLoop{
runStates: map[string]*runState{
"run-200-2": {
runID: "run-200-2",
sessionKey: "s1",
status: runStatusOK,
acceptedAt: time.Now().Add(-time.Minute),
startedAt: time.Now().Add(-time.Minute),
endedAt: time.Now().Add(-20 * time.Second),
done: closedChan(),
},
},
}
handled, out, err := al.handleSlashCommand(context.Background(), bus.InboundMessage{
Content: "/status wait run-200-2 3",
SessionKey: "s1",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !handled {
t.Fatalf("expected command handled")
}
if out == "" || !containsAnySubstring(out, "run-200-2", "Run ID: run-200-2") {
t.Fatalf("unexpected output: %s", out)
}
}
func closedChan() chan struct{} {
ch := make(chan struct{})
close(ch)
return ch
}