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 }