github.phpd.cn/goreleaser/goreleaser@v0.92.0/internal/http/http_test.go (about) 1 package http 2 3 import ( 4 "bytes" 5 "encoding/pem" 6 "fmt" 7 "io" 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/pkg/errors" 18 "github.com/stretchr/testify/require" 19 20 "github.com/goreleaser/goreleaser/internal/artifact" 21 "github.com/goreleaser/goreleaser/pkg/config" 22 "github.com/goreleaser/goreleaser/pkg/context" 23 ) 24 25 func TestAssetOpenDefault(t *testing.T) { 26 tf, err := ioutil.TempFile("", "") 27 if err != nil { 28 t.Fatalf("can not create tmp file: %v", err) 29 return 30 } 31 fmt.Fprint(tf, "a") 32 tf.Close() 33 a, err := assetOpenDefault("blah", &artifact.Artifact{ 34 Path: tf.Name(), 35 }) 36 if err != nil { 37 t.Fatalf("can not open asset: %v", err) 38 } 39 bs, err := ioutil.ReadAll(a.ReadCloser) 40 if err != nil { 41 t.Fatalf("can not read asset: %v", err) 42 } 43 if string(bs) != "a" { 44 t.Fatalf("unexpected read content") 45 } 46 os.Remove(tf.Name()) 47 _, err = assetOpenDefault("blah", &artifact.Artifact{ 48 Path: "blah", 49 }) 50 if err == nil { 51 t.Fatalf("should fail on missing file") 52 } 53 _, err = assetOpenDefault("blah", &artifact.Artifact{ 54 Path: os.TempDir(), 55 }) 56 if err == nil { 57 t.Fatalf("should fail on existing dir") 58 } 59 } 60 61 func TestDefaults(t *testing.T) { 62 type args struct { 63 puts []config.Put 64 } 65 tests := []struct { 66 name string 67 args args 68 wantErr bool 69 wantMode string 70 }{ 71 {"set default", args{[]config.Put{{Name: "a", Target: "http://"}}}, false, ModeArchive}, 72 {"keep value", args{[]config.Put{{Name: "a", Target: "http://...", Mode: ModeBinary}}}, false, ModeBinary}, 73 } 74 for _, tt := range tests { 75 t.Run(tt.name, func(t *testing.T) { 76 if err := Defaults(tt.args.puts); (err != nil) != tt.wantErr { 77 t.Errorf("Defaults() error = %v, wantErr %v", err, tt.wantErr) 78 } 79 if tt.wantMode != tt.args.puts[0].Mode { 80 t.Errorf("Incorrect Defaults() mode %q , wanted %q", tt.args.puts[0].Mode, tt.wantMode) 81 } 82 }) 83 } 84 } 85 86 func TestCheckConfig(t *testing.T) { 87 ctx := context.New(config.Project{ProjectName: "blah"}) 88 ctx.Env["TEST_A_SECRET"] = "x" 89 type args struct { 90 ctx *context.Context 91 upload *config.Put 92 kind string 93 } 94 tests := []struct { 95 name string 96 args args 97 wantErr bool 98 }{ 99 {"ok", args{ctx, &config.Put{Name: "a", Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, false}, 100 {"secret missing", args{ctx, &config.Put{Name: "b", Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, true}, 101 {"target missing", args{ctx, &config.Put{Name: "a", Username: "pepe", Mode: ModeArchive}, "test"}, true}, 102 {"name missing", args{ctx, &config.Put{Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, true}, 103 {"mode missing", args{ctx, &config.Put{Name: "a", Target: "http://blabla", Username: "pepe"}, "test"}, true}, 104 {"mode invalid", args{ctx, &config.Put{Name: "a", Target: "http://blabla", Username: "pepe", Mode: "blabla"}, "test"}, true}, 105 {"cert invalid", args{ctx, &config.Put{Name: "a", Target: "http://blabla", Username: "pepe", Mode: ModeBinary, TrustedCerts: "bad cert!"}, "test"}, true}, 106 } 107 for _, tt := range tests { 108 t.Run(tt.name, func(t *testing.T) { 109 if err := CheckConfig(tt.args.ctx, tt.args.upload, tt.args.kind); (err != nil) != tt.wantErr { 110 t.Errorf("CheckConfig() error = %v, wantErr %v", err, tt.wantErr) 111 } 112 }) 113 } 114 } 115 116 func count(r io.Reader) (int64, error) { 117 var ( 118 c int64 119 b int64 120 err error 121 buf = make([]byte, 16) 122 ) 123 for b >= 0 && err == nil { 124 b, err := r.Read(buf) 125 if err != nil { 126 return c, err 127 } 128 c = c + int64(b) 129 } 130 return c, nil 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 if len(rs) != len(checks) { 144 return errors.New("expectations mismatch requests") 145 } 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 errors.Errorf("check not found for request %+v", r) 160 } 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 errors.Errorf("request content-length header value %v unexpected, wanted %v", r.ContentLength, contentLength) 170 } 171 bs, err := ioutil.ReadAll(r.Body) 172 if err != nil { 173 return errors.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 errors.Errorf("request content length %v unexpected, wanted %v", int64(len(bs)), contentLength) 180 } 181 if r.RequestURI != c.path { 182 return errors.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 errors.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 errors.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 := ioutil.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 = ioutil.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: ioutil.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 errors.Errorf("unexpected http status code: %v", r.StatusCode) 226 } 227 ctx := context.New(config.Project{ProjectName: "blah"}) 228 ctx.Env["TEST_A_SECRET"] = "x" 229 ctx.Env["TEST_A_USERNAME"] = "u2" 230 ctx.Version = "2.1.0" 231 ctx.Artifacts = artifact.New() 232 folder, err := ioutil.TempDir("", "goreleasertest") 233 require.NoError(t, err) 234 for _, a := range []struct { 235 ext string 236 typ artifact.Type 237 }{ 238 {"---", artifact.DockerImage}, 239 {"deb", artifact.LinuxPackage}, 240 {"bin", artifact.Binary}, 241 {"tar", artifact.UploadableArchive}, 242 {"ubi", artifact.UploadableBinary}, 243 {"sum", artifact.Checksum}, 244 {"sig", artifact.Signature}, 245 } { 246 var file = filepath.Join(folder, "a."+a.ext) 247 require.NoError(t, ioutil.WriteFile(file, []byte("lorem ipsum"), 0644)) 248 ctx.Artifacts.Add(artifact.Artifact{Name: "a." + a.ext, Path: file, Type: a.typ}) 249 } 250 251 tests := []struct { 252 name string 253 tryPlain bool 254 tryTLS bool 255 wantErrPlain bool 256 wantErrTLS bool 257 setup func(*httptest.Server) (*context.Context, config.Put) 258 check func(r []*h.Request) error 259 }{ 260 {"wrong-mode", true, true, true, true, 261 func(s *httptest.Server) (*context.Context, config.Put) { 262 return ctx, config.Put{ 263 Mode: "wrong-mode", 264 Name: "a", 265 Target: s.URL + "/{{.ProjectName}}/{{.Version}}/", 266 Username: "u1", 267 TrustedCerts: cert(s), 268 } 269 }, 270 checks(), 271 }, 272 {"username-from-env", true, true, false, false, 273 func(s *httptest.Server) (*context.Context, config.Put) { 274 return ctx, config.Put{ 275 Mode: ModeArchive, 276 Name: "a", 277 Target: s.URL + "/{{.ProjectName}}/{{.Version}}/", 278 TrustedCerts: cert(s), 279 } 280 }, 281 checks( 282 check{"/blah/2.1.0/a.deb", "u2", "x", content, map[string]string{}}, 283 check{"/blah/2.1.0/a.tar", "u2", "x", content, map[string]string{}}, 284 ), 285 }, 286 {"archive", true, true, false, false, 287 func(s *httptest.Server) (*context.Context, config.Put) { 288 return ctx, config.Put{ 289 Mode: ModeArchive, 290 Name: "a", 291 Target: s.URL + "/{{.ProjectName}}/{{.Version}}/", 292 Username: "u1", 293 TrustedCerts: cert(s), 294 } 295 }, 296 checks( 297 check{"/blah/2.1.0/a.deb", "u1", "x", content, map[string]string{}}, 298 check{"/blah/2.1.0/a.tar", "u1", "x", content, map[string]string{}}, 299 ), 300 }, 301 {"binary", true, true, false, false, 302 func(s *httptest.Server) (*context.Context, config.Put) { 303 return ctx, config.Put{ 304 Mode: ModeBinary, 305 Name: "a", 306 Target: s.URL + "/{{.ProjectName}}/{{.Version}}/", 307 Username: "u2", 308 TrustedCerts: cert(s), 309 } 310 }, 311 checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{}}), 312 }, 313 {"binary-add-ending-bar", true, true, false, false, 314 func(s *httptest.Server) (*context.Context, config.Put) { 315 return ctx, config.Put{ 316 Mode: ModeBinary, 317 Name: "a", 318 Target: s.URL + "/{{.ProjectName}}/{{.Version}}", 319 Username: "u2", 320 TrustedCerts: cert(s), 321 } 322 }, 323 checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{}}), 324 }, 325 {"archive-with-checksum-and-signature", true, true, false, false, 326 func(s *httptest.Server) (*context.Context, config.Put) { 327 return ctx, config.Put{ 328 Mode: ModeArchive, 329 Name: "a", 330 Target: s.URL + "/{{.ProjectName}}/{{.Version}}/", 331 Username: "u3", 332 Checksum: true, 333 Signature: true, 334 TrustedCerts: cert(s), 335 } 336 }, 337 checks( 338 check{"/blah/2.1.0/a.deb", "u3", "x", content, map[string]string{}}, 339 check{"/blah/2.1.0/a.tar", "u3", "x", content, map[string]string{}}, 340 check{"/blah/2.1.0/a.sum", "u3", "x", content, map[string]string{}}, 341 check{"/blah/2.1.0/a.sig", "u3", "x", content, map[string]string{}}, 342 ), 343 }, 344 {"bad-template", true, true, true, true, 345 func(s *httptest.Server) (*context.Context, config.Put) { 346 return ctx, config.Put{ 347 Mode: ModeBinary, 348 Name: "a", 349 Target: s.URL + "/{{.ProjectNameXXX}}/{{.VersionXXX}}/", 350 Username: "u3", 351 Checksum: true, 352 Signature: true, 353 TrustedCerts: cert(s), 354 } 355 }, 356 checks(), 357 }, 358 {"failed-request", true, true, true, true, 359 func(s *httptest.Server) (*context.Context, config.Put) { 360 return ctx, config.Put{ 361 Mode: ModeBinary, 362 Name: "a", 363 Target: s.URL[0:strings.LastIndex(s.URL, ":")] + "/{{.ProjectName}}/{{.Version}}/", 364 Username: "u3", 365 Checksum: true, 366 Signature: true, 367 TrustedCerts: cert(s), 368 } 369 }, 370 checks(), 371 }, 372 {"broken-cert", false, true, false, true, 373 func(s *httptest.Server) (*context.Context, config.Put) { 374 return ctx, config.Put{ 375 Mode: ModeBinary, 376 Name: "a", 377 Target: s.URL + "/{{.ProjectName}}/{{.Version}}/", 378 Username: "u3", 379 Checksum: false, 380 Signature: false, 381 TrustedCerts: "bad certs!", 382 } 383 }, 384 checks(), 385 }, 386 {"skip-publishing", true, true, true, true, 387 func(s *httptest.Server) (*context.Context, config.Put) { 388 c := *ctx 389 c.SkipPublish = true 390 return &c, config.Put{} 391 }, 392 checks(), 393 }, 394 {"checksumheader", true, true, false, false, 395 func(s *httptest.Server) (*context.Context, config.Put) { 396 return ctx, config.Put{ 397 Mode: ModeBinary, 398 Name: "a", 399 Target: s.URL + "/{{.ProjectName}}/{{.Version}}/", 400 Username: "u2", 401 ChecksumHeader: "-x-sha256", 402 TrustedCerts: cert(s), 403 } 404 }, 405 checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{"-x-sha256": "5e2bf57d3f40c4b6df69daf1936cb766f832374b4fc0259a7cbff06e2f70f269"}}), 406 }, 407 } 408 409 uploadAndCheck := func(setup func(*httptest.Server) (*context.Context, config.Put), wantErrPlain, wantErrTLS bool, check func(r []*h.Request) error, srv *httptest.Server) { 410 requests = nil 411 ctx, put := setup(srv) 412 wantErr := wantErrPlain 413 if srv.Certificate() != nil { 414 wantErr = wantErrTLS 415 } 416 if err := Upload(ctx, []config.Put{put}, "test", is2xx); (err != nil) != wantErr { 417 t.Errorf("Upload() error = %v, wantErr %v", err, wantErr) 418 } 419 if err := check(requests); err != nil { 420 t.Errorf("Upload() request invalid. Error: %v", err) 421 } 422 } 423 424 for _, tt := range tests { 425 if tt.tryPlain { 426 t.Run(tt.name, func(t *testing.T) { 427 srv := httptest.NewServer(mux) 428 defer srv.Close() 429 uploadAndCheck(tt.setup, tt.wantErrPlain, tt.wantErrTLS, tt.check, srv) 430 }) 431 } 432 if tt.tryTLS { 433 t.Run(tt.name+"-tls", func(t *testing.T) { 434 srv := httptest.NewUnstartedServer(mux) 435 srv.StartTLS() 436 defer srv.Close() 437 uploadAndCheck(tt.setup, tt.wantErrPlain, tt.wantErrTLS, tt.check, srv) 438 }) 439 } 440 } 441 442 } 443 444 func cert(srv *httptest.Server) string { 445 if srv == nil || srv.Certificate() == nil { 446 return "" 447 } 448 block := &pem.Block{ 449 Type: "CERTIFICATE", 450 Bytes: srv.Certificate().Raw, 451 } 452 return string(pem.EncodeToMemory(block)) 453 }