github.com/goreleaser/goreleaser@v1.25.1/internal/http/http_test.go (about)

     1  package http
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/tls"
     6  	"encoding/pem"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	h "net/http"
    11  	"net/http/httptest"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  	"sync"
    16  	"testing"
    17  
    18  	"github.com/goreleaser/goreleaser/internal/artifact"
    19  	"github.com/goreleaser/goreleaser/internal/testctx"
    20  	"github.com/goreleaser/goreleaser/pkg/config"
    21  	"github.com/goreleaser/goreleaser/pkg/context"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  func TestAssetOpenDefault(t *testing.T) {
    26  	tf := filepath.Join(t.TempDir(), "asset")
    27  	require.NoError(t, os.WriteFile(tf, []byte("a"), 0o765))
    28  
    29  	a, err := assetOpenDefault("blah", &artifact.Artifact{
    30  		Path: tf,
    31  	})
    32  	if err != nil {
    33  		t.Fatalf("can not open asset: %v", err)
    34  	}
    35  	t.Cleanup(func() {
    36  		require.NoError(t, a.ReadCloser.Close())
    37  	})
    38  	bs, err := io.ReadAll(a.ReadCloser)
    39  	if err != nil {
    40  		t.Fatalf("can not read asset: %v", err)
    41  	}
    42  	if string(bs) != "a" {
    43  		t.Fatalf("unexpected read content")
    44  	}
    45  	_, err = assetOpenDefault("blah", &artifact.Artifact{
    46  		Path: "blah",
    47  	})
    48  	if err == nil {
    49  		t.Fatalf("should fail on missing file")
    50  	}
    51  	_, err = assetOpenDefault("blah", &artifact.Artifact{
    52  		Path: os.TempDir(),
    53  	})
    54  	if err == nil {
    55  		t.Fatalf("should fail on existing dir")
    56  	}
    57  }
    58  
    59  func TestDefaults(t *testing.T) {
    60  	type args struct {
    61  		uploads []config.Upload
    62  	}
    63  	tests := []struct {
    64  		name     string
    65  		args     args
    66  		wantErr  bool
    67  		wantMode string
    68  	}{
    69  		{"set default", args{[]config.Upload{{Name: "a", Target: "http://"}}}, false, ModeArchive},
    70  		{"keep value", args{[]config.Upload{{Name: "a", Target: "http://...", Mode: ModeBinary}}}, false, ModeBinary},
    71  	}
    72  	for _, tt := range tests {
    73  		t.Run(tt.name, func(t *testing.T) {
    74  			if err := Defaults(tt.args.uploads); (err != nil) != tt.wantErr {
    75  				t.Errorf("Defaults() error = %v, wantErr %v", err, tt.wantErr)
    76  			}
    77  			if tt.wantMode != tt.args.uploads[0].Mode {
    78  				t.Errorf("Incorrect Defaults() mode %q , wanted %q", tt.args.uploads[0].Mode, tt.wantMode)
    79  			}
    80  		})
    81  	}
    82  }
    83  
    84  func TestCheckConfig(t *testing.T) {
    85  	ctx := testctx.NewWithCfg(config.Project{
    86  		ProjectName: "blah",
    87  		Env:         []string{"TEST_A_SECRET=x"},
    88  	})
    89  	type args struct {
    90  		ctx    *context.Context
    91  		upload *config.Upload
    92  		kind   string
    93  	}
    94  	tests := []struct {
    95  		name    string
    96  		args    args
    97  		wantErr bool
    98  	}{
    99  		{"ok", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, false},
   100  		{"secret missing", args{ctx, &config.Upload{Name: "b", Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, true},
   101  		{"target missing", args{ctx, &config.Upload{Name: "a", Username: "pepe", Mode: ModeArchive}, "test"}, true},
   102  		{"name missing", args{ctx, &config.Upload{Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, true},
   103  		{"username missing", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Mode: ModeArchive}, "test"}, true},
   104  		{"username present", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, false},
   105  		{"mode missing", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Username: "pepe"}, "test"}, true},
   106  		{"mode invalid", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Username: "pepe", Mode: "blabla"}, "test"}, true},
   107  		{"cert invalid", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Username: "pepe", Mode: ModeBinary, TrustedCerts: "bad cert!"}, "test"}, true},
   108  	}
   109  	for _, tt := range tests {
   110  		t.Run(tt.name, func(t *testing.T) {
   111  			if err := CheckConfig(tt.args.ctx, tt.args.upload, tt.args.kind); (err != nil) != tt.wantErr {
   112  				t.Errorf("CheckConfig() error = %v, wantErr %v", err, tt.wantErr)
   113  			}
   114  		})
   115  	}
   116  
   117  	delete(ctx.Env, "TEST_A_SECRET")
   118  
   119  	tests = []struct {
   120  		name    string
   121  		args    args
   122  		wantErr bool
   123  	}{
   124  		{"username missing", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Mode: ModeArchive}, "test"}, false},
   125  		{"username present", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, true},
   126  	}
   127  	for _, tt := range tests {
   128  		t.Run(tt.name, func(t *testing.T) {
   129  			if err := CheckConfig(tt.args.ctx, tt.args.upload, tt.args.kind); (err != nil) != tt.wantErr {
   130  				t.Errorf("CheckConfig() error = %v, wantErr %v", err, tt.wantErr)
   131  			}
   132  		})
   133  	}
   134  }
   135  
   136  type check struct {
   137  	path    string
   138  	user    string
   139  	pass    string
   140  	content []byte
   141  	headers map[string]string
   142  }
   143  
   144  func checks(checks ...check) func(rs []*h.Request) error {
   145  	return func(rs []*h.Request) error {
   146  		for _, r := range rs {
   147  			found := false
   148  			for _, c := range checks {
   149  				if c.path == r.RequestURI {
   150  					found = true
   151  					err := doCheck(c, r)
   152  					if err != nil {
   153  						return err
   154  					}
   155  					break
   156  				}
   157  			}
   158  			if !found {
   159  				return fmt.Errorf("check not found for request %+v", r)
   160  			}
   161  		}
   162  		if len(rs) != len(checks) {
   163  			return fmt.Errorf("expected %d requests, got %d", len(checks), len(rs))
   164  		}
   165  		return nil
   166  	}
   167  }
   168  
   169  func doCheck(c check, r *h.Request) error {
   170  	contentLength := int64(len(c.content))
   171  	if r.ContentLength != contentLength {
   172  		return fmt.Errorf("request content-length header value %v unexpected, wanted %v", r.ContentLength, contentLength)
   173  	}
   174  	bs, err := io.ReadAll(r.Body)
   175  	if err != nil {
   176  		return fmt.Errorf("reading request body: %v", err)
   177  	}
   178  	if !bytes.Equal(bs, c.content) {
   179  		return errors.New("content does not match")
   180  	}
   181  	if int64(len(bs)) != contentLength {
   182  		return fmt.Errorf("request content length %v unexpected, wanted %v", int64(len(bs)), contentLength)
   183  	}
   184  	if r.RequestURI != c.path {
   185  		return fmt.Errorf("bad request uri %q, expecting %q", r.RequestURI, c.path)
   186  	}
   187  	if u, p, ok := r.BasicAuth(); !ok || u != c.user || p != c.pass {
   188  		return fmt.Errorf("bad basic auth credentials: %s/%s", u, p)
   189  	}
   190  	for k, v := range c.headers {
   191  		if r.Header.Get(k) != v {
   192  			return fmt.Errorf("bad header value for %s: expected %s, got %s", k, v, r.Header.Get(k))
   193  		}
   194  	}
   195  	return nil
   196  }
   197  
   198  func TestUpload(t *testing.T) {
   199  	content := []byte("blah!")
   200  	requests := []*h.Request{}
   201  	var m sync.Mutex
   202  	mux := h.NewServeMux()
   203  	mux.Handle("/", h.HandlerFunc(func(w h.ResponseWriter, r *h.Request) {
   204  		bs, err := io.ReadAll(r.Body)
   205  		if err != nil {
   206  			w.WriteHeader(h.StatusInternalServerError)
   207  			fmt.Fprintf(w, "reading request body: %v", err)
   208  			return
   209  		}
   210  		r.Body = io.NopCloser(bytes.NewReader(bs))
   211  		m.Lock()
   212  		requests = append(requests, r)
   213  		m.Unlock()
   214  		w.WriteHeader(h.StatusCreated)
   215  		w.Header().Set("Location", r.URL.RequestURI())
   216  	}))
   217  	assetOpen = func(_ string, _ *artifact.Artifact) (*asset, error) {
   218  		return &asset{
   219  			ReadCloser: io.NopCloser(bytes.NewReader(content)),
   220  			Size:       int64(len(content)),
   221  		}, nil
   222  	}
   223  	defer assetOpenReset()
   224  	var is2xx ResponseChecker = func(r *h.Response) error {
   225  		if r.StatusCode/100 == 2 {
   226  			return nil
   227  		}
   228  		return fmt.Errorf("unexpected http status code: %v", r.StatusCode)
   229  	}
   230  	ctx := testctx.NewWithCfg(config.Project{
   231  		ProjectName: "blah",
   232  		Env: []string{
   233  			"TEST_A_SECRET=x",
   234  			"TEST_A_USERNAME=u2",
   235  		},
   236  	}, testctx.WithVersion("2.1.0"))
   237  	folder := t.TempDir()
   238  	for _, a := range []struct {
   239  		ext string
   240  		typ artifact.Type
   241  	}{
   242  		{"---", artifact.DockerImage},
   243  		{"deb", artifact.LinuxPackage},
   244  		{"bin", artifact.Binary},
   245  		{"tar", artifact.UploadableArchive},
   246  		{"tar.gz", artifact.UploadableSourceArchive},
   247  		{"ubi", artifact.UploadableBinary},
   248  		{"sum", artifact.Checksum},
   249  		{"meta", artifact.Metadata},
   250  		{"sig", artifact.Signature},
   251  		{"pem", artifact.Certificate},
   252  	} {
   253  		file := filepath.Join(folder, "a."+a.ext)
   254  		require.NoError(t, os.WriteFile(file, []byte("lorem ipsum"), 0o644))
   255  		ctx.Artifacts.Add(&artifact.Artifact{
   256  			Name:   "a." + a.ext,
   257  			Goos:   "linux",
   258  			Goarch: "amd64",
   259  			Path:   file,
   260  			Type:   a.typ,
   261  			Extra: map[string]interface{}{
   262  				artifact.ExtraID:  "foo",
   263  				artifact.ExtraExt: a.ext,
   264  			},
   265  		})
   266  	}
   267  
   268  	tests := []struct {
   269  		name         string
   270  		tryPlain     bool
   271  		tryTLS       bool
   272  		wantErrPlain bool
   273  		wantErrTLS   bool
   274  		setup        func(*httptest.Server) (*context.Context, config.Upload)
   275  		check        func(r []*h.Request) error
   276  	}{
   277  		{
   278  			"wrong-mode", true, true, true, true,
   279  			func(s *httptest.Server) (*context.Context, config.Upload) {
   280  				return ctx, config.Upload{
   281  					Mode:         "wrong-mode",
   282  					Name:         "a",
   283  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   284  					Username:     "u1",
   285  					TrustedCerts: cert(s),
   286  				}
   287  			},
   288  			checks(),
   289  		},
   290  		{
   291  			"username-from-env", true, true, false, false,
   292  			func(s *httptest.Server) (*context.Context, config.Upload) {
   293  				return ctx, config.Upload{
   294  					Mode:         ModeArchive,
   295  					Name:         "a",
   296  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   297  					TrustedCerts: cert(s),
   298  				}
   299  			},
   300  			checks(
   301  				check{"/blah/2.1.0/a.deb", "u2", "x", content, map[string]string{}},
   302  				check{"/blah/2.1.0/a.tar", "u2", "x", content, map[string]string{}},
   303  				check{"/blah/2.1.0/a.tar.gz", "u2", "x", content, map[string]string{}},
   304  			),
   305  		},
   306  		{
   307  			"post", true, true, false, false,
   308  			func(s *httptest.Server) (*context.Context, config.Upload) {
   309  				return ctx, config.Upload{
   310  					Method:       h.MethodPost,
   311  					Mode:         ModeArchive,
   312  					Name:         "a",
   313  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   314  					Username:     "u1",
   315  					TrustedCerts: cert(s),
   316  				}
   317  			},
   318  			checks(
   319  				check{"/blah/2.1.0/a.deb", "u1", "x", content, map[string]string{}},
   320  				check{"/blah/2.1.0/a.tar", "u1", "x", content, map[string]string{}},
   321  				check{"/blah/2.1.0/a.tar.gz", "u1", "x", content, map[string]string{}},
   322  			),
   323  		},
   324  		{
   325  			"archive", true, true, false, false,
   326  			func(s *httptest.Server) (*context.Context, config.Upload) {
   327  				return ctx, config.Upload{
   328  					Mode:         ModeArchive,
   329  					Name:         "a",
   330  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   331  					Username:     "u1",
   332  					TrustedCerts: cert(s),
   333  				}
   334  			},
   335  			checks(
   336  				check{"/blah/2.1.0/a.deb", "u1", "x", content, map[string]string{}},
   337  				check{"/blah/2.1.0/a.tar", "u1", "x", content, map[string]string{}},
   338  				check{"/blah/2.1.0/a.tar.gz", "u1", "x", content, map[string]string{}},
   339  			),
   340  		},
   341  		{
   342  			"archive_with_os_tmpl", true, true, false, false,
   343  			func(s *httptest.Server) (*context.Context, config.Upload) {
   344  				return ctx, config.Upload{
   345  					Mode:         ModeArchive,
   346  					Name:         "a",
   347  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/{{.Os}}/{{.Arch}}",
   348  					Username:     "u1",
   349  					TrustedCerts: cert(s),
   350  				}
   351  			},
   352  			checks(
   353  				check{"/blah/2.1.0/linux/amd64/a.deb", "u1", "x", content, map[string]string{}},
   354  				check{"/blah/2.1.0/linux/amd64/a.tar", "u1", "x", content, map[string]string{}},
   355  				check{"/blah/2.1.0/linux/amd64/a.tar.gz", "u1", "x", content, map[string]string{}},
   356  			),
   357  		},
   358  		{
   359  			"archive_with_ids", true, true, false, false,
   360  			func(s *httptest.Server) (*context.Context, config.Upload) {
   361  				return ctx, config.Upload{
   362  					Mode:         ModeArchive,
   363  					Name:         "a",
   364  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   365  					Username:     "u1",
   366  					TrustedCerts: cert(s),
   367  					IDs:          []string{"foo"},
   368  				}
   369  			},
   370  			checks(
   371  				check{"/blah/2.1.0/a.deb", "u1", "x", content, map[string]string{}},
   372  				check{"/blah/2.1.0/a.tar", "u1", "x", content, map[string]string{}},
   373  				check{"/blah/2.1.0/a.tar.gz", "u1", "x", content, map[string]string{}},
   374  			),
   375  		},
   376  		{
   377  			"binary", true, true, false, false,
   378  			func(s *httptest.Server) (*context.Context, config.Upload) {
   379  				return ctx, config.Upload{
   380  					Mode:         ModeBinary,
   381  					Name:         "a",
   382  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   383  					Username:     "u2",
   384  					TrustedCerts: cert(s),
   385  				}
   386  			},
   387  			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{}}),
   388  		},
   389  		{
   390  			"binary_with_os_tmpl", true, true, false, false,
   391  			func(s *httptest.Server) (*context.Context, config.Upload) {
   392  				return ctx, config.Upload{
   393  					Mode:         ModeBinary,
   394  					Name:         "a",
   395  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/{{.Os}}/{{.Arch}}",
   396  					Username:     "u2",
   397  					TrustedCerts: cert(s),
   398  				}
   399  			},
   400  			checks(check{"/blah/2.1.0/linux/amd64/a.ubi", "u2", "x", content, map[string]string{}}),
   401  		},
   402  		{
   403  			"binary_with_ids", true, true, false, false,
   404  			func(s *httptest.Server) (*context.Context, config.Upload) {
   405  				return ctx, config.Upload{
   406  					Mode:         ModeBinary,
   407  					Name:         "a",
   408  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   409  					Username:     "u2",
   410  					TrustedCerts: cert(s),
   411  					IDs:          []string{"foo"},
   412  				}
   413  			},
   414  			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{}}),
   415  		},
   416  		{
   417  			"binary-add-ending-bar", true, true, false, false,
   418  			func(s *httptest.Server) (*context.Context, config.Upload) {
   419  				return ctx, config.Upload{
   420  					Mode:         ModeBinary,
   421  					Name:         "a",
   422  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}",
   423  					Username:     "u2",
   424  					TrustedCerts: cert(s),
   425  				}
   426  			},
   427  			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{}}),
   428  		},
   429  		{
   430  			"archive-with-checksum-and-signature", true, true, false, false,
   431  			func(s *httptest.Server) (*context.Context, config.Upload) {
   432  				return ctx, config.Upload{
   433  					Mode:         ModeArchive,
   434  					Name:         "a",
   435  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   436  					Username:     "u3",
   437  					Checksum:     true,
   438  					Signature:    true,
   439  					TrustedCerts: cert(s),
   440  				}
   441  			},
   442  			checks(
   443  				check{"/blah/2.1.0/a.deb", "u3", "x", content, map[string]string{}},
   444  				check{"/blah/2.1.0/a.tar", "u3", "x", content, map[string]string{}},
   445  				check{"/blah/2.1.0/a.tar.gz", "u3", "x", content, map[string]string{}},
   446  				check{"/blah/2.1.0/a.sum", "u3", "x", content, map[string]string{}},
   447  				check{"/blah/2.1.0/a.sig", "u3", "x", content, map[string]string{}},
   448  				check{"/blah/2.1.0/a.pem", "u3", "x", content, map[string]string{}},
   449  			),
   450  		},
   451  		{
   452  			"metadata", true, true, false, false,
   453  			func(s *httptest.Server) (*context.Context, config.Upload) {
   454  				return ctx, config.Upload{
   455  					Mode:         ModeArchive,
   456  					Name:         "a",
   457  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   458  					Username:     "u3",
   459  					Meta:         true,
   460  					TrustedCerts: cert(s),
   461  				}
   462  			},
   463  			checks(
   464  				check{"/blah/2.1.0/a.deb", "u3", "x", content, map[string]string{}},
   465  				check{"/blah/2.1.0/a.tar", "u3", "x", content, map[string]string{}},
   466  				check{"/blah/2.1.0/a.tar.gz", "u3", "x", content, map[string]string{}},
   467  				check{"/blah/2.1.0/a.meta", "u3", "x", content, map[string]string{}},
   468  			),
   469  		},
   470  		{
   471  			"bad-template", true, true, true, true,
   472  			func(s *httptest.Server) (*context.Context, config.Upload) {
   473  				return ctx, config.Upload{
   474  					Mode:         ModeBinary,
   475  					Name:         "a",
   476  					Target:       s.URL + "/{{.ProjectNameXXX}}/{{.VersionXXX}}/",
   477  					Username:     "u3",
   478  					Checksum:     true,
   479  					Signature:    true,
   480  					TrustedCerts: cert(s),
   481  				}
   482  			},
   483  			checks(),
   484  		},
   485  		{
   486  			"failed-request", true, true, true, true,
   487  			func(s *httptest.Server) (*context.Context, config.Upload) {
   488  				return ctx, config.Upload{
   489  					Mode:         ModeBinary,
   490  					Name:         "a",
   491  					Target:       s.URL[0:strings.LastIndex(s.URL, ":")] + "/{{.ProjectName}}/{{.Version}}/",
   492  					Username:     "u3",
   493  					Checksum:     true,
   494  					Signature:    true,
   495  					TrustedCerts: cert(s),
   496  				}
   497  			},
   498  			checks(),
   499  		},
   500  		{
   501  			"broken-cert", false, true, false, true,
   502  			func(s *httptest.Server) (*context.Context, config.Upload) {
   503  				return ctx, config.Upload{
   504  					Mode:         ModeBinary,
   505  					Name:         "a",
   506  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   507  					Username:     "u3",
   508  					Checksum:     false,
   509  					Signature:    false,
   510  					TrustedCerts: "bad certs!",
   511  				}
   512  			},
   513  			checks(),
   514  		},
   515  		{
   516  			"checksumheader", true, true, false, false,
   517  			func(s *httptest.Server) (*context.Context, config.Upload) {
   518  				return ctx, config.Upload{
   519  					Mode:           ModeBinary,
   520  					Name:           "a",
   521  					Target:         s.URL + "/{{.ProjectName}}/{{.Version}}/",
   522  					Username:       "u2",
   523  					ChecksumHeader: "-x-sha256",
   524  					TrustedCerts:   cert(s),
   525  				}
   526  			},
   527  			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{"-x-sha256": "5e2bf57d3f40c4b6df69daf1936cb766f832374b4fc0259a7cbff06e2f70f269"}}),
   528  		},
   529  		{
   530  			"custom-headers", true, true, false, false,
   531  			func(s *httptest.Server) (*context.Context, config.Upload) {
   532  				return ctx, config.Upload{
   533  					Mode:     ModeBinary,
   534  					Name:     "a",
   535  					Target:   s.URL + "/{{.ProjectName}}/{{.Version}}/",
   536  					Username: "u2",
   537  					CustomHeaders: map[string]string{
   538  						"x-custom-header-name": "custom-header-value",
   539  					},
   540  					TrustedCerts: cert(s),
   541  				}
   542  			},
   543  			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{"x-custom-header-name": "custom-header-value"}}),
   544  		},
   545  		{
   546  			"custom-headers-with-template", true, true, false, false,
   547  			func(s *httptest.Server) (*context.Context, config.Upload) {
   548  				return ctx, config.Upload{
   549  					Mode:     ModeBinary,
   550  					Name:     "a",
   551  					Target:   s.URL + "/{{.ProjectName}}/{{.Version}}/",
   552  					Username: "u2",
   553  					CustomHeaders: map[string]string{
   554  						"x-project-name": "{{ .ProjectName }}",
   555  					},
   556  					TrustedCerts: cert(s),
   557  				}
   558  			},
   559  			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{"x-project-name": "blah"}}),
   560  		},
   561  		{
   562  			"invalid-template-in-custom-headers", true, true, true, true,
   563  			func(s *httptest.Server) (*context.Context, config.Upload) {
   564  				return ctx, config.Upload{
   565  					Mode:     ModeBinary,
   566  					Name:     "a",
   567  					Target:   s.URL + "/{{.ProjectName}}/{{.Version}}/",
   568  					Username: "u2",
   569  					CustomHeaders: map[string]string{
   570  						"x-custom-header-name": "{{ .Env.NONEXISTINGVARIABLE and some bad expressions }}",
   571  					},
   572  					TrustedCerts: cert(s),
   573  				}
   574  			},
   575  			checks(),
   576  		},
   577  		{
   578  			"filtering-by-ext", true, true, false, false,
   579  			func(s *httptest.Server) (*context.Context, config.Upload) {
   580  				return ctx, config.Upload{
   581  					Mode:         ModeArchive,
   582  					Name:         "a",
   583  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   584  					Username:     "u3",
   585  					TrustedCerts: cert(s),
   586  					Exts:         []string{"deb", "rpm"},
   587  				}
   588  			},
   589  			checks(
   590  				check{"/blah/2.1.0/a.deb", "u3", "x", content, map[string]string{}},
   591  			),
   592  		},
   593  		{
   594  			name: "given a server with ClientAuth = RequireAnyClientCert, " +
   595  				"and an Upload with ClientX509Cert and ClientX509Key set, " +
   596  				"then the response should pass",
   597  			tryTLS: true,
   598  			setup: func(s *httptest.Server) (*context.Context, config.Upload) {
   599  				s.TLS.ClientAuth = tls.RequireAnyClientCert
   600  				return ctx, config.Upload{
   601  					Mode:           ModeArchive,
   602  					Name:           "a",
   603  					Target:         s.URL + "/{{.ProjectName}}/{{.Version}}/",
   604  					Username:       "u3",
   605  					TrustedCerts:   cert(s),
   606  					ClientX509Cert: "testcert.pem",
   607  					ClientX509Key:  "testkey.pem",
   608  					Exts:           []string{"deb", "rpm"},
   609  				}
   610  			},
   611  			check: checks(
   612  				check{"/blah/2.1.0/a.deb", "u3", "x", content, map[string]string{}},
   613  			),
   614  		},
   615  		{
   616  			name: "given a server with ClientAuth = RequireAnyClientCert, " +
   617  				"and an Upload without either ClientX509Cert or ClientX509Key set, " +
   618  				"then the response should fail",
   619  			tryTLS: true,
   620  			setup: func(s *httptest.Server) (*context.Context, config.Upload) {
   621  				s.TLS.ClientAuth = tls.RequireAnyClientCert
   622  				return ctx, config.Upload{
   623  					Mode:         ModeArchive,
   624  					Name:         "a",
   625  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   626  					Username:     "u3",
   627  					TrustedCerts: cert(s),
   628  					Exts:         []string{"deb", "rpm"},
   629  				}
   630  			},
   631  			wantErrTLS: true,
   632  			check:      checks(),
   633  		},
   634  	}
   635  
   636  	uploadAndCheck := func(t *testing.T, setup func(*httptest.Server) (*context.Context, config.Upload), wantErrPlain, wantErrTLS bool, check func(r []*h.Request) error, srv *httptest.Server) {
   637  		t.Helper()
   638  		requests = nil
   639  		ctx, upload := setup(srv)
   640  		wantErr := wantErrPlain
   641  		if srv.Certificate() != nil {
   642  			wantErr = wantErrTLS
   643  		}
   644  		if err := Upload(ctx, []config.Upload{upload}, "test", is2xx); (err != nil) != wantErr {
   645  			t.Errorf("Upload() error = %v, wantErr %v", err, wantErr)
   646  		}
   647  		if err := check(requests); err != nil {
   648  			t.Errorf("Upload() request invalid. Error: %v", err)
   649  		}
   650  	}
   651  
   652  	for _, tt := range tests {
   653  		t.Run(tt.name, func(t *testing.T) {
   654  			if tt.tryPlain {
   655  				t.Run(tt.name, func(t *testing.T) {
   656  					srv := httptest.NewServer(mux)
   657  					defer srv.Close()
   658  					uploadAndCheck(t, tt.setup, tt.wantErrPlain, tt.wantErrTLS, tt.check, srv)
   659  				})
   660  			}
   661  			if tt.tryTLS {
   662  				t.Run(tt.name+"-tls", func(t *testing.T) {
   663  					srv := httptest.NewUnstartedServer(mux)
   664  					srv.StartTLS()
   665  					defer srv.Close()
   666  					uploadAndCheck(t, tt.setup, tt.wantErrPlain, tt.wantErrTLS, tt.check, srv)
   667  				})
   668  			}
   669  		})
   670  	}
   671  }
   672  
   673  func cert(srv *httptest.Server) string {
   674  	if srv == nil || srv.Certificate() == nil {
   675  		return ""
   676  	}
   677  	block := &pem.Block{
   678  		Type:  "CERTIFICATE",
   679  		Bytes: srv.Certificate().Raw,
   680  	}
   681  	return string(pem.EncodeToMemory(block))
   682  }