github.com/windmeup/goreleaser@v1.21.95/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/stretchr/testify/require"
    19  	"github.com/windmeup/goreleaser/internal/artifact"
    20  	"github.com/windmeup/goreleaser/internal/testctx"
    21  	"github.com/windmeup/goreleaser/pkg/config"
    22  	"github.com/windmeup/goreleaser/pkg/context"
    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(k string, a *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  		{"ubi", artifact.UploadableBinary},
   247  		{"sum", artifact.Checksum},
   248  		{"sig", artifact.Signature},
   249  		{"pem", artifact.Certificate},
   250  	} {
   251  		file := filepath.Join(folder, "a."+a.ext)
   252  		require.NoError(t, os.WriteFile(file, []byte("lorem ipsum"), 0o644))
   253  		ctx.Artifacts.Add(&artifact.Artifact{
   254  			Name:   "a." + a.ext,
   255  			Goos:   "linux",
   256  			Goarch: "amd64",
   257  			Path:   file,
   258  			Type:   a.typ,
   259  			Extra: map[string]interface{}{
   260  				artifact.ExtraID:  "foo",
   261  				artifact.ExtraExt: a.ext,
   262  			},
   263  		})
   264  	}
   265  
   266  	tests := []struct {
   267  		name         string
   268  		tryPlain     bool
   269  		tryTLS       bool
   270  		wantErrPlain bool
   271  		wantErrTLS   bool
   272  		setup        func(*httptest.Server) (*context.Context, config.Upload)
   273  		check        func(r []*h.Request) error
   274  	}{
   275  		{
   276  			"wrong-mode", true, true, true, true,
   277  			func(s *httptest.Server) (*context.Context, config.Upload) {
   278  				return ctx, config.Upload{
   279  					Mode:         "wrong-mode",
   280  					Name:         "a",
   281  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   282  					Username:     "u1",
   283  					TrustedCerts: cert(s),
   284  				}
   285  			},
   286  			checks(),
   287  		},
   288  		{
   289  			"username-from-env", true, true, false, false,
   290  			func(s *httptest.Server) (*context.Context, config.Upload) {
   291  				return ctx, config.Upload{
   292  					Mode:         ModeArchive,
   293  					Name:         "a",
   294  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   295  					TrustedCerts: cert(s),
   296  				}
   297  			},
   298  			checks(
   299  				check{"/blah/2.1.0/a.deb", "u2", "x", content, map[string]string{}},
   300  				check{"/blah/2.1.0/a.tar", "u2", "x", content, map[string]string{}},
   301  			),
   302  		},
   303  		{
   304  			"post", true, true, false, false,
   305  			func(s *httptest.Server) (*context.Context, config.Upload) {
   306  				return ctx, config.Upload{
   307  					Method:       h.MethodPost,
   308  					Mode:         ModeArchive,
   309  					Name:         "a",
   310  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   311  					Username:     "u1",
   312  					TrustedCerts: cert(s),
   313  				}
   314  			},
   315  			checks(
   316  				check{"/blah/2.1.0/a.deb", "u1", "x", content, map[string]string{}},
   317  				check{"/blah/2.1.0/a.tar", "u1", "x", content, map[string]string{}},
   318  			),
   319  		},
   320  		{
   321  			"archive", true, true, false, false,
   322  			func(s *httptest.Server) (*context.Context, config.Upload) {
   323  				return ctx, config.Upload{
   324  					Mode:         ModeArchive,
   325  					Name:         "a",
   326  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   327  					Username:     "u1",
   328  					TrustedCerts: cert(s),
   329  				}
   330  			},
   331  			checks(
   332  				check{"/blah/2.1.0/a.deb", "u1", "x", content, map[string]string{}},
   333  				check{"/blah/2.1.0/a.tar", "u1", "x", content, map[string]string{}},
   334  			),
   335  		},
   336  		{
   337  			"archive_with_os_tmpl", true, true, false, false,
   338  			func(s *httptest.Server) (*context.Context, config.Upload) {
   339  				return ctx, config.Upload{
   340  					Mode:         ModeArchive,
   341  					Name:         "a",
   342  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/{{.Os}}/{{.Arch}}",
   343  					Username:     "u1",
   344  					TrustedCerts: cert(s),
   345  				}
   346  			},
   347  			checks(
   348  				check{"/blah/2.1.0/linux/amd64/a.deb", "u1", "x", content, map[string]string{}},
   349  				check{"/blah/2.1.0/linux/amd64/a.tar", "u1", "x", content, map[string]string{}},
   350  			),
   351  		},
   352  		{
   353  			"archive_with_ids", true, true, false, false,
   354  			func(s *httptest.Server) (*context.Context, config.Upload) {
   355  				return ctx, config.Upload{
   356  					Mode:         ModeArchive,
   357  					Name:         "a",
   358  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   359  					Username:     "u1",
   360  					TrustedCerts: cert(s),
   361  					IDs:          []string{"foo"},
   362  				}
   363  			},
   364  			checks(
   365  				check{"/blah/2.1.0/a.deb", "u1", "x", content, map[string]string{}},
   366  				check{"/blah/2.1.0/a.tar", "u1", "x", content, map[string]string{}},
   367  			),
   368  		},
   369  		{
   370  			"binary", true, true, false, false,
   371  			func(s *httptest.Server) (*context.Context, config.Upload) {
   372  				return ctx, config.Upload{
   373  					Mode:         ModeBinary,
   374  					Name:         "a",
   375  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   376  					Username:     "u2",
   377  					TrustedCerts: cert(s),
   378  				}
   379  			},
   380  			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{}}),
   381  		},
   382  		{
   383  			"binary_with_os_tmpl", true, true, false, false,
   384  			func(s *httptest.Server) (*context.Context, config.Upload) {
   385  				return ctx, config.Upload{
   386  					Mode:         ModeBinary,
   387  					Name:         "a",
   388  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/{{.Os}}/{{.Arch}}",
   389  					Username:     "u2",
   390  					TrustedCerts: cert(s),
   391  				}
   392  			},
   393  			checks(check{"/blah/2.1.0/linux/amd64/a.ubi", "u2", "x", content, map[string]string{}}),
   394  		},
   395  		{
   396  			"binary_with_ids", true, true, false, false,
   397  			func(s *httptest.Server) (*context.Context, config.Upload) {
   398  				return ctx, config.Upload{
   399  					Mode:         ModeBinary,
   400  					Name:         "a",
   401  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   402  					Username:     "u2",
   403  					TrustedCerts: cert(s),
   404  					IDs:          []string{"foo"},
   405  				}
   406  			},
   407  			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{}}),
   408  		},
   409  		{
   410  			"binary-add-ending-bar", true, true, false, false,
   411  			func(s *httptest.Server) (*context.Context, config.Upload) {
   412  				return ctx, config.Upload{
   413  					Mode:         ModeBinary,
   414  					Name:         "a",
   415  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}",
   416  					Username:     "u2",
   417  					TrustedCerts: cert(s),
   418  				}
   419  			},
   420  			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{}}),
   421  		},
   422  		{
   423  			"archive-with-checksum-and-signature", true, true, false, false,
   424  			func(s *httptest.Server) (*context.Context, config.Upload) {
   425  				return ctx, config.Upload{
   426  					Mode:         ModeArchive,
   427  					Name:         "a",
   428  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   429  					Username:     "u3",
   430  					Checksum:     true,
   431  					Signature:    true,
   432  					TrustedCerts: cert(s),
   433  				}
   434  			},
   435  			checks(
   436  				check{"/blah/2.1.0/a.deb", "u3", "x", content, map[string]string{}},
   437  				check{"/blah/2.1.0/a.tar", "u3", "x", content, map[string]string{}},
   438  				check{"/blah/2.1.0/a.sum", "u3", "x", content, map[string]string{}},
   439  				check{"/blah/2.1.0/a.sig", "u3", "x", content, map[string]string{}},
   440  				check{"/blah/2.1.0/a.pem", "u3", "x", content, map[string]string{}},
   441  			),
   442  		},
   443  		{
   444  			"bad-template", true, true, true, true,
   445  			func(s *httptest.Server) (*context.Context, config.Upload) {
   446  				return ctx, config.Upload{
   447  					Mode:         ModeBinary,
   448  					Name:         "a",
   449  					Target:       s.URL + "/{{.ProjectNameXXX}}/{{.VersionXXX}}/",
   450  					Username:     "u3",
   451  					Checksum:     true,
   452  					Signature:    true,
   453  					TrustedCerts: cert(s),
   454  				}
   455  			},
   456  			checks(),
   457  		},
   458  		{
   459  			"failed-request", true, true, true, true,
   460  			func(s *httptest.Server) (*context.Context, config.Upload) {
   461  				return ctx, config.Upload{
   462  					Mode:         ModeBinary,
   463  					Name:         "a",
   464  					Target:       s.URL[0:strings.LastIndex(s.URL, ":")] + "/{{.ProjectName}}/{{.Version}}/",
   465  					Username:     "u3",
   466  					Checksum:     true,
   467  					Signature:    true,
   468  					TrustedCerts: cert(s),
   469  				}
   470  			},
   471  			checks(),
   472  		},
   473  		{
   474  			"broken-cert", false, true, false, true,
   475  			func(s *httptest.Server) (*context.Context, config.Upload) {
   476  				return ctx, config.Upload{
   477  					Mode:         ModeBinary,
   478  					Name:         "a",
   479  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   480  					Username:     "u3",
   481  					Checksum:     false,
   482  					Signature:    false,
   483  					TrustedCerts: "bad certs!",
   484  				}
   485  			},
   486  			checks(),
   487  		},
   488  		{
   489  			"checksumheader", true, true, false, false,
   490  			func(s *httptest.Server) (*context.Context, config.Upload) {
   491  				return ctx, config.Upload{
   492  					Mode:           ModeBinary,
   493  					Name:           "a",
   494  					Target:         s.URL + "/{{.ProjectName}}/{{.Version}}/",
   495  					Username:       "u2",
   496  					ChecksumHeader: "-x-sha256",
   497  					TrustedCerts:   cert(s),
   498  				}
   499  			},
   500  			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{"-x-sha256": "5e2bf57d3f40c4b6df69daf1936cb766f832374b4fc0259a7cbff06e2f70f269"}}),
   501  		},
   502  		{
   503  			"custom-headers", true, true, false, false,
   504  			func(s *httptest.Server) (*context.Context, config.Upload) {
   505  				return ctx, config.Upload{
   506  					Mode:     ModeBinary,
   507  					Name:     "a",
   508  					Target:   s.URL + "/{{.ProjectName}}/{{.Version}}/",
   509  					Username: "u2",
   510  					CustomHeaders: map[string]string{
   511  						"x-custom-header-name": "custom-header-value",
   512  					},
   513  					TrustedCerts: cert(s),
   514  				}
   515  			},
   516  			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{"x-custom-header-name": "custom-header-value"}}),
   517  		},
   518  		{
   519  			"custom-headers-with-template", true, true, false, false,
   520  			func(s *httptest.Server) (*context.Context, config.Upload) {
   521  				return ctx, config.Upload{
   522  					Mode:     ModeBinary,
   523  					Name:     "a",
   524  					Target:   s.URL + "/{{.ProjectName}}/{{.Version}}/",
   525  					Username: "u2",
   526  					CustomHeaders: map[string]string{
   527  						"x-project-name": "{{ .ProjectName }}",
   528  					},
   529  					TrustedCerts: cert(s),
   530  				}
   531  			},
   532  			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{"x-project-name": "blah"}}),
   533  		},
   534  		{
   535  			"invalid-template-in-custom-headers", true, true, true, true,
   536  			func(s *httptest.Server) (*context.Context, config.Upload) {
   537  				return ctx, config.Upload{
   538  					Mode:     ModeBinary,
   539  					Name:     "a",
   540  					Target:   s.URL + "/{{.ProjectName}}/{{.Version}}/",
   541  					Username: "u2",
   542  					CustomHeaders: map[string]string{
   543  						"x-custom-header-name": "{{ .Env.NONEXISTINGVARIABLE and some bad expressions }}",
   544  					},
   545  					TrustedCerts: cert(s),
   546  				}
   547  			},
   548  			checks(),
   549  		},
   550  		{
   551  			"filtering-by-ext", true, true, false, false,
   552  			func(s *httptest.Server) (*context.Context, config.Upload) {
   553  				return ctx, config.Upload{
   554  					Mode:         ModeArchive,
   555  					Name:         "a",
   556  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   557  					Username:     "u3",
   558  					TrustedCerts: cert(s),
   559  					Exts:         []string{"deb", "rpm"},
   560  				}
   561  			},
   562  			checks(
   563  				check{"/blah/2.1.0/a.deb", "u3", "x", content, map[string]string{}},
   564  			),
   565  		},
   566  		{
   567  			name: "given a server with ClientAuth = RequireAnyClientCert, " +
   568  				"and an Upload with ClientX509Cert and ClientX509Key set, " +
   569  				"then the response should pass",
   570  			tryTLS: true,
   571  			setup: func(s *httptest.Server) (*context.Context, config.Upload) {
   572  				s.TLS.ClientAuth = tls.RequireAnyClientCert
   573  				return ctx, config.Upload{
   574  					Mode:           ModeArchive,
   575  					Name:           "a",
   576  					Target:         s.URL + "/{{.ProjectName}}/{{.Version}}/",
   577  					Username:       "u3",
   578  					TrustedCerts:   cert(s),
   579  					ClientX509Cert: "testcert.pem",
   580  					ClientX509Key:  "testkey.pem",
   581  					Exts:           []string{"deb", "rpm"},
   582  				}
   583  			},
   584  			check: checks(
   585  				check{"/blah/2.1.0/a.deb", "u3", "x", content, map[string]string{}},
   586  			),
   587  		},
   588  		{
   589  			name: "given a server with ClientAuth = RequireAnyClientCert, " +
   590  				"and an Upload without either ClientX509Cert or ClientX509Key set, " +
   591  				"then the response should fail",
   592  			tryTLS: true,
   593  			setup: func(s *httptest.Server) (*context.Context, config.Upload) {
   594  				s.TLS.ClientAuth = tls.RequireAnyClientCert
   595  				return ctx, config.Upload{
   596  					Mode:         ModeArchive,
   597  					Name:         "a",
   598  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   599  					Username:     "u3",
   600  					TrustedCerts: cert(s),
   601  					Exts:         []string{"deb", "rpm"},
   602  				}
   603  			},
   604  			wantErrTLS: true,
   605  			check:      checks(),
   606  		},
   607  	}
   608  
   609  	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) {
   610  		t.Helper()
   611  		requests = nil
   612  		ctx, upload := setup(srv)
   613  		wantErr := wantErrPlain
   614  		if srv.Certificate() != nil {
   615  			wantErr = wantErrTLS
   616  		}
   617  		if err := Upload(ctx, []config.Upload{upload}, "test", is2xx); (err != nil) != wantErr {
   618  			t.Errorf("Upload() error = %v, wantErr %v", err, wantErr)
   619  		}
   620  		if err := check(requests); err != nil {
   621  			t.Errorf("Upload() request invalid. Error: %v", err)
   622  		}
   623  	}
   624  
   625  	for _, tt := range tests {
   626  		t.Run(tt.name, func(t *testing.T) {
   627  			if tt.tryPlain {
   628  				t.Run(tt.name, func(t *testing.T) {
   629  					srv := httptest.NewServer(mux)
   630  					defer srv.Close()
   631  					uploadAndCheck(t, tt.setup, tt.wantErrPlain, tt.wantErrTLS, tt.check, srv)
   632  				})
   633  			}
   634  			if tt.tryTLS {
   635  				t.Run(tt.name+"-tls", func(t *testing.T) {
   636  					srv := httptest.NewUnstartedServer(mux)
   637  					srv.StartTLS()
   638  					defer srv.Close()
   639  					uploadAndCheck(t, tt.setup, tt.wantErrPlain, tt.wantErrTLS, tt.check, srv)
   640  				})
   641  			}
   642  		})
   643  	}
   644  }
   645  
   646  func cert(srv *httptest.Server) string {
   647  	if srv == nil || srv.Certificate() == nil {
   648  		return ""
   649  	}
   650  	block := &pem.Block{
   651  		Type:  "CERTIFICATE",
   652  		Bytes: srv.Certificate().Raw,
   653  	}
   654  	return string(pem.EncodeToMemory(block))
   655  }