2026-10-13 13:11:49 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"io"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type recordedRequest struct {
|
|
|
|
|
method, path, query, authorization, body string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func hostedTestServer(t *testing.T, status int, response string) (*httptest.Server, *recordedRequest) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
recorded := &recordedRequest{}
|
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
body, _ := io.ReadAll(r.Body)
|
|
|
|
|
*recorded = recordedRequest{
|
|
|
|
|
method: r.Method,
|
|
|
|
|
path: r.URL.EscapedPath(),
|
|
|
|
|
query: r.URL.RawQuery,
|
|
|
|
|
authorization: r.Header.Get("Authorization"),
|
|
|
|
|
body: string(body),
|
|
|
|
|
}
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
w.WriteHeader(status)
|
|
|
|
|
_, _ = io.WriteString(w, response)
|
|
|
|
|
}))
|
|
|
|
|
t.Cleanup(srv.Close)
|
|
|
|
|
return srv, recorded
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func hostedEnv(vars map[string]string) func(string) string {
|
|
|
|
|
return func(key string) string { return vars[key] }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func writeArchive(t *testing.T, name, content string) string {
|
|
|
|
|
t.Helper()
|
|
|
|
|
path := filepath.Join(t.TempDir(), name)
|
|
|
|
|
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
|
|
|
|
|
t.Fatalf("writing archive: %v", err)
|
|
|
|
|
}
|
|
|
|
|
return path
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunPush(t *testing.T) {
|
|
|
|
|
srv, recorded := hostedTestServer(t, http.StatusCreated,
|
|
|
|
|
`{"ecosystem":"npm","name":"@types/node","version":"20.1.0","filename":"node-20.1.0.tgz","sha256":"abc123","size":7}`)
|
|
|
|
|
archive := writeArchive(t, "node-20.1.0.tgz", "tarball")
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
|
|
|
|
|
err := runPush([]string{"-url", srv.URL, "npm", "@types/node", "20.1.0", archive},
|
|
|
|
|
hostedEnv(map[string]string{"PROXY_HOSTED_TOKEN": "tok"}), &stdout, srv.Client())
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("runPush: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if recorded.method != http.MethodPut || recorded.path != "/api/hosted/npm/@types/node/20.1.0" {
|
|
|
|
|
t.Errorf("request = %s %s", recorded.method, recorded.path)
|
|
|
|
|
}
|
|
|
|
|
if recorded.authorization != "Bearer tok" || recorded.body != "tarball" || recorded.query != "" {
|
|
|
|
|
t.Errorf("authorization/body/query = %q/%q/%q", recorded.authorization, recorded.body, recorded.query)
|
|
|
|
|
}
|
|
|
|
|
if out := stdout.String(); !strings.Contains(out, "node-20.1.0.tgz") || !strings.Contains(out, "abc123") {
|
|
|
|
|
t.Errorf("output = %q, want filename and sha256", out)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunPushErrors(t *testing.T) {
|
|
|
|
|
srv, _ := hostedTestServer(t, http.StatusBadRequest, `{"code":"BAD_REQUEST","message":"pushing packages is not supported for this ecosystem: cargo"}`)
|
|
|
|
|
archive := writeArchive(t, "serde-1.0.200.crate", "crate")
|
|
|
|
|
|
|
|
|
|
err := runPush([]string{"-url", srv.URL, "cargo", "serde", "1.0.200", archive}, hostedEnv(nil), io.Discard, srv.Client())
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "PROXY_HOSTED_TOKEN") {
|
|
|
|
|
t.Errorf("push without token error = %v, want a PROXY_HOSTED_TOKEN hint", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = runPush([]string{"-url", srv.URL, "cargo", "serde", "1.0.200", archive},
|
|
|
|
|
hostedEnv(map[string]string{"PROXY_HOSTED_TOKEN": "tok"}), io.Discard, srv.Client())
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "not supported for this ecosystem") {
|
|
|
|
|
t.Errorf("rejected push error = %v, want the server's message", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = runPush([]string{"-url", srv.URL, "npm", "request"}, hostedEnv(map[string]string{"PROXY_HOSTED_TOKEN": "tok"}), io.Discard, srv.Client())
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "usage") {
|
|
|
|
|
t.Errorf("push with missing arguments error = %v, want usage", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunPushUsesProxyURLFromEnv(t *testing.T) {
|
|
|
|
|
srv, recorded := hostedTestServer(t, http.StatusCreated, `{}`)
|
|
|
|
|
archive := writeArchive(t, "request-2.88.3.tgz", "tarball")
|
|
|
|
|
|
|
|
|
|
err := runPush([]string{"npm", "request", "2.88.3", archive},
|
|
|
|
|
hostedEnv(map[string]string{"PROXY_HOSTED_TOKEN": "tok", "PROXY_URL": srv.URL}), io.Discard, srv.Client())
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("runPush: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if recorded.path != "/api/hosted/npm/request/2.88.3" {
|
|
|
|
|
t.Errorf("path = %q", recorded.path)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunList(t *testing.T) {
|
|
|
|
|
srv, recorded := hostedTestServer(t, http.StatusOK,
|
|
|
|
|
`{"ecosystem":"npm","name":"request","artifacts":[{"version":"2.88.3","filename":"request-2.88.3.tgz","sha256":"abc123","size":7,"pushed_at":"2026-10-13T10:00:00Z"}]}`)
|
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
|
|
|
|
|
|
if err := runList([]string{"-url", srv.URL, "npm", "request"}, hostedEnv(nil), &stdout, srv.Client()); err != nil {
|
|
|
|
|
t.Fatalf("runList: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if recorded.method != http.MethodGet || recorded.path != "/api/hosted/npm/request" || recorded.authorization != "" {
|
|
|
|
|
t.Errorf("request = %s %s (authorization %q)", recorded.method, recorded.path, recorded.authorization)
|
|
|
|
|
}
|
|
|
|
|
if out := stdout.String(); !strings.Contains(out, "2.88.3") || !strings.Contains(out, "request-2.88.3.tgz") {
|
|
|
|
|
t.Errorf("output = %q", out)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRunDelete(t *testing.T) {
|
|
|
|
|
srv, recorded := hostedTestServer(t, http.StatusNoContent, "")
|
|
|
|
|
|
|
|
|
|
err := runDelete([]string{"-url", srv.URL, "npm", "request", "2.88.3"},
|
|
|
|
|
hostedEnv(map[string]string{"PROXY_HOSTED_TOKEN": "tok"}), io.Discard, srv.Client())
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("runDelete: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if recorded.method != http.MethodDelete || recorded.path != "/api/hosted/npm/request/2.88.3" || recorded.authorization != "Bearer tok" {
|
|
|
|
|
t.Errorf("request = %s %s (authorization %q)", recorded.method, recorded.path, recorded.authorization)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
missing, _ := hostedTestServer(t, http.StatusNotFound, `{"code":"NOT_FOUND","message":"no pushed artifacts for this version"}`)
|
|
|
|
|
err = runDelete([]string{"-url", missing.URL, "npm", "request", "2.88.3"},
|
|
|
|
|
hostedEnv(map[string]string{"PROXY_HOSTED_TOKEN": "tok"}), io.Discard, missing.Client())
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "no pushed artifacts") {
|
|
|
|
|
t.Errorf("delete of a missing version error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-13 15:20:57 +02:00
|
|
|
|
|
|
|
|
func TestRunHostedDispatchesSubcommands(t *testing.T) {
|
|
|
|
|
srv, recorded := hostedTestServer(t, http.StatusOK, `{"ecosystem":"npm","name":"request","artifacts":[]}`)
|
|
|
|
|
|
|
|
|
|
if err := runHosted([]string{"list", "-url", srv.URL, "npm", "request"}, hostedEnv(nil), io.Discard, srv.Client()); err != nil {
|
|
|
|
|
t.Fatalf("runHosted list: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if recorded.method != http.MethodGet || recorded.path != "/api/hosted/npm/request" {
|
|
|
|
|
t.Errorf("request = %s %s", recorded.method, recorded.path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, args := range [][]string{nil, {"bogus"}} {
|
|
|
|
|
err := runHosted(args, hostedEnv(nil), io.Discard, srv.Client())
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "usage: proxy hosted <push|list|delete>") {
|
|
|
|
|
t.Errorf("runHosted(%q) error = %v, want the hosted usage", args, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHostedSubcommandUsageNamesTheHostedCommand(t *testing.T) {
|
|
|
|
|
err := runPush([]string{"npm"}, hostedEnv(map[string]string{"PROXY_HOSTED_TOKEN": "tok"}), io.Discard, http.DefaultClient)
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "usage: proxy hosted push") {
|
|
|
|
|
t.Errorf("push usage error = %v, want it to name proxy hosted push", err)
|
|
|
|
|
}
|
|
|
|
|
}
|