Files
Agent-System-Backend/internal/app/mcp_cache_test.go
T
weichen 642fec6397 Complete the B10 assembly: MCP config, subagent bridge, explicit roles, and skill-borne recipes
Why: the full-agent batch left three B10 sub-items open - the
composition root lacked MCP server injection and a Subagent factory,
builtin squad roles carried no explicit Tools/Skills/BashPermission
declarations, and the three Python deliverable recipes still lived
inline in the base.deliverables prompt instead of the skills system.

Evidence: go vet ./... clean; go test ./... 21 packages ok. New cases:
skills seeding idempotency with user-edit preservation and sorted
builtin names; the SubagentFactory bridge mapping delegation fields
into SpawnSubagent task briefs and status/errors back; builtin-role
assertions (all four explicit bash permissions, researcher
search+fetch, reviewer grep+glob, writer ask + 3 skills +
run_python/load_skill); MCP manager cache reuse/rebuild/disable
behavior including invalid JSON and config races.

Impact: internal/skills embeds docx/xlsx/pptx-author SKILL.md and
seeds <data>/skills at startup; base.deliverables references the
skills and keeps only import one-liners; squadadapter.AgentNativeOptions
now receives rc and gains SubagentFactory(rc); app.go adds
tools.mcp.servers loading with a config-keyed manager cache (old
manager closed on rebuild).

Refs: Dev task feature/full-agent-b10-r2 (verifier follow-ups).
2026-10-01 06:19:32 +00:00

74 lines
1.6 KiB
Go

package app
import (
"sync"
"testing"
)
func resetMcpCache() {
mcpMu.Lock()
defer mcpMu.Unlock()
if mcpManager != nil {
mcpManager.Close()
}
mcpKey, mcpManager = "", nil
}
func TestMcpManagerForCacheAndRebuild(t *testing.T) {
resetMcpCache()
defer resetMcpCache()
cfgA := `[{"name":"a","command":"/bin/true"}]`
cfgB := `[{"name":"b","command":"/bin/true"}]`
m1 := mcpManagerFor(cfgA)
if m1 == nil {
t.Fatal("valid config must build a manager")
}
if m2 := mcpManagerFor(cfgA); m2 != m1 {
t.Fatal("same config must reuse the cached manager")
}
m3 := mcpManagerFor(cfgB)
if m3 == nil || m3 == m1 {
t.Fatal("changed config must rebuild a fresh manager")
}
if mcpManagerFor("") != nil {
t.Fatal("empty config disables MCP and clears the cache")
}
if mcpManagerFor(`{bad json`) != nil {
t.Fatal("invalid JSON must disable MCP (nil), not panic")
}
if mcpManagerFor(`[{"name":"bad name","command":"x"}]`) != nil {
t.Fatal("invalid server config must disable MCP (nil)")
}
// 并发调用不死锁、返回同一实例或各自重建(缓存一致性由锁保证)。
var wg sync.WaitGroup
for i := 0; i < 8; i++ {
wg.Add(1)
go func() {
defer wg.Done()
_ = mcpManagerFor(cfgB)
}()
}
wg.Wait()
}
func TestMcpCacheConcurrencyWithSettingsResolution(t *testing.T) {
resetMcpCache()
defer resetMcpCache()
var wg sync.WaitGroup
cfgs := []string{
`[{"name":"a","command":"/bin/true"}]`,
`[{"name":"b","command":"/bin/true"}]`,
"",
}
for i := 0; i < 12; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
_ = mcpManagerFor(cfgs[i%len(cfgs)])
}(i)
}
wg.Wait()
}