package pluginstore import ( "archive/zip" "bytes" "context" "crypto/sha256" "encoding/hex" "errors" "io" "net/http" "os" "path/filepath" "runtime" "strings" "testing") func TestInstallBlocksLoadedWindowsPlugin(t *testing.T) { t.Parallel() tests := []struct { name string goos string loaded bool wantBlocked bool }{ {name: "windows loaded", goos: "windows", loaded: true, wantBlocked: false}, {name: "windows not loaded", goos: "windows", loaded: false, wantBlocked: false}, {name: "linux loaded", goos: "linux", loaded: true, wantBlocked: false}, {name: "darwin loaded", goos: "darwin", loaded: true, wantBlocked: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() _, errInstall := Client{HTTPClient: failingHTTPDoer{}}.Install(context.Background(), testPlugin(), InstallOptions{ PluginsDir: t.TempDir(), GOOS: tt.goos, GOARCH: "amd64", PluginLoaded: func() bool { return tt.loaded }, }) if errInstall == nil { t.Fatal("Install() error = nil") } if gotBlocked := errors.Is(errInstall, ErrLoadedPluginLocked); gotBlocked != tt.wantBlocked { t.Fatalf("Install() error = %v, blocked = %v, want %v", errInstall, gotBlocked, tt.wantBlocked) } }) }} func TestInstallArchiveBlocksLoadedWindowsPluginBeforeWrite(t *testing.T) { t.Parallel() root := t.TempDir() targetDir := filepath.Join(root, "windows", "amd64") if errMkdir := os.MkdirAll(targetDir, 0o755); errMkdir != nil { t.Fatalf("MkdirAll() error = %v", errMkdir) } if errWrite := os.WriteFile(filepath.Join(targetDir, "sample-provider-v0.1.0.dll"), []byte("old"), 0o644); errWrite != nil { t.Fatalf("WriteFile() error = %v", errWrite) } _, errInstall := InstallArchive(makeZip(t, map[string]string{ "sample-provider.dll": "library-data", }), testPlugin(), InstallOptions{ PluginsDir: root, GOOS: "windows", GOARCH: "amd64", PluginLoaded: func() bool { return true }, }) if !errors.Is(errInstall, ErrLoadedPluginLocked) { t.Fatalf("InstallArchive() error = %v, want ErrLoadedPluginLocked", errInstall) }} func TestInstallArchivePreparesLoadedWindowsPluginBeforeWrite(t *testing.T) { t.Parallel() root := t.TempDir() targetDir := filepath.Join(root, "windows", "amd64") if errMkdir := os.MkdirAll(targetDir, 0o755); errMkdir != nil { t.Fatalf("MkdirAll() error = %v", errMkdir) } targetPath := filepath.Join(targetDir, "sample-provider-v0.1.0.dll") if errWrite := os.WriteFile(targetPath, []byte("old"), 0o644); errWrite != nil { t.Fatalf("WriteFile() error = %v", errWrite) } loaded := true prepared := false result, errInstall := InstallArchive(makeZip(t, map[string]string{ "sample-provider.dll": "new", }), testPlugin(), InstallOptions{ PluginsDir: root, GOOS: "windows", GOARCH: "amd64", PluginLoaded: func() bool { return loaded }, BeforeWrite: func() error { prepared = true loaded = false return nil }, }) if errInstall != nil { t.Fatalf("InstallArchive() error = %v", errInstall) } if !prepared { t.Fatal("BeforeWrite was not called") } if !result.Overwritten { t.Fatal("Overwritten = false, want true") } data, errRead := os.ReadFile(targetPath) if errRead != nil { t.Fatalf("ReadFile() error = %v", errRead) } if string(data) != "new" { t.Fatalf("installed data = %q, want new", data) }} func TestInstallArchiveSkipsIdenticalLoadedWindowsPlugin(t *testing.T) { t.Parallel() root := t.TempDir() targetDir := filepath.Join(root, "windows", "amd64") if errMkdir := os.MkdirAll(targetDir, 0o755); errMkdir != nil { t.Fatalf("MkdirAll() error = %v", errMkdir) } targetPath := filepath.Join(targetDir, "sample-provider-v0.1.0.dll") if errWrite := os.WriteFile(targetPath, []byte("same"), 0o644); errWrite != nil { t.Fatalf("WriteFile() error = %v", errWrite) } beforeWriteCalled := false result, errInstall := InstallArchive(makeZip(t, map[string]string{ "sample-provider.dll": "same", }), testPlugin(), InstallOptions{ PluginsDir: root, GOOS: "windows", GOARCH: "amd64", PluginLoaded: func() bool { return true }, BeforeWrite: func() error { beforeWriteCalled = true return errors.New("before write should not run") }, }) if errInstall != nil { t.Fatalf("InstallArchive() error = %v", errInstall) } if beforeWriteCalled { t.Fatal("BeforeWrite was called for identical artifact") } if !result.Overwritten { t.Fatal("Overwritten = false, want true") } if !result.Skipped { t.Fatal("Skipped = false, want true") } data, errRead := os.ReadFile(targetPath) if errRead != nil { t.Fatalf("ReadFile() error = %v", errRead) } if string(data) != "same" { t.Fatalf("installed data = %q, want same", data) }} func TestInstallArchiveWritesPlatformPlugin(t *testing.T) { t.Parallel() root := t.TempDir() result, errInstall := InstallArchive(makeZip(t, map[string]string{ "README.md": "ignored", "sample-provider.dylib": "library-data", }), testPlugin(), InstallOptions{PluginsDir: root, GOOS: "darwin", GOARCH: "arm64"}) if errInstall != nil { t.Fatalf("InstallArchive() error = %v", errInstall) } wantPath := filepath.Join(root, "darwin", "arm64", "sample-provider-v0.1.0.dylib") if result.Path != wantPath { t.Fatalf("Path = %q, want %q", result.Path, wantPath) } data, errRead := os.ReadFile(wantPath) if errRead != nil { t.Fatalf("ReadFile() error = %v", errRead) } if string(data) != "library-data" { t.Fatalf("installed data = %q", data) }} func TestInstallArchiveReportsOverwrite(t *testing.T) { t.Parallel() root := t.TempDir() targetDir := filepath.Join(root, "darwin", "arm64") if errMkdir := os.MkdirAll(targetDir, 0o755); errMkdir != nil { t.Fatalf("MkdirAll() error = %v", errMkdir) } if errWrite := os.WriteFile(filepath.Join(targetDir, "sample-provider-v0.1.0.dylib"), []byte("old"), 0o644); errWrite != nil { t.Fatalf("WriteFile() error = %v", errWrite) } result, errInstall := InstallArchive(makeZip(t, map[string]string{ "sample-provider.dylib": "new", }), testPlugin(), InstallOptions{PluginsDir: root, GOOS: "darwin", GOARCH: "arm64"}) if errInstall != nil { t.Fatalf("InstallArchive() error = %v", errInstall) } if !result.Overwritten { t.Fatal("Overwritten = false, want true") }} func TestInstallArchiveOverwritesRuntimeSelectedPlugin(t *testing.T) { t.Parallel() root := t.TempDir() existingPath := filepath.Join(root, runtime.GOOS, runtime.GOARCH, "sample-provider-v0.1.0"+pluginExtension(runtime.GOOS)) if errMkdir := os.MkdirAll(filepath.Dir(existingPath), 0o755); errMkdir != nil { t.Fatalf("MkdirAll() error = %v", errMkdir) } if errWrite := os.WriteFile(existingPath, []byte("old"), 0o644); errWrite != nil { t.Fatalf("WriteFile() error = %v", errWrite) } result, errInstall := InstallArchive(makeZip(t, map[string]string{ "sample-provider" + pluginExtension(runtime.GOOS): "new", }), testPlugin(), InstallOptions{PluginsDir: root, GOOS: runtime.GOOS, GOARCH: runtime.GOARCH}) if errInstall != nil { t.Fatalf("InstallArchive() error = %v", errInstall) } if result.Path != existingPath { t.Fatalf("Path = %q, want selected runtime plugin %q", result.Path, existingPath) } if !result.Overwritten { t.Fatal("Overwritten = false, want true") } data, errRead := os.ReadFile(existingPath) if errRead != nil { t.Fatalf("ReadFile() error = %v", errRead) } if string(data) != "new" { t.Fatalf("installed data = %q, want new", data) }} func TestInstallArchiveRejectsUnsafeArchives(t *testing.T) { t.Parallel() tests := []struct { name string files map[string]string wantErr string }{ { name: "zip slip", files: map[string]string{"../sample-provider.dylib": "library"}, wantErr: "escapes archive root", }, { name: "absolute path", files: map[string]string{"/sample-provider.dylib": "library"}, wantErr: "is absolute", }, { name: "nested target", files: map[string]string{"nested/sample-provider.dylib": "library"}, wantErr: "zip root", }, { name: "extension mismatch", files: map[string]string{"sample-provider.so": "library"}, wantErr: "sample-provider.dylib", }, { name: "filename mismatch", files: map[string]string{"other.dylib": "library"}, wantErr: "sample-provider.dylib", }, { name: "missing target", files: map[string]string{"README.md": "library"}, wantErr: "does not contain", }, { name: "multiple targets", files: map[string]string{ "sample-provider.dylib": "library", "copy.dylib": "library", }, wantErr: "sample-provider.dylib", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() _, errInstall := InstallArchive(makeZip(t, tt.files), testPlugin(), InstallOptions{PluginsDir: t.TempDir(), GOOS: "darwin", GOARCH: "arm64"}) if errInstall == nil { t.Fatal("InstallArchive() error = nil") } if !strings.Contains(errInstall.Error(), tt.wantErr) { t.Fatalf("InstallArchive() error = %v, want substring %q", errInstall, tt.wantErr) } }) }} func TestInstallUsesLatestReleaseVersion(t *testing.T) { t.Parallel() root := t.TempDir() archiveData := makeZip(t, map[string]string{"sample-provider.dylib": "library-data"}) archiveName := "sample-provider_0.2.0_darwin_arm64.zip" checksum := sha256.Sum256(archiveData) client := Client{HTTPClient: mapHTTPDoer{ "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/latest": []byte(`{ "tag_name": "v0.2.0", "assets": [ { "name": "` + archiveName + `", "url": "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/1", "browser_download_url": "https://downloads.example/` + archiveName + `" }, { "name": "checksums.txt", "url": "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/2", "browser_download_url": "https://downloads.example/checksums.txt" } ] }`), "https://downloads.example/" + archiveName: archiveData, "https://downloads.example/checksums.txt": []byte(hex.EncodeToString(checksum[:]) + " " + archiveName + "\n"), }} result, errInstall := client.Install(context.Background(), testPlugin(), InstallOptions{ PluginsDir: root, GOOS: "darwin", GOARCH: "arm64", }) if errInstall != nil { t.Fatalf("Install() error = %v", errInstall) } if result.Version != "0.2.0" { t.Fatalf("Version = %q, want 0.2.0 from latest release tag", result.Version) } data, errRead := os.ReadFile(filepath.Join(root, "darwin", "arm64", "sample-provider-v0.2.0.dylib")) if errRead != nil { t.Fatalf("ReadFile() error = %v", errRead) } if string(data) != "library-data" { t.Fatalf("installed data = %q", data) }} func TestDownloadAssetFallsBackToReleaseAssetAPIURLWhenBrowserDownloadURLEmpty(t *testing.T) { apiURL := "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/1" client := Client{HTTPClient: mapHTTPDoer{ apiURL: []byte("artifact-data"), }} data, errDownload := client.DownloadAsset(context.Background(), ReleaseAsset{ Name: "sample-provider_0.2.0_darwin_arm64.zip", APIURL: apiURL, }) if errDownload != nil { t.Fatalf("DownloadAsset() error = %v", errDownload) } if string(data) != "artifact-data" { t.Fatalf("DownloadAsset() = %q, want artifact-data", data) }} func TestDownloadAssetUsesAPIURLWhenAuthMatchesArtifact(t *testing.T) { t.Setenv("PLUGIN_STORE_TOKEN", "secret-token") apiURL := "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/1" client := Client{ HTTPClient: authCheckingHTTPDoer{ url: apiURL, wantAuth: "Bearer secret-token", responseBytes: []byte("artifact-data"), }, Auth: []AuthConfig{{ Match: "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/", ApplyTo: []string{RequestKindArtifact}, Type: AuthTypeBearer, TokenEnv: "PLUGIN_STORE_TOKEN", }}, } data, errDownload := client.DownloadAsset(context.Background(), ReleaseAsset{ Name: "sample-provider_0.2.0_darwin_arm64.zip", APIURL: apiURL, BrowserDownloadURL: "https://downloads.example/sample-provider.zip", }) if errDownload != nil { t.Fatalf("DownloadAsset() error = %v", errDownload) } if string(data) != "artifact-data" { t.Fatalf("DownloadAsset() = %q, want artifact-data", data) }} func TestDownloadAssetUsesAPIURLWhenResolvedAuthMatchesArtifact(t *testing.T) { apiURL := "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/1" client := Client{ HTTPClient: authCheckingHTTPDoer{ url: apiURL, wantAuth: "Bearer temporary-token", responseBytes: []byte("artifact-data"), }, ResolvedAuth: []ResolvedAuthConfig{{ Match: "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/", ApplyTo: []string{RequestKindArtifact}, Type: AuthTypeGitHubToken, Token: Secret("temporary-token"), }}, } data, errDownload := client.DownloadAsset(context.Background(), ReleaseAsset{ Name: "sample-provider_0.2.0_darwin_arm64.zip", APIURL: apiURL, BrowserDownloadURL: "https://downloads.example/sample-provider.zip", }) if errDownload != nil { t.Fatalf("DownloadAsset() error = %v", errDownload) } if string(data) != "artifact-data" { t.Fatalf("DownloadAsset() = %q, want artifact-data", data) }} func TestDownloadAssetUsesBrowserDownloadURLWithUnrelatedAuth(t *testing.T) { t.Setenv("PLUGIN_STORE_TOKEN", "secret-token") browserURL := "https://downloads.example/sample-provider.zip" client := Client{ HTTPClient: mapHTTPDoer{ browserURL: []byte("artifact-data"), }, Auth: []AuthConfig{{ Match: "https://registry.example/", ApplyTo: []string{RequestKindRegistry}, Type: AuthTypeBearer, TokenEnv: "PLUGIN_STORE_TOKEN", }}, } data, errDownload := client.DownloadAsset(context.Background(), ReleaseAsset{ Name: "sample-provider_0.2.0_darwin_arm64.zip", APIURL: "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/1", BrowserDownloadURL: browserURL, }) if errDownload != nil { t.Fatalf("DownloadAsset() error = %v", errDownload) } if string(data) != "artifact-data" { t.Fatalf("DownloadAsset() = %q, want artifact-data", data) }} func TestInstallVersionUsesPinnedReleaseTag(t *testing.T) { t.Parallel() root := t.TempDir() archiveData := makeZip(t, map[string]string{"sample-provider.so": "library-data"}) archiveName := "sample-provider_0.3.0_linux_amd64.zip" checksum := sha256.Sum256(archiveData) client := Client{HTTPClient: mapHTTPDoer{ "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/tags/v0.3.0": []byte(`{ "tag_name": "v0.3.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"), }} result, errInstall := client.InstallVersion(context.Background(), testPlugin(), "v0.3.0", "0.3.0", InstallOptions{ PluginsDir: root, GOOS: "linux", GOARCH: "amd64", }) if errInstall != nil { t.Fatalf("InstallVersion() error = %v", errInstall) } if result.Version != "0.3.0" { t.Fatalf("Version = %q, want 0.3.0", result.Version) } data, errRead := os.ReadFile(filepath.Join(root, "linux", "amd64", "sample-provider-v0.3.0.so")) if errRead != nil { t.Fatalf("ReadFile() error = %v", errRead) } if string(data) != "library-data" { t.Fatalf("installed data = %q", data) }}Lines 1–500 of 814. Load more
package pluginstore import ( "archive/zip" "bytes" "context" "crypto/sha256" "encoding/hex" "errors" "io" "net/http" "os" "path/filepath" "runtime" "strings" "testing" ) func TestInstallBlocksLoadedWindowsPlugin(t *testing.T) { t.Parallel() tests := []struct { name string goos string loaded bool wantBlocked bool }{ {name: "windows loaded", goos: "windows", loaded: true, wantBlocked: false}, {name: "windows not loaded", goos: "windows", loaded: false, wantBlocked: false}, {name: "linux loaded", goos: "linux", loaded: true, wantBlocked: false}, {name: "darwin loaded", goos: "darwin", loaded: true, wantBlocked: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() _, errInstall := Client{HTTPClient: failingHTTPDoer{}}.Install(context.Background(), testPlugin(), InstallOptions{ PluginsDir: t.TempDir(), GOOS: tt.goos, GOARCH: "amd64", PluginLoaded: func() bool { return tt.loaded }, }) if errInstall == nil { t.Fatal("Install() error = nil") } if gotBlocked := errors.Is(errInstall, ErrLoadedPluginLocked); gotBlocked != tt.wantBlocked { t.Fatalf("Install() error = %v, blocked = %v, want %v", errInstall, gotBlocked, tt.wantBlocked) } }) } } func TestInstallArchiveBlocksLoadedWindowsPluginBeforeWrite(t *testing.T) { t.Parallel() root := t.TempDir() targetDir := filepath.Join(root, "windows", "amd64") if errMkdir := os.MkdirAll(targetDir, 0o755); errMkdir != nil { t.Fatalf("MkdirAll() error = %v", errMkdir) } if errWrite := os.WriteFile(filepath.Join(targetDir, "sample-provider-v0.1.0.dll"), []byte("old"), 0o644); errWrite != nil { t.Fatalf("WriteFile() error = %v", errWrite) } _, errInstall := InstallArchive(makeZip(t, map[string]string{ "sample-provider.dll": "library-data", }), testPlugin(), InstallOptions{ PluginsDir: root, GOOS: "windows", GOARCH: "amd64", PluginLoaded: func() bool { return true }, }) if !errors.Is(errInstall, ErrLoadedPluginLocked) { t.Fatalf("InstallArchive() error = %v, want ErrLoadedPluginLocked", errInstall) } } func TestInstallArchivePreparesLoadedWindowsPluginBeforeWrite(t *testing.T) { t.Parallel() root := t.TempDir() targetDir := filepath.Join(root, "windows", "amd64") if errMkdir := os.MkdirAll(targetDir, 0o755); errMkdir != nil { t.Fatalf("MkdirAll() error = %v", errMkdir) } targetPath := filepath.Join(targetDir, "sample-provider-v0.1.0.dll") if errWrite := os.WriteFile(targetPath, []byte("old"), 0o644); errWrite != nil { t.Fatalf("WriteFile() error = %v", errWrite) } loaded := true prepared := false result, errInstall := InstallArchive(makeZip(t, map[string]string{ "sample-provider.dll": "new", }), testPlugin(), InstallOptions{ PluginsDir: root, GOOS: "windows", GOARCH: "amd64", PluginLoaded: func() bool { return loaded }, BeforeWrite: func() error { prepared = true loaded = false return nil }, }) if errInstall != nil { t.Fatalf("InstallArchive() error = %v", errInstall) } if !prepared { t.Fatal("BeforeWrite was not called") } if !result.Overwritten { t.Fatal("Overwritten = false, want true") } data, errRead := os.ReadFile(targetPath) if errRead != nil { t.Fatalf("ReadFile() error = %v", errRead) } if string(data) != "new" { t.Fatalf("installed data = %q, want new", data) } } func TestInstallArchiveSkipsIdenticalLoadedWindowsPlugin(t *testing.T) { t.Parallel() root := t.TempDir() targetDir := filepath.Join(root, "windows", "amd64") if errMkdir := os.MkdirAll(targetDir, 0o755); errMkdir != nil { t.Fatalf("MkdirAll() error = %v", errMkdir) } targetPath := filepath.Join(targetDir, "sample-provider-v0.1.0.dll") if errWrite := os.WriteFile(targetPath, []byte("same"), 0o644); errWrite != nil { t.Fatalf("WriteFile() error = %v", errWrite) } beforeWriteCalled := false result, errInstall := InstallArchive(makeZip(t, map[string]string{ "sample-provider.dll": "same", }), testPlugin(), InstallOptions{ PluginsDir: root, GOOS: "windows", GOARCH: "amd64", PluginLoaded: func() bool { return true }, BeforeWrite: func() error { beforeWriteCalled = true return errors.New("before write should not run") }, }) if errInstall != nil { t.Fatalf("InstallArchive() error = %v", errInstall) } if beforeWriteCalled { t.Fatal("BeforeWrite was called for identical artifact") } if !result.Overwritten { t.Fatal("Overwritten = false, want true") } if !result.Skipped { t.Fatal("Skipped = false, want true") } data, errRead := os.ReadFile(targetPath) if errRead != nil { t.Fatalf("ReadFile() error = %v", errRead) } if string(data) != "same" { t.Fatalf("installed data = %q, want same", data) } } func TestInstallArchiveWritesPlatformPlugin(t *testing.T) { t.Parallel() root := t.TempDir() result, errInstall := InstallArchive(makeZip(t, map[string]string{ "README.md": "ignored", "sample-provider.dylib": "library-data", }), testPlugin(), InstallOptions{PluginsDir: root, GOOS: "darwin", GOARCH: "arm64"}) if errInstall != nil { t.Fatalf("InstallArchive() error = %v", errInstall) } wantPath := filepath.Join(root, "darwin", "arm64", "sample-provider-v0.1.0.dylib") if result.Path != wantPath { t.Fatalf("Path = %q, want %q", result.Path, wantPath) } data, errRead := os.ReadFile(wantPath) if errRead != nil { t.Fatalf("ReadFile() error = %v", errRead) } if string(data) != "library-data" { t.Fatalf("installed data = %q", data) } } func TestInstallArchiveReportsOverwrite(t *testing.T) { t.Parallel() root := t.TempDir() targetDir := filepath.Join(root, "darwin", "arm64") if errMkdir := os.MkdirAll(targetDir, 0o755); errMkdir != nil { t.Fatalf("MkdirAll() error = %v", errMkdir) } if errWrite := os.WriteFile(filepath.Join(targetDir, "sample-provider-v0.1.0.dylib"), []byte("old"), 0o644); errWrite != nil { t.Fatalf("WriteFile() error = %v", errWrite) } result, errInstall := InstallArchive(makeZip(t, map[string]string{ "sample-provider.dylib": "new", }), testPlugin(), InstallOptions{PluginsDir: root, GOOS: "darwin", GOARCH: "arm64"}) if errInstall != nil { t.Fatalf("InstallArchive() error = %v", errInstall) } if !result.Overwritten { t.Fatal("Overwritten = false, want true") } } func TestInstallArchiveOverwritesRuntimeSelectedPlugin(t *testing.T) { t.Parallel() root := t.TempDir() existingPath := filepath.Join(root, runtime.GOOS, runtime.GOARCH, "sample-provider-v0.1.0"+pluginExtension(runtime.GOOS)) if errMkdir := os.MkdirAll(filepath.Dir(existingPath), 0o755); errMkdir != nil { t.Fatalf("MkdirAll() error = %v", errMkdir) } if errWrite := os.WriteFile(existingPath, []byte("old"), 0o644); errWrite != nil { t.Fatalf("WriteFile() error = %v", errWrite) } result, errInstall := InstallArchive(makeZip(t, map[string]string{ "sample-provider" + pluginExtension(runtime.GOOS): "new", }), testPlugin(), InstallOptions{PluginsDir: root, GOOS: runtime.GOOS, GOARCH: runtime.GOARCH}) if errInstall != nil { t.Fatalf("InstallArchive() error = %v", errInstall) } if result.Path != existingPath { t.Fatalf("Path = %q, want selected runtime plugin %q", result.Path, existingPath) } if !result.Overwritten { t.Fatal("Overwritten = false, want true") } data, errRead := os.ReadFile(existingPath) if errRead != nil { t.Fatalf("ReadFile() error = %v", errRead) } if string(data) != "new" { t.Fatalf("installed data = %q, want new", data) } } func TestInstallArchiveRejectsUnsafeArchives(t *testing.T) { t.Parallel() tests := []struct { name string files map[string]string wantErr string }{ { name: "zip slip", files: map[string]string{"../sample-provider.dylib": "library"}, wantErr: "escapes archive root", }, { name: "absolute path", files: map[string]string{"/sample-provider.dylib": "library"}, wantErr: "is absolute", }, { name: "nested target", files: map[string]string{"nested/sample-provider.dylib": "library"}, wantErr: "zip root", }, { name: "extension mismatch", files: map[string]string{"sample-provider.so": "library"}, wantErr: "sample-provider.dylib", }, { name: "filename mismatch", files: map[string]string{"other.dylib": "library"}, wantErr: "sample-provider.dylib", }, { name: "missing target", files: map[string]string{"README.md": "library"}, wantErr: "does not contain", }, { name: "multiple targets", files: map[string]string{ "sample-provider.dylib": "library", "copy.dylib": "library", }, wantErr: "sample-provider.dylib", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() _, errInstall := InstallArchive(makeZip(t, tt.files), testPlugin(), InstallOptions{PluginsDir: t.TempDir(), GOOS: "darwin", GOARCH: "arm64"}) if errInstall == nil { t.Fatal("InstallArchive() error = nil") } if !strings.Contains(errInstall.Error(), tt.wantErr) { t.Fatalf("InstallArchive() error = %v, want substring %q", errInstall, tt.wantErr) } }) } } func TestInstallUsesLatestReleaseVersion(t *testing.T) { t.Parallel() root := t.TempDir() archiveData := makeZip(t, map[string]string{"sample-provider.dylib": "library-data"}) archiveName := "sample-provider_0.2.0_darwin_arm64.zip" checksum := sha256.Sum256(archiveData) client := Client{HTTPClient: mapHTTPDoer{ "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/latest": []byte(`{ "tag_name": "v0.2.0", "assets": [ { "name": "` + archiveName + `", "url": "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/1", "browser_download_url": "https://downloads.example/` + archiveName + `" }, { "name": "checksums.txt", "url": "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/2", "browser_download_url": "https://downloads.example/checksums.txt" } ] }`), "https://downloads.example/" + archiveName: archiveData, "https://downloads.example/checksums.txt": []byte(hex.EncodeToString(checksum[:]) + " " + archiveName + "\n"), }} result, errInstall := client.Install(context.Background(), testPlugin(), InstallOptions{ PluginsDir: root, GOOS: "darwin", GOARCH: "arm64", }) if errInstall != nil { t.Fatalf("Install() error = %v", errInstall) } if result.Version != "0.2.0" { t.Fatalf("Version = %q, want 0.2.0 from latest release tag", result.Version) } data, errRead := os.ReadFile(filepath.Join(root, "darwin", "arm64", "sample-provider-v0.2.0.dylib")) if errRead != nil { t.Fatalf("ReadFile() error = %v", errRead) } if string(data) != "library-data" { t.Fatalf("installed data = %q", data) } } func TestDownloadAssetFallsBackToReleaseAssetAPIURLWhenBrowserDownloadURLEmpty(t *testing.T) { apiURL := "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/1" client := Client{HTTPClient: mapHTTPDoer{ apiURL: []byte("artifact-data"), }} data, errDownload := client.DownloadAsset(context.Background(), ReleaseAsset{ Name: "sample-provider_0.2.0_darwin_arm64.zip", APIURL: apiURL, }) if errDownload != nil { t.Fatalf("DownloadAsset() error = %v", errDownload) } if string(data) != "artifact-data" { t.Fatalf("DownloadAsset() = %q, want artifact-data", data) } } func TestDownloadAssetUsesAPIURLWhenAuthMatchesArtifact(t *testing.T) { t.Setenv("PLUGIN_STORE_TOKEN", "secret-token") apiURL := "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/1" client := Client{ HTTPClient: authCheckingHTTPDoer{ url: apiURL, wantAuth: "Bearer secret-token", responseBytes: []byte("artifact-data"), }, Auth: []AuthConfig{{ Match: "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/", ApplyTo: []string{RequestKindArtifact}, Type: AuthTypeBearer, TokenEnv: "PLUGIN_STORE_TOKEN", }}, } data, errDownload := client.DownloadAsset(context.Background(), ReleaseAsset{ Name: "sample-provider_0.2.0_darwin_arm64.zip", APIURL: apiURL, BrowserDownloadURL: "https://downloads.example/sample-provider.zip", }) if errDownload != nil { t.Fatalf("DownloadAsset() error = %v", errDownload) } if string(data) != "artifact-data" { t.Fatalf("DownloadAsset() = %q, want artifact-data", data) } } func TestDownloadAssetUsesAPIURLWhenResolvedAuthMatchesArtifact(t *testing.T) { apiURL := "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/1" client := Client{ HTTPClient: authCheckingHTTPDoer{ url: apiURL, wantAuth: "Bearer temporary-token", responseBytes: []byte("artifact-data"), }, ResolvedAuth: []ResolvedAuthConfig{{ Match: "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/", ApplyTo: []string{RequestKindArtifact}, Type: AuthTypeGitHubToken, Token: Secret("temporary-token"), }}, } data, errDownload := client.DownloadAsset(context.Background(), ReleaseAsset{ Name: "sample-provider_0.2.0_darwin_arm64.zip", APIURL: apiURL, BrowserDownloadURL: "https://downloads.example/sample-provider.zip", }) if errDownload != nil { t.Fatalf("DownloadAsset() error = %v", errDownload) } if string(data) != "artifact-data" { t.Fatalf("DownloadAsset() = %q, want artifact-data", data) } } func TestDownloadAssetUsesBrowserDownloadURLWithUnrelatedAuth(t *testing.T) { t.Setenv("PLUGIN_STORE_TOKEN", "secret-token") browserURL := "https://downloads.example/sample-provider.zip" client := Client{ HTTPClient: mapHTTPDoer{ browserURL: []byte("artifact-data"), }, Auth: []AuthConfig{{ Match: "https://registry.example/", ApplyTo: []string{RequestKindRegistry}, Type: AuthTypeBearer, TokenEnv: "PLUGIN_STORE_TOKEN", }}, } data, errDownload := client.DownloadAsset(context.Background(), ReleaseAsset{ Name: "sample-provider_0.2.0_darwin_arm64.zip", APIURL: "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/1", BrowserDownloadURL: browserURL, }) if errDownload != nil { t.Fatalf("DownloadAsset() error = %v", errDownload) } if string(data) != "artifact-data" { t.Fatalf("DownloadAsset() = %q, want artifact-data", data) } } func TestInstallVersionUsesPinnedReleaseTag(t *testing.T) { t.Parallel() root := t.TempDir() archiveData := makeZip(t, map[string]string{"sample-provider.so": "library-data"}) archiveName := "sample-provider_0.3.0_linux_amd64.zip" checksum := sha256.Sum256(archiveData) client := Client{HTTPClient: mapHTTPDoer{ "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/tags/v0.3.0": []byte(`{ "tag_name": "v0.3.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"), }} result, errInstall := client.InstallVersion(context.Background(), testPlugin(), "v0.3.0", "0.3.0", InstallOptions{ PluginsDir: root, GOOS: "linux", GOARCH: "amd64", }) if errInstall != nil { t.Fatalf("InstallVersion() error = %v", errInstall) } if result.Version != "0.3.0" { t.Fatalf("Version = %q, want 0.3.0", result.Version) } data, errRead := os.ReadFile(filepath.Join(root, "linux", "amd64", "sample-provider-v0.3.0.so")) if errRead != nil { t.Fatalf("ReadFile() error = %v", errRead) } if string(data) != "library-data" { t.Fatalf("installed data = %q", data) } }