405 lines
10 KiB
Go
405 lines
10 KiB
Go
package controller
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/kandev/kandev/internal/agent/registry"
|
|
"github.com/kandev/kandev/internal/agent/settings/dto"
|
|
"github.com/kandev/kandev/internal/common/logger"
|
|
)
|
|
|
|
func newTestController(agents map[string]*registry.AgentTypeConfig) *Controller {
|
|
log, _ := logger.NewLogger(logger.LoggingConfig{
|
|
Level: "error",
|
|
Format: "json",
|
|
})
|
|
// Create a real registry and register the agents
|
|
reg := registry.NewRegistry(log)
|
|
for _, agent := range agents {
|
|
_ = reg.Register(agent)
|
|
}
|
|
return &Controller{
|
|
agentRegistry: reg,
|
|
logger: log,
|
|
}
|
|
}
|
|
|
|
func TestController_PreviewAgentCommand_StandardCommand(t *testing.T) {
|
|
agents := map[string]*registry.AgentTypeConfig{
|
|
"test-agent": {
|
|
ID: "test-agent",
|
|
Name: "test-agent",
|
|
Cmd: []string{"test-cli", "--verbose"},
|
|
ModelFlag: "--model {model}",
|
|
ResourceLimits: registry.ResourceLimits{
|
|
MemoryMB: 1024,
|
|
CPUCores: 1.0,
|
|
TimeoutSeconds: 3600,
|
|
},
|
|
PermissionSettings: map[string]registry.PermissionSetting{
|
|
"auto_approve": {
|
|
Supported: true,
|
|
ApplyMethod: "cli_flag",
|
|
CLIFlag: "--yes",
|
|
},
|
|
},
|
|
PassthroughConfig: registry.PassthroughConfig{
|
|
Supported: false,
|
|
},
|
|
},
|
|
}
|
|
|
|
controller := newTestController(agents)
|
|
|
|
req := CommandPreviewRequest{
|
|
Model: "gpt-4",
|
|
PermissionSettings: map[string]bool{"auto_approve": true},
|
|
CLIPassthrough: false,
|
|
}
|
|
|
|
result, err := controller.PreviewAgentCommand(context.Background(), "test-agent", req)
|
|
if err != nil {
|
|
t.Fatalf("PreviewAgentCommand() error = %v", err)
|
|
}
|
|
|
|
if !result.Supported {
|
|
t.Error("PreviewAgentCommand() Supported = false, want true")
|
|
}
|
|
|
|
// Verify command contains expected parts
|
|
expectedParts := []string{"test-cli", "--verbose", "--model", "gpt-4", "--yes"}
|
|
for _, part := range expectedParts {
|
|
found := false
|
|
for _, cmdPart := range result.Command {
|
|
if cmdPart == part {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("PreviewAgentCommand() command missing %q, got %v", part, result.Command)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestController_PreviewAgentCommand_PassthroughCommand(t *testing.T) {
|
|
agents := map[string]*registry.AgentTypeConfig{
|
|
"claude-code": {
|
|
ID: "claude-code",
|
|
Name: "claude-code",
|
|
Cmd: []string{"claude"},
|
|
ModelFlag: "--model {model}",
|
|
ResourceLimits: registry.ResourceLimits{
|
|
MemoryMB: 1024,
|
|
CPUCores: 1.0,
|
|
TimeoutSeconds: 3600,
|
|
},
|
|
PassthroughConfig: registry.PassthroughConfig{
|
|
Supported: true,
|
|
PassthroughCmd: []string{"npx", "-y", "@anthropic-ai/claude-code"},
|
|
ModelFlag: "--model {model}",
|
|
PromptFlag: "--prompt {prompt}",
|
|
},
|
|
PermissionSettings: map[string]registry.PermissionSetting{
|
|
"dangerously_skip_permissions": {
|
|
Supported: true,
|
|
ApplyMethod: "cli_flag",
|
|
CLIFlag: "--dangerously-skip-permissions",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
controller := newTestController(agents)
|
|
|
|
req := CommandPreviewRequest{
|
|
Model: "claude-sonnet-4-20250514",
|
|
PermissionSettings: map[string]bool{"dangerously_skip_permissions": true},
|
|
CLIPassthrough: true,
|
|
}
|
|
|
|
result, err := controller.PreviewAgentCommand(context.Background(), "claude-code", req)
|
|
if err != nil {
|
|
t.Fatalf("PreviewAgentCommand() error = %v", err)
|
|
}
|
|
|
|
// Verify it uses passthrough command
|
|
if len(result.Command) < 3 || result.Command[0] != "npx" {
|
|
t.Errorf("PreviewAgentCommand() should use passthrough command, got %v", result.Command)
|
|
}
|
|
|
|
// Verify model flag is present
|
|
hasModel := false
|
|
for i, part := range result.Command {
|
|
if part == "--model" && i+1 < len(result.Command) && result.Command[i+1] == "claude-sonnet-4-20250514" {
|
|
hasModel = true
|
|
break
|
|
}
|
|
}
|
|
if !hasModel {
|
|
t.Errorf("PreviewAgentCommand() missing model flag, got %v", result.Command)
|
|
}
|
|
|
|
// Verify permission flag is present
|
|
hasPermFlag := false
|
|
for _, part := range result.Command {
|
|
if part == "--dangerously-skip-permissions" {
|
|
hasPermFlag = true
|
|
break
|
|
}
|
|
}
|
|
if !hasPermFlag {
|
|
t.Errorf("PreviewAgentCommand() missing permission flag, got %v", result.Command)
|
|
}
|
|
|
|
// Verify prompt placeholder is present
|
|
hasPrompt := false
|
|
for _, part := range result.Command {
|
|
if part == "--prompt" || part == "{prompt}" {
|
|
hasPrompt = true
|
|
break
|
|
}
|
|
}
|
|
if !hasPrompt {
|
|
t.Errorf("PreviewAgentCommand() missing prompt placeholder, got %v", result.Command)
|
|
}
|
|
}
|
|
|
|
func TestController_PreviewAgentCommand_AgentNotFound(t *testing.T) {
|
|
controller := newTestController(map[string]*registry.AgentTypeConfig{})
|
|
|
|
_, err := controller.PreviewAgentCommand(context.Background(), "nonexistent", CommandPreviewRequest{})
|
|
if err == nil {
|
|
t.Error("PreviewAgentCommand() should return error for unknown agent")
|
|
}
|
|
}
|
|
|
|
func TestController_PreviewAgentCommand_PassthroughDisabled(t *testing.T) {
|
|
agents := map[string]*registry.AgentTypeConfig{
|
|
"test-agent": {
|
|
ID: "test-agent",
|
|
Name: "test-agent",
|
|
Cmd: []string{"test-cli"},
|
|
ResourceLimits: registry.ResourceLimits{
|
|
MemoryMB: 1024,
|
|
CPUCores: 1.0,
|
|
TimeoutSeconds: 3600,
|
|
},
|
|
PassthroughConfig: registry.PassthroughConfig{
|
|
Supported: true,
|
|
PassthroughCmd: []string{"passthrough-cli"},
|
|
},
|
|
},
|
|
}
|
|
|
|
controller := newTestController(agents)
|
|
|
|
// CLIPassthrough is false, so should use standard command
|
|
req := CommandPreviewRequest{
|
|
CLIPassthrough: false,
|
|
}
|
|
|
|
result, err := controller.PreviewAgentCommand(context.Background(), "test-agent", req)
|
|
if err != nil {
|
|
t.Fatalf("PreviewAgentCommand() error = %v", err)
|
|
}
|
|
|
|
if result.Command[0] != "test-cli" {
|
|
t.Errorf("PreviewAgentCommand() should use standard command when passthrough disabled, got %v", result.Command)
|
|
}
|
|
}
|
|
|
|
func TestController_BuildCommandString(t *testing.T) {
|
|
controller := newTestController(nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
cmd []string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "simple command",
|
|
cmd: []string{"echo", "hello"},
|
|
expected: "echo hello",
|
|
},
|
|
{
|
|
name: "command with spaces",
|
|
cmd: []string{"echo", "hello world"},
|
|
expected: `echo "hello world"`,
|
|
},
|
|
{
|
|
name: "command with quotes",
|
|
cmd: []string{"echo", `say "hi"`},
|
|
expected: `echo "say \"hi\""`,
|
|
},
|
|
{
|
|
name: "command with special chars",
|
|
cmd: []string{"bash", "-c", "echo $HOME"},
|
|
expected: `bash -c "echo $HOME"`,
|
|
},
|
|
{
|
|
name: "empty command",
|
|
cmd: []string{},
|
|
expected: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := controller.buildCommandString(tt.cmd)
|
|
if result != tt.expected {
|
|
t.Errorf("buildCommandString(%v) = %q, want %q", tt.cmd, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestController_ApplyPermissionFlags(t *testing.T) {
|
|
controller := newTestController(nil)
|
|
|
|
agentConfig := ®istry.AgentTypeConfig{
|
|
PermissionSettings: map[string]registry.PermissionSetting{
|
|
"auto_approve": {
|
|
Supported: true,
|
|
ApplyMethod: "cli_flag",
|
|
CLIFlag: "--yes",
|
|
},
|
|
"skip_permissions": {
|
|
Supported: true,
|
|
ApplyMethod: "cli_flag",
|
|
CLIFlag: "--skip",
|
|
CLIFlagValue: "all",
|
|
},
|
|
"unsupported": {
|
|
Supported: false,
|
|
CLIFlag: "--unsupported",
|
|
},
|
|
"env_method": {
|
|
Supported: true,
|
|
ApplyMethod: "env_var", // Not cli_flag
|
|
CLIFlag: "--env",
|
|
},
|
|
},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
initial []string
|
|
permissions map[string]bool
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "boolean flag enabled",
|
|
initial: []string{"cmd"},
|
|
permissions: map[string]bool{"auto_approve": true},
|
|
expected: []string{"cmd", "--yes"},
|
|
},
|
|
{
|
|
name: "flag with value",
|
|
initial: []string{"cmd"},
|
|
permissions: map[string]bool{"skip_permissions": true},
|
|
expected: []string{"cmd", "--skip", "all"},
|
|
},
|
|
{
|
|
name: "flag disabled",
|
|
initial: []string{"cmd"},
|
|
permissions: map[string]bool{"auto_approve": false},
|
|
expected: []string{"cmd"},
|
|
},
|
|
{
|
|
name: "unsupported flag ignored",
|
|
initial: []string{"cmd"},
|
|
permissions: map[string]bool{"unsupported": true},
|
|
expected: []string{"cmd"},
|
|
},
|
|
{
|
|
name: "non-cli method ignored",
|
|
initial: []string{"cmd"},
|
|
permissions: map[string]bool{"env_method": true},
|
|
expected: []string{"cmd"},
|
|
},
|
|
{
|
|
name: "multiple flags",
|
|
initial: []string{"cmd"},
|
|
permissions: map[string]bool{"auto_approve": true, "skip_permissions": true},
|
|
expected: []string{"cmd", "--yes", "--skip", "all"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := controller.applyPermissionFlags(tt.initial, agentConfig, tt.permissions)
|
|
|
|
// Check that all expected parts are present (order may vary for maps)
|
|
if len(result) != len(tt.expected) {
|
|
t.Errorf("applyPermissionFlags() length = %d, want %d", len(result), len(tt.expected))
|
|
t.Errorf("got: %v, want: %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestController_AppendPromptPlaceholder(t *testing.T) {
|
|
controller := newTestController(nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
cmd []string
|
|
promptFlag string
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "with prompt flag",
|
|
cmd: []string{"cli"},
|
|
promptFlag: "--prompt {prompt}",
|
|
expected: []string{"cli", "--prompt", "{prompt}"},
|
|
},
|
|
{
|
|
name: "without prompt flag",
|
|
cmd: []string{"cli"},
|
|
promptFlag: "",
|
|
expected: []string{"cli", "{prompt}"},
|
|
},
|
|
{
|
|
name: "single flag format",
|
|
cmd: []string{"cli"},
|
|
promptFlag: "-p {prompt}",
|
|
expected: []string{"cli", "-p", "{prompt}"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := controller.appendPromptPlaceholder(tt.cmd, tt.promptFlag)
|
|
if len(result) != len(tt.expected) {
|
|
t.Errorf("appendPromptPlaceholder() = %v, want %v", result, tt.expected)
|
|
return
|
|
}
|
|
for i := range result {
|
|
if result[i] != tt.expected[i] {
|
|
t.Errorf("appendPromptPlaceholder()[%d] = %q, want %q", i, result[i], tt.expected[i])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCommandPreviewResponse_DTO(t *testing.T) {
|
|
resp := dto.CommandPreviewResponse{
|
|
Supported: true,
|
|
Command: []string{"npx", "claude-code", "--model", "gpt-4"},
|
|
CommandString: `npx claude-code --model gpt-4`,
|
|
}
|
|
|
|
if !resp.Supported {
|
|
t.Error("CommandPreviewResponse.Supported should be true")
|
|
}
|
|
if len(resp.Command) != 4 {
|
|
t.Errorf("CommandPreviewResponse.Command length = %d, want 4", len(resp.Command))
|
|
}
|
|
if resp.CommandString == "" {
|
|
t.Error("CommandPreviewResponse.CommandString should not be empty")
|
|
}
|
|
}
|