Files
kandev/apps/backend/internal/agent/agents/mock_test.go
T
2026-05-16 20:07:57 +01:00

193 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package agents
import (
"regexp"
"testing"
"github.com/kandev/kandev/internal/agentruntime"
"github.com/kandev/kandev/pkg/agent"
)
func TestMockAgent_BuildCommand_NoModelFlag(t *testing.T) {
// ACP agents apply model via session/set_model after session/new, not
// via --model CLI flag. BuildCommand must not add --model.
a := NewMockAgent()
cmd := a.BuildCommand(CommandOptions{Model: "mock-fast"})
args := cmd.Args()
for _, arg := range args {
if arg == "--model" {
t.Errorf("--model CLI flag should not be emitted, got %v", args)
}
}
}
func TestMockAgent_BuildCommand_NoResumeFlag(t *testing.T) {
a := NewMockAgent()
// ACP handles resume via session/load, so --resume should not appear.
cmd := a.BuildCommand(CommandOptions{Model: "mock-fast", SessionID: "sess-123"})
args := cmd.Args()
for _, arg := range args {
if arg == "--resume" {
t.Errorf("--resume flag should not be present for ACP agent, got args %v", args)
}
}
}
// TestMockAgent_BuildCommand_ContainerizedUsesBareName pins the docker
// branch of BuildCommand: when opts.Runtime is containerized (docker,
// remote_docker, sprites), MockAgent must emit the bare binary name so
// PATH lookup inside the container resolves to the bind-mounted
// linux/amd64 mock-agent at /usr/local/bin. Regressing this would
// cause docker e2e to fail with `exec: "<host-abs-path>": not found`,
// which is exactly the bug commit `8518f65` fixed.
func TestMockAgent_BuildCommand_ContainerizedUsesBareName(t *testing.T) {
a := NewMockAgent()
a.SetBinaryPath("/Users/dev/kandev/apps/backend/bin/mock-agent")
for _, rt := range []agentruntime.Runtime{
agentruntime.RuntimeDocker,
agentruntime.RuntimeRemoteDocker,
agentruntime.RuntimeSprites,
} {
t.Run(string(rt), func(t *testing.T) {
cmd := a.BuildCommand(CommandOptions{Runtime: rt})
args := cmd.Args()
if len(args) == 0 || args[0] != mockAgentDefaultID {
t.Errorf("containerized runtime %q: BuildCommand args = %v, want first arg %q (bare name for PATH lookup in container)",
rt, args, mockAgentDefaultID)
}
})
}
}
// TestMockAgent_BuildCommand_HostUsesBinaryPath pins the host branch:
// when opts.Runtime is host-side (standalone) and binaryPath is set
// (which configureMockAgent does via os.Executable()), MockAgent must
// emit the absolute host path so dev mode works without
// apps/backend/bin being on the shell's $PATH.
func TestMockAgent_BuildCommand_HostUsesBinaryPath(t *testing.T) {
a := NewMockAgent()
const absPath = "/Users/dev/kandev/apps/backend/bin/mock-agent"
a.SetBinaryPath(absPath)
cmd := a.BuildCommand(CommandOptions{Runtime: agentruntime.RuntimeStandalone})
args := cmd.Args()
if len(args) == 0 || args[0] != absPath {
t.Errorf("host runtime: BuildCommand args = %v, want first arg %q (absolute host path)",
args, absPath)
}
}
// TestMockAgent_BuildCommand_HostNoBinaryPathFallsBack covers the e2e
// case where configureMockAgent hasn't run yet (or has been bypassed)
// but the test fixture has prepended apps/backend/bin to PATH. Host
// runtime + empty binaryPath → bare name, resolved via PATH.
func TestMockAgent_BuildCommand_HostNoBinaryPathFallsBack(t *testing.T) {
a := NewMockAgent()
cmd := a.BuildCommand(CommandOptions{Runtime: agentruntime.RuntimeStandalone})
args := cmd.Args()
if len(args) == 0 || args[0] != mockAgentDefaultID {
t.Errorf("host runtime without binaryPath: BuildCommand args = %v, want first arg %q (bare name fallback)",
args, mockAgentDefaultID)
}
}
func TestMockAgent_Runtime_CanRecover(t *testing.T) {
a := NewMockAgent()
rt := a.Runtime()
if !rt.SessionConfig.SupportsRecovery() {
t.Error("expected SupportsRecovery() to return true for mock agent")
}
}
func TestMockAgent_Runtime_ProtocolACP(t *testing.T) {
a := NewMockAgent()
rt := a.Runtime()
if rt.Protocol != agent.ProtocolACP {
t.Errorf("expected Protocol = %q, got %q", agent.ProtocolACP, rt.Protocol)
}
}
func TestMockAgent_Runtime_NoResumeFlag(t *testing.T) {
a := NewMockAgent()
rt := a.Runtime()
// ACP agents handle resume via session/load, not CLI flags.
if !rt.SessionConfig.ResumeFlag.IsEmpty() {
t.Error("expected ResumeFlag to be empty for ACP agent")
}
}
func TestMockAgent_PassthroughConfig_ResumeFlags(t *testing.T) {
a := NewMockAgent()
pt := a.PassthroughConfig()
// Generic resume flag (-c) — still used for TUI passthrough mode
if pt.ResumeFlag.IsEmpty() {
t.Error("expected ResumeFlag to be set on PassthroughConfig")
}
resumeArgs := pt.ResumeFlag.Args()
if len(resumeArgs) == 0 || resumeArgs[0] != "-c" {
t.Errorf("expected ResumeFlag = -c, got %v", resumeArgs)
}
// Session-specific resume flag (--resume) — TUI passthrough mode
if pt.SessionResumeFlag.IsEmpty() {
t.Error("expected SessionResumeFlag to be set on PassthroughConfig")
}
sessionArgs := pt.SessionResumeFlag.Args()
if len(sessionArgs) == 0 || sessionArgs[0] != "--resume" {
t.Errorf("expected SessionResumeFlag = --resume, got %v", sessionArgs)
}
}
func TestNewMockAgentWithID_AppliesIdentityOverrides(t *testing.T) {
a := NewMockAgentWithID("claude-acp", "Mock Claude", "Claude (Mock)")
if a.ID() != "claude-acp" {
t.Errorf("ID() = %q, want claude-acp", a.ID())
}
if a.Name() != "Mock Claude" {
t.Errorf("Name() = %q, want Mock Claude", a.Name())
}
if a.DisplayName() != "Claude (Mock)" {
t.Errorf("DisplayName() = %q, want Claude (Mock)", a.DisplayName())
}
}
func TestNewMockAgent_DefaultIdentity(t *testing.T) {
a := NewMockAgent()
if a.ID() != "mock-agent" {
t.Errorf("ID() = %q, want mock-agent", a.ID())
}
if a.Name() != "Mock Agent" {
t.Errorf("Name() = %q, want Mock Agent", a.Name())
}
if a.DisplayName() != "Mock" {
t.Errorf("DisplayName() = %q, want Mock", a.DisplayName())
}
}
func TestMockAgent_PassthroughConfig_PromptPattern(t *testing.T) {
a := NewMockAgent()
pattern := a.PassthroughConfig().PromptPattern
if pattern == "" {
t.Fatal("expected PromptPattern to be set for mock TUI passthrough")
}
re, err := regexp.Compile(pattern)
if err != nil {
t.Fatalf("expected PromptPattern to compile: %v", err)
}
readyPrompt := "\x1b[1;32m❯\x1b[0m "
if !re.MatchString(readyPrompt) {
t.Fatalf("expected PromptPattern to match ready prompt %q", readyPrompt)
}
initialPromptLine := "\x1b[1;32m❯\x1b[0m hello from stdin\r\n"
if re.MatchString(initialPromptLine) {
t.Fatalf("expected PromptPattern not to match prompt echo %q", initialPromptLine)
}
}