github.com/triarius/goreleaser@v1.12.5/internal/client/gitlab_test.go (about)

     1  package client
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"os"
    10  	"strconv"
    11  	"strings"
    12  	"testing"
    13  	"text/template"
    14  
    15  	"github.com/triarius/goreleaser/internal/artifact"
    16  	"github.com/triarius/goreleaser/pkg/config"
    17  	"github.com/triarius/goreleaser/pkg/context"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  func TestGitLabReleaseURLTemplate(t *testing.T) {
    22  	repo := config.Repo{
    23  		Owner: "owner",
    24  		Name:  "name",
    25  	}
    26  	tests := []struct {
    27  		name            string
    28  		repo            config.Repo
    29  		downloadURL     string
    30  		wantDownloadURL string
    31  		wantErr         bool
    32  	}{
    33  		{
    34  			name:            "default_download_url",
    35  			downloadURL:     DefaultGitLabDownloadURL,
    36  			repo:            repo,
    37  			wantDownloadURL: "https://gitlab.com/owner/name/-/releases/{{ .Tag }}/downloads/{{ .ArtifactName }}",
    38  		},
    39  		{
    40  			name:            "default_download_url_no_owner",
    41  			downloadURL:     DefaultGitLabDownloadURL,
    42  			repo:            config.Repo{Name: "name"},
    43  			wantDownloadURL: "https://gitlab.com/name/-/releases/{{ .Tag }}/downloads/{{ .ArtifactName }}",
    44  		},
    45  		{
    46  			name:            "download_url_template",
    47  			repo:            repo,
    48  			downloadURL:     "{{ .Env.GORELEASER_TEST_GITLAB_URLS_DOWNLOAD }}",
    49  			wantDownloadURL: "https://gitlab.mycompany.com/owner/name/-/releases/{{ .Tag }}/downloads/{{ .ArtifactName }}",
    50  		},
    51  		{
    52  			name:        "download_url_template_invalid_value",
    53  			downloadURL: "{{ .Eenv.GORELEASER_NOT_EXISTS }}",
    54  			wantErr:     true,
    55  		},
    56  		{
    57  			name:        "download_url_template_invalid",
    58  			downloadURL: "{{.dddddddddd",
    59  			wantErr:     true,
    60  		},
    61  		{
    62  			name:            "download_url_string",
    63  			downloadURL:     "https://gitlab.mycompany.com",
    64  			wantDownloadURL: "https://gitlab.mycompany.com/",
    65  		},
    66  	}
    67  
    68  	for _, tt := range tests {
    69  		ctx := context.New(config.Project{
    70  			Env: []string{
    71  				"GORELEASER_TEST_GITLAB_URLS_DOWNLOAD=https://gitlab.mycompany.com",
    72  			},
    73  			GitLabURLs: config.GitLabURLs{
    74  				Download: tt.downloadURL,
    75  			},
    76  			Release: config.Release{
    77  				GitLab: tt.repo,
    78  			},
    79  		})
    80  		client, err := NewGitLab(ctx, ctx.Token)
    81  		require.NoError(t, err)
    82  
    83  		urlTpl, err := client.ReleaseURLTemplate(ctx)
    84  		if tt.wantErr {
    85  			require.Error(t, err)
    86  			return
    87  		}
    88  
    89  		require.NoError(t, err)
    90  		require.Equal(t, tt.wantDownloadURL, urlTpl)
    91  	}
    92  }
    93  
    94  func TestGitLabURLsAPITemplate(t *testing.T) {
    95  	tests := []struct {
    96  		name     string
    97  		apiURL   string
    98  		wantHost string
    99  	}{
   100  		{
   101  			name:     "default_values",
   102  			wantHost: "gitlab.com",
   103  		},
   104  		{
   105  			name:     "speicifed_api_env_key",
   106  			apiURL:   "https://gitlab.mycompany.com",
   107  			wantHost: "gitlab.mycompany.com",
   108  		},
   109  	}
   110  
   111  	for _, tt := range tests {
   112  		t.Run(tt.name, func(t *testing.T) {
   113  			envs := []string{}
   114  			gitlabURLs := config.GitLabURLs{}
   115  
   116  			if tt.apiURL != "" {
   117  				envs = append(envs, fmt.Sprintf("GORELEASER_TEST_GITLAB_URLS_API=%s", tt.apiURL))
   118  				gitlabURLs.API = "{{ .Env.GORELEASER_TEST_GITLAB_URLS_API }}"
   119  			}
   120  
   121  			ctx := context.New(config.Project{
   122  				Env:        envs,
   123  				GitLabURLs: gitlabURLs,
   124  			})
   125  
   126  			client, err := NewGitLab(ctx, ctx.Token)
   127  			require.NoError(t, err)
   128  
   129  			gitlabClient, ok := client.(*gitlabClient)
   130  			require.True(t, ok)
   131  
   132  			require.Equal(t, tt.wantHost, gitlabClient.client.BaseURL().Host)
   133  		})
   134  	}
   135  
   136  	t.Run("no_env_specified", func(t *testing.T) {
   137  		ctx := context.New(config.Project{
   138  			GitLabURLs: config.GitLabURLs{
   139  				API: "{{ .Env.GORELEASER_NOT_EXISTS }}",
   140  			},
   141  		})
   142  
   143  		_, err := NewGitLab(ctx, ctx.Token)
   144  		require.ErrorAs(t, err, &template.ExecError{})
   145  	})
   146  
   147  	t.Run("invalid_template", func(t *testing.T) {
   148  		ctx := context.New(config.Project{
   149  			GitLabURLs: config.GitLabURLs{
   150  				API: "{{.dddddddddd",
   151  			},
   152  		})
   153  
   154  		_, err := NewGitLab(ctx, ctx.Token)
   155  		require.Error(t, err)
   156  	})
   157  }
   158  
   159  func TestGitLabURLsDownloadTemplate(t *testing.T) {
   160  	tests := []struct {
   161  		name               string
   162  		usePackageRegistry bool
   163  		downloadURL        string
   164  		wantURL            string
   165  		wantErr            bool
   166  	}{
   167  		{
   168  			name:    "empty_download_url",
   169  			wantURL: "/",
   170  		},
   171  		{
   172  			name:        "download_url_template",
   173  			downloadURL: "{{ .Env.GORELEASER_TEST_GITLAB_URLS_DOWNLOAD }}",
   174  			wantURL:     "https://gitlab.mycompany.com/",
   175  		},
   176  		{
   177  			name:        "download_url_template_invalid_value",
   178  			downloadURL: "{{ .Eenv.GORELEASER_NOT_EXISTS }}",
   179  			wantErr:     true,
   180  		},
   181  		{
   182  			name:        "download_url_template_invalid",
   183  			downloadURL: "{{.dddddddddd",
   184  			wantErr:     true,
   185  		},
   186  		{
   187  			name:        "download_url_string",
   188  			downloadURL: "https://gitlab.mycompany.com",
   189  			wantURL:     "https://gitlab.mycompany.com/",
   190  		},
   191  		{
   192  			name:               "url_registry",
   193  			wantURL:            "/api/v4/projects/test%2Ftest/packages/generic/projectname/v1%2E0%2E0/test",
   194  			usePackageRegistry: true,
   195  		},
   196  	}
   197  
   198  	for _, tt := range tests {
   199  		t.Run(tt.name, func(t *testing.T) {
   200  			srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   201  				defer fmt.Fprint(w, "{}")
   202  				defer w.WriteHeader(http.StatusOK)
   203  				defer r.Body.Close()
   204  
   205  				if !strings.Contains(r.URL.Path, "assets/links") {
   206  					_, _ = io.Copy(io.Discard, r.Body)
   207  					return
   208  				}
   209  
   210  				b, err := io.ReadAll(r.Body)
   211  				require.NoError(t, err)
   212  
   213  				reqBody := map[string]interface{}{}
   214  				err = json.Unmarshal(b, &reqBody)
   215  				require.NoError(t, err)
   216  
   217  				url := reqBody["url"].(string)
   218  				require.Truef(t, strings.HasSuffix(url, tt.wantURL), "expected %q to end with %q", url, tt.wantURL)
   219  			}))
   220  			defer srv.Close()
   221  
   222  			ctx := context.New(config.Project{
   223  				ProjectName: "projectname",
   224  				Env: []string{
   225  					"GORELEASER_TEST_GITLAB_URLS_DOWNLOAD=https://gitlab.mycompany.com",
   226  				},
   227  				Release: config.Release{
   228  					GitLab: config.Repo{
   229  						Owner: "test",
   230  						Name:  "test",
   231  					},
   232  				},
   233  				GitLabURLs: config.GitLabURLs{
   234  					API:                srv.URL,
   235  					Download:           tt.downloadURL,
   236  					UsePackageRegistry: tt.usePackageRegistry,
   237  				},
   238  			})
   239  
   240  			ctx.Version = "v1.0.0"
   241  
   242  			tmpFile, err := os.CreateTemp(t.TempDir(), "")
   243  			require.NoError(t, err)
   244  
   245  			client, err := NewGitLab(ctx, ctx.Token)
   246  			require.NoError(t, err)
   247  
   248  			err = client.Upload(ctx, "1234", &artifact.Artifact{Name: "test", Path: "some-path"}, tmpFile)
   249  			if tt.wantErr {
   250  				require.Error(t, err)
   251  				return
   252  			}
   253  			require.NoError(t, err)
   254  		})
   255  	}
   256  }
   257  
   258  func TestGitLabCreateReleaseUknownHost(t *testing.T) {
   259  	ctx := context.New(config.Project{
   260  		Release: config.Release{
   261  			GitLab: config.Repo{
   262  				Owner: "owner",
   263  				Name:  "name",
   264  			},
   265  		},
   266  		GitLabURLs: config.GitLabURLs{
   267  			API: "http://goreleaser-notexists",
   268  		},
   269  	})
   270  	client, err := NewGitLab(ctx, "test-token")
   271  	require.NoError(t, err)
   272  
   273  	_, err = client.CreateRelease(ctx, "body")
   274  	require.Error(t, err)
   275  }
   276  
   277  func TestGitLabCreateReleaseReleaseNotExists(t *testing.T) {
   278  	notExistsStatusCodes := []int{http.StatusNotFound, http.StatusForbidden}
   279  
   280  	for _, tt := range notExistsStatusCodes {
   281  		t.Run(strconv.Itoa(tt), func(t *testing.T) {
   282  			totalRequests := 0
   283  			createdRelease := false
   284  			srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   285  				defer r.Body.Close()
   286  				totalRequests++
   287  
   288  				if !strings.Contains(r.URL.Path, "releases") {
   289  					w.WriteHeader(http.StatusOK)
   290  					fmt.Fprint(w, "{}")
   291  					return
   292  				}
   293  
   294  				// Check if release exists
   295  				if r.Method == http.MethodGet {
   296  					w.WriteHeader(tt)
   297  					fmt.Fprint(w, "{}")
   298  					return
   299  				}
   300  
   301  				// Create release if it doens't exists
   302  				if r.Method == http.MethodPost {
   303  					createdRelease = true
   304  					w.WriteHeader(http.StatusOK)
   305  					fmt.Fprint(w, "{}")
   306  					return
   307  				}
   308  
   309  				require.FailNow(t, "should not reach here")
   310  			}))
   311  			defer srv.Close()
   312  
   313  			ctx := context.New(config.Project{
   314  				GitLabURLs: config.GitLabURLs{
   315  					API: srv.URL,
   316  				},
   317  			})
   318  			client, err := NewGitLab(ctx, "test-token")
   319  			require.NoError(t, err)
   320  
   321  			_, err = client.CreateRelease(ctx, "body")
   322  			require.NoError(t, err)
   323  			require.True(t, createdRelease)
   324  			require.Equal(t, 3, totalRequests)
   325  		})
   326  	}
   327  }
   328  
   329  func TestGitLabCreateReleaseUnkownHTTPError(t *testing.T) {
   330  	totalRequests := 0
   331  	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   332  		totalRequests++
   333  		defer r.Body.Close()
   334  
   335  		w.WriteHeader(http.StatusUnprocessableEntity)
   336  		fmt.Fprint(w, "{}")
   337  	}))
   338  	defer srv.Close()
   339  
   340  	ctx := context.New(config.Project{
   341  		GitLabURLs: config.GitLabURLs{
   342  			API: srv.URL,
   343  		},
   344  	})
   345  	client, err := NewGitLab(ctx, "test-token")
   346  	require.NoError(t, err)
   347  
   348  	_, err = client.CreateRelease(ctx, "body")
   349  	require.Error(t, err)
   350  	require.Equal(t, 2, totalRequests)
   351  }
   352  
   353  func TestGitlabGetDefaultBranch(t *testing.T) {
   354  	totalRequests := 0
   355  	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   356  		totalRequests++
   357  		defer r.Body.Close()
   358  
   359  		// Assume the request to create a branch was good
   360  		w.WriteHeader(http.StatusOK)
   361  		fmt.Fprint(w, "{}")
   362  	}))
   363  	defer srv.Close()
   364  
   365  	ctx := context.New(config.Project{
   366  		GitLabURLs: config.GitLabURLs{
   367  			API: srv.URL,
   368  		},
   369  	})
   370  	client, err := NewGitLab(ctx, "test-token")
   371  	require.NoError(t, err)
   372  	repo := Repo{
   373  		Owner:  "someone",
   374  		Name:   "something",
   375  		Branch: "somebranch",
   376  	}
   377  
   378  	_, err = client.GetDefaultBranch(ctx, repo)
   379  	require.NoError(t, err)
   380  	require.Equal(t, 2, totalRequests)
   381  }
   382  
   383  func TestGitlabGetDefaultBranchErr(t *testing.T) {
   384  	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   385  		defer r.Body.Close()
   386  
   387  		// Assume the request to create a branch was good
   388  		w.WriteHeader(http.StatusNotImplemented)
   389  		fmt.Fprint(w, "{}")
   390  	}))
   391  	defer srv.Close()
   392  
   393  	ctx := context.New(config.Project{
   394  		GitLabURLs: config.GitLabURLs{
   395  			API: srv.URL,
   396  		},
   397  	})
   398  	client, err := NewGitLab(ctx, "test-token")
   399  	require.NoError(t, err)
   400  	repo := Repo{
   401  		Owner:  "someone",
   402  		Name:   "something",
   403  		Branch: "somebranch",
   404  	}
   405  
   406  	_, err = client.GetDefaultBranch(ctx, repo)
   407  	require.Error(t, err)
   408  }
   409  
   410  func TestGitlabChangelog(t *testing.T) {
   411  	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   412  		if strings.HasSuffix(r.URL.Path, "projects/someone/something/repository/compare") {
   413  			r, err := os.Open("testdata/gitlab/compare.json")
   414  			require.NoError(t, err)
   415  			_, err = io.Copy(w, r)
   416  			require.NoError(t, err)
   417  			return
   418  		}
   419  		defer r.Body.Close()
   420  	}))
   421  	defer srv.Close()
   422  
   423  	ctx := context.New(config.Project{
   424  		GitLabURLs: config.GitLabURLs{
   425  			API: srv.URL,
   426  		},
   427  	})
   428  	client, err := NewGitLab(ctx, "test-token")
   429  	require.NoError(t, err)
   430  	repo := Repo{
   431  		Owner:  "someone",
   432  		Name:   "something",
   433  		Branch: "somebranch",
   434  	}
   435  
   436  	log, err := client.Changelog(ctx, repo, "v1.0.0", "v1.1.0")
   437  	require.NoError(t, err)
   438  	require.Equal(t, "6dcb09b5: Fix all the bugs (Joey User <joey@user.edu>)", log)
   439  }
   440  
   441  func TestGitlabCreateFile(t *testing.T) {
   442  	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   443  		// Handle the test where we know the branch
   444  		if strings.HasSuffix(r.URL.Path, "projects/someone/something/repository/files/newfile.txt") {
   445  			_, err := io.Copy(w, strings.NewReader(`{ "file_path": "newfile.txt", "branch": "somebranch" }`))
   446  			require.NoError(t, err)
   447  			return
   448  		}
   449  		// Handle the test where we detect the branch
   450  		if strings.HasSuffix(r.URL.Path, "projects/someone/something/repository/files/newfile-in-default.txt") {
   451  			_, err := io.Copy(w, strings.NewReader(`{ "file_path": "newfile.txt", "branch": "main" }`))
   452  			require.NoError(t, err)
   453  			return
   454  		}
   455  		// File of doooom...gets created, but 404s when getting fetched
   456  		if strings.HasSuffix(r.URL.Path, "projects/someone/something/repository/files/doomed-file-404.txt") {
   457  			if r.Method == "PUT" {
   458  				_, err := io.Copy(w, strings.NewReader(`{ "file_path": "doomed-file-404.txt", "branch": "main" }`))
   459  				require.NoError(t, err)
   460  			} else {
   461  				w.WriteHeader(http.StatusNotFound)
   462  			}
   463  			return
   464  		}
   465  
   466  		defer r.Body.Close()
   467  	}))
   468  	defer srv.Close()
   469  
   470  	ctx := context.New(config.Project{
   471  		GitLabURLs: config.GitLabURLs{
   472  			API: srv.URL,
   473  		},
   474  	})
   475  
   476  	client, err := NewGitLab(ctx, "test-token")
   477  	require.NoError(t, err)
   478  
   479  	// Test using an arbitrary branch
   480  	repo := Repo{
   481  		Owner:  "someone",
   482  		Name:   "something",
   483  		Branch: "somebranch",
   484  	}
   485  
   486  	err = client.CreateFile(ctx, config.CommitAuthor{Name: repo.Owner}, repo, []byte("Hello there"), "newfile.txt", "test: test commit")
   487  	require.NoError(t, err)
   488  
   489  	// Test detecting the default branch
   490  	repo = Repo{
   491  		Owner: "someone",
   492  		Name:  "something",
   493  		// Note there is no branch here, gonna try and guess it!
   494  	}
   495  
   496  	err = client.CreateFile(ctx, config.CommitAuthor{Name: repo.Owner}, repo, []byte("Hello there"), "newfile-in-default.txt", "test: test commit")
   497  	require.NoError(t, err)
   498  
   499  	// Test a doomed file. This is a file that is 'successfully' created, but returns a 404 when trying to fetch
   500  	repo = Repo{
   501  		Owner:  "someone",
   502  		Name:   "something",
   503  		Branch: "doomed",
   504  	}
   505  
   506  	err = client.CreateFile(ctx, config.CommitAuthor{Name: repo.Owner}, repo, []byte("Hello there"), "doomed-file-404.txt", "test: test commit")
   507  	require.Error(t, err)
   508  }
   509  
   510  func TestCloseMileston(t *testing.T) {
   511  	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   512  		if strings.HasSuffix(r.URL.Path, "projects/someone/something/milestones") {
   513  			r, err := os.Open("testdata/gitlab/milestones.json")
   514  			require.NoError(t, err)
   515  			_, err = io.Copy(w, r)
   516  			require.NoError(t, err)
   517  			return
   518  		} else if strings.HasSuffix(r.URL.Path, "projects/someone/something/milestones/12") {
   519  			r, err := os.Open("testdata/gitlab/milestone.json")
   520  			require.NoError(t, err)
   521  			_, err = io.Copy(w, r)
   522  			require.NoError(t, err)
   523  			return
   524  		}
   525  		defer r.Body.Close()
   526  	}))
   527  	defer srv.Close()
   528  
   529  	ctx := context.New(config.Project{
   530  		GitLabURLs: config.GitLabURLs{
   531  			API: srv.URL,
   532  		},
   533  	})
   534  	client, err := NewGitLab(ctx, "test-token")
   535  	require.NoError(t, err)
   536  
   537  	repo := Repo{
   538  		Owner: "someone",
   539  		Name:  "something",
   540  	}
   541  
   542  	err = client.CloseMilestone(ctx, repo, "10.0")
   543  	require.NoError(t, err)
   544  
   545  	// Be sure to error on missing milestones
   546  	err = client.CloseMilestone(ctx, repo, "never-will-exist")
   547  	require.Error(t, err)
   548  }
   549  
   550  func TestCheckUseJobToken(t *testing.T) {
   551  	tests := []struct {
   552  		useJobToken bool
   553  		token       string
   554  		ciToken     string
   555  		want        bool
   556  		desc        string
   557  		name        string
   558  	}{
   559  		{
   560  			useJobToken: true,
   561  			token:       "real-ci-token",
   562  			ciToken:     "real-ci-token",
   563  			desc:        "token and CI_JOB_TOKEN match so should return true",
   564  			want:        true,
   565  			name:        "UseJobToken-tokens-equal",
   566  		},
   567  		{
   568  			useJobToken: true,
   569  			token:       "some-random-token",
   570  			ciToken:     "real-ci-token",
   571  			desc:        "token and CI_JOB_TOKEN do NOT match so should return false",
   572  			want:        false,
   573  			name:        "UseJobToken-tokens-diff",
   574  		},
   575  		{
   576  			useJobToken: false,
   577  			token:       "real-ci-token",
   578  			ciToken:     "real-ci-token",
   579  			desc:        "token and CI_JOB_TOKEN match, however UseJobToken is set to false, so return false",
   580  			want:        false,
   581  			name:        "NoUseJobToken-tokens-equal",
   582  		},
   583  		{
   584  			useJobToken: false,
   585  			token:       "real-ci-token",
   586  			ciToken:     "real-ci-token",
   587  			desc:        "token and CI_JOB_TOKEN do not match, and UseJobToken is set to false, should return false",
   588  			want:        false,
   589  			name:        "NoUseJobToken-tokens-diff",
   590  		},
   591  	}
   592  	for _, tt := range tests {
   593  		t.Run(tt.name, func(t *testing.T) {
   594  			t.Setenv("CI_JOB_TOKEN", tt.ciToken)
   595  			ctx := context.New(config.Project{
   596  				GitLabURLs: config.GitLabURLs{
   597  					UseJobToken: tt.useJobToken,
   598  				},
   599  			})
   600  			got := checkUseJobToken(*ctx, tt.token)
   601  			require.Equal(t, tt.want, got, tt.desc)
   602  		})
   603  	}
   604  }