f4690a2ba6
Agregar tipos puros en pkg/skills/ para el sistema de skills: - SkillMeta: metadata de skills (name, description, category) - Skill: representacion completa con instrucciones y recursos - SkillMatch: resultado de matching con confidence score - Match(): funcion pura de matching por keywords - FilterByCategory(): filtrado de skills por categorias Incluye tests unitarios completos para matching y filtrado. Arquitectura: pure core, cero side effects en pkg/.
137 lines
3.4 KiB
Go
137 lines
3.4 KiB
Go
package skills
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestMatch(t *testing.T) {
|
|
skills := []SkillMeta{
|
|
{Name: "deploy-service", Description: "Deploy a service via SSH to a remote server", Category: "devops"},
|
|
{Name: "log-analyzer", Description: "Analyze logs for errors and patterns", Category: "analysis"},
|
|
{Name: "health-check", Description: "Check the health of services and systems", Category: "system"},
|
|
{Name: "daily-report", Description: "Generate daily report with metrics", Category: "communication"},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
query string
|
|
expectMatches int
|
|
firstMatch string // expected first match name
|
|
}{
|
|
{
|
|
name: "exact match in name",
|
|
query: "deploy-service",
|
|
expectMatches: 1,
|
|
firstMatch: "deploy-service",
|
|
},
|
|
{
|
|
name: "partial match in name",
|
|
query: "deploy",
|
|
expectMatches: 1,
|
|
firstMatch: "deploy-service",
|
|
},
|
|
{
|
|
name: "match in description",
|
|
query: "analyze logs",
|
|
expectMatches: 2, // log-analyzer and daily-report (both have similar words)
|
|
firstMatch: "log-analyzer",
|
|
},
|
|
{
|
|
name: "multiple matches",
|
|
query: "service",
|
|
expectMatches: 2, // deploy-service and health-check (services)
|
|
},
|
|
{
|
|
name: "no match",
|
|
query: "nonexistent",
|
|
expectMatches: 0,
|
|
},
|
|
{
|
|
name: "empty query",
|
|
query: "",
|
|
expectMatches: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
matches := Match(tt.query, skills)
|
|
|
|
if len(matches) != tt.expectMatches {
|
|
t.Errorf("expected %d matches, got %d", tt.expectMatches, len(matches))
|
|
}
|
|
|
|
if tt.firstMatch != "" && len(matches) > 0 {
|
|
if matches[0].Skill.Name != tt.firstMatch {
|
|
t.Errorf("expected first match %q, got %q", tt.firstMatch, matches[0].Skill.Name)
|
|
}
|
|
}
|
|
|
|
// Verify confidence is in valid range
|
|
for _, match := range matches {
|
|
if match.Confidence < 0 || match.Confidence > 1 {
|
|
t.Errorf("invalid confidence: %f (must be 0-1)", match.Confidence)
|
|
}
|
|
}
|
|
|
|
// Verify matches are sorted by confidence descending
|
|
for i := 1; i < len(matches); i++ {
|
|
if matches[i].Confidence > matches[i-1].Confidence {
|
|
t.Errorf("matches not sorted: %f > %f", matches[i].Confidence, matches[i-1].Confidence)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFilterByCategory(t *testing.T) {
|
|
skills := []SkillMeta{
|
|
{Name: "deploy-service", Category: "devops"},
|
|
{Name: "log-analyzer", Category: "analysis"},
|
|
{Name: "health-check", Category: "system"},
|
|
{Name: "daily-report", Category: "communication"},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
categories []string
|
|
expectLen int
|
|
}{
|
|
{
|
|
name: "no filter (all skills)",
|
|
categories: nil,
|
|
expectLen: 4,
|
|
},
|
|
{
|
|
name: "single category",
|
|
categories: []string{"devops"},
|
|
expectLen: 1,
|
|
},
|
|
{
|
|
name: "multiple categories",
|
|
categories: []string{"devops", "system"},
|
|
expectLen: 2,
|
|
},
|
|
{
|
|
name: "nonexistent category",
|
|
categories: []string{"nonexistent"},
|
|
expectLen: 0,
|
|
},
|
|
{
|
|
name: "case insensitive",
|
|
categories: []string{"DEVOPS"},
|
|
expectLen: 1,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
filtered := FilterByCategory(skills, tt.categories)
|
|
|
|
if len(filtered) != tt.expectLen {
|
|
t.Errorf("expected %d skills, got %d", tt.expectLen, len(filtered))
|
|
}
|
|
})
|
|
}
|
|
}
|