package skills import ( "os" "path/filepath" "testing" ) func TestParseSkillWithYAML(t *testing.T) { data := []byte(`--- name: test-skill description: A test skill author: test version: "1.0" target: both tags: - test - demo --- # Test Skill Content This is the body. `) skill, err := parseSkill(data) if err != nil { t.Fatalf("parseSkill failed: %v", err) } if skill.Description != "A test skill" { t.Errorf("Expected 'A test skill', got %s", skill.Description) } if skill.Author != "test" { t.Errorf("Expected 'test', got %s", skill.Author) } if skill.Version != "1.0" { t.Errorf("Expected '1.0', got %s", skill.Version) } if skill.Target != "both" { t.Errorf("Expected 'both', got %s", skill.Target) } if len(skill.Tags) != 2 { t.Errorf("Expected 2 tags, got %d", len(skill.Tags)) } if skill.Content == "" { t.Error("Content should not be empty") } } func TestParseSkillNoFrontmatter(t *testing.T) { data := []byte("Just plain content here") skill, err := parseSkill(data) if err != nil { t.Fatalf("parseSkill failed: %v", err) } if skill.Content != "Just plain content here" { t.Errorf("Unexpected content: %s", skill.Content) } } func TestParseSkillIncompleteFrontmatter(t *testing.T) { data := []byte("---\nname: incomplete\n---\nBody content") skill, err := parseSkill(data) if err != nil { t.Fatalf("parseSkill failed: %v", err) } if skill.Content != "Body content" { t.Errorf("Expected 'Body content', got %s", skill.Content) } } func TestRenderSkill(t *testing.T) { skill := &Skill{ Name: "test", Description: "A test", Author: "author", Version: "1.0", Target: "both", Tags: []string{"a", "b"}, Content: "Body", } rendered := renderSkill(skill) if rendered == "" { t.Error("Rendered skill should not be empty") } if len(rendered) < 20 { t.Error("Rendered skill seems too short") } } func TestListEmpty(t *testing.T) { tmpDir := t.TempDir() origHome := os.Getenv("HOME") os.Setenv("HOME", tmpDir) defer os.Setenv("HOME", origHome) skills, err := List() if err != nil { t.Fatalf("List failed: %v", err) } if len(skills) != 0 { t.Errorf("Expected 0 skills, got %d", len(skills)) } } func TestCreateAndGet(t *testing.T) { tmpDir := t.TempDir() origHome := os.Getenv("HOME") os.Setenv("HOME", tmpDir) defer os.Setenv("HOME", origHome) skill := &Skill{ Name: "test-skill", Description: "Test description", Content: "Test content body", Author: "tester", Version: "1.0.0", Target: "both", } if err := Create(skill); err != nil { t.Fatalf("Create failed: %v", err) } dir, _ := SkillsDir() skillPath := filepath.Join(dir, "test-skill", "SKILL.md") if _, err := os.Stat(skillPath); os.IsNotExist(err) { t.Error("Skill file should exist") } got, err := Get("test-skill") if err != nil { t.Fatalf("Get failed: %v", err) } if got.Name != "test-skill" { t.Errorf("Expected test-skill, got %s", got.Name) } } func TestDelete(t *testing.T) { tmpDir := t.TempDir() origHome := os.Getenv("HOME") os.Setenv("HOME", tmpDir) defer os.Setenv("HOME", origHome) skill := &Skill{ Name: "to-delete", Description: "Will be deleted", Content: "content", Target: "both", } Create(skill) if err := Delete("to-delete"); err != nil { t.Fatalf("Delete failed: %v", err) } _, err := Get("to-delete") if err == nil { t.Error("Skill should be deleted") } } func TestBuildAIGeneratePrompt(t *testing.T) { prompt := BuildAIGeneratePrompt("docker", "Set up Docker", "both") if prompt == "" { t.Error("Prompt should not be empty") } if len(prompt) < 50 { t.Error("Prompt seems too short") } } func TestInstallBuiltinSkills(t *testing.T) { tmpDir := t.TempDir() origHome := os.Getenv("HOME") os.Setenv("HOME", tmpDir) defer os.Setenv("HOME", origHome) if err := InstallBuiltinSkills(); err != nil { t.Fatalf("InstallBuiltinSkills failed: %v", err) } skills, err := List() if err != nil { t.Fatalf("List failed: %v", err) } if len(skills) == 0 { t.Error("Expected at least one builtin skill") } found := false for _, s := range skills { if s.Name == "env-setup" { found = true } } if !found { t.Error("Expected env-setup skill") } } func TestValidate(t *testing.T) { skill := &Skill{ Name: "valid-skill", Description: "A valid skill", Content: "## Steps\nDo things", Version: "1.0.0", Target: "both", } errs := Validate(skill) if len(errs) != 0 { t.Errorf("Valid skill should have no errors, got %v", errs) } } func TestValidateMissingFields(t *testing.T) { skill := &Skill{} errs := Validate(skill) if len(errs) == 0 { t.Error("Empty skill should have validation errors") } fields := map[string]bool{} for _, e := range errs { fields[e.Field] = true } if !fields["name"] { t.Error("Should require name") } if !fields["description"] { t.Error("Should require description") } if !fields["content"] { t.Error("Should require content") } } func TestValidateBadVersion(t *testing.T) { skill := &Skill{ Name: "test-skill", Description: "desc", Content: "content", Version: "not-semver", } errs := Validate(skill) hasVersionErr := false for _, e := range errs { if e.Field == "version" { hasVersionErr = true } } if !hasVersionErr { t.Error("Should reject non-semver version") } } func TestValidateBadTarget(t *testing.T) { skill := &Skill{ Name: "test", Description: "desc", Content: "content", Target: "invalid", } errs := Validate(skill) hasTargetErr := false for _, e := range errs { if e.Field == "target" { hasTargetErr = true } } if !hasTargetErr { t.Error("Should reject invalid target") } } func TestValidateBadName(t *testing.T) { skill := &Skill{ Name: "INVALID", Description: "desc", Content: "content", } errs := Validate(skill) hasNameErr := false for _, e := range errs { if e.Field == "name" { hasNameErr = true } } if !hasNameErr { t.Error("Should reject uppercase name") } } func TestValidateDependencies(t *testing.T) { skill := &Skill{ Name: "test", Description: "desc", Content: "content", Dependencies: []SkillDependency{ {Type: "mcp_server", Name: "github", Required: true}, {Type: "invalid_type", Name: "test"}, }, } errs := Validate(skill) hasDepErr := false for _, e := range errs { if e.Field == "dependencies[1].type" { hasDepErr = true } } if !hasDepErr { t.Error("Should reject invalid dependency type") } } func TestExportImport(t *testing.T) { tmpDir := t.TempDir() os.Setenv("HOME", tmpDir) defer os.Setenv("HOME", tmpDir) skill := &Skill{ Name: "export-test", Description: "Export test skill", Content: "## Content", Author: "tester", Version: "1.0.0", Target: "both", Tags: []string{"test"}, } Create(skill) exportPath := filepath.Join(tmpDir, "export", "export-test.md") if err := Export("export-test", exportPath); err != nil { t.Fatalf("Export failed: %v", err) } if _, err := os.Stat(exportPath); os.IsNotExist(err) { t.Error("Export file should exist") } imported, err := Import(exportPath) if err != nil { t.Fatalf("Import failed: %v", err) } if imported.Description != "Export test skill" { t.Errorf("Expected 'Export test skill', got %s", imported.Description) } } func TestDryRun(t *testing.T) { tmpDir := t.TempDir() os.Setenv("HOME", tmpDir) defer os.Setenv("HOME", tmpDir) skill := &Skill{ Name: "dry-run-test", Description: "Dry run test", Content: "## Steps\nDo something", Version: "1.0.0", Target: "both", Tags: []string{"test"}, } Create(skill) result := DryRun("dry-run-test", "test something") if !result.Passed { t.Errorf("DryRun should pass, got: %s", result.Message) } } func TestDryRunMissing(t *testing.T) { result := DryRun("nonexistent", "") if result.Passed { t.Error("DryRun of nonexistent skill should fail") } } func TestUpdate(t *testing.T) { tmpDir := t.TempDir() os.Setenv("HOME", tmpDir) defer os.Setenv("HOME", tmpDir) skill := &Skill{ Name: "update-test", Description: "Original", Content: "Original content", Version: "1.0.0", Target: "both", } Create(skill) skill.Description = "Updated" skill.Content = "Updated content" skill.Version = "2.0.0" if err := Update(skill); err != nil { t.Fatalf("Update failed: %v", err) } got, err := Get("update-test") if err != nil { t.Fatalf("Get failed: %v", err) } if got.Description != "Updated" { t.Errorf("Expected 'Updated', got %s", got.Description) } } func TestBuiltinSkillCount(t *testing.T) { if len(builtinSkills) < 5 { t.Errorf("Expected at least 5 builtin skills, got %d", len(builtinSkills)) } expectedSkills := []string{"env-setup", "git-workflow", "api-design", "debug-assist", "code-review", "docker-setup", "security-audit", "mcp-setup", "lsp-setup", "workflow-design"} for _, name := range expectedSkills { found := false for _, s := range builtinSkills { if s.Name == name { found = true break } } if !found { t.Errorf("Expected builtin skill: %s", name) } } } func TestBuiltinSkillsHaveDependencies(t *testing.T) { hasDeps := 0 for _, s := range builtinSkills { if len(s.Dependencies) > 0 { hasDeps++ } } if hasDeps == 0 { t.Error("At least some builtin skills should declare dependencies") } }