1package skills
2
3import (
4 "os"
5 "path/filepath"
6 "testing"
7
8 "github.com/stretchr/testify/require"
9)
10
11func TestApproxTokenCount(t *testing.T) {
12 t.Parallel()
13
14 require.Equal(t, 0, ApproxTokenCount(""))
15 require.Equal(t, 1, ApproxTokenCount("a"))
16 require.Equal(t, 1, ApproxTokenCount("abcd"))
17 require.Equal(t, 2, ApproxTokenCount("abcde"))
18 // 12 chars → 3 tokens.
19 require.Equal(t, 3, ApproxTokenCount("abcdefghijkl"))
20}
21
22func TestTracker_LoadedNamesAndCount(t *testing.T) {
23 t.Parallel()
24
25 active := []*Skill{{Name: "b"}, {Name: "a"}, {Name: "c"}}
26 tr := NewTracker(active)
27 require.Equal(t, 0, tr.LoadedCount())
28 require.Empty(t, tr.LoadedNames())
29
30 tr.MarkLoaded("b")
31 tr.MarkLoaded("a")
32 require.Equal(t, 2, tr.LoadedCount())
33 require.Equal(t, []string{"a", "b"}, tr.LoadedNames())
34
35 // Nil safety.
36 var nilTr *Tracker
37 require.Equal(t, 0, nilTr.LoadedCount())
38 require.Nil(t, nilTr.LoadedNames())
39}
40
41func TestDiscoverBuiltinWithStates(t *testing.T) {
42 t.Parallel()
43
44 skills, states := DiscoverBuiltinWithStates()
45 require.NotEmpty(t, skills)
46 require.NotEmpty(t, states)
47
48 // Every returned skill should have a corresponding StateNormal entry.
49 ok := 0
50 for _, s := range states {
51 if s.State == StateNormal {
52 ok++
53 }
54 }
55 require.Equal(t, len(skills), ok)
56}
57
58func TestDiscoverWithStates_MissingPath(t *testing.T) {
59 t.Parallel()
60
61 // A clearly nonexistent path should not panic; it may log an error.
62 skills, _ := DiscoverWithStates([]string{"/nonexistent/crush/skills/path"})
63 require.Empty(t, skills)
64}
65
66func TestGetLatestStates(t *testing.T) {
67 // Not parallel - manipulates package-level cache.
68 prev := GetLatestStates()
69 t.Cleanup(func() { SetLatestStates(prev) })
70
71 SetLatestStates(nil)
72 require.Nil(t, GetLatestStates())
73
74 dir := t.TempDir()
75 skillDir := filepath.Join(dir, "my-skill")
76 require.NoError(t, os.MkdirAll(skillDir, 0o755))
77 require.NoError(t, os.WriteFile(
78 filepath.Join(skillDir, SkillFileName),
79 []byte("---\nname: my-skill\ndescription: A test skill.\n---\nInstructions.\n"),
80 0o644,
81 ))
82
83 _, states := DiscoverWithStates([]string{dir})
84 SetLatestStates(states)
85
86 got := GetLatestStates()
87 require.Len(t, got, 1)
88 require.Equal(t, "my-skill", got[0].Name)
89}
90
91func TestGetLatestStates_Isolation(t *testing.T) {
92 // Not parallel - manipulates package-level cache.
93 prev := GetLatestStates()
94 t.Cleanup(func() { SetLatestStates(prev) })
95
96 initial := []*SkillState{{Name: "test"}}
97 SetLatestStates(initial)
98
99 got := GetLatestStates()
100 got[0].Name = "corrupted"
101
102 check := GetLatestStates()
103 require.Equal(t, "test", check[0].Name, "Cache should be isolated from caller mutations")
104}