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  }