Skip to main content
Glama

mcp-prompt-engine

by vasayxtx
prompts_parser_test.go13.2 kB
package main import ( "os" "path/filepath" "sort" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) type PromptsParserTestSuite struct { suite.Suite parser *PromptsParser tempDir string } func TestPromptsParserTestSuite(t *testing.T) { suite.Run(t, new(PromptsParserTestSuite)) } func (s *PromptsParserTestSuite) SetupTest() { s.parser = &PromptsParser{} s.tempDir = s.T().TempDir() } // TestExtractTemplateArgumentsFromTemplate tests template argument extraction with various scenarios func (s *PromptsParserTestSuite) TestExtractTemplateArgumentsFromTemplate() { tests := []struct { name string content string partials map[string]string expected []string description string shouldError bool }{ { name: "empty template", content: "{{/* Empty template */}}\nNo arguments here", partials: map[string]string{}, expected: []string{}, description: "Empty template", shouldError: false, }, { name: "single argument", content: "{{/* Single argument template */}}\nHello {{.name}}", partials: map[string]string{}, expected: []string{"name"}, description: "Single argument template", shouldError: false, }, { name: "multiple arguments", content: "{{/* Multiple arguments template */}}\nHello {{.name}}, your project is {{.project}} and language is {{.language}}", partials: map[string]string{}, expected: []string{"name", "project", "language"}, description: "Multiple arguments template", shouldError: false, }, { name: "arguments with built-in date", content: "{{/* Template with date */}}\nToday is {{.date}} and user is {{.username}}", partials: map[string]string{}, expected: []string{"username"}, // date is built-in, should be filtered out description: "Template with date", shouldError: false, }, { name: "template with used partial only", content: "{{/* Template with used partial only */}}\n{{template \"_header\" dict \"role\" .role \"task\" .task}}\nUser: {{.username}}", partials: map[string]string{"_header": "You are {{.role}} doing {{.task}}", "_footer": "End with {{.conclusion}}"}, expected: []string{"role", "task", "username"}, // should NOT include conclusion from unused footer description: "Template with used partial only", shouldError: false, }, { name: "template with multiple used partials", content: "{{/* Template with multiple partials */}}\n{{template \"_header\" dict \"role\" .role}}\n{{template \"_footer\" dict \"conclusion\" .conclusion}}\nUser: {{.username}}", partials: map[string]string{"_header": "You are {{.role}}", "_footer": "End with {{.conclusion}}", "_unused": "This has {{.unused_var}}"}, expected: []string{"role", "conclusion", "username"}, // should NOT include unused_var description: "Template with multiple partials", shouldError: false, }, { name: "duplicate arguments", content: "{{/* Duplicate arguments */}}\n{{.user}} said hello to {{.user}} again", partials: map[string]string{}, expected: []string{"user"}, description: "Duplicate arguments", shouldError: false, }, { name: "cyclic partial references", content: "{{/* Template with cyclic partials */}}\n{{template \"_a\" .}}\nMain content: {{.main}}", partials: map[string]string{ "_a": "Partial A with {{.a_var}} {{template \"_b\" .}}", "_b": "Partial B with {{.b_var}} {{template \"_c\" .}}", "_c": "Partial C with {{.c_var}} {{template \"_a\" .}}", // Creates a cycle: a -> b -> c -> a }, expected: nil, description: "Template with cyclic partials", shouldError: true, }, { name: "template with or condition", content: "{{/* Template with or condition */}}\n{{if or .show_message .show_alert}}Message: {{.message}}{{end}}\nAlways: {{.name}}", partials: map[string]string{}, expected: []string{"show_message", "show_alert", "message", "name"}, description: "Template with or condition", shouldError: false, }, { name: "template with variables", content: "{{/* Template with variables */}}\n{{$name := .user_name}}{{$email := .user_email}}User: {{$name}} ({{$email}}) - Role: {{.role}}", partials: map[string]string{}, expected: []string{"user_name", "user_email", "role"}, description: "Template with variables", shouldError: false, }, { name: "template with range node", content: "{{/* Template with range */}}\n{{range .items}}Item: {{.name}} - {{.value}}{{end}}\nTotal: {{.total}}", partials: map[string]string{}, expected: []string{"items", "name", "value", "total"}, description: "Template with range", shouldError: false, }, } for _, tt := range tests { s.Run(tt.name, func() { // Create a temporary directory for this test testDir := filepath.Join(s.tempDir, tt.name) err := os.MkdirAll(testDir, 0755) require.NoError(s.T(), err, "Failed to create test directory") // Write the main template file testFile := filepath.Join(testDir, tt.name+".tmpl") err = os.WriteFile(testFile, []byte(tt.content), 0644) require.NoError(s.T(), err, "Failed to write test file") // Write partial files for partialName, partialContent := range tt.partials { partialFile := filepath.Join(testDir, partialName+".tmpl") err = os.WriteFile(partialFile, []byte(partialContent), 0644) require.NoError(s.T(), err, "Failed to write partial file") } // Parse all templates in the test directory tmpl, err := s.parser.ParseDir(testDir) require.NoError(s.T(), err, "Failed to parse templates") got, err := s.parser.ExtractPromptArgumentsFromTemplate(tmpl, tt.name) if tt.shouldError { assert.Error(s.T(), err, "ExtractPromptArgumentsFromTemplate() expected error, but got none") return } require.NoError(s.T(), err, "ExtractPromptArgumentsFromTemplate() unexpected error") // Sort both slices for consistent comparison sort.Strings(got) sort.Strings(tt.expected) assert.Equal(s.T(), tt.expected, got, "ExtractPromptArgumentsFromTemplate() returned unexpected arguments") }) } } // TestExtractPromptDescriptionFromFile tests description extraction from template comments func (s *PromptsParserTestSuite) TestExtractPromptDescriptionFromFile() { tests := []struct { name string content string expectedDescription string }{ { name: "valid template with description", content: "{{/* Template description */}}", expectedDescription: "Template description", }, { name: "valid template with description, comment starts with dash", content: "{{- /* Template description */}}", expectedDescription: "Template description", }, { name: "valid template with description, comment ends with dash", content: "{{/* Template description */ -}}", expectedDescription: "Template description", }, { name: "valid template with description, comment starts and ends with dash", content: "{{- /* Template description */ -}}", expectedDescription: "Template description", }, { name: "template without description", content: "Hello {{.name}}", expectedDescription: "", }, { name: "template with valid comment and trim", content: "{{/* Comment */}}", expectedDescription: "Comment", }, } for _, tt := range tests { s.Run(tt.name, func() { testFile := filepath.Join(s.tempDir, tt.name+".tmpl") err := os.WriteFile(testFile, []byte(tt.content), 0644) require.NoError(s.T(), err, "Failed to write test file") description, err := s.parser.ExtractPromptDescriptionFromFile(testFile) require.NoError(s.T(), err, "ExtractPromptDescriptionFromFile() unexpected error") assert.Equal(s.T(), tt.expectedDescription, description, "ExtractPromptDescriptionFromFile() returned unexpected description") }) } } // TestExtractPromptDescriptionFromFileErrorCases tests error cases for description extraction func (s *PromptsParserTestSuite) TestExtractPromptDescriptionFromFileErrorCases() { // Test non-existent file _, err := s.parser.ExtractPromptDescriptionFromFile("/non/existent/file.tmpl") assert.Error(s.T(), err, "ExtractPromptDescriptionFromFile() expected error for non-existent file, but got none") } // TestExtractPromptArgumentsFromTemplateErrorCases tests error cases for argument extraction func (s *PromptsParserTestSuite) TestExtractPromptArgumentsFromTemplateErrorCases() { // Create a valid template file so ParseDir doesn't fail testFile := filepath.Join(s.tempDir, "test.tmpl") err := os.WriteFile(testFile, []byte("{{/* Test */}}\nHello {{.name}}"), 0644) require.NoError(s.T(), err, "Failed to write test file") // Test non-existent template tmpl, err := s.parser.ParseDir(s.tempDir) require.NoError(s.T(), err, "Failed to parse templates") _, err = s.parser.ExtractPromptArgumentsFromTemplate(tmpl, "non_existent_template") assert.Error(s.T(), err, "ExtractPromptArgumentsFromTemplate() expected error for non-existent template, but got none") } // TestParseDirErrorCases tests error cases for template parsing func (s *PromptsParserTestSuite) TestParseDirErrorCases() { // Test non-existent directory _, err := s.parser.ParseDir("/non/existent/directory") assert.Error(s.T(), err, "ParseDir() expected error for non-existent directory, but got none") // Test directory with invalid template syntax invalidFile := filepath.Join(s.tempDir, "invalid.tmpl") err = os.WriteFile(invalidFile, []byte("{{/* Invalid template */}}\n{{.unclosed"), 0644) require.NoError(s.T(), err, "Failed to write invalid template file") _, err = s.parser.ParseDir(s.tempDir) assert.Error(s.T(), err, "ParseDir() expected error for invalid template syntax, but got none") } // TestWalkNodesNilHandling tests nil node handling in walkNodes func (s *PromptsParserTestSuite) TestWalkNodesNilHandling() { argsMap := make(map[string]struct{}) builtInFields := map[string]struct{}{"date": {}} processedTemplates := make(map[string]bool) // This should return nil immediately for nil node err := s.parser.walkNodes(nil, argsMap, builtInFields, nil, processedTemplates, []string{}) assert.NoError(s.T(), err, "walkNodes() with nil node should return nil") // argsMap should remain empty assert.Empty(s.T(), argsMap, "walkNodes() with nil node should not modify argsMap") } // TestWalkNodesVariableHandling tests variable node handling in walkNodes func (s *PromptsParserTestSuite) TestWalkNodesVariableHandling() { // Create a template with a variable (non-$ variable) testFile := filepath.Join(s.tempDir, "test.tmpl") err := os.WriteFile(testFile, []byte("{{/* Test template */}}\n{{$var := .input}}{{$var}}"), 0644) require.NoError(s.T(), err, "Failed to write test file") tmpl, err := s.parser.ParseDir(s.tempDir) require.NoError(s.T(), err, "Failed to parse templates") // Test extracting arguments - should handle variable nodes properly args, err := s.parser.ExtractPromptArgumentsFromTemplate(tmpl, "test") require.NoError(s.T(), err, "ExtractPromptArgumentsFromTemplate() unexpected error") // Should only contain "input", not the template variables expected := []string{"input"} assert.Equal(s.T(), expected, args, "ExtractPromptArgumentsFromTemplate() should only return template data arguments, not dollar variables") } // TestDict tests the dict helper function func (s *PromptsParserTestSuite) TestDict() { tests := []struct { name string args []string expected map[string]interface{} hasError bool }{ { name: "empty args", args: []string{}, expected: map[string]interface{}{}, hasError: false, }, { name: "single key-value pair", args: []string{"key", "value"}, expected: map[string]interface{}{"key": "value"}, hasError: false, }, { name: "multiple key-value pairs", args: []string{"key1", "value1", "key2", "value2"}, expected: map[string]interface{}{"key1": "value1", "key2": "value2"}, hasError: false, }, { name: "odd number of arguments", args: []string{"key1", "value1", "key2"}, expected: nil, hasError: true, }, } for _, tt := range tests { s.Run(tt.name, func() { // Convert string slice to interface slice args := make([]interface{}, len(tt.args)) for i, v := range tt.args { args[i] = v } result := dict(args...) if tt.hasError { assert.Nil(s.T(), result, "dict() expected nil result for error case") return } assert.NotNil(s.T(), result, "dict() unexpected nil result") assert.Equal(s.T(), tt.expected, result, "dict() returned unexpected result") }) } // Test non-string key s.Run("non-string key", func() { result := dict(123, "value") assert.Nil(s.T(), result, "dict() expected nil result for non-string key") }) }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vasayxtx/mcp-prompt-engine'

If you have feedback or need assistance with the MCP directory API, please join our Discord server