Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

383 lines
11 KiB
Go

package agentruntime
import (
"strings"
"testing"
)
func TestNormalizeACPAskUserPermissionBridgeRejectsUnsupportedShapes(t *testing.T) {
t.Parallel()
tests := []struct {
name string
questions []any
wantState acpAskUserPermissionBridgeState
wantError string
}{
{
name: "single select",
questions: []any{map[string]any{
"question": "How are you?",
"options": []any{
map[string]any{"label": "Good"},
map[string]any{"label": "Okay"},
},
}},
wantState: acpAskUserPermissionBridgeSupported,
},
{
name: "multiple questions",
questions: []any{
map[string]any{"question": "First?", "options": []any{map[string]any{"label": "Good"}, map[string]any{"label": "Okay"}}},
map[string]any{"question": "Second?", "options": []any{map[string]any{"label": "Good"}, map[string]any{"label": "Okay"}}},
},
wantState: acpAskUserPermissionBridgeUnsupported,
wantError: "exactly one question",
},
{
name: "malformed question list",
questions: []any{
map[string]any{"question": "First?", "options": []any{map[string]any{"label": "Good"}, map[string]any{"label": "Okay"}}},
"not a question",
},
wantState: acpAskUserPermissionBridgeUnsupported,
wantError: "malformed question list",
},
{
name: "nil question",
questions: []any{map[string]any(nil)},
wantState: acpAskUserPermissionBridgeUnsupported,
wantError: "malformed question list",
},
{
name: "multi select",
questions: []any{map[string]any{
"question": "Pick any",
"multiSelect": true,
"options": []any{map[string]any{"label": "Good"}, map[string]any{"label": "Okay"}},
}},
wantState: acpAskUserPermissionBridgeUnsupported,
wantError: "does not support multi-select",
},
{
name: "invalid multi-select flag",
questions: []any{map[string]any{
"question": "Pick one",
"multiSelect": "sometimes",
"options": []any{map[string]any{"label": "Good"}, map[string]any{"label": "Okay"}},
}},
wantState: acpAskUserPermissionBridgeUnsupported,
wantError: "invalid multi-select flag",
},
{
name: "conflicting multi-select aliases",
questions: []any{map[string]any{
"question": "Pick one",
"multiSelect": true,
"multi_select": false,
"options": []any{map[string]any{"label": "Good"}, map[string]any{"label": "Okay"}},
}},
wantState: acpAskUserPermissionBridgeUnsupported,
wantError: "invalid multi-select flag",
},
{
name: "free text",
questions: []any{map[string]any{
"question": "How are you?",
"allowFreeText": true,
"options": []any{map[string]any{"label": "Good"}, map[string]any{"label": "Okay"}},
}},
wantState: acpAskUserPermissionBridgeUnsupported,
wantError: "does not support free-text",
},
{
name: "conflicting free-text aliases",
questions: []any{map[string]any{
"question": "How are you?",
"allowFreeText": true,
"allow_free_text": false,
"options": []any{map[string]any{"label": "Good"}, map[string]any{"label": "Okay"}},
}},
wantState: acpAskUserPermissionBridgeUnsupported,
wantError: "invalid free-text flag",
},
{
name: "malformed question options",
questions: []any{map[string]any{
"question": "How are you?",
"options": []any{map[string]any{"label": "Good"}, "not an option", map[string]any{"label": "Okay"}},
}},
wantState: acpAskUserPermissionBridgeUnsupported,
wantError: "malformed question option list",
},
{
name: "provider option mismatch",
questions: []any{map[string]any{
"question": "How are you?",
"options": []any{map[string]any{"label": "Good"}},
}},
wantState: acpAskUserPermissionBridgeUnsupported,
wantError: "do not match",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
pending := acpAskUserPermissionTestPending(tt.questions)
_, state, err := normalizeACPAskUserPermissionBridge(pending)
if state != tt.wantState {
t.Fatalf("bridge state = %v, want %v", state, tt.wantState)
}
if tt.wantError == "" {
if err != nil {
t.Fatalf("normalize bridge: %v", err)
}
questions := payloadArray(pending.input["questions"])
if len(questions) != 1 ||
asString(questions[0]["id"]) != "question-1" ||
questions[0]["allowFreeText"] != false {
t.Fatalf("normalized questions = %#v, want stable option-only question", questions)
}
return
}
if err == nil || !strings.Contains(err.Error(), tt.wantError) {
t.Fatalf("normalize bridge error = %v, want containing %q", err, tt.wantError)
}
})
}
}
func TestNormalizeACPAskUserPermissionBridgeUsesPermissionKindForRejection(t *testing.T) {
t.Parallel()
pending := acpAskUserPermissionTestPending([]any{map[string]any{
"question": "Continue?",
"options": []any{
map[string]any{"label": "Good"},
map[string]any{"label": "No"},
},
}})
pending.options[1]["name"] = "No"
bridge, state, err := normalizeACPAskUserPermissionBridge(pending)
if err != nil || state != acpAskUserPermissionBridgeSupported {
t.Fatalf("normalize bridge state = %v, error = %v, want supported", state, err)
}
if bridge.optionIDByLabel["No"] != "q0_opt_1" {
t.Fatalf("option map = %#v, want allow_once option named No preserved", bridge.optionIDByLabel)
}
if bridge.rejectionOption != "q0_skip" {
t.Fatalf("rejection option = %q, want q0_skip", bridge.rejectionOption)
}
}
func TestNormalizeACPAskUserPermissionBridgeWaitsForQuestionOptions(t *testing.T) {
t.Parallel()
pending := acpAskUserPermissionTestPending([]any{map[string]any{
"question": "How are you?",
}})
_, state, err := normalizeACPAskUserPermissionBridge(pending)
if state != acpAskUserPermissionBridgeIncomplete || err != nil {
t.Fatalf("normalize bridge state = %v, error = %v, want incomplete", state, err)
}
}
func TestNormalizeACPAskUserPermissionBridgeRejectsDuplicateProviderOptionIDs(t *testing.T) {
t.Parallel()
pending := acpAskUserPermissionTestPending([]any{map[string]any{
"question": "How are you?",
"options": []any{
map[string]any{"label": "Good"},
map[string]any{"label": "Okay"},
},
}})
pending.options[1]["optionId"] = "q0_opt_0"
_, state, err := normalizeACPAskUserPermissionBridge(pending)
if state != acpAskUserPermissionBridgeUnsupported ||
err == nil ||
!strings.Contains(err.Error(), "duplicate option id") {
t.Fatalf("normalize bridge state = %v, error = %v, want duplicate option id rejection", state, err)
}
}
func TestNormalizeACPAskUserPermissionBridgeRejectsAmbiguousOptionMappings(t *testing.T) {
t.Parallel()
tests := []struct {
name string
mutate func(*pendingInteractiveRequest)
wantError string
}{
{
name: "duplicate provider label",
mutate: func(pending *pendingInteractiveRequest) {
pending.options[1]["name"] = "Good"
},
wantError: "duplicate option label",
},
{
name: "duplicate question label",
mutate: func(pending *pendingInteractiveRequest) {
questions := pending.input["questions"].([]any)
question := questions[0].(map[string]any)
options := question["options"].([]any)
options[1].(map[string]any)["label"] = "Good"
},
wantError: "duplicate option label",
},
{
name: "rejection id conflicts with selectable id",
mutate: func(pending *pendingInteractiveRequest) {
pending.options[2]["optionId"] = "q0_opt_0"
},
wantError: "duplicate option id",
},
{
name: "question label has no provider option",
mutate: func(pending *pendingInteractiveRequest) {
questions := pending.input["questions"].([]any)
question := questions[0].(map[string]any)
options := question["options"].([]any)
options[1].(map[string]any)["label"] = "Different"
},
wantError: "has no provider permission option",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
pending := acpAskUserPermissionTestPending([]any{map[string]any{
"question": "How are you?",
"options": []any{
map[string]any{"label": "Good"},
map[string]any{"label": "Okay"},
},
}})
tt.mutate(pending)
_, state, err := normalizeACPAskUserPermissionBridge(pending)
if state != acpAskUserPermissionBridgeUnsupported ||
err == nil ||
!strings.Contains(err.Error(), tt.wantError) {
t.Fatalf("normalize bridge state = %v, error = %v, want containing %q", state, err, tt.wantError)
}
})
}
}
func TestACPAskUserPermissionOptionIDUsesOnlyCanonicalSingleAnswer(t *testing.T) {
t.Parallel()
tests := []struct {
name string
optionID string
action string
payload map[string]any
want string
wantError string
}{
{
name: "canonical answer",
action: "submit",
payload: map[string]any{
"answers": []any{"Okay"},
"answersByQuestionId": map[string]any{"question-1": "Good"},
},
want: "q0_opt_0",
},
{
name: "display answer is not a routing fallback",
action: "submit",
payload: map[string]any{"answers": []any{"Good"}},
wantError: "exactly one canonical answer",
},
{
name: "multiple question answers",
action: "submit",
payload: map[string]any{
"answersByQuestionId": map[string]any{"question-1": "Good", "question-2": "Okay"},
},
wantError: "exactly one canonical answer",
},
{
name: "array answer",
action: "submit",
payload: map[string]any{
"answersByQuestionId": map[string]any{"question-1": []any{"Good"}},
},
wantError: "must be one selected option",
},
{
name: "wrong question id",
action: "submit",
payload: map[string]any{
"answersByQuestionId": map[string]any{"other": "Good"},
},
wantError: `question "question-1" is required`,
},
{
name: "Other free text is not mapped",
action: "submit",
payload: map[string]any{
"answersByQuestionId": map[string]any{"question-1": "Other: custom answer"},
},
wantError: "does not match",
},
{
name: "explicit option conflicts",
optionID: "q0_opt_1",
action: "submit",
payload: map[string]any{
"answersByQuestionId": map[string]any{"question-1": "Good"},
},
wantError: "conflicts",
},
{
name: "cancel maps to provider dismissal",
action: "cancel",
want: "q0_skip",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
pending := acpAskUserPermissionTestPending([]any{map[string]any{
"question": "How are you?",
"options": []any{
map[string]any{"label": "Good"},
map[string]any{"label": "Okay"},
},
}})
got, err := acpAskUserPermissionOptionID(pending, tt.optionID, tt.action, tt.payload)
if tt.wantError == "" {
if err != nil || got != tt.want {
t.Fatalf("permission option = %q, error = %v, want %q", got, err, tt.want)
}
return
}
if err == nil || !strings.Contains(err.Error(), tt.wantError) {
t.Fatalf("permission option error = %v, want containing %q", err, tt.wantError)
}
})
}
}
func acpAskUserPermissionTestPending(questions []any) *pendingInteractiveRequest {
return &pendingInteractiveRequest{
kind: "ask-user",
input: map[string]any{"questions": questions},
options: []map[string]any{
{"optionId": "q0_opt_0", "name": "Good", "kind": "allow_once"},
{"optionId": "q0_opt_1", "name": "Okay", "kind": "allow_once"},
{"optionId": "q0_skip", "name": "Skip", "kind": "reject_once"},
},
}
}