github.com/ahmet2mir/goreleaser@v0.180.3-0.20210927151101-8e5ee5a9b8c5/internal/http/http_test.go (about)

     1  package http
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/pem"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	h "net/http"
    10  	"net/http/httptest"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"sync"
    15  	"testing"
    16  
    17  	"github.com/goreleaser/goreleaser/internal/artifact"
    18  	"github.com/goreleaser/goreleaser/pkg/config"
    19  	"github.com/goreleaser/goreleaser/pkg/context"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  func TestAssetOpenDefault(t *testing.T) {
    24  	tf := filepath.Join(t.TempDir(), "asset")
    25  	require.NoError(t, os.WriteFile(tf, []byte("a"), 0o765))
    26  
    27  	a, err := assetOpenDefault("blah", &artifact.Artifact{
    28  		Path: tf,
    29  	})
    30  	if err != nil {
    31  		t.Fatalf("can not open asset: %v", err)
    32  	}
    33  	t.Cleanup(func() {
    34  		require.NoError(t, a.ReadCloser.Close())
    35  	})
    36  	bs, err := io.ReadAll(a.ReadCloser)
    37  	if err != nil {
    38  		t.Fatalf("can not read asset: %v", err)
    39  	}
    40  	if string(bs) != "a" {
    41  		t.Fatalf("unexpected read content")
    42  	}
    43  	_, err = assetOpenDefault("blah", &artifact.Artifact{
    44  		Path: "blah",
    45  	})
    46  	if err == nil {
    47  		t.Fatalf("should fail on missing file")
    48  	}
    49  	_, err = assetOpenDefault("blah", &artifact.Artifact{
    50  		Path: os.TempDir(),
    51  	})
    52  	if err == nil {
    53  		t.Fatalf("should fail on existing dir")
    54  	}
    55  }
    56  
    57  func TestDefaults(t *testing.T) {
    58  	type args struct {
    59  		uploads []config.Upload
    60  	}
    61  	tests := []struct {
    62  		name     string
    63  		args     args
    64  		wantErr  bool
    65  		wantMode string
    66  	}{
    67  		{"set default", args{[]config.Upload{{Name: "a", Target: "http://"}}}, false, ModeArchive},
    68  		{"keep value", args{[]config.Upload{{Name: "a", Target: "http://...", Mode: ModeBinary}}}, false, ModeBinary},
    69  	}
    70  	for _, tt := range tests {
    71  		t.Run(tt.name, func(t *testing.T) {
    72  			if err := Defaults(tt.args.uploads); (err != nil) != tt.wantErr {
    73  				t.Errorf("Defaults() error = %v, wantErr %v", err, tt.wantErr)
    74  			}
    75  			if tt.wantMode != tt.args.uploads[0].Mode {
    76  				t.Errorf("Incorrect Defaults() mode %q , wanted %q", tt.args.uploads[0].Mode, tt.wantMode)
    77  			}
    78  		})
    79  	}
    80  }
    81  
    82  func TestCheckConfig(t *testing.T) {
    83  	ctx := context.New(config.Project{ProjectName: "blah"})
    84  	ctx.Env["TEST_A_SECRET"] = "x"
    85  	type args struct {
    86  		ctx    *context.Context
    87  		upload *config.Upload
    88  		kind   string
    89  	}
    90  	tests := []struct {
    91  		name    string
    92  		args    args
    93  		wantErr bool
    94  	}{
    95  		{"ok", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, false},
    96  		{"secret missing", args{ctx, &config.Upload{Name: "b", Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, true},
    97  		{"target missing", args{ctx, &config.Upload{Name: "a", Username: "pepe", Mode: ModeArchive}, "test"}, true},
    98  		{"name missing", args{ctx, &config.Upload{Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, true},
    99  		{"username missing", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Mode: ModeArchive}, "test"}, true},
   100  		{"username present", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, false},
   101  		{"mode missing", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Username: "pepe"}, "test"}, true},
   102  		{"mode invalid", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Username: "pepe", Mode: "blabla"}, "test"}, true},
   103  		{"cert invalid", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Username: "pepe", Mode: ModeBinary, TrustedCerts: "bad cert!"}, "test"}, true},
   104  	}
   105  	for _, tt := range tests {
   106  		t.Run(tt.name, func(t *testing.T) {
   107  			if err := CheckConfig(tt.args.ctx, tt.args.upload, tt.args.kind); (err != nil) != tt.wantErr {
   108  				t.Errorf("CheckConfig() error = %v, wantErr %v", err, tt.wantErr)
   109  			}
   110  		})
   111  	}
   112  
   113  	delete(ctx.Env, "TEST_A_SECRET")
   114  
   115  	tests = []struct {
   116  		name    string
   117  		args    args
   118  		wantErr bool
   119  	}{
   120  		{"username missing", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Mode: ModeArchive}, "test"}, false},
   121  		{"username present", args{ctx, &config.Upload{Name: "a", Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, true},
   122  	}
   123  	for _, tt := range tests {
   124  		t.Run(tt.name, func(t *testing.T) {
   125  			if err := CheckConfig(tt.args.ctx, tt.args.upload, tt.args.kind); (err != nil) != tt.wantErr {
   126  				t.Errorf("CheckConfig() error = %v, wantErr %v", err, tt.wantErr)
   127  			}
   128  		})
   129  	}
   130  }
   131  
   132  type check struct {
   133  	path    string
   134  	user    string
   135  	pass    string
   136  	content []byte
   137  	headers map[string]string
   138  }
   139  
   140  func checks(checks ...check) func(rs []*h.Request) error {
   141  	return func(rs []*h.Request) error {
   142  		if len(rs) != len(checks) {
   143  			return fmt.Errorf("expected %d requests, got %d", len(checks), len(rs))
   144  		}
   145  		for _, r := range rs {
   146  			found := false
   147  			for _, c := range checks {
   148  				if c.path == r.RequestURI {
   149  					found = true
   150  					err := doCheck(c, r)
   151  					if err != nil {
   152  						return err
   153  					}
   154  					break
   155  				}
   156  			}
   157  			if !found {
   158  				return fmt.Errorf("check not found for request %+v", r)
   159  			}
   160  		}
   161  		return nil
   162  	}
   163  }
   164  
   165  func doCheck(c check, r *h.Request) error {
   166  	contentLength := int64(len(c.content))
   167  	if r.ContentLength != contentLength {
   168  		return fmt.Errorf("request content-length header value %v unexpected, wanted %v", r.ContentLength, contentLength)
   169  	}
   170  	bs, err := io.ReadAll(r.Body)
   171  	if err != nil {
   172  		return fmt.Errorf("reading request body: %v", err)
   173  	}
   174  	if !bytes.Equal(bs, c.content) {
   175  		return errors.New("content does not match")
   176  	}
   177  	if int64(len(bs)) != contentLength {
   178  		return fmt.Errorf("request content length %v unexpected, wanted %v", int64(len(bs)), contentLength)
   179  	}
   180  	if r.RequestURI != c.path {
   181  		return fmt.Errorf("bad request uri %q, expecting %q", r.RequestURI, c.path)
   182  	}
   183  	if u, p, ok := r.BasicAuth(); !ok || u != c.user || p != c.pass {
   184  		return fmt.Errorf("bad basic auth credentials: %s/%s", u, p)
   185  	}
   186  	for k, v := range c.headers {
   187  		if r.Header.Get(k) != v {
   188  			return fmt.Errorf("bad header value for %s: expected %s, got %s", k, v, r.Header.Get(k))
   189  		}
   190  	}
   191  	return nil
   192  }
   193  
   194  func TestUpload(t *testing.T) {
   195  	content := []byte("blah!")
   196  	requests := []*h.Request{}
   197  	var m sync.Mutex
   198  	mux := h.NewServeMux()
   199  	mux.Handle("/", h.HandlerFunc(func(w h.ResponseWriter, r *h.Request) {
   200  		bs, err := io.ReadAll(r.Body)
   201  		if err != nil {
   202  			w.WriteHeader(h.StatusInternalServerError)
   203  			fmt.Fprintf(w, "reading request body: %v", err)
   204  			return
   205  		}
   206  		r.Body = io.NopCloser(bytes.NewReader(bs))
   207  		m.Lock()
   208  		requests = append(requests, r)
   209  		m.Unlock()
   210  		w.WriteHeader(h.StatusCreated)
   211  		w.Header().Set("Location", r.URL.RequestURI())
   212  	}))
   213  	assetOpen = func(k string, a *artifact.Artifact) (*asset, error) {
   214  		return &asset{
   215  			ReadCloser: io.NopCloser(bytes.NewReader(content)),
   216  			Size:       int64(len(content)),
   217  		}, nil
   218  	}
   219  	defer assetOpenReset()
   220  	var is2xx ResponseChecker = func(r *h.Response) error {
   221  		if r.StatusCode/100 == 2 {
   222  			return nil
   223  		}
   224  		return fmt.Errorf("unexpected http status code: %v", r.StatusCode)
   225  	}
   226  	ctx := context.New(config.Project{
   227  		ProjectName: "blah",
   228  		Archives: []config.Archive{
   229  			{
   230  				Replacements: map[string]string{
   231  					"linux": "Linux",
   232  				},
   233  			},
   234  		},
   235  	})
   236  	ctx.Env["TEST_A_SECRET"] = "x"
   237  	ctx.Env["TEST_A_USERNAME"] = "u2"
   238  	ctx.Version = "2.1.0"
   239  	ctx.Artifacts = artifact.New()
   240  	folder := t.TempDir()
   241  	for _, a := range []struct {
   242  		ext string
   243  		typ artifact.Type
   244  	}{
   245  		{"---", artifact.DockerImage},
   246  		{"deb", artifact.LinuxPackage},
   247  		{"bin", artifact.Binary},
   248  		{"tar", artifact.UploadableArchive},
   249  		{"ubi", artifact.UploadableBinary},
   250  		{"sum", artifact.Checksum},
   251  		{"sig", artifact.Signature},
   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  				"ID": "foo",
   263  			},
   264  		})
   265  	}
   266  
   267  	tests := []struct {
   268  		name         string
   269  		tryPlain     bool
   270  		tryTLS       bool
   271  		wantErrPlain bool
   272  		wantErrTLS   bool
   273  		setup        func(*httptest.Server) (*context.Context, config.Upload)
   274  		check        func(r []*h.Request) error
   275  	}{
   276  		{
   277  			"wrong-mode", true, true, true, true,
   278  			func(s *httptest.Server) (*context.Context, config.Upload) {
   279  				return ctx, config.Upload{
   280  					Mode:         "wrong-mode",
   281  					Name:         "a",
   282  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   283  					Username:     "u1",
   284  					TrustedCerts: cert(s),
   285  				}
   286  			},
   287  			checks(),
   288  		},
   289  		{
   290  			"username-from-env", true, true, false, false,
   291  			func(s *httptest.Server) (*context.Context, config.Upload) {
   292  				return ctx, config.Upload{
   293  					Mode:         ModeArchive,
   294  					Name:         "a",
   295  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   296  					TrustedCerts: cert(s),
   297  				}
   298  			},
   299  			checks(
   300  				check{"/blah/2.1.0/a.deb", "u2", "x", content, map[string]string{}},
   301  				check{"/blah/2.1.0/a.tar", "u2", "x", content, map[string]string{}},
   302  			),
   303  		},
   304  		{
   305  			"post", true, true, false, false,
   306  			func(s *httptest.Server) (*context.Context, config.Upload) {
   307  				return ctx, config.Upload{
   308  					Method:       h.MethodPost,
   309  					Mode:         ModeArchive,
   310  					Name:         "a",
   311  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   312  					Username:     "u1",
   313  					TrustedCerts: cert(s),
   314  				}
   315  			},
   316  			checks(
   317  				check{"/blah/2.1.0/a.deb", "u1", "x", content, map[string]string{}},
   318  				check{"/blah/2.1.0/a.tar", "u1", "x", content, map[string]string{}},
   319  			),
   320  		},
   321  		{
   322  			"archive", true, true, false, false,
   323  			func(s *httptest.Server) (*context.Context, config.Upload) {
   324  				return ctx, config.Upload{
   325  					Mode:         ModeArchive,
   326  					Name:         "a",
   327  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   328  					Username:     "u1",
   329  					TrustedCerts: cert(s),
   330  				}
   331  			},
   332  			checks(
   333  				check{"/blah/2.1.0/a.deb", "u1", "x", content, map[string]string{}},
   334  				check{"/blah/2.1.0/a.tar", "u1", "x", content, map[string]string{}},
   335  			),
   336  		},
   337  		{
   338  			"archive_with_os_tmpl", true, true, false, false,
   339  			func(s *httptest.Server) (*context.Context, config.Upload) {
   340  				return ctx, config.Upload{
   341  					Mode:         ModeArchive,
   342  					Name:         "a",
   343  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/{{.Os}}/{{.Arch}}",
   344  					Username:     "u1",
   345  					TrustedCerts: cert(s),
   346  				}
   347  			},
   348  			checks(
   349  				check{"/blah/2.1.0/linux/amd64/a.deb", "u1", "x", content, map[string]string{}},
   350  				check{"/blah/2.1.0/linux/amd64/a.tar", "u1", "x", content, map[string]string{}},
   351  			),
   352  		},
   353  		{
   354  			"archive_with_ids", true, true, false, false,
   355  			func(s *httptest.Server) (*context.Context, config.Upload) {
   356  				return ctx, config.Upload{
   357  					Mode:         ModeArchive,
   358  					Name:         "a",
   359  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   360  					Username:     "u1",
   361  					TrustedCerts: cert(s),
   362  					IDs:          []string{"foo"},
   363  				}
   364  			},
   365  			checks(
   366  				check{"/blah/2.1.0/a.deb", "u1", "x", content, map[string]string{}},
   367  				check{"/blah/2.1.0/a.tar", "u1", "x", content, map[string]string{}},
   368  			),
   369  		},
   370  		{
   371  			"binary", true, true, false, false,
   372  			func(s *httptest.Server) (*context.Context, config.Upload) {
   373  				return ctx, config.Upload{
   374  					Mode:         ModeBinary,
   375  					Name:         "a",
   376  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   377  					Username:     "u2",
   378  					TrustedCerts: cert(s),
   379  				}
   380  			},
   381  			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{}}),
   382  		},
   383  		{
   384  			"binary_with_os_tmpl", true, true, false, false,
   385  			func(s *httptest.Server) (*context.Context, config.Upload) {
   386  				return ctx, config.Upload{
   387  					Mode:         ModeBinary,
   388  					Name:         "a",
   389  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/{{.Os}}/{{.Arch}}",
   390  					Username:     "u2",
   391  					TrustedCerts: cert(s),
   392  				}
   393  			},
   394  			checks(check{"/blah/2.1.0/Linux/amd64/a.ubi", "u2", "x", content, map[string]string{}}),
   395  		},
   396  		{
   397  			"binary_with_ids", true, true, false, false,
   398  			func(s *httptest.Server) (*context.Context, config.Upload) {
   399  				return ctx, config.Upload{
   400  					Mode:         ModeBinary,
   401  					Name:         "a",
   402  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   403  					Username:     "u2",
   404  					TrustedCerts: cert(s),
   405  					IDs:          []string{"foo"},
   406  				}
   407  			},
   408  			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{}}),
   409  		},
   410  		{
   411  			"binary-add-ending-bar", true, true, false, false,
   412  			func(s *httptest.Server) (*context.Context, config.Upload) {
   413  				return ctx, config.Upload{
   414  					Mode:         ModeBinary,
   415  					Name:         "a",
   416  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}",
   417  					Username:     "u2",
   418  					TrustedCerts: cert(s),
   419  				}
   420  			},
   421  			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{}}),
   422  		},
   423  		{
   424  			"archive-with-checksum-and-signature", true, true, false, false,
   425  			func(s *httptest.Server) (*context.Context, config.Upload) {
   426  				return ctx, config.Upload{
   427  					Mode:         ModeArchive,
   428  					Name:         "a",
   429  					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/",
   430  					Username:     "u3",
   431  					Checksum:     true,
   432  					Signature:    true,
   433  					TrustedCerts: cert(s),
   434  				}
   435  			},
   436  			checks(
   437  				check{"/blah/2.1.0/a.deb", "u3", "x", content, map[string]string{}},
   438  				check{"/blah/2.1.0/a.tar", "u3", "x", content, map[string]string{}},
   439  				check{"/blah/2.1.0/a.sum", "u3", "x", content, map[string]string{}},
   440  				check{"/blah/2.1.0/a.sig", "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  
   552  	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) {
   553  		t.Helper()
   554  		requests = nil
   555  		ctx, upload := setup(srv)
   556  		wantErr := wantErrPlain
   557  		if srv.Certificate() != nil {
   558  			wantErr = wantErrTLS
   559  		}
   560  		if err := Upload(ctx, []config.Upload{upload}, "test", is2xx); (err != nil) != wantErr {
   561  			t.Errorf("Upload() error = %v, wantErr %v", err, wantErr)
   562  		}
   563  		if err := check(requests); err != nil {
   564  			t.Errorf("Upload() request invalid. Error: %v", err)
   565  		}
   566  	}
   567  
   568  	for _, tt := range tests {
   569  		if tt.tryPlain {
   570  			t.Run(tt.name, func(t *testing.T) {
   571  				srv := httptest.NewServer(mux)
   572  				defer srv.Close()
   573  				uploadAndCheck(t, tt.setup, tt.wantErrPlain, tt.wantErrTLS, tt.check, srv)
   574  			})
   575  		}
   576  		if tt.tryTLS {
   577  			t.Run(tt.name+"-tls", func(t *testing.T) {
   578  				srv := httptest.NewUnstartedServer(mux)
   579  				srv.StartTLS()
   580  				defer srv.Close()
   581  				uploadAndCheck(t, tt.setup, tt.wantErrPlain, tt.wantErrTLS, tt.check, srv)
   582  			})
   583  		}
   584  	}
   585  }
   586  
   587  func cert(srv *httptest.Server) string {
   588  	if srv == nil || srv.Certificate() == nil {
   589  		return ""
   590  	}
   591  	block := &pem.Block{
   592  		Type:  "CERTIFICATE",
   593  		Bytes: srv.Certificate().Raw,
   594  	}
   595  	return string(pem.EncodeToMemory(block))
   596  }