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  }