Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2710e094b9 | |||
| 3c708b6aaa |
@@ -1,287 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// setupTestPlugins creates a temporary plugins directory with named
|
||||
// subdirectories, each optionally containing a plugin.yaml manifest.
|
||||
func setupTestPlugins(t *testing.T, plugins map[string]string /* name → yamlContents */) string {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
for name, yaml := range plugins {
|
||||
plugDir := filepath.Join(dir, name)
|
||||
if err := os.MkdirAll(plugDir, 0755); err != nil {
|
||||
t.Fatalf("setupTestPlugins: mkdir %s: %v", plugDir, err)
|
||||
}
|
||||
if yaml != "" {
|
||||
if err := os.WriteFile(filepath.Join(plugDir, "plugin.yaml"), []byte(yaml), 0644); err != nil {
|
||||
t.Fatalf("setupTestPlugins: write plugin.yaml for %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// makeTestHandler creates a PluginsHandler wired with a stub runtimeLookup
|
||||
// that always returns "claude-code".
|
||||
func makeTestHandler(t *testing.T, pluginsDir string) *PluginsHandler {
|
||||
t.Helper()
|
||||
h := NewPluginsHandler(pluginsDir, nil, nil)
|
||||
h.WithRuntimeLookup(func(workspaceID string) (string, error) {
|
||||
return "claude-code", nil
|
||||
})
|
||||
return h
|
||||
}
|
||||
|
||||
// listRegistry fires ListRegistry and returns the parsed []pluginInfo.
|
||||
func listRegistry(h *PluginsHandler, runtime string) []pluginInfo {
|
||||
gin.SetMode(gin.TestMode)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = httptest.NewRequest("GET", "/plugins", nil)
|
||||
if runtime != "" {
|
||||
c.Request.URL.RawQuery = "runtime=" + runtime
|
||||
}
|
||||
h.ListRegistry(c)
|
||||
var out []pluginInfo
|
||||
json.Unmarshal(w.Body.Bytes(), &out)
|
||||
return out
|
||||
}
|
||||
|
||||
// listAvailable fires ListAvailableForWorkspace and returns the parsed []pluginInfo.
|
||||
func listAvailable(h *PluginsHandler, workspaceID string) []pluginInfo {
|
||||
gin.SetMode(gin.TestMode)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = httptest.NewRequest("GET", "/workspaces/"+workspaceID+"/plugins/available", nil)
|
||||
c.Params = []gin.Param{{Key: "id", Value: workspaceID}}
|
||||
h.ListAvailableForWorkspace(c)
|
||||
var out []pluginInfo
|
||||
json.Unmarshal(w.Body.Bytes(), &out)
|
||||
return out
|
||||
}
|
||||
|
||||
// --- ListRegistry ---
|
||||
|
||||
func TestListRegistry_ReturnsAllPlugins(t *testing.T) {
|
||||
pluginsDir := setupTestPlugins(t, map[string]string{
|
||||
"plugin-a": `name: plugin-a`,
|
||||
"plugin-b": `name: plugin-b`,
|
||||
})
|
||||
h := makeTestHandler(t, pluginsDir)
|
||||
plugins := listRegistry(h, "")
|
||||
if len(plugins) != 2 {
|
||||
t.Fatalf("expected 2 plugins, got %d", len(plugins))
|
||||
}
|
||||
names := make(map[string]bool)
|
||||
for _, p := range plugins {
|
||||
names[p.Name] = true
|
||||
}
|
||||
if !names["plugin-a"] || !names["plugin-b"] {
|
||||
t.Errorf("unexpected plugin names: %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListRegistry_EmptyDirectory(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
h := makeTestHandler(t, dir)
|
||||
plugins := listRegistry(h, "")
|
||||
if len(plugins) != 0 {
|
||||
t.Fatalf("expected 0 plugins for empty dir, got %d", len(plugins))
|
||||
}
|
||||
}
|
||||
|
||||
func TestListRegistry_FiltersByRuntime(t *testing.T) {
|
||||
// plugin-a supports claude-code; plugin-b supports langgraph only.
|
||||
pluginsDir := setupTestPlugins(t, map[string]string{
|
||||
"plugin-a": `
|
||||
name: plugin-a
|
||||
runtimes:
|
||||
- claude_code
|
||||
`,
|
||||
"plugin-b": `
|
||||
name: plugin-b
|
||||
runtimes:
|
||||
- langgraph
|
||||
`,
|
||||
})
|
||||
h := makeTestHandler(t, pluginsDir)
|
||||
|
||||
// Filter by claude-code — only plugin-a matches.
|
||||
plugins := listRegistry(h, "claude-code")
|
||||
if len(plugins) != 1 || plugins[0].Name != "plugin-a" {
|
||||
t.Errorf("expected [plugin-a], got %v", plugins)
|
||||
}
|
||||
|
||||
// Filter by langgraph — only plugin-b matches.
|
||||
plugins = listRegistry(h, "langgraph")
|
||||
if len(plugins) != 1 || plugins[0].Name != "plugin-b" {
|
||||
t.Errorf("expected [plugin-b], got %v", plugins)
|
||||
}
|
||||
|
||||
// Filter by unknown runtime — plugins that declare specific runtimes
|
||||
// are excluded; only unspecified (empty Runtimes) would appear.
|
||||
plugins = listRegistry(h, "autogen")
|
||||
if len(plugins) != 0 {
|
||||
t.Errorf("expected 0 for unknown runtime, got %v", plugins)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListRegistry_UnspecifiedRuntimesIncludedForAll(t *testing.T) {
|
||||
// A plugin with no runtimes field is treated as "unspecified" — included
|
||||
// for every runtime filter.
|
||||
pluginsDir := setupTestPlugins(t, map[string]string{
|
||||
"generic-plugin": `name: generic-plugin`,
|
||||
"claude-plugin": `
|
||||
name: claude-plugin
|
||||
runtimes:
|
||||
- claude_code
|
||||
`,
|
||||
})
|
||||
h := makeTestHandler(t, pluginsDir)
|
||||
plugins := listRegistry(h, "claude-code")
|
||||
names := make(map[string]bool)
|
||||
for _, p := range plugins {
|
||||
names[p.Name] = true
|
||||
}
|
||||
// Both should appear: generic-plugin (unspecified) + claude-plugin.
|
||||
if !names["generic-plugin"] || !names["claude-plugin"] {
|
||||
t.Errorf("expected both plugins, got %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListRegistry_ManifestFieldsParsed(t *testing.T) {
|
||||
// Verify that ListRegistry returns the full pluginInfo shape from plugin.yaml.
|
||||
pluginsDir := setupTestPlugins(t, map[string]string{
|
||||
"my-plugin": `
|
||||
name: my-plugin
|
||||
version: 1.2.3
|
||||
description: A test plugin
|
||||
author: Test Author
|
||||
tags:
|
||||
- testing
|
||||
- demo
|
||||
skills:
|
||||
- tdd-loop
|
||||
- code-review
|
||||
runtimes:
|
||||
- claude_code
|
||||
`,
|
||||
})
|
||||
h := makeTestHandler(t, pluginsDir)
|
||||
plugins := listRegistry(h, "")
|
||||
if len(plugins) != 1 {
|
||||
t.Fatalf("expected 1 plugin, got %d", len(plugins))
|
||||
}
|
||||
p := plugins[0]
|
||||
if p.Name != "my-plugin" {
|
||||
t.Errorf("Name = %q; want my-plugin", p.Name)
|
||||
}
|
||||
if p.Version != "1.2.3" {
|
||||
t.Errorf("Version = %q; want 1.2.3", p.Version)
|
||||
}
|
||||
if p.Description != "A test plugin" {
|
||||
t.Errorf("Description = %q; want 'A test plugin'", p.Description)
|
||||
}
|
||||
if p.Author != "Test Author" {
|
||||
t.Errorf("Author = %q; want 'Test Author'", p.Author)
|
||||
}
|
||||
if len(p.Tags) != 2 {
|
||||
t.Errorf("Tags = %v; want 2 entries", p.Tags)
|
||||
}
|
||||
if len(p.Skills) != 2 {
|
||||
t.Errorf("Skills = %v; want 2 entries", p.Skills)
|
||||
}
|
||||
if len(p.Runtimes) != 1 {
|
||||
t.Errorf("Runtimes = %v; want 1 entry", p.Runtimes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListRegistry_NoManifestFile(t *testing.T) {
|
||||
// A plugin directory without plugin.yaml should still appear with its
|
||||
// directory name as the Name.
|
||||
pluginsDir := setupTestPlugins(t, map[string]string{
|
||||
"no-manifest-plugin": ``, // directory with no plugin.yaml
|
||||
})
|
||||
h := makeTestHandler(t, pluginsDir)
|
||||
plugins := listRegistry(h, "")
|
||||
if len(plugins) != 1 {
|
||||
t.Fatalf("expected 1 plugin, got %d", len(plugins))
|
||||
}
|
||||
if plugins[0].Name != "no-manifest-plugin" {
|
||||
t.Errorf("expected Name to be directory name, got %q", plugins[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
// --- ListAvailableForWorkspace ---
|
||||
|
||||
func TestListAvailableForWorkspace_WithRuntimeLookup(t *testing.T) {
|
||||
// When runtimeLookup is wired, ListAvailableForWorkspace filters by the
|
||||
// workspace's runtime (claude-code), which is set by makeTestHandler.
|
||||
pluginsDir := setupTestPlugins(t, map[string]string{
|
||||
"runtime-plugin": `
|
||||
name: runtime-plugin
|
||||
runtimes:
|
||||
- claude_code
|
||||
`,
|
||||
"langgraph-plugin": `
|
||||
name: langgraph-plugin
|
||||
runtimes:
|
||||
- langgraph
|
||||
`,
|
||||
})
|
||||
h := makeTestHandler(t, pluginsDir)
|
||||
plugins := listAvailable(h, "ws-123")
|
||||
if len(plugins) != 1 || plugins[0].Name != "runtime-plugin" {
|
||||
t.Errorf("expected [runtime-plugin], got %v", plugins)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAvailableForWorkspace_UnspecifiedRuntime(t *testing.T) {
|
||||
// A plugin without runtimes is included for all workspaces.
|
||||
pluginsDir := setupTestPlugins(t, map[string]string{
|
||||
"generic": `name: generic`,
|
||||
})
|
||||
h := makeTestHandler(t, pluginsDir)
|
||||
plugins := listAvailable(h, "ws-456")
|
||||
if len(plugins) != 1 || plugins[0].Name != "generic" {
|
||||
t.Errorf("expected [generic], got %v", plugins)
|
||||
}
|
||||
}
|
||||
|
||||
// --- HTTP response codes ---
|
||||
|
||||
func TestListRegistry_HTTPStatusOK(t *testing.T) {
|
||||
pluginsDir := setupTestPlugins(t, map[string]string{"foo": "name: foo"})
|
||||
h := NewPluginsHandler(pluginsDir, nil, nil)
|
||||
gin.SetMode(gin.TestMode)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = httptest.NewRequest("GET", "/plugins", nil)
|
||||
h.ListRegistry(c)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAvailableForWorkspace_HTTPStatusOK(t *testing.T) {
|
||||
pluginsDir := setupTestPlugins(t, map[string]string{"bar": "name: bar"})
|
||||
h := makeTestHandler(t, pluginsDir)
|
||||
gin.SetMode(gin.TestMode)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = httptest.NewRequest("GET", "/workspaces/ws-789/plugins/available", nil)
|
||||
c.Params = []gin.Param{{Key: "id", Value: "ws-789"}}
|
||||
h.ListAvailableForWorkspace(c)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user