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