github.com/goreleaser/goreleaser@v1.25.1/internal/pipe/upload/upload_test.go (about)

     1  package upload
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"os"
     8  	"path/filepath"
     9  	"sync"
    10  	"syscall"
    11  	"testing"
    12  
    13  	"github.com/goreleaser/goreleaser/internal/artifact"
    14  	"github.com/goreleaser/goreleaser/internal/pipe"
    15  	"github.com/goreleaser/goreleaser/internal/testctx"
    16  	"github.com/goreleaser/goreleaser/internal/testlib"
    17  	"github.com/goreleaser/goreleaser/pkg/config"
    18  	"github.com/goreleaser/goreleaser/pkg/context"
    19  	"github.com/stretchr/testify/require"
    20  )
    21  
    22  var (
    23  	// mux is the HTTP request multiplexer used with the test server.
    24  	mux *http.ServeMux
    25  
    26  	// server is a test HTTP server used to provide mock API responses.
    27  	server *httptest.Server
    28  )
    29  
    30  func setup() {
    31  	// test server
    32  	mux = http.NewServeMux()
    33  	server = httptest.NewServer(mux)
    34  }
    35  
    36  // teardown closes the test HTTP server.
    37  func teardown() {
    38  	server.Close()
    39  }
    40  
    41  func requireMethodPut(t *testing.T, r *http.Request) {
    42  	t.Helper()
    43  	require.Equal(t, http.MethodPut, r.Method)
    44  }
    45  
    46  func requireHeader(t *testing.T, r *http.Request, header, want string) {
    47  	t.Helper()
    48  	require.Equal(t, want, r.Header.Get(header))
    49  }
    50  
    51  // TODO: improve all tests below by checking whether the mocked handlers
    52  // were called or not.
    53  
    54  func TestRunPipe_ModeBinary(t *testing.T) {
    55  	setup()
    56  	defer teardown()
    57  
    58  	folder := t.TempDir()
    59  	dist := filepath.Join(folder, "dist")
    60  	require.NoError(t, os.Mkdir(dist, 0o755))
    61  	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
    62  	binPath := filepath.Join(dist, "mybin", "mybin")
    63  	d1 := []byte("hello\ngo\n")
    64  	require.NoError(t, os.WriteFile(binPath, d1, 0o666))
    65  
    66  	// Dummy http server
    67  	mux.HandleFunc("/example-repo-local/mybin/darwin/amd64/mybin", func(w http.ResponseWriter, r *http.Request) {
    68  		requireMethodPut(t, r)
    69  		requireHeader(t, r, "Content-Length", "9")
    70  		// Basic auth of user "deployuser" with secret "deployuser-secret"
    71  		requireHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
    72  
    73  		w.Header().Set("Location", "/production-repo-remote/mybin/linux/amd64/mybin")
    74  		w.WriteHeader(http.StatusCreated)
    75  	})
    76  	mux.HandleFunc("/example-repo-local/mybin/linux/amd64/mybin", func(w http.ResponseWriter, r *http.Request) {
    77  		requireMethodPut(t, r)
    78  		requireHeader(t, r, "Content-Length", "9")
    79  		// Basic auth of user "deployuser" with secret "deployuser-secret"
    80  		requireHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
    81  
    82  		w.Header().Set("Location", "/production-repo-remote/mybin/linux/amd64/mybin")
    83  		w.WriteHeader(http.StatusCreated)
    84  	})
    85  	mux.HandleFunc("/production-repo-remote/mybin/darwin/amd64/mybin", func(w http.ResponseWriter, r *http.Request) {
    86  		requireMethodPut(t, r)
    87  		requireHeader(t, r, "Content-Length", "9")
    88  		// Basic auth of user "productionuser" with secret "productionuser-apikey"
    89  		requireHeader(t, r, "Authorization", "Basic cHJvZHVjdGlvbnVzZXI6cHJvZHVjdGlvbnVzZXItYXBpa2V5")
    90  
    91  		w.Header().Set("Location", "/production-repo-remote/mybin/linux/amd64/mybin")
    92  		w.WriteHeader(http.StatusCreated)
    93  	})
    94  	mux.HandleFunc("/production-repo-remote/mybin/linux/amd64/mybin", func(w http.ResponseWriter, r *http.Request) {
    95  		requireMethodPut(t, r)
    96  		requireHeader(t, r, "Content-Length", "9")
    97  		// Basic auth of user "productionuser" with secret "productionuser-apikey"
    98  		requireHeader(t, r, "Authorization", "Basic cHJvZHVjdGlvbnVzZXI6cHJvZHVjdGlvbnVzZXItYXBpa2V5")
    99  
   100  		w.Header().Set("Location", "/production-repo-remote/mybin/linux/amd64/mybin")
   101  		w.WriteHeader(http.StatusCreated)
   102  	})
   103  
   104  	ctx := testctx.NewWithCfg(config.Project{
   105  		ProjectName: "mybin",
   106  		Dist:        dist,
   107  		Uploads: []config.Upload{
   108  			{
   109  				Method:   http.MethodPut,
   110  				Name:     "production-us",
   111  				Mode:     "binary",
   112  				Target:   fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL),
   113  				Username: "deployuser",
   114  			},
   115  			{
   116  				Method:   http.MethodPut,
   117  				Name:     "production-eu",
   118  				Mode:     "binary",
   119  				Target:   fmt.Sprintf("%s/production-repo-remote/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL),
   120  				Username: "productionuser",
   121  			},
   122  		},
   123  		Archives: []config.Archive{{}},
   124  		Env: []string{
   125  			"UPLOAD_PRODUCTION-US_SECRET=deployuser-secret",
   126  			"UPLOAD_PRODUCTION-EU_SECRET=productionuser-apikey",
   127  		},
   128  	})
   129  	for _, goos := range []string{"linux", "darwin"} {
   130  		ctx.Artifacts.Add(&artifact.Artifact{
   131  			Name:   "mybin",
   132  			Path:   binPath,
   133  			Goarch: "amd64",
   134  			Goos:   goos,
   135  			Type:   artifact.UploadableBinary,
   136  		})
   137  	}
   138  
   139  	require.NoError(t, Pipe{}.Publish(ctx))
   140  }
   141  
   142  func TestRunPipe_ModeArchive(t *testing.T) {
   143  	setup()
   144  	defer teardown()
   145  
   146  	folder := t.TempDir()
   147  	tarfile, err := os.Create(filepath.Join(folder, "bin.tar.gz"))
   148  	require.NoError(t, err)
   149  	require.NoError(t, tarfile.Close())
   150  	debfile, err := os.Create(filepath.Join(folder, "bin.deb"))
   151  	require.NoError(t, err)
   152  	require.NoError(t, debfile.Close())
   153  
   154  	ctx := testctx.NewWithCfg(config.Project{
   155  		ProjectName: "goreleaser",
   156  		Dist:        folder,
   157  		Uploads: []config.Upload{
   158  			{
   159  				Method:   http.MethodPut,
   160  				Name:     "production",
   161  				Mode:     "archive",
   162  				Target:   fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Version }}/", server.URL),
   163  				Username: "deployuser",
   164  			},
   165  		},
   166  		Archives: []config.Archive{{}},
   167  		Env:      []string{"UPLOAD_PRODUCTION_SECRET=deployuser-secret"},
   168  	}, testctx.WithVersion("1.0.0"))
   169  	ctx.Artifacts.Add(&artifact.Artifact{
   170  		Type: artifact.UploadableArchive,
   171  		Name: "bin.tar.gz",
   172  		Path: tarfile.Name(),
   173  	})
   174  	ctx.Artifacts.Add(&artifact.Artifact{
   175  		Type: artifact.LinuxPackage,
   176  		Name: "bin.deb",
   177  		Path: debfile.Name(),
   178  	})
   179  
   180  	var uploads sync.Map
   181  
   182  	// Dummy http server
   183  	mux.HandleFunc("/example-repo-local/goreleaser/1.0.0/bin.tar.gz", func(w http.ResponseWriter, r *http.Request) {
   184  		requireMethodPut(t, r)
   185  		// Basic auth of user "deployuser" with secret "deployuser-secret"
   186  		requireHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
   187  
   188  		w.Header().Set("Location", "/example-repo-local/goreleaser/1.0.0/bin.tar.gz")
   189  		w.WriteHeader(http.StatusCreated)
   190  		uploads.Store("targz", true)
   191  	})
   192  	mux.HandleFunc("/example-repo-local/goreleaser/1.0.0/bin.deb", func(w http.ResponseWriter, r *http.Request) {
   193  		requireMethodPut(t, r)
   194  		// Basic auth of user "deployuser" with secret "deployuser-secret"
   195  		requireHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
   196  
   197  		w.Header().Set("Location", "/example-repo-local/goreleaser/1.0.0/bin.deb")
   198  		w.WriteHeader(http.StatusCreated)
   199  		uploads.Store("deb", true)
   200  	})
   201  
   202  	require.NoError(t, Pipe{}.Publish(ctx))
   203  	_, ok := uploads.Load("targz")
   204  	require.True(t, ok, "tar.gz file was not uploaded")
   205  	_, ok = uploads.Load("deb")
   206  	require.True(t, ok, "deb file was not uploaded")
   207  }
   208  
   209  func TestRunPipe_ModeBinary_CustomArtifactName(t *testing.T) {
   210  	setup()
   211  	defer teardown()
   212  
   213  	folder := t.TempDir()
   214  	dist := filepath.Join(folder, "dist")
   215  	require.NoError(t, os.Mkdir(dist, 0o755))
   216  	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
   217  	binPath := filepath.Join(dist, "mybin", "mybin")
   218  	d1 := []byte("hello\ngo\n")
   219  	require.NoError(t, os.WriteFile(binPath, d1, 0o666))
   220  
   221  	// Dummy http server
   222  	mux.HandleFunc("/example-repo-local/mybin/darwin/amd64/mybin;deb.distribution=xenial", func(w http.ResponseWriter, r *http.Request) {
   223  		requireMethodPut(t, r)
   224  		requireHeader(t, r, "Content-Length", "9")
   225  		// Basic auth of user "deployuser" with secret "deployuser-secret"
   226  		requireHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
   227  
   228  		w.Header().Set("Location", "/production-repo-remote/mybin/linux/amd64/mybin;deb.distribution=xenial")
   229  		w.WriteHeader(http.StatusCreated)
   230  	})
   231  	mux.HandleFunc("/example-repo-local/mybin/linux/amd64/mybin;deb.distribution=xenial", func(w http.ResponseWriter, r *http.Request) {
   232  		requireMethodPut(t, r)
   233  		requireHeader(t, r, "Content-Length", "9")
   234  		// Basic auth of user "deployuser" with secret "deployuser-secret"
   235  		requireHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
   236  
   237  		w.Header().Set("Location", "/example-repo-local/mybin/linux/amd64/mybin;deb.distribution=xenial")
   238  		w.WriteHeader(http.StatusCreated)
   239  	})
   240  
   241  	ctx := testctx.NewWithCfg(config.Project{
   242  		ProjectName: "mybin",
   243  		Dist:        dist,
   244  		Uploads: []config.Upload{
   245  			{
   246  				Method:             http.MethodPut,
   247  				Name:               "production-us",
   248  				Mode:               "binary",
   249  				Target:             fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}/{{ .ArtifactName }};deb.distribution=xenial", server.URL),
   250  				Username:           "deployuser",
   251  				CustomArtifactName: true,
   252  			},
   253  		},
   254  		Archives: []config.Archive{{}},
   255  		Env:      []string{"UPLOAD_PRODUCTION-US_SECRET=deployuser-secret"},
   256  	})
   257  	for _, goos := range []string{"linux", "darwin"} {
   258  		ctx.Artifacts.Add(&artifact.Artifact{
   259  			Name:   "mybin",
   260  			Path:   binPath,
   261  			Goarch: "amd64",
   262  			Goos:   goos,
   263  			Type:   artifact.UploadableBinary,
   264  		})
   265  	}
   266  
   267  	require.NoError(t, Pipe{}.Publish(ctx))
   268  }
   269  
   270  func TestRunPipe_ModeArchive_CustomArtifactName(t *testing.T) {
   271  	setup()
   272  	defer teardown()
   273  
   274  	folder := t.TempDir()
   275  	tarfile, err := os.Create(filepath.Join(folder, "bin.tar.gz"))
   276  	require.NoError(t, err)
   277  	require.NoError(t, tarfile.Close())
   278  	debfile, err := os.Create(filepath.Join(folder, "bin.deb"))
   279  	require.NoError(t, err)
   280  	require.NoError(t, debfile.Close())
   281  
   282  	ctx := testctx.NewWithCfg(config.Project{
   283  		ProjectName: "goreleaser",
   284  		Dist:        folder,
   285  		Uploads: []config.Upload{
   286  			{
   287  				Method:             http.MethodPut,
   288  				Name:               "production",
   289  				Mode:               "archive",
   290  				Target:             fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Version }}/{{ .ArtifactName }};deb.distribution=xenial", server.URL),
   291  				Username:           "deployuser",
   292  				CustomArtifactName: true,
   293  			},
   294  		},
   295  		Archives: []config.Archive{{}},
   296  		Env: []string{
   297  			"UPLOAD_PRODUCTION_SECRET=deployuser-secret",
   298  		},
   299  	}, testctx.WithVersion("1.0.0"))
   300  	ctx.Artifacts.Add(&artifact.Artifact{
   301  		Type: artifact.UploadableArchive,
   302  		Name: "bin.tar.gz",
   303  		Path: tarfile.Name(),
   304  	})
   305  	ctx.Artifacts.Add(&artifact.Artifact{
   306  		Type: artifact.LinuxPackage,
   307  		Name: "bin.deb",
   308  		Path: debfile.Name(),
   309  	})
   310  
   311  	var uploads sync.Map
   312  
   313  	// Dummy http server
   314  	mux.HandleFunc("/example-repo-local/goreleaser/1.0.0/bin.tar.gz;deb.distribution=xenial", func(w http.ResponseWriter, r *http.Request) {
   315  		requireMethodPut(t, r)
   316  		// Basic auth of user "deployuser" with secret "deployuser-secret"
   317  		requireHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
   318  
   319  		w.Header().Set("Location", "/example-repo-local/goreleaser/1.0.0/bin.tar.gz;deb.distribution=xenial")
   320  		w.WriteHeader(http.StatusCreated)
   321  		uploads.Store("targz", true)
   322  	})
   323  	mux.HandleFunc("/example-repo-local/goreleaser/1.0.0/bin.deb;deb.distribution=xenial", func(w http.ResponseWriter, r *http.Request) {
   324  		requireMethodPut(t, r)
   325  		// Basic auth of user "deployuser" with secret "deployuser-secret"
   326  		requireHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
   327  
   328  		w.Header().Set("Location", "/example-repo-local/goreleaser/1.0.0/bin.deb;deb.distribution=xenial")
   329  		w.WriteHeader(http.StatusCreated)
   330  		uploads.Store("deb", true)
   331  	})
   332  
   333  	require.NoError(t, Pipe{}.Publish(ctx))
   334  	_, ok := uploads.Load("targz")
   335  	require.True(t, ok, "tar.gz file was not uploaded")
   336  	_, ok = uploads.Load("deb")
   337  	require.True(t, ok, "deb file was not uploaded")
   338  }
   339  
   340  func TestRunPipe_ServerDown(t *testing.T) {
   341  	folder := t.TempDir()
   342  	tarfile, err := os.Create(filepath.Join(folder, "bin.tar.gz"))
   343  	require.NoError(t, err)
   344  	require.NoError(t, tarfile.Close())
   345  
   346  	ctx := testctx.NewWithCfg(config.Project{
   347  		ProjectName: "goreleaser",
   348  		Dist:        folder,
   349  		Uploads: []config.Upload{
   350  			{
   351  				Method:   http.MethodPut,
   352  				Name:     "production",
   353  				Mode:     "archive",
   354  				Target:   "http://localhost:1234/example-repo-local/{{ .ProjectName }}/{{ .Version }}/",
   355  				Username: "deployuser",
   356  			},
   357  		},
   358  		Env: []string{"UPLOAD_PRODUCTION_SECRET=deployuser-secret"},
   359  	}, testctx.WithVersion("2.0.0"))
   360  	ctx.Artifacts.Add(&artifact.Artifact{
   361  		Type: artifact.UploadableArchive,
   362  		Name: "bin.tar.gz",
   363  		Path: tarfile.Name(),
   364  	})
   365  	require.ErrorIs(t, Pipe{}.Publish(ctx), syscall.ECONNREFUSED)
   366  }
   367  
   368  func TestRunPipe_TargetTemplateError(t *testing.T) {
   369  	folder := t.TempDir()
   370  	dist := filepath.Join(folder, "dist")
   371  	binPath := filepath.Join(dist, "mybin", "mybin")
   372  
   373  	ctx := testctx.NewWithCfg(config.Project{
   374  		ProjectName: "mybin",
   375  		Dist:        dist,
   376  		Uploads: []config.Upload{
   377  			{
   378  				Method: http.MethodPut,
   379  				Name:   "production",
   380  				Mode:   "binary",
   381  				// This template is not correct and should fail
   382  				Target:   "http://storage.company.com/example-repo-local/{{ .ProjectName}",
   383  				Username: "deployuser",
   384  			},
   385  		},
   386  		Archives: []config.Archive{{}},
   387  		Env:      []string{"UPLOAD_PRODUCTION_SECRET=deployuser-secret"},
   388  	})
   389  	ctx.Artifacts.Add(&artifact.Artifact{
   390  		Name:   "mybin",
   391  		Path:   binPath,
   392  		Goarch: "amd64",
   393  		Goos:   "darwin",
   394  		Type:   artifact.UploadableBinary,
   395  	})
   396  	testlib.RequireTemplateError(t, Pipe{}.Publish(ctx))
   397  }
   398  
   399  func TestRunPipe_BadCredentials(t *testing.T) {
   400  	setup()
   401  	defer teardown()
   402  
   403  	folder := t.TempDir()
   404  	dist := filepath.Join(folder, "dist")
   405  	require.NoError(t, os.Mkdir(dist, 0o755))
   406  	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
   407  	binPath := filepath.Join(dist, "mybin", "mybin")
   408  	d1 := []byte("hello\ngo\n")
   409  	require.NoError(t, os.WriteFile(binPath, d1, 0o666))
   410  
   411  	// Dummy http server
   412  	mux.HandleFunc("/example-repo-local/mybin/darwin/amd64/mybin", func(w http.ResponseWriter, r *http.Request) {
   413  		requireMethodPut(t, r)
   414  		requireHeader(t, r, "Content-Length", "9")
   415  		// Basic auth of user "deployuser" with secret "deployuser-secret"
   416  		requireHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
   417  
   418  		w.WriteHeader(http.StatusUnauthorized)
   419  	})
   420  
   421  	ctx := testctx.NewWithCfg(config.Project{
   422  		ProjectName: "mybin",
   423  		Dist:        dist,
   424  		Uploads: []config.Upload{
   425  			{
   426  				Method:   http.MethodPut,
   427  				Name:     "production",
   428  				Mode:     "binary",
   429  				Target:   fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL),
   430  				Username: "deployuser",
   431  			},
   432  		},
   433  		Archives: []config.Archive{{}},
   434  		Env:      []string{"UPLOAD_PRODUCTION_SECRET=deployuser-secret"},
   435  	})
   436  	ctx.Artifacts.Add(&artifact.Artifact{
   437  		Name:   "mybin",
   438  		Path:   binPath,
   439  		Goarch: "amd64",
   440  		Goos:   "darwin",
   441  		Type:   artifact.UploadableBinary,
   442  	})
   443  
   444  	err := Pipe{}.Publish(ctx)
   445  	require.ErrorContains(t, err, "Unauthorized")
   446  }
   447  
   448  func TestRunPipe_FileNotFound(t *testing.T) {
   449  	ctx := context.New(config.Project{
   450  		ProjectName: "mybin",
   451  		Dist:        "archivetest/dist",
   452  		Uploads: []config.Upload{
   453  			{
   454  				Method:   http.MethodPut,
   455  				Name:     "production",
   456  				Mode:     "binary",
   457  				Target:   "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}",
   458  				Username: "deployuser",
   459  			},
   460  		},
   461  		Archives: []config.Archive{{}},
   462  		Env:      []string{"UPLOAD_PRODUCTION_SECRET=deployuser-secret"},
   463  	})
   464  	ctx.Artifacts.Add(&artifact.Artifact{
   465  		Name:   "mybin",
   466  		Path:   "archivetest/dist/mybin/mybin",
   467  		Goarch: "amd64",
   468  		Goos:   "darwin",
   469  		Type:   artifact.UploadableBinary,
   470  	})
   471  
   472  	require.ErrorIs(t, Pipe{}.Publish(ctx), os.ErrNotExist)
   473  }
   474  
   475  func TestRunPipe_UnparsableTarget(t *testing.T) {
   476  	folder := t.TempDir()
   477  	dist := filepath.Join(folder, "dist")
   478  	require.NoError(t, os.Mkdir(dist, 0o755))
   479  	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
   480  	binPath := filepath.Join(dist, "mybin", "mybin")
   481  	d1 := []byte("hello\ngo\n")
   482  	require.NoError(t, os.WriteFile(binPath, d1, 0o666))
   483  
   484  	ctx := testctx.NewWithCfg(config.Project{
   485  		ProjectName: "mybin",
   486  		Dist:        dist,
   487  		Uploads: []config.Upload{
   488  			{
   489  				Method:   http.MethodPut,
   490  				Name:     "production",
   491  				Mode:     "binary",
   492  				Target:   "://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}",
   493  				Username: "deployuser",
   494  			},
   495  		},
   496  		Archives: []config.Archive{
   497  			{},
   498  		},
   499  		Env: []string{"UPLOAD_PRODUCTION_SECRET=deployuser-secret"},
   500  	})
   501  	ctx.Env = map[string]string{
   502  		"UPLOAD_PRODUCTION_SECRET": "deployuser-secret",
   503  	}
   504  	ctx.Artifacts.Add(&artifact.Artifact{
   505  		Name:   "mybin",
   506  		Path:   binPath,
   507  		Goarch: "amd64",
   508  		Goos:   "darwin",
   509  		Type:   artifact.UploadableBinary,
   510  	})
   511  
   512  	require.EqualError(t, Pipe{}.Publish(ctx), `production: upload: upload failed: parse "://artifacts.company.com/example-repo-local/mybin/darwin/amd64/mybin": missing protocol scheme`)
   513  }
   514  
   515  func TestRunPipe_DirUpload(t *testing.T) {
   516  	folder := t.TempDir()
   517  	dist := filepath.Join(folder, "dist")
   518  	require.NoError(t, os.Mkdir(dist, 0o755))
   519  	require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0o755))
   520  	binPath := filepath.Join(dist, "mybin")
   521  
   522  	ctx := testctx.NewWithCfg(config.Project{
   523  		ProjectName: "mybin",
   524  		Dist:        dist,
   525  		Uploads: []config.Upload{
   526  			{
   527  				Method:   http.MethodPut,
   528  				Name:     "production",
   529  				Mode:     "binary",
   530  				Target:   "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}",
   531  				Username: "deployuser",
   532  			},
   533  		},
   534  		Archives: []config.Archive{{}},
   535  		Env:      []string{"UPLOAD_PRODUCTION_SECRET=deployuser-secret"},
   536  	})
   537  	ctx.Artifacts.Add(&artifact.Artifact{
   538  		Name:   "mybin",
   539  		Path:   filepath.Dir(binPath),
   540  		Goarch: "amd64",
   541  		Goos:   "darwin",
   542  		Type:   artifact.UploadableBinary,
   543  	})
   544  
   545  	require.EqualError(t, Pipe{}.Publish(ctx), `upload: upload failed: the asset to upload can't be a directory`)
   546  }
   547  
   548  func TestDescription(t *testing.T) {
   549  	require.NotEmpty(t, Pipe{}.String())
   550  }
   551  
   552  func TestPutsWithoutTarget(t *testing.T) {
   553  	ctx := testctx.NewWithCfg(config.Project{
   554  		Uploads: []config.Upload{
   555  			{
   556  				Method:   http.MethodPut,
   557  				Name:     "production",
   558  				Username: "deployuser",
   559  			},
   560  		},
   561  		Env: []string{"UPLOAD_PRODUCTION_SECRET=deployuser-secret"},
   562  	})
   563  
   564  	require.True(t, pipe.IsSkip(Pipe{}.Publish(ctx)))
   565  }
   566  
   567  func TestPutsWithoutUsername(t *testing.T) {
   568  	ctx := testctx.NewWithCfg(config.Project{
   569  		Uploads: []config.Upload{
   570  			{
   571  				Method: http.MethodPut,
   572  				Name:   "production",
   573  				Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}",
   574  			},
   575  		},
   576  		Env: []string{"UPLOAD_PRODUCTION_SECRET=deployuser-secret"},
   577  	})
   578  
   579  	require.True(t, pipe.IsSkip(Pipe{}.Publish(ctx)))
   580  }
   581  
   582  func TestPutsWithoutName(t *testing.T) {
   583  	require.True(t, pipe.IsSkip(Pipe{}.Publish(testctx.NewWithCfg(config.Project{
   584  		Uploads: []config.Upload{
   585  			{
   586  				Method:   http.MethodPut,
   587  				Username: "deployuser",
   588  				Target:   "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}",
   589  			},
   590  		},
   591  	}))))
   592  }
   593  
   594  func TestPutsWithoutSecret(t *testing.T) {
   595  	require.True(t, pipe.IsSkip(Pipe{}.Publish(testctx.NewWithCfg(config.Project{
   596  		Uploads: []config.Upload{
   597  			{
   598  				Method:   http.MethodPut,
   599  				Name:     "production",
   600  				Target:   "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}",
   601  				Username: "deployuser",
   602  			},
   603  		},
   604  	}))))
   605  }
   606  
   607  func TestPutsWithInvalidMode(t *testing.T) {
   608  	ctx := testctx.NewWithCfg(config.Project{
   609  		Uploads: []config.Upload{
   610  			{
   611  				Method:   http.MethodPut,
   612  				Name:     "production",
   613  				Mode:     "does-not-exists",
   614  				Target:   "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}",
   615  				Username: "deployuser",
   616  			},
   617  		},
   618  		Env: []string{"UPLOAD_PRODUCTION_SECRET=deployuser-secret"},
   619  	})
   620  	require.Error(t, Pipe{}.Publish(ctx))
   621  }
   622  
   623  func TestDefault(t *testing.T) {
   624  	ctx := testctx.NewWithCfg(config.Project{
   625  		Uploads: []config.Upload{
   626  			{
   627  				Name:     "production",
   628  				Target:   "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}",
   629  				Username: "deployuser",
   630  			},
   631  		},
   632  	})
   633  	require.NoError(t, Pipe{}.Default(ctx))
   634  	require.Len(t, ctx.Config.Uploads, 1)
   635  	upload := ctx.Config.Uploads[0]
   636  	require.Equal(t, "archive", upload.Mode)
   637  	require.Equal(t, http.MethodPut, upload.Method)
   638  }
   639  
   640  func TestDefaultNoPuts(t *testing.T) {
   641  	ctx := testctx.NewWithCfg(config.Project{
   642  		Uploads: []config.Upload{},
   643  	})
   644  	require.NoError(t, Pipe{}.Default(ctx))
   645  	require.Empty(t, ctx.Config.Uploads)
   646  }
   647  
   648  func TestDefaultSet(t *testing.T) {
   649  	ctx := testctx.NewWithCfg(config.Project{
   650  		Uploads: []config.Upload{
   651  			{
   652  				Method: http.MethodPost,
   653  				Mode:   "custom",
   654  			},
   655  		},
   656  	})
   657  	require.NoError(t, Pipe{}.Default(ctx))
   658  	require.Len(t, ctx.Config.Uploads, 1)
   659  	upload := ctx.Config.Uploads[0]
   660  	require.Equal(t, "custom", upload.Mode)
   661  	require.Equal(t, http.MethodPost, upload.Method)
   662  }
   663  
   664  func TestSkip(t *testing.T) {
   665  	t.Run("skip", func(t *testing.T) {
   666  		require.True(t, Pipe{}.Skip(testctx.New()))
   667  	})
   668  
   669  	t.Run("dont skip", func(t *testing.T) {
   670  		ctx := testctx.NewWithCfg(config.Project{
   671  			Uploads: []config.Upload{
   672  				{},
   673  			},
   674  		})
   675  		require.False(t, Pipe{}.Skip(ctx))
   676  	})
   677  }