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