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 }