diff --git a/docs/features.html b/docs/features.html index 519d21d..b5756b8 100644 --- a/docs/features.html +++ b/docs/features.html @@ -23,6 +23,7 @@

Features

Everything you need to build MCP servers quickly and efficiently

+

Version: v0.4.1 (latest release)

Implemented diff --git a/internal/commands/root_test.go b/internal/commands/root_test.go index 9e81ff9..63a4d5a 100644 --- a/internal/commands/root_test.go +++ b/internal/commands/root_test.go @@ -57,3 +57,11 @@ func TestGlobalFlags(t *testing.T) { t.Error("expected global flag 'quiet' to be defined") } } + +func TestRootCommand_ExecuteUnknown(t *testing.T) { + rootCmd := MakeRootCommand(version) + rootCmd.SetArgs([]string{"unknown"}) + if err := rootCmd.Execute(); err != nil { + t.Fatalf("unexpected execute error: %v", err) + } +} diff --git a/internal/generators/python.go b/internal/generators/python.go index 7657425..3e57a92 100644 --- a/internal/generators/python.go +++ b/internal/generators/python.go @@ -121,7 +121,9 @@ func (g *PythonGenerator) generateTemplateMap(output string, templates map[strin return nil } -// generateEntities renders a template for each provided item. +// generateEntities iterates over the given items, converts each to template data +// using conv, and writes the resulting file to the specified directory. +// The file is named after the item with a .py extension. func generateEntities[T any](g *PythonGenerator, output, dir, tmpl string, items []T, conv func(T) (string, interface{})) error { for _, item := range items { name, d := conv(item) diff --git a/internal/generators/python_test.go b/internal/generators/python_test.go index bfae82c..227bebe 100644 --- a/internal/generators/python_test.go +++ b/internal/generators/python_test.go @@ -3,6 +3,7 @@ package generators import ( "os" "path/filepath" + "strings" "testing" "github.com/aawadall/mcpcli/internal/core" @@ -85,3 +86,84 @@ func TestPythonGenerator_GenerateWithExtras(t *testing.T) { } } } + +func TestPythonCreateDirectoryStructure_Error(t *testing.T) { + tmpDir := t.TempDir() + // create a file where a directory is expected + if err := os.WriteFile(filepath.Join(tmpDir, "src"), []byte("file"), 0644); err != nil { + t.Fatalf("setup failed: %v", err) + } + g := NewPythonGenerator() + if err := g.createDirectoryStructure(tmpDir); err == nil { + t.Fatal("expected error when creating directories over existing file") + } +} + +func TestPythonGenerateTemplate_ReadError(t *testing.T) { + g := NewPythonGenerator() + tmpDir := t.TempDir() + err := g.generateTemplate("missing.tmpl", filepath.Join(tmpDir, "out.py"), nil) + if err == nil || !strings.Contains(err.Error(), "failed to read template") { + t.Fatalf("expected template read error, got %v", err) + } +} + +func TestPythonGenerateTemplate_CreateError(t *testing.T) { + g := NewPythonGenerator() + tmpDir := t.TempDir() + tPath := "templates/python/stdio/src/main.py.tmpl" + outPath := filepath.Join(tmpDir, "nope", "file.py") + err := g.generateTemplate(tPath, outPath, nil) + if err == nil || !strings.Contains(err.Error(), "failed to create file") { + t.Fatalf("expected file creation error, got %v", err) + } +} + +func TestPythonGenerateEntities_Error(t *testing.T) { + g := NewPythonGenerator() + tmpDir := t.TempDir() + items := []core.Tool{{Name: "bad"}} + conv := func(t core.Tool) (string, interface{}) { return t.Name, struct{ Tool core.Tool }{t} } + err := generateEntities(g, tmpDir, "src/tools", "missing.tmpl", items, conv) + if err == nil || !strings.Contains(err.Error(), "failed to read template") { + t.Fatalf("expected template read error, got %v", err) + } +} + +func TestPythonGenerateTemplateMap_Error(t *testing.T) { + g := NewPythonGenerator() + tmpDir := t.TempDir() + templates := map[string]string{"missing.tmpl": "out.py"} + err := g.generateTemplateMap(tmpDir, templates, nil) + if err == nil || !strings.Contains(err.Error(), "failed to read template") { + t.Fatalf("expected template map error, got %v", err) + } +} + +func TestPythonGenerateFromTemplates_Error(t *testing.T) { + g := NewPythonGenerator() + tmpDir := t.TempDir() + // create a file "src" to block directory creation + if err := os.WriteFile(filepath.Join(tmpDir, "src"), []byte("file"), 0644); err != nil { + t.Fatalf("setup failed: %v", err) + } + cfg := &core.ProjectConfig{Name: "bad", Language: "python", Transport: "stdio", Output: tmpDir} + data := cfg.GetTemplateData() + err := g.generateFromTemplates(tmpDir, data) + if err == nil || !strings.Contains(err.Error(), "failed to create file") { + t.Fatalf("expected file creation error, got %v", err) + } +} + +func TestPythonGenerator_Generate_DirectoryError(t *testing.T) { + tmpDir := t.TempDir() + if err := os.WriteFile(filepath.Join(tmpDir, "src"), []byte("file"), 0644); err != nil { + t.Fatalf("setup failed: %v", err) + } + cfg := &core.ProjectConfig{Name: "badpython", Language: "python", Transport: "stdio", Output: tmpDir} + g := NewPythonGenerator() + err := g.Generate(cfg) + if err == nil || !strings.Contains(err.Error(), "failed to create directory structure") { + t.Fatalf("expected directory structure error, got %v", err) + } +} diff --git a/internal/handlers/generate_test.go b/internal/handlers/generate_test.go index b5f65d1..a805374 100644 --- a/internal/handlers/generate_test.go +++ b/internal/handlers/generate_test.go @@ -76,3 +76,34 @@ func TestPrintNextSteps(t *testing.T) { t.Fatalf("output missing next steps") } } + +func captureGenOutput(f func()) string { + r, w, _ := os.Pipe() + stdout := os.Stdout + os.Stdout = w + defer func() { os.Stdout = stdout }() + f() + w.Close() + var buf bytes.Buffer + io.Copy(&buf, r) + return buf.String() +} + +func TestPrintNextSteps_OtherLanguages(t *testing.T) { + cases := []struct { + lang string + expect string + }{ + {"javascript", "npm install"}, + {"java", "mvn package"}, + {"python", "python src/main.py"}, + } + for _, c := range cases { + out := captureGenOutput(func() { + printNextSteps(&GenerateOptions{Name: "p", Language: c.lang, Output: "out"}) + }) + if !strings.Contains(out, c.expect) { + t.Fatalf("expected %s instructions", c.lang) + } + } +} diff --git a/internal/handlers/test_test.go b/internal/handlers/test_test.go index 0cfef1b..1de0b03 100644 --- a/internal/handlers/test_test.go +++ b/internal/handlers/test_test.go @@ -1,9 +1,13 @@ package handlers import ( + "bytes" "encoding/json" + "fmt" + "io" "os" "path/filepath" + "strings" "testing" "github.com/aawadall/mcpcli/internal/core" @@ -81,3 +85,44 @@ func TestLoadMCPConfig_Project(t *testing.T) { t.Fatalf("expected proj name, got %s", cfg.Name) } } + +func captureOutput(f func()) string { + r, w, _ := os.Pipe() + stdout := os.Stdout + os.Stdout = w + defer func() { os.Stdout = stdout }() + f() + w.Close() + var buf bytes.Buffer + io.Copy(&buf, r) + return buf.String() +} + +func TestFormatAndPrintResult_Error(t *testing.T) { + out := captureOutput(func() { + formatAndPrintResult("Tools", nil, fmt.Errorf("boom")) + }) + if !strings.Contains(out, "Failed to list tools") { + t.Fatalf("unexpected output: %s", out) + } +} + +func TestFormatAndPrintResult_MCPError(t *testing.T) { + resp := &core.Response{Error: &core.Error{Message: "bad"}} + out := captureOutput(func() { + formatAndPrintResult("Tools", resp, nil) + }) + if !strings.Contains(out, "MCP error") { + t.Fatalf("unexpected output: %s", out) + } +} + +func TestFormatAndPrintResult_Success(t *testing.T) { + resp := &core.Response{Result: "ok"} + out := captureOutput(func() { + formatAndPrintResult("Tools", resp, nil) + }) + if !strings.Contains(out, "✅ Tools: ok") { + t.Fatalf("unexpected output: %s", out) + } +}