package mockagent_test import ( "context" "io" "strings" "sync" "testing" "time" acp "github.com/coder/acp-go-sdk" "github.com/bots-garden/ori/internal/agent" "github.com/bots-garden/ori/internal/mockagent" ) // panelHandler records everything the mock streams, and answers the // permission request with a fixed choice. type panelHandler struct { mu sync.Mutex optionId string texts []string updateKinds []string permissions int } func (h *panelHandler) HandleSessionUpdate(_ context.Context, n acp.SessionNotification) { h.mu.Lock() defer h.mu.Unlock() switch { case n.Update.AgentMessageChunk != nil: h.updateKinds = append(h.updateKinds, "message") if t := n.Update.AgentMessageChunk.Content.Text; t != nil { h.texts = append(h.texts, t.Text) } case n.Update.AgentThoughtChunk != nil: h.updateKinds = append(h.updateKinds, "thought") case n.Update.ToolCall != nil: h.updateKinds = append(h.updateKinds, "tool_call") case n.Update.ToolCallUpdate != nil: h.updateKinds = append(h.updateKinds, "tool_call_update") case n.Update.Plan != nil: h.updateKinds = append(h.updateKinds, "plan") } } func (h *panelHandler) HandlePermissionRequest(_ context.Context, req acp.RequestPermissionRequest) (acp.RequestPermissionResponse, error) { h.mu.Lock() defer h.mu.Unlock() h.permissions++ _ = req return acp.RequestPermissionResponse{Outcome: acp.NewRequestPermissionOutcomeSelected(acp.PermissionOptionId(h.optionId))}, nil } func (h *panelHandler) kinds() map[string]int { h.mu.Lock() defer h.mu.Unlock() counts := map[string]int{} for _, k := range h.updateKinds { counts[k]++ } return counts } // connectPanel wires the mock agent to ori's real client code over pipes. func connectPanel(t *testing.T, handler agent.Handler) *agent.Session { t.Helper() clientToAgentR, clientToAgentW := io.Pipe() agentToClientR, agentToClientW := io.Pipe() mock := mockagent.New() go func() { _ = mock.Run(clientToAgentR, agentToClientW) }() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) t.Cleanup(cancel) session, err := agent.Connect(ctx, clientToAgentW, agentToClientR, agent.Options{ Cwd: t.TempDir(), Handler: handler, }) if err != nil { t.Fatalf("Connect to mock agent failed: %v", err) } return session } func TestScenarioStreamsEveryPanelFeature(t *testing.T) { handler := &panelHandler{optionId: "allow-once"} session := connectPanel(t, handler) stop, err := session.PromptText(context.Background(), "show me everything") if err != nil { t.Fatalf("PromptText returned an error: %v", err) } if stop != acp.StopReasonEndTurn { t.Errorf("stop reason = %q, want end_turn", stop) } counts := handler.kinds() for kind, want := range map[string]int{"thought": 1, "plan": 2, "tool_call": 2, "tool_call_update": 2} { if counts[kind] < want { t.Errorf("streamed %d %q updates, want at least %d (all: %v)", counts[kind], kind, want, counts) } } if handler.permissions != 1 { t.Errorf("permission requests = %d, want 1", handler.permissions) } if all := strings.Join(handler.texts, ""); !strings.Contains(all, "show me everything") { t.Errorf("final message %q does not echo the prompt", all) } } func TestScenarioStopsOnRejectedPermission(t *testing.T) { handler := &panelHandler{optionId: "reject-once"} session := connectPanel(t, handler) stop, err := session.PromptText(context.Background(), "try to write") if err != nil { t.Fatalf("PromptText returned an error: %v", err) } if stop != acp.StopReasonRefusal { t.Errorf("stop reason = %q, want refusal after a rejected permission", stop) } if counts := handler.kinds(); counts["tool_call"] != 1 { t.Errorf("tool calls = %d, want only the read call (no edit after rejection)", counts["tool_call"]) } }