Files
tutti/services/tuttid/api/daemon_agent_plan_decisions_test.go
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

157 lines
6.7 KiB
Go

package api
import (
"context"
"errors"
"net/http"
"testing"
agentactivitybiz "github.com/tutti-os/tutti/packages/agent/store-sqlite"
tuttigenerated "github.com/tutti-os/tutti/services/tuttid/api/generated"
agentservice "github.com/tutti-os/tutti/services/tuttid/service/agent"
)
func TestDaemonAPIGeneratedRoutesSubmitWorkspaceAgentPlanDecision(t *testing.T) {
mux := http.NewServeMux()
RegisterRoutes(mux, NewRoutes(DaemonAPI{
AgentSessionService: stubAgentSessionService{planDecisionFn: func(
_ context.Context, workspaceID, sessionID, turnID, requestID string, input agentservice.SubmitPlanDecisionInput,
) (agentactivitybiz.RuntimeOperation, error) {
return agentactivitybiz.RuntimeOperation{
OperationID: "operation-1", WorkspaceID: workspaceID, AgentSessionID: sessionID,
TurnID: turnID, RequestID: requestID, Status: agentactivitybiz.RuntimeOperationStatusPrepared,
Payload: map[string]any{"idempotencyKey": input.IdempotencyKey},
}, nil
}},
}))
recorder := performGeneratedRouteRequest(
t,
mux,
http.MethodPost,
"/v1/workspaces/ws-1/agent-sessions/session-1/turns/turn-1/plan-decisions/request-1",
map[string]any{
"action": "implement",
"idempotencyKey": "decision-1",
"promptKind": "plan-implementation",
},
)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d; body: %s", recorder.Code, http.StatusOK, recorder.Body.String())
}
}
func TestSubmitWorkspaceAgentPlanDecisionHandlerReturnsScopedOperationForEveryState(t *testing.T) {
for _, status := range []string{
agentactivitybiz.RuntimeOperationStatusPrepared,
agentactivitybiz.RuntimeOperationStatusLeased,
agentactivitybiz.RuntimeOperationStatusCompleted,
agentactivitybiz.RuntimeOperationStatusFailed,
} {
t.Run(status, func(t *testing.T) {
var captured agentservice.SubmitPlanDecisionInput
api := DaemonAPI{AgentSessionService: stubAgentSessionService{planDecisionFn: func(
_ context.Context, workspaceID, sessionID, turnID, requestID string, input agentservice.SubmitPlanDecisionInput,
) (agentactivitybiz.RuntimeOperation, error) {
captured = input
return agentactivitybiz.RuntimeOperation{
OperationID: "operation-1", WorkspaceID: workspaceID, AgentSessionID: sessionID,
TurnID: turnID, RequestID: requestID, Status: status,
Payload: map[string]any{"idempotencyKey": input.IdempotencyKey},
}, nil
}}}
response, err := api.SubmitWorkspaceAgentPlanDecision(context.Background(), planDecisionRequest())
if err != nil {
t.Fatal(err)
}
okResponse, ok := response.(tuttigenerated.SubmitWorkspaceAgentPlanDecision200JSONResponse)
if !ok || okResponse.Operation.OperationId != "operation-1" ||
okResponse.Operation.WorkspaceId != "ws-1" || okResponse.Operation.AgentSessionId != "session-1" ||
okResponse.Operation.TurnId != "turn-1" || okResponse.Operation.RequestId != "turn-1" ||
okResponse.Operation.IdempotencyKey != "decision-1" || string(okResponse.Operation.Status) != status {
t.Fatalf("response=%#v", response)
}
if captured.PromptKind != "plan-implementation" || captured.Action != "implement" || captured.IdempotencyKey != "decision-1" {
t.Fatalf("captured=%#v", captured)
}
})
}
}
func TestSubmitWorkspaceAgentPlanDecisionHandlerRejectsMissingBody(t *testing.T) {
api := DaemonAPI{AgentSessionService: stubAgentSessionService{}}
request := planDecisionRequest()
request.Body = nil
response, err := api.SubmitWorkspaceAgentPlanDecision(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if _, ok := response.(tuttigenerated.SubmitWorkspaceAgentPlanDecision400JSONResponse); !ok {
t.Fatalf("response=%T", response)
}
}
func TestSubmitWorkspaceAgentPlanDecisionHandlerReturnsUnavailableAndScopedConflict(t *testing.T) {
response, err := (DaemonAPI{}).SubmitWorkspaceAgentPlanDecision(context.Background(), planDecisionRequest())
if err != nil {
t.Fatal(err)
}
if _, ok := response.(tuttigenerated.SubmitWorkspaceAgentPlanDecision503JSONResponse); !ok {
t.Fatalf("unavailable response=%T", response)
}
api := DaemonAPI{AgentSessionService: stubAgentSessionService{planDecisionFn: func(
context.Context, string, string, string, string, agentservice.SubmitPlanDecisionInput,
) (agentactivitybiz.RuntimeOperation, error) {
return agentactivitybiz.RuntimeOperation{}, agentactivitybiz.ErrRuntimeOperationSubjectState
}}}
response, err = api.SubmitWorkspaceAgentPlanDecision(context.Background(), planDecisionRequest())
if err != nil {
t.Fatal(err)
}
if _, ok := response.(tuttigenerated.SubmitWorkspaceAgentPlanDecision409JSONResponse); !ok {
t.Fatalf("conflict response=%T", response)
}
}
func planDecisionRequest() tuttigenerated.SubmitWorkspaceAgentPlanDecisionRequestObject {
return tuttigenerated.SubmitWorkspaceAgentPlanDecisionRequestObject{
WorkspaceID: "ws-1", AgentSessionID: "session-1", TurnID: "turn-1", RequestID: "turn-1",
Body: &tuttigenerated.SubmitWorkspaceAgentPlanDecisionRequest{
PromptKind: tuttigenerated.PlanImplementation,
Action: tuttigenerated.Implement,
IdempotencyKey: "decision-1",
},
}
}
func TestGeneratedPlanDecisionOperationPreservesScopeAndTerminalFailure(t *testing.T) {
operation := generatedPlanDecisionOperation(agentactivitybiz.RuntimeOperation{
OperationID: "operation-1", WorkspaceID: "ws-1", AgentSessionID: "session-1",
TurnID: "turn-1", RequestID: "request-1", Status: agentactivitybiz.RuntimeOperationStatusFailed,
Result: agentactivitybiz.RuntimeOperationResultFailed, LastError: "provider rejected setting",
Payload: map[string]any{"idempotencyKey": "decision-1"},
})
if operation.OperationId != "operation-1" || operation.WorkspaceId != "ws-1" ||
operation.AgentSessionId != "session-1" || operation.TurnId != "turn-1" ||
operation.RequestId != "request-1" || operation.IdempotencyKey != "decision-1" ||
operation.Status != tuttigenerated.WorkspaceAgentPlanDecisionOperationStatus(agentactivitybiz.RuntimeOperationStatusFailed) ||
operation.Result == nil || *operation.Result != agentactivitybiz.RuntimeOperationResultFailed ||
operation.Error == nil || *operation.Error == "" {
t.Fatalf("operation=%#v", operation)
}
}
func TestPlanDecisionConflictMapsTo409(t *testing.T) {
response := writeSubmitWorkspaceAgentPlanDecisionError(agentactivitybiz.ErrRuntimeOperationConflict)
if _, ok := response.(tuttigenerated.SubmitWorkspaceAgentPlanDecision409JSONResponse); !ok {
t.Fatalf("response=%T", response)
}
}
func TestPlanDecisionSubjectStateFailureMapsToOperationError(t *testing.T) {
response := writeSubmitWorkspaceAgentPlanDecisionError(errors.Join(agentactivitybiz.ErrRuntimeOperationSubjectState))
if _, ok := response.(tuttigenerated.SubmitWorkspaceAgentPlanDecision409JSONResponse); !ok {
t.Fatalf("subject persistence response=%T", response)
}
}