Skip to content

Commit 99858f9

Browse files
committed
add more unit tests to cover parsing and serialization of prompts
Signed-off-by: Nader Ziada <[email protected]>
1 parent 7994bf0 commit 99858f9

File tree

3 files changed

+599
-8
lines changed

3 files changed

+599
-8
lines changed

pkg/api/prompt_parsing_test.go

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
package api
2+
3+
import (
4+
"testing"
5+
6+
"github.com/containers/kubernetes-mcp-server/pkg/config"
7+
"github.com/stretchr/testify/suite"
8+
)
9+
10+
// PromptParsingTestSuite tests TOML parsing of prompts through the config system
11+
type PromptParsingTestSuite struct {
12+
suite.Suite
13+
}
14+
15+
func (s *PromptParsingTestSuite) TestParseSinglePrompt() {
16+
s.Run("parses single prompt with all fields", func() {
17+
configData := `
18+
[[prompts]]
19+
name = "test-prompt"
20+
title = "Test Prompt"
21+
description = "A test prompt for validation"
22+
23+
[[prompts.arguments]]
24+
name = "pod_name"
25+
description = "Name of the pod"
26+
required = true
27+
28+
[[prompts.arguments]]
29+
name = "namespace"
30+
description = "Namespace of the pod"
31+
required = false
32+
33+
[[prompts.messages]]
34+
role = "user"
35+
content = "Describe pod {{pod_name}} in namespace {{namespace}}"
36+
37+
[[prompts.messages]]
38+
role = "assistant"
39+
content = "I'll help you with pod {{pod_name}}"
40+
`
41+
42+
cfg, err := config.ReadToml([]byte(configData))
43+
s.Require().NoError(err, "failed to parse config")
44+
45+
parsedPrompts := cfg.GetParsedPrompts()
46+
s.Require().NotNil(parsedPrompts, "parsed prompts should not be nil")
47+
48+
prompts, ok := parsedPrompts.([]Prompt)
49+
s.Require().True(ok, "parsed prompts should be []Prompt")
50+
s.Require().Len(prompts, 1, "should have exactly one prompt")
51+
52+
prompt := prompts[0]
53+
s.Equal("test-prompt", prompt.Name)
54+
s.Equal("Test Prompt", prompt.Title)
55+
s.Equal("A test prompt for validation", prompt.Description)
56+
57+
// Verify arguments
58+
s.Require().Len(prompt.Arguments, 2)
59+
s.Equal("pod_name", prompt.Arguments[0].Name)
60+
s.True(prompt.Arguments[0].Required)
61+
s.Equal("namespace", prompt.Arguments[1].Name)
62+
s.False(prompt.Arguments[1].Required)
63+
64+
// Verify templates
65+
s.Require().Len(prompt.Templates, 2)
66+
s.Equal("user", prompt.Templates[0].Role)
67+
s.Contains(prompt.Templates[0].Content, "{{pod_name}}")
68+
s.Equal("assistant", prompt.Templates[1].Role)
69+
})
70+
71+
s.Run("parses prompt with minimal fields", func() {
72+
configData := `
73+
[[prompts]]
74+
name = "minimal-prompt"
75+
description = "Minimal prompt"
76+
77+
[[prompts.messages]]
78+
role = "user"
79+
content = "Hello"
80+
`
81+
82+
cfg, err := config.ReadToml([]byte(configData))
83+
s.Require().NoError(err)
84+
85+
parsedPrompts := cfg.GetParsedPrompts()
86+
prompts, ok := parsedPrompts.([]Prompt)
87+
s.Require().True(ok)
88+
s.Require().Len(prompts, 1)
89+
90+
prompt := prompts[0]
91+
s.Equal("minimal-prompt", prompt.Name)
92+
s.Empty(prompt.Title, "title should be empty")
93+
s.Empty(prompt.Arguments, "arguments should be empty")
94+
s.Require().Len(prompt.Templates, 1)
95+
})
96+
}
97+
98+
func (s *PromptParsingTestSuite) TestParseMultiplePrompts() {
99+
s.Run("parses multiple prompts correctly", func() {
100+
configData := `
101+
[[prompts]]
102+
name = "prompt-1"
103+
description = "First prompt"
104+
105+
[[prompts.messages]]
106+
role = "user"
107+
content = "Message 1"
108+
109+
[[prompts]]
110+
name = "prompt-2"
111+
description = "Second prompt"
112+
113+
[[prompts.arguments]]
114+
name = "arg1"
115+
required = true
116+
117+
[[prompts.messages]]
118+
role = "user"
119+
content = "Message 2 with {{arg1}}"
120+
121+
[[prompts]]
122+
name = "prompt-3"
123+
description = "Third prompt"
124+
125+
[[prompts.messages]]
126+
role = "assistant"
127+
content = "Message 3"
128+
`
129+
130+
cfg, err := config.ReadToml([]byte(configData))
131+
s.Require().NoError(err)
132+
133+
parsedPrompts := cfg.GetParsedPrompts()
134+
prompts, ok := parsedPrompts.([]Prompt)
135+
s.Require().True(ok)
136+
s.Require().Len(prompts, 3, "should parse all three prompts")
137+
138+
s.Equal("prompt-1", prompts[0].Name)
139+
s.Equal("prompt-2", prompts[1].Name)
140+
s.Equal("prompt-3", prompts[2].Name)
141+
142+
// Verify second prompt has arguments
143+
s.Require().Len(prompts[1].Arguments, 1)
144+
s.Equal("arg1", prompts[1].Arguments[0].Name)
145+
s.True(prompts[1].Arguments[0].Required)
146+
})
147+
}
148+
149+
func (s *PromptParsingTestSuite) TestParsePromptsWithRequiredAndOptionalArgs() {
150+
s.Run("correctly parses required vs optional arguments", func() {
151+
configData := `
152+
[[prompts]]
153+
name = "mixed-args-prompt"
154+
description = "Prompt with mixed argument requirements"
155+
156+
[[prompts.arguments]]
157+
name = "required_arg1"
158+
description = "Required argument 1"
159+
required = true
160+
161+
[[prompts.arguments]]
162+
name = "required_arg2"
163+
description = "Required argument 2"
164+
required = true
165+
166+
[[prompts.arguments]]
167+
name = "optional_arg1"
168+
description = "Optional argument 1"
169+
required = false
170+
171+
[[prompts.arguments]]
172+
name = "optional_arg2"
173+
description = "Optional argument 2"
174+
required = false
175+
176+
[[prompts.messages]]
177+
role = "user"
178+
content = "Test {{required_arg1}} {{optional_arg1}}"
179+
`
180+
181+
cfg, err := config.ReadToml([]byte(configData))
182+
s.Require().NoError(err)
183+
184+
parsedPrompts := cfg.GetParsedPrompts()
185+
prompts, ok := parsedPrompts.([]Prompt)
186+
s.Require().True(ok)
187+
s.Require().Len(prompts, 1)
188+
189+
prompt := prompts[0]
190+
s.Require().Len(prompt.Arguments, 4)
191+
192+
// Count required vs optional
193+
requiredCount := 0
194+
optionalCount := 0
195+
for _, arg := range prompt.Arguments {
196+
if arg.Required {
197+
requiredCount++
198+
} else {
199+
optionalCount++
200+
}
201+
}
202+
203+
s.Equal(2, requiredCount, "should have 2 required arguments")
204+
s.Equal(2, optionalCount, "should have 2 optional arguments")
205+
})
206+
}
207+
208+
func (s *PromptParsingTestSuite) TestParseNoPrompts() {
209+
s.Run("handles config without prompts", func() {
210+
configData := `
211+
log_level = 2
212+
`
213+
214+
cfg, err := config.ReadToml([]byte(configData))
215+
s.Require().NoError(err)
216+
217+
parsedPrompts := cfg.GetParsedPrompts()
218+
s.Nil(parsedPrompts, "should return nil when no prompts defined")
219+
})
220+
}
221+
222+
func (s *PromptParsingTestSuite) TestParseInvalidPromptTOML() {
223+
s.Run("returns error for invalid TOML", func() {
224+
configData := `
225+
[[prompts]]
226+
name = "invalid
227+
description = "Missing quote
228+
`
229+
230+
_, err := config.ReadToml([]byte(configData))
231+
s.Error(err, "should return error for invalid TOML")
232+
})
233+
}
234+
235+
func (s *PromptParsingTestSuite) TestPromptLoaderIntegration() {
236+
s.Run("PromptLoader correctly loads from parsed config", func() {
237+
configData := `
238+
[[prompts]]
239+
name = "loader-test"
240+
description = "Test prompt loader"
241+
242+
[[prompts.arguments]]
243+
name = "arg1"
244+
required = true
245+
246+
[[prompts.messages]]
247+
role = "user"
248+
content = "Hello {{arg1}}"
249+
`
250+
251+
cfg, err := config.ReadToml([]byte(configData))
252+
s.Require().NoError(err)
253+
254+
loader := NewPromptLoader()
255+
err = loader.LoadFromParsedConfig(cfg.GetParsedPrompts())
256+
s.Require().NoError(err)
257+
258+
serverPrompts := loader.GetServerPrompts()
259+
s.Require().Len(serverPrompts, 1)
260+
261+
serverPrompt := serverPrompts[0]
262+
s.Equal("loader-test", serverPrompt.Prompt.Name)
263+
s.Equal("Test prompt loader", serverPrompt.Prompt.Description)
264+
s.Require().Len(serverPrompt.Prompt.Arguments, 1)
265+
s.Equal("arg1", serverPrompt.Prompt.Arguments[0].Name)
266+
s.NotNil(serverPrompt.Handler, "handler should be created")
267+
})
268+
}
269+
270+
func TestPromptParsing(t *testing.T) {
271+
suite.Run(t, new(PromptParsingTestSuite))
272+
}

0 commit comments

Comments
 (0)