814 lines · 29.6 KB · Go
RawDownload
package homeplugins import (	"archive/zip"	"bytes"	"context"	"crypto/sha256"	"encoding/hex"	"errors"	"io"	"net/http"	"net/http/httptest"	"os"	"path/filepath"	"runtime"	"strings"	"testing"	"time" 	"github.com/router-for-me/CLIProxyAPI/v7/internal/config"	sdkpluginstore "github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginstore"	"gopkg.in/yaml.v3") type fakePluginRuntime struct {	busy     bool	unloaded []string} type fakePluginLoadInspector map[string]bool func (r *fakePluginRuntime) PluginBusy(id string) bool {	return r.busy} func (r *fakePluginRuntime) UnloadPlugin(id string) bool {	r.unloaded = append(r.unloaded, id)	r.busy = false	return true} func (i fakePluginLoadInspector) PluginRegistered(id string) bool {	return i[id]} type contextPluginRuntime struct {	fakePluginRuntime	unloadContext context.Context} func (r *contextPluginRuntime) UnloadPluginContext(ctx context.Context, id string) bool {	r.unloadContext = ctx	return r.UnloadPlugin(id)} func TestSyncPlatformInstallsManifestArtifact(t *testing.T) {	root := t.TempDir()	archiveData := makeZip(t, map[string]string{"sample.dll": "library-data"})	archiveName := "sample_0.2.0_windows_amd64.zip"	checksum := sha256.Sum256(archiveData)	httpClient := mapHTTPDoer{		"https://api.github.com/repos/owner/sample-plugin/releases/tags/v0.2.0": []byte(`{			"tag_name": "v0.2.0",			"assets": [				{"name": "` + archiveName + `", "browser_download_url": "https://downloads.example/` + archiveName + `"},				{"name": "checksums.txt", "browser_download_url": "https://downloads.example/checksums.txt"}			]		}`),		"https://downloads.example/" + archiveName: archiveData,		"https://downloads.example/checksums.txt":  []byte(hex.EncodeToString(checksum[:]) + "  " + archiveName + "\n"),	}	restore := replacePluginStoreClientForTest(httpClient)	defer restore() 	if errSync := SyncPlatform(context.Background(), syncTestConfig(t, root), nil, Platform{GOOS: "windows", GOARCH: "amd64"}); errSync != nil {		t.Fatalf("SyncPlatform() error = %v", errSync)	}	target := pluginTestPath(root, "windows", "amd64", "sample", "0.2.0")	got, errRead := os.ReadFile(target)	if errRead != nil {		t.Fatalf("read target: %v", errRead)	}	if string(got) != "library-data" {		t.Fatalf("target data = %q, want library-data", string(got))	}} func TestSyncResolvedWithReportUsesTemporaryAuthAndClearsIt(t *testing.T) {	root := t.TempDir()	libraryName := "sample" + pluginExtension(runtime.GOOS)	archiveData := makeZip(t, map[string]string{libraryName: "library-data"})	checksum := sha256.Sum256(archiveData)	var authenticated bool	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {		if r.Header.Get("Authorization") != "Bearer temporary-token" {			http.Error(w, "unauthorized", http.StatusUnauthorized)			return		}		authenticated = true		_, _ = w.Write(archiveData)	}))	t.Cleanup(server.Close)	response, errUnauthenticated := server.Client().Get(server.URL + "/private/sample.zip")	if errUnauthenticated != nil {		t.Fatalf("unauthenticated GET error = %v", errUnauthenticated)	}	_ = response.Body.Close()	if response.StatusCode != http.StatusUnauthorized {		t.Fatalf("unauthenticated status = %d, want 401", response.StatusCode)	} 	originalClient := newResolvedPluginStoreClient	newResolvedPluginStoreClient = func(_ *config.Config, auth []sdkpluginstore.ResolvedAuthConfig, expiresAt time.Time) sdkpluginstore.Client {		return sdkpluginstore.NewClientWithResolvedAuthExpiry(server.Client(), "", auth, expiresAt)	}	defer func() { newResolvedPluginStoreClient = originalClient }()	token := sdkpluginstore.Secret("temporary-token")	backing := token	items := []sdkpluginstore.PluginSyncItem{{		Manifest: sdkpluginstore.Manifest{			SchemaVersion: sdkpluginstore.SchemaVersionV2,			ID:            "sample",			Version:       "1.0.0",			Install: sdkpluginstore.InstallPlan{Type: sdkpluginstore.InstallTypeDirect, Artifacts: []sdkpluginstore.Artifact{{				GOOS: runtime.GOOS, GOARCH: runtime.GOARCH, URL: server.URL + "/private/sample.zip",				SHA256: hex.EncodeToString(checksum[:]), Size: int64(len(archiveData)),			}}},		},		Auth: []sdkpluginstore.ResolvedAuthConfig{{			Match: server.URL + "/private/", ApplyTo: []string{sdkpluginstore.RequestKindArtifact}, Type: sdkpluginstore.AuthTypeBearer, Token: token,		}},	}}	enabled := true	cfg := &config.Config{		Home:    config.HomeConfig{Enabled: true},		Plugins: config.PluginsConfig{Enabled: true, Dir: root, Configs: map[string]config.PluginInstanceConfig{"sample": {Enabled: &enabled}}},	} 	report, errSync := SyncResolvedWithReport(context.Background(), cfg, items, time.Now().UTC().Add(time.Minute), map[string]string{"sample": "0.9.0"}, nil)	if errSync != nil {		t.Fatalf("SyncResolvedWithReport() error = %v", errSync)	}	if !authenticated || !report.OK || len(report.Plugins) != 1 || report.Plugins[0].Version != "1.0.0" {		t.Fatalf("authenticated=%v report=%+v, want successful authenticated install", authenticated, report)	}	for index, value := range backing {		if value != 0 {			t.Fatalf("token byte %d = %d, want zero after sync", index, value)		}	}	if items[0].Auth != nil {		t.Fatalf("sync item retained auth references: %#v", items[0].Auth)	}	target := pluginTestPath(root, runtime.GOOS, runtime.GOARCH, "sample", "1.0.0")	if got, errRead := os.ReadFile(target); errRead != nil || string(got) != "library-data" {		t.Fatalf("installed plugin = %q, error = %v", got, errRead)	}} func TestSyncResolvedWithReportIncludesUnchangedInstalledPlugins(t *testing.T) {	root := t.TempDir()	target := pluginTestPath(root, runtime.GOOS, runtime.GOARCH, "sample", "1.0.0")	if errMkdir := os.MkdirAll(filepath.Dir(target), 0o755); errMkdir != nil {		t.Fatalf("MkdirAll() error = %v", errMkdir)	}	if errWrite := os.WriteFile(target, []byte("plugin"), 0o644); errWrite != nil {		t.Fatalf("WriteFile() error = %v", errWrite)	}	cfg := &config.Config{		Home: config.HomeConfig{Enabled: true},		Plugins: config.PluginsConfig{			Enabled: true,			Dir:     root,			Configs: map[string]config.PluginInstanceConfig{				"sample": pluginConfigFromYAML(t, `enabled: truestore:  id: sample  name: Sample  description: Adds sample support.  author: owner  version: 1.0.0  release-tag: v1.0.0  repository: https://github.com/owner/sample-plugin`),			},		},	} 	report, errSync := SyncResolvedWithReport(		context.Background(),		cfg,		nil,		time.Now().UTC().Add(time.Minute),		map[string]string{"sample": "1.0.0"},		nil,	)	if errSync != nil {		t.Fatalf("SyncResolvedWithReport() error = %v", errSync)	}	if len(report.Plugins) != 1 || report.Plugins[0].ID != "sample" || report.Plugins[0].InstallStatus != pluginInstallStatusSkipped {		t.Fatalf("report plugins = %+v, want unchanged installed sample", report.Plugins)	}	status := report.Plugins[0]	if status.Path != target || status.ReleaseTag != "v1.0.0" || status.Repository != "https://github.com/owner/sample-plugin" || status.InstallType != sdkpluginstore.InstallTypeGitHubRelease {		t.Fatalf("unchanged plugin status = %+v, want preserved path and manifest metadata", status)	}	if errLoad := MarkLoadResults(&report, fakePluginLoadInspector{}); errLoad == nil {		t.Fatal("MarkLoadResults() error = nil, want installed plugin load failure")	}	if report.Plugins[0].LoadStatus != pluginLoadStatusFailed {		t.Fatalf("load status = %q, want failed", report.Plugins[0].LoadStatus)	}} func TestSyncResolvedWithReportDoesNotMixInstalledAndConfiguredMetadata(t *testing.T) {	root := t.TempDir()	target := pluginTestPath(root, runtime.GOOS, runtime.GOARCH, "sample", "1.0.0")	if errMkdir := os.MkdirAll(filepath.Dir(target), 0o755); errMkdir != nil {		t.Fatalf("MkdirAll() error = %v", errMkdir)	}	if errWrite := os.WriteFile(target, []byte("plugin"), 0o644); errWrite != nil {		t.Fatalf("WriteFile() error = %v", errWrite)	}	cfg := &config.Config{		Home: config.HomeConfig{Enabled: true},		Plugins: config.PluginsConfig{			Enabled: true,			Dir:     root,			Configs: map[string]config.PluginInstanceConfig{				"sample": pluginConfigFromYAML(t, `enabled: truestore:  id: sample  name: Sample  description: Adds sample support.  author: owner  version: 2.0.0  release-tag: v2.0.0  repository: https://github.com/owner/sample-plugin-v2`),			},		},	} 	report, errSync := SyncResolvedWithReport(		context.Background(),		cfg,		nil,		time.Now().UTC().Add(time.Minute),		map[string]string{"sample": "1.0.0"},		nil,	)	if errSync != nil {		t.Fatalf("SyncResolvedWithReport() error = %v", errSync)	}	if len(report.Plugins) != 1 {		t.Fatalf("report plugins = %+v, want one installed sample", report.Plugins)	}	status := report.Plugins[0]	if status.Version != "1.0.0" || status.Path != target {		t.Fatalf("installed plugin status = %+v, want version 1.0.0 at %s", status, target)	}	if status.ReleaseTag != "" || status.Repository != "" || status.InstallType != "" {		t.Fatalf("installed plugin status = %+v, want no metadata from configured version 2.0.0", status)	}} func TestInstalledVersionsUsesPluginFilesOnDisk(t *testing.T) {	root := t.TempDir()	target := pluginTestPath(root, runtime.GOOS, runtime.GOARCH, "sample", "2.3.4")	if errMkdir := os.MkdirAll(filepath.Dir(target), 0o755); errMkdir != nil {		t.Fatalf("MkdirAll() error = %v", errMkdir)	}	if errWrite := os.WriteFile(target, []byte("plugin"), 0o644); errWrite != nil {		t.Fatalf("WriteFile() error = %v", errWrite)	}	cfg := &config.Config{Plugins: config.PluginsConfig{Dir: root, Configs: map[string]config.PluginInstanceConfig{"sample": {}}}} 	versions, errVersions := InstalledVersions(cfg)	if errVersions != nil {		t.Fatalf("InstalledVersions() error = %v", errVersions)	}	if versions["sample"] != "2.3.4" {		t.Fatalf("InstalledVersions() = %#v, want sample 2.3.4", versions)	}} func TestSyncPlatformWithReportRecordsSuccessfulInstall(t *testing.T) {	root := t.TempDir()	archiveData := makeZip(t, map[string]string{"sample.dll": "library-data"})	archiveName := "sample_0.2.0_windows_amd64.zip"	checksum := sha256.Sum256(archiveData)	httpClient := mapHTTPDoer{		"https://api.github.com/repos/owner/sample-plugin/releases/tags/v0.2.0": []byte(`{			"tag_name": "v0.2.0",			"assets": [				{"name": "` + archiveName + `", "browser_download_url": "https://downloads.example/` + archiveName + `"},				{"name": "checksums.txt", "browser_download_url": "https://downloads.example/checksums.txt"}			]		}`),		"https://downloads.example/" + archiveName: archiveData,		"https://downloads.example/checksums.txt":  []byte(hex.EncodeToString(checksum[:]) + "  " + archiveName + "\n"),	}	restore := replacePluginStoreClientForTest(httpClient)	defer restore() 	report, errSync := SyncPlatformWithReport(context.Background(), syncTestConfig(t, root), nil, Platform{GOOS: "windows", GOARCH: "amd64"})	if errSync != nil {		t.Fatalf("SyncPlatformWithReport() error = %v", errSync)	}	if !report.OK || report.Status != pluginTaskStatusOK || report.Phase != pluginTaskPhaseInstall {		t.Fatalf("report status = %+v, want successful install phase", report)	}	if len(report.Plugins) != 1 {		t.Fatalf("report plugins len = %d, want 1", len(report.Plugins))	}	plugin := report.Plugins[0]	if plugin.ID != "sample" || plugin.InstallStatus != pluginInstallStatusInstalled || plugin.Version != "0.2.0" {		t.Fatalf("plugin report = %+v, want installed sample 0.2.0", plugin)	}	if wantPath := pluginTestPath(root, "windows", "amd64", "sample", "0.2.0"); plugin.Path != wantPath {		t.Fatalf("plugin path = %q, want %q", plugin.Path, wantPath)	}} func TestSyncPlatformWithReportRecordsSkippedIdenticalArtifact(t *testing.T) {	root := t.TempDir()	targetDir := filepath.Join(root, "windows", "amd64")	if errMkdir := os.MkdirAll(targetDir, 0o755); errMkdir != nil {		t.Fatalf("MkdirAll() error = %v", errMkdir)	}	target := filepath.Join(targetDir, "sample-v0.2.0.dll")	if errWrite := os.WriteFile(target, []byte("library-data"), 0o644); errWrite != nil {		t.Fatalf("WriteFile() error = %v", errWrite)	}	archiveData := makeZip(t, map[string]string{"sample.dll": "library-data"})	archiveName := "sample_0.2.0_windows_amd64.zip"	checksum := sha256.Sum256(archiveData)	httpClient := mapHTTPDoer{		"https://api.github.com/repos/owner/sample-plugin/releases/tags/v0.2.0": []byte(`{			"tag_name": "v0.2.0",			"assets": [				{"name": "` + archiveName + `", "browser_download_url": "https://downloads.example/` + archiveName + `"},				{"name": "checksums.txt", "browser_download_url": "https://downloads.example/checksums.txt"}			]		}`),		"https://downloads.example/" + archiveName: archiveData,		"https://downloads.example/checksums.txt":  []byte(hex.EncodeToString(checksum[:]) + "  " + archiveName + "\n"),	}	restore := replacePluginStoreClientForTest(httpClient)	defer restore() 	report, errSync := SyncPlatformWithReport(context.Background(), syncTestConfig(t, root), nil, Platform{GOOS: "windows", GOARCH: "amd64"})	if errSync != nil {		t.Fatalf("SyncPlatformWithReport() error = %v", errSync)	}	if !report.OK || len(report.Plugins) != 1 {		t.Fatalf("report = %+v, want one successful skipped plugin", report)	}	plugin := report.Plugins[0]	if plugin.ID != "sample" || plugin.InstallStatus != pluginInstallStatusSkipped || !plugin.Skipped {		t.Fatalf("plugin report = %+v, want skipped identical sample", plugin)	}	if plugin.Path != target {		t.Fatalf("plugin path = %q, want %q", plugin.Path, target)	}} func TestSyncPlatformSkipsIdenticalBusyPlugin(t *testing.T) {	root := t.TempDir()	targetDir := filepath.Join(root, "windows", "amd64")	if errMkdir := os.MkdirAll(targetDir, 0o755); errMkdir != nil {		t.Fatalf("MkdirAll() error = %v", errMkdir)	}	target := filepath.Join(targetDir, "sample-v0.2.0.dll")	if errWrite := os.WriteFile(target, []byte("library-data"), 0o644); errWrite != nil {		t.Fatalf("WriteFile() error = %v", errWrite)	}	archiveData := makeZip(t, map[string]string{"sample.dll": "library-data"})	archiveName := "sample_0.2.0_windows_amd64.zip"	checksum := sha256.Sum256(archiveData)	httpClient := mapHTTPDoer{		"https://api.github.com/repos/owner/sample-plugin/releases/tags/v0.2.0": []byte(`{			"tag_name": "v0.2.0",			"assets": [				{"name": "` + archiveName + `", "browser_download_url": "https://downloads.example/` + archiveName + `"},				{"name": "checksums.txt", "browser_download_url": "https://downloads.example/checksums.txt"}			]		}`),		"https://downloads.example/" + archiveName: archiveData,		"https://downloads.example/checksums.txt":  []byte(hex.EncodeToString(checksum[:]) + "  " + archiveName + "\n"),	}	restore := replacePluginStoreClientForTest(httpClient)	defer restore() 	runtime := &fakePluginRuntime{busy: true}	if errSync := SyncPlatform(context.Background(), syncTestConfig(t, root), runtime, Platform{GOOS: "windows", GOARCH: "amd64"}); errSync != nil {		t.Fatalf("SyncPlatform() error = %v", errSync)	}	if len(runtime.unloaded) != 0 {		t.Fatalf("UnloadPlugin() calls = %v, want none", runtime.unloaded)	}	got, errRead := os.ReadFile(target)	if errRead != nil {		t.Fatalf("read target: %v", errRead)	}	if string(got) != "library-data" {		t.Fatalf("target data = %q, want library-data", string(got))	}} func TestSyncPlatformSkipsConfigWithoutManifest(t *testing.T) {	restore := replacePluginStoreClientForTest(mapHTTPDoer{})	defer restore() 	cfg := &config.Config{		Home: config.HomeConfig{Enabled: true},		Plugins: config.PluginsConfig{			Enabled: true,			Dir:     t.TempDir(),			Configs: map[string]config.PluginInstanceConfig{				"sample": pluginConfigFromYAML(t, `enabled: true`),			},		},	}	if errSync := SyncPlatform(context.Background(), cfg, nil, Platform{GOOS: "linux", GOARCH: "amd64"}); errSync != nil {		t.Fatalf("SyncPlatform() error = %v", errSync)	}} func TestSyncPlatformRejectsInvalidManifest(t *testing.T) {	cfg := &config.Config{		Home: config.HomeConfig{Enabled: true},		Plugins: config.PluginsConfig{			Enabled: true,			Dir:     t.TempDir(),			Configs: map[string]config.PluginInstanceConfig{				"sample": pluginConfigFromYAML(t, `enabled: truestore:  id: sample`),			},		},	}	if errSync := SyncPlatform(context.Background(), cfg, nil, Platform{GOOS: "linux", GOARCH: "amd64"}); errSync == nil {		t.Fatal("SyncPlatform() error = nil, want invalid manifest")	}} func TestSyncPlatformWithReportRecordsInvalidManifest(t *testing.T) {	cfg := &config.Config{		Home: config.HomeConfig{Enabled: true},		Plugins: config.PluginsConfig{			Enabled: true,			Dir:     t.TempDir(),			Configs: map[string]config.PluginInstanceConfig{				"sample": pluginConfigFromYAML(t, `enabled: truestore:  id: sample`),			},		},	}	report, errSync := SyncPlatformWithReport(context.Background(), cfg, nil, Platform{GOOS: "linux", GOARCH: "amd64"})	if errSync == nil {		t.Fatal("SyncPlatformWithReport() error = nil, want invalid manifest")	}	if report.OK || report.Status != pluginTaskStatusError || len(report.Plugins) != 1 {		t.Fatalf("report = %+v, want one failed plugin", report)	}	if report.Plugins[0].ID != "sample" || report.Plugins[0].InstallStatus != pluginInstallStatusFailed || !strings.Contains(report.Plugins[0].Error, "invalid store manifest") {		t.Fatalf("plugin report = %+v, want invalid manifest failure", report.Plugins[0])	}} func TestMarkLoadResultsFailsWhenInstalledPluginDidNotLoad(t *testing.T) {	report := SyncReport{		Status:  pluginTaskStatusOK,		OK:      true,		Phase:   pluginTaskPhaseInstall,		Plugins: []PluginInstallStatus{{ID: "sample", InstallStatus: pluginInstallStatusInstalled}},	} 	errLoad := MarkLoadResults(&report, fakePluginLoadInspector{})	if errLoad == nil {		t.Fatal("MarkLoadResults() error = nil, want load failure")	}	if report.OK || report.Status != pluginTaskStatusError || report.Phase != pluginTaskPhaseLoad {		t.Fatalf("report = %+v, want failed load phase", report)	}	if report.Plugins[0].LoadStatus != pluginLoadStatusFailed || !strings.Contains(report.Plugins[0].Error, "installed but not loaded") {		t.Fatalf("plugin report = %+v, want load failure", report.Plugins[0])	}} func TestMarkLoadResultsPreservesInstallFailure(t *testing.T) {	report := SyncReport{

Lines 1–500 of 814.