Files
wehub-resource-sync ead81af521
CI / go (push) Waiting to run
CI / build (darwin, macos-14) (push) Waiting to run
CI / build (windows, windows-2025) (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Failing after 0s
chore: import upstream snapshot with attribution
2026-07-13 12:31:13 +08:00

329 lines
8.7 KiB
Go

package pluginmgr
import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// redirectTransport rewrites every request's Host to point at target.
type redirectTransport struct {
target *url.URL
rt http.RoundTripper
}
func (t redirectTransport) RoundTrip(req *http.Request) (*http.Response, error) {
clone := req.Clone(req.Context())
clone.URL.Scheme = t.target.Scheme
clone.URL.Host = t.target.Host
clone.Host = t.target.Host
return t.rt.RoundTrip(clone)
}
func installTestClient(t *testing.T, serverURL string) {
t.Helper()
u, err := url.Parse(serverURL)
if err != nil {
t.Fatalf("parse: %v", err)
}
old := httpClient
httpClient = &http.Client{
Timeout: 5 * time.Second,
Transport: redirectTransport{target: u, rt: http.DefaultTransport},
}
t.Cleanup(func() { httpClient = old })
}
func withTempHome(t *testing.T) string {
t.Helper()
home := t.TempDir()
t.Setenv("HOME", home)
return home
}
func TestScanPluginsEmptyDir(t *testing.T) {
dir := t.TempDir()
plugins, err := scanPlugins(dir)
if err != nil {
t.Fatalf("scanPlugins: %v", err)
}
if len(plugins) != 0 {
t.Errorf("expected 0 plugins in empty dir, got %d", len(plugins))
}
}
func TestScanPluginsSingleFile(t *testing.T) {
dir := t.TempDir()
src := `plugin.register({
name = "hello",
version = "1.2",
description = "says hi",
type = "visualizer",
})
`
if err := os.WriteFile(filepath.Join(dir, "hello.lua"), []byte(src), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
plugins, err := scanPlugins(dir)
if err != nil {
t.Fatalf("scanPlugins: %v", err)
}
if len(plugins) != 1 {
t.Fatalf("got %d plugins, want 1", len(plugins))
}
got := plugins[0]
if got.name != "hello" {
t.Errorf("name = %q, want hello", got.name)
}
if got.version != "1.2" {
t.Errorf("version = %q, want 1.2", got.version)
}
if got.description != "says hi" {
t.Errorf("description = %q, want 'says hi'", got.description)
}
if got.typ != "visualizer" {
t.Errorf("typ = %q, want visualizer", got.typ)
}
if got.file != "hello.lua" {
t.Errorf("file = %q, want hello.lua", got.file)
}
}
func TestScanPluginsFallsBackToFilename(t *testing.T) {
dir := t.TempDir()
// Plugin without register() call — name should default to filename.
if err := os.WriteFile(filepath.Join(dir, "nameless.lua"), []byte(`-- nothing`), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
plugins, err := scanPlugins(dir)
if err != nil {
t.Fatalf("scanPlugins: %v", err)
}
if len(plugins) != 1 {
t.Fatalf("got %d plugins, want 1", len(plugins))
}
if plugins[0].name != "nameless" {
t.Errorf("name = %q, want 'nameless' (filename fallback)", plugins[0].name)
}
}
func TestScanPluginsDirectoryEntry(t *testing.T) {
dir := t.TempDir()
// Create a directory plugin: <dir>/myplug/init.lua
sub := filepath.Join(dir, "myplug")
if err := os.MkdirAll(sub, 0o755); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
if err := os.WriteFile(filepath.Join(sub, "init.lua"), []byte(`plugin.register({ name = "myplug", version = "0.1" })`), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
plugins, err := scanPlugins(dir)
if err != nil {
t.Fatalf("scanPlugins: %v", err)
}
if len(plugins) != 1 {
t.Fatalf("got %d plugins, want 1", len(plugins))
}
if plugins[0].file != "myplug/" {
t.Errorf("file = %q, want 'myplug/'", plugins[0].file)
}
}
func TestScanPluginsIgnoresNonLua(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "notes.txt"), []byte("nothing"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
plugins, err := scanPlugins(dir)
if err != nil {
t.Fatalf("scanPlugins: %v", err)
}
if len(plugins) != 0 {
t.Errorf("non-lua file should be ignored, got %+v", plugins)
}
}
func TestListNoPlugins(t *testing.T) {
withTempHome(t)
if err := List(); err != nil {
t.Errorf("List on empty dir should not error, got %v", err)
}
}
func TestListPluginsShowsInstalled(t *testing.T) {
home := withTempHome(t)
plugDir := filepath.Join(home, ".config", "cliamp", "plugins")
if err := os.MkdirAll(plugDir, 0o755); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
if err := os.WriteFile(filepath.Join(plugDir, "x.lua"), []byte(`plugin.register({ name = "x", version = "1.0" })`), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
// List writes to stdout — we just verify it doesn't error.
if err := List(); err != nil {
t.Errorf("List: %v", err)
}
}
func TestRemoveFile(t *testing.T) {
home := withTempHome(t)
plugDir := filepath.Join(home, ".config", "cliamp", "plugins")
if err := os.MkdirAll(plugDir, 0o755); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
path := filepath.Join(plugDir, "foo.lua")
if err := os.WriteFile(path, []byte("x"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if err := Remove("foo"); err != nil {
t.Fatalf("Remove: %v", err)
}
if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Errorf("plugin should be removed, stat err=%v", err)
}
}
func TestRemoveDirectory(t *testing.T) {
home := withTempHome(t)
plugDir := filepath.Join(home, ".config", "cliamp", "plugins")
nested := filepath.Join(plugDir, "bar", "init.lua")
if err := os.MkdirAll(filepath.Dir(nested), 0o755); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
if err := os.WriteFile(nested, []byte("x"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if err := Remove("bar"); err != nil {
t.Fatalf("Remove: %v", err)
}
if _, err := os.Stat(filepath.Dir(nested)); !os.IsNotExist(err) {
t.Errorf("plugin dir should be removed, stat err=%v", err)
}
}
func TestRemoveMissing(t *testing.T) {
withTempHome(t)
err := Remove("ghost")
if err == nil {
t.Error("Remove non-existent plugin should error")
}
if !strings.Contains(err.Error(), "not found") {
t.Errorf("error = %q, want to mention 'not found'", err.Error())
}
}
func TestInstallFromRawURL(t *testing.T) {
withTempHome(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !strings.HasSuffix(r.URL.Path, "/example.lua") {
t.Errorf("unexpected path %q", r.URL.Path)
}
_, _ = w.Write([]byte(`plugin.register({ name = "example", version = "1" })`))
}))
defer srv.Close()
installTestClient(t, srv.URL)
if err := Install(srv.URL+"/example.lua", true); err != nil {
t.Fatalf("Install: %v", err)
}
// Verify the installed file exists.
home, _ := os.UserHomeDir()
dest := filepath.Join(home, ".config", "cliamp", "plugins", "example.lua")
if _, err := os.Stat(dest); err != nil {
t.Errorf("installed plugin missing: %v", err)
}
}
func TestInstallAlreadyExists(t *testing.T) {
home := withTempHome(t)
plugDir := filepath.Join(home, ".config", "cliamp", "plugins")
if err := os.MkdirAll(plugDir, 0o755); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
existing := filepath.Join(plugDir, "mypl.lua")
if err := os.WriteFile(existing, []byte("x"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`-- ok`))
}))
defer srv.Close()
installTestClient(t, srv.URL)
err := Install(srv.URL+"/mypl.lua", true)
if err == nil {
t.Fatal("Install over existing plugin should error")
}
if !strings.Contains(err.Error(), "already exists") {
t.Errorf("error = %q, want to mention 'already exists'", err.Error())
}
}
func TestInstallAllURLsFail(t *testing.T) {
withTempHome(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "not found", http.StatusNotFound)
}))
defer srv.Close()
installTestClient(t, srv.URL)
err := Install(srv.URL+"/nonexistent.lua", true)
if err == nil {
t.Error("Install with all failing URLs should error")
}
}
func TestInstallTooLarge(t *testing.T) {
withTempHome(t)
big := strings.Repeat("x", maxPluginSize+1)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(big))
}))
defer srv.Close()
installTestClient(t, srv.URL)
err := Install(srv.URL+"/huge.lua", true)
if err == nil {
t.Error("Install of oversized plugin should fail")
}
}
func TestDownloadErrors(t *testing.T) {
tests := []struct {
name string
handler http.HandlerFunc
wantErr string
}{
{"404", func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "missing", http.StatusNotFound)
}, "HTTP"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srv := httptest.NewServer(tt.handler)
defer srv.Close()
installTestClient(t, srv.URL)
_, err := download(srv.URL + "/x.lua")
if err == nil {
t.Fatal("download should have errored")
}
if !strings.Contains(err.Error(), tt.wantErr) {
t.Errorf("error = %q, want to contain %q", err.Error(), tt.wantErr)
}
})
}
}