- Change the test scope identifier from "@test" to "@yaoagents" in client tests for consistency. - Remove the outdated E2E test file `e2e_test.go` from the manager directory, as it is no longer needed. - Enhance the agent manager to utilize the MCP manager for dependency installations, improving the handling of MCP-type dependencies.
256 lines
8.9 KiB
Go
256 lines
8.9 KiB
Go
package manager_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
agentmgr "github.com/yaoapp/yao/registry/manager/agent"
|
|
"github.com/yaoapp/yao/registry/manager/common"
|
|
mcpmgr "github.com/yaoapp/yao/registry/manager/mcp"
|
|
robotmgr "github.com/yaoapp/yao/registry/manager/robot"
|
|
)
|
|
|
|
// =============================================================================
|
|
// Robot Add with agent + MCP dependencies
|
|
// =============================================================================
|
|
|
|
func TestE2ERobot_AddWithDeps(t *testing.T) {
|
|
c := authClient()
|
|
devApp := appRoot(t)
|
|
|
|
defer cleanupPkg(c, "mcps", "@"+testScope, "registry-mcp", "1.0.0")
|
|
defer cleanupPkg(c, "assistants", "@"+testScope, "registry-agent", "1.0.0")
|
|
defer cleanupPkg(c, "robots", "@"+testScope, "test-bot", "1.0.0")
|
|
|
|
// Push dependencies
|
|
mcpMgr := mcpmgr.New(c, devApp, &common.AutoConfirmPrompter{})
|
|
agentMgr := agentmgr.New(c, devApp, &common.AutoConfirmPrompter{})
|
|
|
|
if err := mcpMgr.Push(testScope+".registry-mcp", mcpmgr.PushOptions{Version: "1.0.0"}); err != nil {
|
|
t.Fatalf("Push MCP: %v", err)
|
|
}
|
|
if err := agentMgr.Push(testScope+".registry-agent", agentmgr.PushOptions{Version: "1.0.0"}); err != nil {
|
|
t.Fatalf("Push agent: %v", err)
|
|
}
|
|
|
|
// Build and push robot package
|
|
robotJSON := map[string]interface{}{
|
|
"display_name": "E2E Test Bot",
|
|
"system_prompt": "You are an E2E test robot.",
|
|
"language_model": "gpt-4o",
|
|
"robot_config": map[string]interface{}{
|
|
"resources": map[string]interface{}{
|
|
"phases": map[string]string{
|
|
"host": testScope + ".registry-agent",
|
|
},
|
|
},
|
|
},
|
|
"mcp_servers": []string{testScope + ".registry-mcp"},
|
|
}
|
|
buildAndPushRobotZip(t, c, "test-bot", robotJSON, "1.0.0")
|
|
|
|
// Install robot to fresh app
|
|
installApp := t.TempDir()
|
|
rMgr := robotmgr.New(c, installApp, &common.AutoConfirmPrompter{})
|
|
|
|
robot, err := rMgr.Add("@"+testScope+"/test-bot", robotmgr.AddOptions{TeamID: "team-e2e"})
|
|
if err != nil {
|
|
t.Fatalf("Add robot: %v", err)
|
|
}
|
|
|
|
if robot.DisplayName != "E2E Test Bot" {
|
|
t.Errorf("want display_name 'E2E Test Bot', got %q", robot.DisplayName)
|
|
}
|
|
if robot.SystemPrompt != "You are an E2E test robot." {
|
|
t.Errorf("unexpected system_prompt: %s", robot.SystemPrompt)
|
|
}
|
|
|
|
// Lockfile: robot entry
|
|
robotPkg := requireLockfileHas(t, installApp, "@"+testScope+"/test-bot")
|
|
if robotPkg.Type != common.TypeRobot {
|
|
t.Errorf("want robot type, got %s", robotPkg.Type)
|
|
}
|
|
if robotPkg.TeamID != "team-e2e" {
|
|
t.Errorf("want team_id team-e2e, got %s", robotPkg.TeamID)
|
|
}
|
|
|
|
// Dependencies auto-installed
|
|
requireLockfileHas(t, installApp, "@"+testScope+"/registry-agent")
|
|
requireLockfileHas(t, installApp, "@"+testScope+"/registry-mcp")
|
|
|
|
// Files on disk
|
|
requireFileExists(t, installApp+"/assistants/"+testScope+"/registry-agent/package.yao")
|
|
requireFileExists(t, installApp+"/mcps/"+testScope+"/registry-mcp/server.mcp.yao")
|
|
|
|
// required_by on agent from robot
|
|
lf, _ := common.LoadLockfile(installApp)
|
|
agentPkg, _ := lf.GetPackage("@" + testScope + "/registry-agent")
|
|
foundRB := false
|
|
for _, rb := range agentPkg.RequiredBy {
|
|
if rb == "@"+testScope+"/test-bot" {
|
|
foundRB = true
|
|
}
|
|
}
|
|
if !foundRB {
|
|
t.Errorf("expected robot in agent's required_by, got %v", agentPkg.RequiredBy)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Robot Add with no dependencies
|
|
// =============================================================================
|
|
|
|
func TestE2ERobot_AddNoDeps(t *testing.T) {
|
|
c := authClient()
|
|
|
|
defer cleanupPkg(c, "robots", "@"+testScope, "simple-bot", "1.0.0")
|
|
|
|
robotJSON := map[string]interface{}{
|
|
"display_name": "Simple Bot",
|
|
"system_prompt": "You are a simple bot with no dependencies.",
|
|
"language_model": "gpt-4o-mini",
|
|
}
|
|
buildAndPushRobotZip(t, c, "simple-bot", robotJSON, "1.0.0")
|
|
|
|
installApp := t.TempDir()
|
|
rMgr := robotmgr.New(c, installApp, &common.AutoConfirmPrompter{})
|
|
|
|
robot, err := rMgr.Add("@"+testScope+"/simple-bot", robotmgr.AddOptions{TeamID: "team-simple"})
|
|
if err != nil {
|
|
t.Fatalf("Add robot: %v", err)
|
|
}
|
|
|
|
if robot.DisplayName != "Simple Bot" {
|
|
t.Errorf("want 'Simple Bot', got %q", robot.DisplayName)
|
|
}
|
|
if robot.LanguageModel != "gpt-4o-mini" {
|
|
t.Errorf("want gpt-4o-mini, got %s", robot.LanguageModel)
|
|
}
|
|
|
|
robotPkg := requireLockfileHas(t, installApp, "@"+testScope+"/simple-bot")
|
|
if robotPkg.Type != common.TypeRobot {
|
|
t.Errorf("want robot type, got %s", robotPkg.Type)
|
|
}
|
|
if robotPkg.TeamID != "team-simple" {
|
|
t.Errorf("want team_id team-simple, got %s", robotPkg.TeamID)
|
|
}
|
|
|
|
// No other packages should be installed
|
|
lf, _ := common.LoadLockfile(installApp)
|
|
for id := range lf.Packages {
|
|
if id != "@"+testScope+"/simple-bot" {
|
|
t.Errorf("unexpected package %s in lockfile (no-dep robot should be alone)", id)
|
|
}
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Robot Add: team ID is required
|
|
// =============================================================================
|
|
|
|
func TestE2ERobot_AddRequiresTeam(t *testing.T) {
|
|
c := authClient()
|
|
|
|
defer cleanupPkg(c, "robots", "@"+testScope, "simple-bot", "1.0.0")
|
|
|
|
robotJSON := map[string]interface{}{
|
|
"display_name": "Simple Bot",
|
|
"system_prompt": "You are a simple bot.",
|
|
}
|
|
buildAndPushRobotZip(t, c, "simple-bot", robotJSON, "1.0.0")
|
|
|
|
installApp := t.TempDir()
|
|
rMgr := robotmgr.New(c, installApp, &common.AutoConfirmPrompter{})
|
|
|
|
_, err := rMgr.Add("@"+testScope+"/simple-bot", robotmgr.AddOptions{})
|
|
if err == nil {
|
|
t.Fatal("expected error when team is missing")
|
|
}
|
|
if err.Error() != "--team is required for robot add" {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Robot → Agent → MCP dependency chain: required_by propagation
|
|
// =============================================================================
|
|
|
|
func TestE2ERobot_RequiredByChain(t *testing.T) {
|
|
c := authClient()
|
|
devApp := appRoot(t)
|
|
|
|
defer cleanupPkg(c, "mcps", "@"+testScope, "registry-mcp", "1.0.0")
|
|
defer cleanupPkg(c, "mcps", "@"+testScope, "data-tools", "1.0.0")
|
|
defer cleanupPkg(c, "assistants", "@"+testScope, "analytics", "1.0.0")
|
|
defer cleanupPkg(c, "robots", "@"+testScope, "analytics-bot", "1.0.0")
|
|
|
|
// Push full dependency tree: 2 MCPs → analytics agent → robot
|
|
mcpMgr := mcpmgr.New(c, devApp, &common.AutoConfirmPrompter{})
|
|
mcpMgr.Push(testScope+".registry-mcp", mcpmgr.PushOptions{Version: "1.0.0"})
|
|
mcpMgr.Push(testScope+".data-tools", mcpmgr.PushOptions{Version: "1.0.0"})
|
|
|
|
agentMgr := agentmgr.New(c, devApp, &common.AutoConfirmPrompter{})
|
|
agentMgr.Push(testScope+".analytics", agentmgr.PushOptions{Version: "1.0.0"})
|
|
|
|
robotJSON := map[string]interface{}{
|
|
"display_name": "Analytics Bot",
|
|
"system_prompt": "You are an analytics bot.",
|
|
"language_model": "gpt-4o",
|
|
"robot_config": map[string]interface{}{
|
|
"resources": map[string]interface{}{
|
|
"phases": map[string]string{
|
|
"host": testScope + ".analytics",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
buildAndPushRobotZip(t, c, "analytics-bot", robotJSON, "1.0.0")
|
|
|
|
// Install robot to fresh app
|
|
installApp := t.TempDir()
|
|
rMgr := robotmgr.New(c, installApp, &common.AutoConfirmPrompter{})
|
|
|
|
_, err := rMgr.Add("@"+testScope+"/analytics-bot", robotmgr.AddOptions{TeamID: "team-chain"})
|
|
if err != nil {
|
|
t.Fatalf("Add robot: %v", err)
|
|
}
|
|
|
|
// Entire dependency chain should be installed
|
|
requireLockfileHas(t, installApp, "@"+testScope+"/analytics-bot")
|
|
requireLockfileHas(t, installApp, "@"+testScope+"/analytics")
|
|
requireLockfileHas(t, installApp, "@"+testScope+"/registry-mcp")
|
|
requireLockfileHas(t, installApp, "@"+testScope+"/data-tools")
|
|
|
|
// required_by: robot → analytics
|
|
lf, _ := common.LoadLockfile(installApp)
|
|
analyticsPkg, _ := lf.GetPackage("@" + testScope + "/analytics")
|
|
foundRobot := false
|
|
for _, rb := range analyticsPkg.RequiredBy {
|
|
if rb == "@"+testScope+"/analytics-bot" {
|
|
foundRobot = true
|
|
}
|
|
}
|
|
if !foundRobot {
|
|
t.Errorf("expected analytics-bot in analytics's required_by, got %v", analyticsPkg.RequiredBy)
|
|
}
|
|
|
|
// required_by: analytics → MCPs (set by agent Add's dependency installation)
|
|
// The MCP's required_by may include analytics (set by agent add) and/or analytics-bot (set by robot add)
|
|
for _, mcpID := range []string{"@" + testScope + "/registry-mcp", "@" + testScope + "/data-tools"} {
|
|
mcpPkg, ok := lf.GetPackage(mcpID)
|
|
if !ok {
|
|
t.Errorf("MCP %s not found in lockfile", mcpID)
|
|
continue
|
|
}
|
|
if len(mcpPkg.RequiredBy) == 0 {
|
|
t.Errorf("expected required_by on %s, got empty", mcpID)
|
|
}
|
|
}
|
|
|
|
// Verify disk completeness
|
|
requireFileExists(t, installApp+"/assistants/"+testScope+"/analytics/package.yao")
|
|
requireFileExists(t, installApp+"/mcps/"+testScope+"/registry-mcp/server.mcp.yao")
|
|
requireFileExists(t, installApp+"/mcps/"+testScope+"/data-tools/server.mcp.yao")
|
|
requireFileExists(t, installApp+"/scripts/"+testScope+"/registry_mcp.ts")
|
|
requireFileExists(t, installApp+"/scripts/"+testScope+"/data_tools.ts")
|
|
requireFileExists(t, installApp+"/scripts/"+testScope+"/data_utils.ts")
|
|
}
|