Files
picoclaw/pkg/skills/github_registry_test.go
T
lxowalle 0425cd4d77 refactor skills registries and add GitHub-backed skill discovery (#2442)
* refactor skills registries and add GitHub-backed skill discovery

* fix ci

* fix command error

* fix default skills install registry behavior

* fix github registry URL parsing and versioned skill links

* fix skills registry config compatibility and URL installs

* * fix lint

* fix deprecated github base url compatibility

* fix skills registry yaml and github default branch handling

* fix github skills registry fallback and install metadata

* fix cli skills install origin metadata

* fix clawhub registry env compatibility

* fix skills registry config merge compatibility

* fix skill install metadata consistency and onboard template copy

* fix yaml overrides for default skills registries

* fix install_skill registry metadata normalization

* fix github skill URL parsing for slash branch names

* fix skills registry install/search validation and github URLs

* fix github skill URL host validation

* fix install_skill validation for invalid registry archives

* fix redundant skills registry names in saved config

* fix github blob skill URL installs and metadata links

* fix github registry URL scheme validation

* fix v0 skills migration preserving github registry defaults

* fix github blob skill install directory resolution

* fix install_skill rollback on origin metadata write failure

* fix github skill URL validation and registry JSON merging

* fix github registry target resolution and metadata links

* fix install_skill force reinstall rollback

* fix skills config compatibility and legacy security overlays

* fix ci
2026-04-14 15:14:16 +08:00

219 lines
6.9 KiB
Go

package skills
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/sipeed/picoclaw/pkg/config"
)
func TestGitHubRegistrySearch(t *testing.T) {
var server *httptest.Server
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/api/v3/search/code", r.URL.Path)
assert.Equal(t, "Bearer test-token", r.Header.Get("Authorization"))
assert.Equal(t, "skill search filename:SKILL.md", r.URL.Query().Get("q"))
assert.Equal(t, "2", r.URL.Query().Get("per_page"))
w.Header().Set("Content-Type", "application/json")
require.NoError(t, json.NewEncoder(w).Encode(gitHubCodeSearchResponse{
Items: []gitHubCodeSearchItem{
{
Path: "skills/pr-review/SKILL.md",
Score: 10,
HTMLURL: server.URL + "/foo/bar/blob/main/skills/pr-review/SKILL.md",
Repository: struct {
FullName string `json:"full_name"`
Name string `json:"name"`
Description string `json:"description"`
DefaultBranch string `json:"default_branch"`
}{
FullName: "foo/bar",
Name: "bar",
Description: "Review pull requests",
DefaultBranch: "main",
},
},
{
Path: "SKILL.md",
Score: 5,
HTMLURL: server.URL + "/foo/root/blob/main/SKILL.md",
Repository: struct {
FullName string `json:"full_name"`
Name string `json:"name"`
Description string `json:"description"`
DefaultBranch string `json:"default_branch"`
}{
FullName: "foo/root",
Name: "root",
Description: "Root skill",
DefaultBranch: "master",
},
},
},
}))
}))
defer server.Close()
provider := GitHubRegistryConfig{
Enabled: true,
BaseURL: server.URL,
AuthToken: "test-token",
}
registry := provider.BuildRegistry()
require.NotNil(t, registry)
results, err := registry.Search(context.Background(), "skill search", 2)
require.NoError(t, err)
require.Len(t, results, 2)
assert.Equal(t, "foo/bar/skills/pr-review", results[0].Slug)
assert.Equal(t, "pr-review", results[0].DisplayName)
assert.Equal(t, "Review pull requests", results[0].Summary)
assert.Equal(t, "main", results[0].Version)
assert.Equal(t, "github", results[0].RegistryName)
assert.Equal(t, "foo/root", results[1].Slug)
assert.Equal(t, "root", results[1].DisplayName)
assert.Equal(t, "master", results[1].Version)
}
func TestGitHubRegistryProviderDecodesProxyParam(t *testing.T) {
builder := buildRegistryProvider("github", config.SkillRegistryConfig{
Name: "github",
Enabled: true,
BaseURL: "https://github.com",
AuthToken: *config.NewSecureString("test-token"),
Param: map[string]any{
"proxy": "http://127.0.0.1:7890",
},
})
require.NotNil(t, builder)
registry := builder.BuildRegistry()
require.NotNil(t, registry)
ghRegistry, ok := registry.(*GitHubRegistry)
require.True(t, ok)
assert.Equal(t, "http://127.0.0.1:7890", ghRegistry.installer.proxy)
}
func TestGitHubRegistrySearchReturnsNoResultsOnUnauthenticatedRateLimit(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Empty(t, r.Header.Get("Authorization"))
w.WriteHeader(http.StatusForbidden)
_, _ = w.Write([]byte(`{"message":"API rate limit exceeded for 1.2.3.4"}`))
}))
defer server.Close()
registry := GitHubRegistryConfig{Enabled: true, BaseURL: server.URL}.BuildRegistry()
require.NotNil(t, registry)
results, err := registry.Search(context.Background(), "pr review", 5)
require.NoError(t, err)
assert.Empty(t, results)
}
func TestGitHubRegistrySearchReturnsNoResultsOnUnauthenticatedAuthRequired(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Empty(t, r.Header.Get("Authorization"))
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(
`{"message":"Requires authentication","errors":[{"message":"Must be authenticated to access the code search API"}]}`,
))
}))
defer server.Close()
registry := GitHubRegistryConfig{Enabled: true, BaseURL: server.URL}.BuildRegistry()
require.NotNil(t, registry)
results, err := registry.Search(context.Background(), "pr review", 5)
require.NoError(t, err)
assert.Empty(t, results)
}
func TestGitHubRegistryGetSkillMetaCanonicalizesURLSlug(t *testing.T) {
registry := GitHubRegistryConfig{
Enabled: true,
BaseURL: "https://ghe.example.com/git",
}.BuildRegistry()
require.NotNil(t, registry)
meta, err := registry.GetSkillMeta(
context.Background(),
"https://ghe.example.com/git/org/repo/tree/dev/skills/pr-review",
)
require.NoError(t, err)
require.NotNil(t, meta)
assert.Equal(t, "org/repo/skills/pr-review", meta.Slug)
assert.Equal(t, "dev", meta.LatestVersion)
}
func TestGitHubRegistrySkillURLUsesProvidedVersionAndBasePath(t *testing.T) {
registry := GitHubRegistryConfig{
Enabled: true,
BaseURL: "https://ghe.example.com/git",
}.BuildRegistry()
require.NotNil(t, registry)
assert.Equal(
t,
"https://ghe.example.com/git/org/repo/tree/master/skills/pr-review",
registry.SkillURL("org/repo/skills/pr-review", "master"),
)
assert.Equal(
t,
"https://ghe.example.com/git/org/repo/tree/dev/skills/pr-review",
registry.SkillURL("https://ghe.example.com/git/org/repo/tree/dev/skills/pr-review", ""),
)
assert.Equal(
t,
"https://ghe.example.com/git/org/repo/tree/feature/skills-registry/skills/pr-review",
registry.SkillURL("org/repo/skills/pr-review", "feature/skills-registry"),
)
assert.Equal(
t,
"https://ghe.example.com/git/org/repo/blob/main/.agents/skills/pr-review/SKILL.md",
registry.SkillURL("https://ghe.example.com/git/org/repo/blob/main/.agents/skills/pr-review/SKILL.md", ""),
)
assert.Equal(
t,
"https://github.com/org/repo/tree/main/.agents/skills/pr-review",
registry.SkillURL("https://github.com/org/repo/tree/main/.agents/skills/pr-review", ""),
)
assert.Empty(t, registry.SkillURL("org/repo/.agents/skills/pr-review", ""))
}
func TestGitHubRegistryResolveInstallDirNameSupportsFullURLs(t *testing.T) {
registry := GitHubRegistryConfig{
Enabled: true,
BaseURL: "https://ghe.example.com/git",
}.BuildRegistry()
require.NotNil(t, registry)
dirName, err := registry.ResolveInstallDirName("https://ghe.example.com/git/org/repo/tree/dev/skills/pr-review")
require.NoError(t, err)
assert.Equal(t, "pr-review", dirName)
dirName, err = registry.ResolveInstallDirName("https://github.com/org/repo/tree/main/skills/release-checklist")
require.NoError(t, err)
assert.Equal(t, "release-checklist", dirName)
dirName, err = registry.ResolveInstallDirName(
"https://ghe.example.com/git/org/repo/blob/dev/skills/pr-review/SKILL.md",
)
require.NoError(t, err)
assert.Equal(t, "pr-review", dirName)
dirName, err = registry.ResolveInstallDirName(
"https://ghe.example.com/git/org/repo/blob/dev/SKILL.md",
)
require.NoError(t, err)
assert.Equal(t, "repo", dirName)
}