Files
MuyueWorkspace/internal/orchestrator/orchestrator_test.go
Augustin 3494f6b40d
All checks were successful
CI / build (push) Successful in 2m37s
feat: security hardening, tests, doctor command, CI update, CHANGELOG
- Add AES-256-GCM encryption for API keys (internal/secret)
- Add dangerous command detection in terminal
- Add muyue doctor command for system health checks
- Add scanner TTL cache, orchestrator history mutex, shared HTTP client
- Deduplicate MCP config generation, refactor skills YAML parser
- Add XDG-compliant config dir with legacy migration
- Add cleanup on all TUI quit paths
- Add 8 test files (config, workflow, skills, orchestrator, version,
  platform, scanner, secret)
- Update CI to actions/setup-go@v5
- Add CHANGELOG.md, update README and Makefile

🤖 Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
2026-04-20 19:56:07 +02:00

211 lines
4.9 KiB
Go

package orchestrator
import (
"strings"
"testing"
"github.com/muyue/muyue/internal/config"
)
func testConfig() *config.MuyueConfig {
cfg := config.Default()
cfg.AI.Providers[0].Active = true
cfg.AI.Providers[0].APIKey = "test-api-key-12345"
return cfg
}
func TestCleanAIResponse(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
"removes standard think tags",
"<think internal reasoning</think Hello world",
"<think internal reasoning</think Hello world",
},
{
"removes Think tags",
"<Think>reasoning</Think>response",
"response",
},
{
"removes think with attrs",
"<think type=re>reasoning</think result",
"<think type=re>reasoning</think result",
},
{
"removes stream markers",
"text\n<<\ninternal\n>>\nvisible",
"text\nvisible",
},
{
"removes triple markers",
"text\n<<<\ninternal\n>>>\nvisible",
"text\nvisible",
},
{
"plain text unchanged",
"Hello world",
"Hello world",
},
{
"empty input",
"",
"",
},
{
"removes valid think block",
"<think some reasoning here</think rest",
"<think some reasoning here</think rest",
},
{
"removes simple think",
"before<think reasoning</think after",
"before<think reasoning</think after",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := cleanAIResponse(tt.input)
result = strings.TrimSpace(result)
expected := strings.TrimSpace(tt.expected)
if result != expected {
t.Errorf("cleanAIResponse(%q) = %q, want %q", tt.input, result, expected)
}
})
}
}
func TestCleanAIResponseThinkRegex(t *testing.T) {
input2 := "<Think>some reasoning</Think>actual response"
result2 := cleanAIResponse(input2)
if result2 != "actual response" {
t.Errorf("Valid Think tags should be removed: %q", result2)
}
input3 := "<think\nmultiline\nreasoning</think visible"
result3 := cleanAIResponse(input3)
// No closing > on opening tag, so won't match regex
if result3 != "<think\nmultiline\nreasoning</think visible" {
t.Errorf("Malformed think should not be removed: %q", result3)
}
input4 := "<think type=re>reasoning</think visible"
result4 := cleanAIResponse(input4)
// </think followed by space, not >, so won't match
if result4 != "<think type=re>reasoning</think visible" {
t.Errorf("Malformed closing should not be removed: %q", result4)
}
input_real := "prefix<think reasoning here</think suffix"
result_real := cleanAIResponse(input_real)
// The closing </think has no > after it, so won't match
if result_real != "prefix<think reasoning here</think suffix" {
t.Errorf("Malformed tags should pass through: %q", result_real)
}
input_valid := "<Think>reasoning</Think>result"
result_valid := cleanAIResponse(input_valid)
if result_valid != "result" {
t.Errorf("Valid tags should be removed: %q", result_valid)
}
}
func TestGetProviderBaseURL(t *testing.T) {
tests := []struct {
provider string
want string
}{
{"minimax", "https://api.minimax.io/v1"},
{"anthropic", "https://api.anthropic.com/v1"},
{"openai", "https://api.openai.com/v1"},
{"zai", "https://api.z.ai/v1"},
{"unknown", ""},
}
for _, tt := range tests {
got := getProviderBaseURL(tt.provider)
if got != tt.want {
t.Errorf("getProviderBaseURL(%q) = %q, want %q", tt.provider, got, tt.want)
}
}
}
func TestNewNoProvider(t *testing.T) {
cfg := config.Default()
for i := range cfg.AI.Providers {
cfg.AI.Providers[i].Active = false
}
_, err := New(cfg)
if err == nil {
t.Error("Should fail with no active provider")
}
}
func TestNewNoAPIKey(t *testing.T) {
cfg := config.Default()
cfg.AI.Providers[0].Active = true
cfg.AI.Providers[0].APIKey = ""
_, err := New(cfg)
if err == nil {
t.Error("Should fail with no API key")
}
}
func TestHistoryManagement(t *testing.T) {
cfg := testConfig()
orch, err := New(cfg)
if err != nil {
t.Fatalf("New failed: %v", err)
}
h := orch.History()
if len(h) != 0 {
t.Errorf("Expected empty history, got %d", len(h))
}
orch.ClearHistory()
h = orch.History()
if len(h) != 0 {
t.Errorf("Expected 0 after clear, got %d", len(h))
}
}
func TestHistoryCopy(t *testing.T) {
cfg := testConfig()
orch, _ := New(cfg)
orch.history = []Message{
{Role: "user", Content: "hello"},
}
h := orch.History()
h[0].Content = "modified"
orig := orch.History()
if orig[0].Content == "modified" {
t.Error("History should return a copy")
}
}
func TestMaxHistorySize(t *testing.T) {
cfg := testConfig()
orch, _ := New(cfg)
for i := 0; i < maxHistorySize+10; i++ {
orch.histMu.Lock()
orch.history = append(orch.history, Message{Role: "user", Content: "msg"})
if len(orch.history) > maxHistorySize {
orch.history = orch.history[len(orch.history)-maxHistorySize:]
}
orch.histMu.Unlock()
}
h := orch.History()
if len(h) > maxHistorySize {
t.Errorf("History should be capped at %d, got %d", maxHistorySize, len(h))
}
}