mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
0425cd4d77
* 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
219 lines
6.9 KiB
Go
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)
|
|
}
|