Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

176 lines
4.7 KiB
Go

package parser
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestClassify(t *testing.T) {
tests := []struct {
name string
messages []ParsedMessage
stopReason string
truncated bool
want TerminationStatus
}{
{
name: "empty messages, not truncated",
messages: nil,
want: "",
},
{
name: "empty messages, truncated wins",
messages: nil,
truncated: true,
want: TerminationTruncated,
},
{
name: "awaiting_user: claude end_turn",
messages: []ParsedMessage{
{Role: RoleUser, Content: "hello"},
{Role: RoleAssistant, Content: "hi"},
},
stopReason: "end_turn",
want: TerminationAwaitingUser,
},
{
name: "awaiting_user: codex task_complete",
messages: []ParsedMessage{
{Role: RoleUser, Content: "build it"},
{Role: RoleAssistant, Content: "done"},
},
stopReason: "task_complete",
want: TerminationAwaitingUser,
},
{
name: "clean: stop_reason is max_tokens (not awaiting)",
messages: []ParsedMessage{
{Role: RoleUser, Content: "long task"},
{Role: RoleAssistant, Content: "response"},
},
stopReason: "max_tokens",
want: TerminationClean,
},
{
name: "clean: no stop_reason recorded falls back to clean",
messages: []ParsedMessage{
{Role: RoleUser, Content: "hello"},
{Role: RoleAssistant, Content: "hi"},
},
want: TerminationClean,
},
{
name: "clean: tool call resolved by tool result",
messages: []ParsedMessage{
{Role: RoleUser, Content: "read file"},
{Role: RoleAssistant, ToolCalls: []ParsedToolCall{
{ToolUseID: "toolu_1", ToolName: "Read"},
}},
{Role: RoleUser, ToolResults: []ParsedToolResult{
{ToolUseID: "toolu_1"},
}},
{Role: RoleAssistant, Content: "done"},
},
stopReason: "end_turn",
want: TerminationAwaitingUser,
},
{
name: "tool_call_pending: last assistant has unmatched tool_use",
messages: []ParsedMessage{
{Role: RoleUser, Content: "read file"},
{Role: RoleAssistant, ToolCalls: []ParsedToolCall{
{ToolUseID: "toolu_1", ToolName: "Read"},
}},
},
stopReason: "tool_use",
want: TerminationToolCallPending,
},
{
name: "tool_call_pending: prior turns matched, last has unmatched",
messages: []ParsedMessage{
{Role: RoleAssistant, ToolCalls: []ParsedToolCall{
{ToolUseID: "toolu_1"},
}},
{Role: RoleUser, ToolResults: []ParsedToolResult{
{ToolUseID: "toolu_1"},
}},
{Role: RoleAssistant, ToolCalls: []ParsedToolCall{
{ToolUseID: "toolu_2"},
}},
},
want: TerminationToolCallPending,
},
{
name: "truncated overrides tool_call_pending",
messages: []ParsedMessage{
{Role: RoleAssistant, ToolCalls: []ParsedToolCall{
{ToolUseID: "toolu_1"},
}},
},
truncated: true,
want: TerminationTruncated,
},
{
name: "ignores empty ToolUseID — falls through to clean/awaiting",
messages: []ParsedMessage{
{Role: RoleAssistant, ToolCalls: []ParsedToolCall{
{ToolUseID: ""},
}},
},
stopReason: "end_turn",
want: TerminationAwaitingUser,
},
{
// Regression: an earlier message reusing a ToolUseID
// must NOT mark a later unresolved call as resolved.
// hasOrphanedToolCall only counts results that appear
// strictly AFTER the last assistant message.
name: "earlier matching result does not resolve final orphan",
messages: []ParsedMessage{
{Role: RoleAssistant, ToolCalls: []ParsedToolCall{
{ToolUseID: "toolu_dup"},
}},
{Role: RoleUser, ToolResults: []ParsedToolResult{
{ToolUseID: "toolu_dup"},
}},
{Role: RoleAssistant, ToolCalls: []ParsedToolCall{
{ToolUseID: "toolu_dup"},
}},
},
want: TerminationToolCallPending,
},
{
// Regression: once the user replies after an end_turn,
// the agent is no longer parked. The last assistant's
// stop_reason is still end_turn but the transcript has
// moved on, so awaiting_user would mislead.
name: "user reply after end_turn is not awaiting_user",
messages: []ParsedMessage{
{Role: RoleUser, Content: "hello"},
{Role: RoleAssistant, Content: "hi"},
{Role: RoleUser, Content: "follow-up"},
},
stopReason: "end_turn",
want: TerminationClean,
},
{
name: "user reply after task_complete is not awaiting_user",
messages: []ParsedMessage{
{Role: RoleUser, Content: "build it"},
{Role: RoleAssistant, Content: "done"},
{Role: RoleUser, Content: "now test"},
},
stopReason: "task_complete",
want: TerminationClean,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := Classify(tc.messages, tc.stopReason, tc.truncated)
assert.Equal(t, tc.want, got)
})
}
}