Skip to content

Commit 4abeb99

Browse files
EItanyaclaude
andauthored
Eitanya/cli testing (#1456)
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent ac4663a commit 4abeb99

35 files changed

+4468
-808
lines changed

go/core/cli/TESTING.md

Lines changed: 491 additions & 0 deletions
Large diffs are not rendered by default.

go/core/cli/cmd/kagent/main.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/kagent-dev/kagent/go/core/cli/internal/profiles"
1616
"github.com/kagent-dev/kagent/go/core/cli/internal/tui"
1717
"github.com/spf13/cobra"
18+
"sigs.k8s.io/controller-runtime/pkg/client"
1819
)
1920

2021
func main() {
@@ -324,7 +325,18 @@ Examples:
324325
Run: func(cmd *cobra.Command, args []string) {
325326
deployCfg.ProjectDir = args[0]
326327

327-
if err := cli.DeployCmd(cmd.Context(), deployCfg); err != nil {
328+
// Create Kubernetes client (skip in dry-run mode)
329+
var k8sClient client.Client
330+
var err error
331+
if !deployCfg.DryRun {
332+
k8sClient, err = cli.CreateKubernetesClient()
333+
if err != nil {
334+
fmt.Fprintf(os.Stderr, "Error creating Kubernetes client: %v\n", err)
335+
os.Exit(1)
336+
}
337+
}
338+
339+
if err := cli.DeployCmd(cmd.Context(), k8sClient, deployCfg); err != nil {
328340
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
329341
os.Exit(1)
330342
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package cli
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/kagent-dev/kagent/go/core/cli/internal/config"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
"gopkg.in/yaml.v3"
12+
)
13+
14+
func TestAddMcpCmd_AddRemoteServer(t *testing.T) {
15+
tmpDir := t.TempDir()
16+
17+
// Create initial manifest without MCP servers
18+
manifestContent := `agentName: test-agent
19+
description: Test agent
20+
framework: adk
21+
language: python
22+
modelProvider: anthropic
23+
`
24+
manifestPath := filepath.Join(tmpDir, "kagent.yaml")
25+
err := os.WriteFile(manifestPath, []byte(manifestContent), 0644)
26+
require.NoError(t, err)
27+
28+
cfg := &AddMcpCfg{
29+
ProjectDir: tmpDir,
30+
Name: "github-server",
31+
RemoteURL: "https://api.github.com/mcp",
32+
Headers: []string{"Authorization=Bearer ${GITHUB_TOKEN}"},
33+
Config: &config.Config{},
34+
}
35+
36+
// Call AddMcpCmd
37+
err = AddMcpCmd(cfg)
38+
require.NoError(t, err)
39+
40+
// Verify manifest was updated
41+
content, err := os.ReadFile(manifestPath)
42+
require.NoError(t, err)
43+
44+
var manifest map[string]any
45+
err = yaml.Unmarshal(content, &manifest)
46+
require.NoError(t, err)
47+
48+
mcpServers, ok := manifest["mcpServers"].([]any)
49+
require.True(t, ok, "mcpServers should be an array")
50+
require.Len(t, mcpServers, 1)
51+
52+
server := mcpServers[0].(map[string]any)
53+
assert.Equal(t, "github-server", server["name"])
54+
assert.Equal(t, "remote", server["type"])
55+
assert.Equal(t, "https://api.github.com/mcp", server["url"])
56+
}
57+
58+
func TestAddMcpCmd_AddCommandServer(t *testing.T) {
59+
tmpDir := t.TempDir()
60+
61+
manifestContent := `agentName: test-agent
62+
description: Test agent
63+
framework: adk
64+
language: python
65+
`
66+
manifestPath := filepath.Join(tmpDir, "kagent.yaml")
67+
err := os.WriteFile(manifestPath, []byte(manifestContent), 0644)
68+
require.NoError(t, err)
69+
70+
cfg := &AddMcpCfg{
71+
ProjectDir: tmpDir,
72+
Name: "filesystem-server",
73+
Command: "npx",
74+
Args: []string{"-y", "@modelcontextprotocol/server-filesystem", "/tmp"},
75+
Config: &config.Config{},
76+
}
77+
78+
err = AddMcpCmd(cfg)
79+
require.NoError(t, err)
80+
81+
// Verify manifest was updated
82+
content, err := os.ReadFile(manifestPath)
83+
require.NoError(t, err)
84+
85+
var manifest map[string]any
86+
err = yaml.Unmarshal(content, &manifest)
87+
require.NoError(t, err)
88+
89+
mcpServers := manifest["mcpServers"].([]any)
90+
require.Len(t, mcpServers, 1)
91+
92+
server := mcpServers[0].(map[string]any)
93+
assert.Equal(t, "filesystem-server", server["name"])
94+
assert.Equal(t, "command", server["type"])
95+
assert.Equal(t, "npx", server["command"])
96+
}
97+
98+
func TestAddMcpCmd_MissingManifest(t *testing.T) {
99+
tmpDir := t.TempDir()
100+
101+
cfg := &AddMcpCfg{
102+
ProjectDir: tmpDir,
103+
Name: "test-server",
104+
RemoteURL: "http://example.com",
105+
Config: &config.Config{},
106+
}
107+
108+
err := AddMcpCmd(cfg)
109+
require.Error(t, err)
110+
assert.Contains(t, err.Error(), "failed to load kagent.yaml")
111+
}
112+
113+
func TestAddMcpCmd_AddMultipleServers(t *testing.T) {
114+
tmpDir := t.TempDir()
115+
116+
manifestContent := `agentName: test-agent
117+
description: Test agent
118+
framework: adk
119+
language: python
120+
`
121+
manifestPath := filepath.Join(tmpDir, "kagent.yaml")
122+
err := os.WriteFile(manifestPath, []byte(manifestContent), 0644)
123+
require.NoError(t, err)
124+
125+
// Add first server (remote)
126+
cfg1 := &AddMcpCfg{
127+
ProjectDir: tmpDir,
128+
Name: "server-1",
129+
RemoteURL: "http://server1.com",
130+
Config: &config.Config{},
131+
}
132+
err = AddMcpCmd(cfg1)
133+
require.NoError(t, err)
134+
135+
// Add second server (command)
136+
cfg2 := &AddMcpCfg{
137+
ProjectDir: tmpDir,
138+
Name: "server-2",
139+
Command: "python",
140+
Args: []string{"server.py"},
141+
Config: &config.Config{},
142+
}
143+
err = AddMcpCmd(cfg2)
144+
require.NoError(t, err)
145+
146+
// Verify both servers exist
147+
content, err := os.ReadFile(manifestPath)
148+
require.NoError(t, err)
149+
150+
var manifest map[string]any
151+
err = yaml.Unmarshal(content, &manifest)
152+
require.NoError(t, err)
153+
154+
mcpServers := manifest["mcpServers"].([]any)
155+
require.Len(t, mcpServers, 2)
156+
157+
// Verify first server
158+
server1 := mcpServers[0].(map[string]any)
159+
assert.Equal(t, "server-1", server1["name"])
160+
161+
// Verify second server
162+
server2 := mcpServers[1].(map[string]any)
163+
assert.Equal(t, "server-2", server2["name"])
164+
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package cli
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/kagent-dev/kagent/go/core/cli/internal/config"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestBuildCmd_Validation(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
cfg *BuildCfg
17+
setup func(t *testing.T) string // Returns temp dir path
18+
wantErr bool
19+
errMsg string
20+
}{
21+
{
22+
name: "missing project directory",
23+
cfg: &BuildCfg{
24+
ProjectDir: "",
25+
Config: &config.Config{},
26+
},
27+
setup: func(t *testing.T) string { return "" },
28+
wantErr: true,
29+
errMsg: "project directory is required",
30+
},
31+
{
32+
name: "non-existent project directory",
33+
cfg: &BuildCfg{
34+
ProjectDir: "/nonexistent/path",
35+
Config: &config.Config{},
36+
},
37+
setup: func(t *testing.T) string { return "" },
38+
wantErr: true,
39+
errMsg: "project directory does not exist",
40+
},
41+
{
42+
name: "missing Dockerfile",
43+
cfg: &BuildCfg{
44+
Config: &config.Config{},
45+
},
46+
setup: func(t *testing.T) string {
47+
tmpDir := t.TempDir()
48+
return tmpDir
49+
},
50+
wantErr: true,
51+
errMsg: "dockerfile not found",
52+
},
53+
}
54+
55+
for _, tt := range tests {
56+
t.Run(tt.name, func(t *testing.T) {
57+
if tt.setup != nil {
58+
dir := tt.setup(t)
59+
if dir != "" && tt.cfg.ProjectDir == "" {
60+
tt.cfg.ProjectDir = dir
61+
}
62+
}
63+
64+
err := BuildCmd(tt.cfg)
65+
if tt.wantErr {
66+
require.Error(t, err)
67+
if tt.errMsg != "" {
68+
assert.Contains(t, err.Error(), tt.errMsg)
69+
}
70+
} else {
71+
require.NoError(t, err)
72+
}
73+
})
74+
}
75+
}
76+
77+
func TestConstructImageName(t *testing.T) {
78+
tests := []struct {
79+
name string
80+
cfg *BuildCfg
81+
setupFile func(t *testing.T, dir string) // Create kagent.yaml if needed
82+
want string
83+
}{
84+
{
85+
name: "custom image name provided",
86+
cfg: &BuildCfg{
87+
Image: "myregistry/myagent:v1.0",
88+
ProjectDir: "",
89+
},
90+
setupFile: nil,
91+
want: "myregistry/myagent:v1.0",
92+
},
93+
{
94+
name: "fallback to directory name",
95+
cfg: &BuildCfg{
96+
Image: "",
97+
ProjectDir: "/path/to/my-agent",
98+
},
99+
setupFile: nil,
100+
want: "localhost:5001/my-agent:latest",
101+
},
102+
}
103+
104+
for _, tt := range tests {
105+
t.Run(tt.name, func(t *testing.T) {
106+
if tt.setupFile != nil {
107+
tmpDir := t.TempDir()
108+
tt.cfg.ProjectDir = tmpDir
109+
tt.setupFile(t, tmpDir)
110+
}
111+
112+
got := constructImageName(tt.cfg)
113+
assert.Equal(t, tt.want, got)
114+
})
115+
}
116+
}
117+
118+
func TestGetAgentNameFromManifest(t *testing.T) {
119+
tests := []struct {
120+
name string
121+
manifestYAML string
122+
want string
123+
}{
124+
{
125+
name: "valid manifest with agent name",
126+
manifestYAML: `agentName: test-agent
127+
description: Test agent
128+
framework: adk
129+
language: python
130+
`,
131+
want: "test-agent",
132+
},
133+
{
134+
name: "no manifest file",
135+
manifestYAML: "",
136+
want: "",
137+
},
138+
{
139+
name: "invalid yaml",
140+
manifestYAML: `invalid: yaml: content:
141+
- broken`,
142+
want: "",
143+
},
144+
}
145+
146+
for _, tt := range tests {
147+
t.Run(tt.name, func(t *testing.T) {
148+
tmpDir := t.TempDir()
149+
150+
if tt.manifestYAML != "" {
151+
manifestPath := filepath.Join(tmpDir, "kagent.yaml")
152+
err := os.WriteFile(manifestPath, []byte(tt.manifestYAML), 0644)
153+
require.NoError(t, err)
154+
}
155+
156+
got := getAgentNameFromManifest(tmpDir)
157+
assert.Equal(t, tt.want, got)
158+
})
159+
}
160+
}
161+
162+
func TestConstructMcpServerImageName(t *testing.T) {
163+
tests := []struct {
164+
name string
165+
projectDir string
166+
serverName string
167+
want string
168+
}{
169+
{
170+
name: "basic mcp server image",
171+
projectDir: "/path/to/my-agent",
172+
serverName: "weather-server",
173+
want: "localhost:5001/my-agent-weather-server:latest",
174+
},
175+
}
176+
177+
for _, tt := range tests {
178+
t.Run(tt.name, func(t *testing.T) {
179+
cfg := &BuildCfg{
180+
ProjectDir: tt.projectDir,
181+
}
182+
183+
got := constructMcpServerImageName(cfg, tt.serverName)
184+
assert.Equal(t, tt.want, got)
185+
})
186+
}
187+
}

0 commit comments

Comments
 (0)