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 }