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)
+ }
+}