github.com/goreleaser/goreleaser@v1.25.1/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/goreleaser/goreleaser/internal/artifact" 16 "github.com/goreleaser/goreleaser/internal/testctx" 17 "github.com/goreleaser/goreleaser/pkg/config" 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 := testctx.NewWithCfg(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 := testctx.NewWithCfg(config.Project{ 122 Env: envs, 123 GitLabURLs: gitlabURLs, 124 }) 125 126 client, err := newGitLab(ctx, ctx.Token) 127 require.NoError(t, err) 128 require.Equal(t, tt.wantHost, client.client.BaseURL().Host) 129 }) 130 } 131 132 t.Run("no_env_specified", func(t *testing.T) { 133 ctx := testctx.NewWithCfg(config.Project{ 134 GitLabURLs: config.GitLabURLs{ 135 API: "{{ .Env.GORELEASER_NOT_EXISTS }}", 136 }, 137 }) 138 139 _, err := newGitLab(ctx, ctx.Token) 140 require.ErrorAs(t, err, &template.ExecError{}) 141 }) 142 143 t.Run("invalid_template", func(t *testing.T) { 144 ctx := testctx.NewWithCfg(config.Project{ 145 GitLabURLs: config.GitLabURLs{ 146 API: "{{.dddddddddd", 147 }, 148 }) 149 150 _, err := newGitLab(ctx, ctx.Token) 151 require.Error(t, err) 152 }) 153 } 154 155 func TestGitLabURLsDownloadTemplate(t *testing.T) { 156 tests := []struct { 157 name string 158 usePackageRegistry bool 159 downloadURL string 160 wantURL string 161 wantErr bool 162 }{ 163 { 164 name: "empty_download_url", 165 wantURL: "/", 166 }, 167 { 168 name: "download_url_template", 169 downloadURL: "{{ .Env.GORELEASER_TEST_GITLAB_URLS_DOWNLOAD }}", 170 wantURL: "https://gitlab.mycompany.com/", 171 }, 172 { 173 name: "download_url_template_invalid_value", 174 downloadURL: "{{ .Eenv.GORELEASER_NOT_EXISTS }}", 175 wantErr: true, 176 }, 177 { 178 name: "download_url_template_invalid", 179 downloadURL: "{{.dddddddddd", 180 wantErr: true, 181 }, 182 { 183 name: "download_url_string", 184 downloadURL: "https://gitlab.mycompany.com", 185 wantURL: "https://gitlab.mycompany.com/", 186 }, 187 { 188 name: "url_registry", 189 wantURL: "/api/v4/projects/test%2Ftest/packages/generic/projectname/1%2E0%2E0/test", 190 usePackageRegistry: true, 191 }, 192 } 193 194 for _, tt := range tests { 195 t.Run(tt.name, func(t *testing.T) { 196 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 197 defer fmt.Fprint(w, "{}") 198 defer w.WriteHeader(http.StatusOK) 199 defer r.Body.Close() 200 201 if !strings.Contains(r.URL.Path, "assets/links") { 202 _, _ = io.Copy(io.Discard, r.Body) 203 return 204 } 205 206 b, err := io.ReadAll(r.Body) 207 require.NoError(t, err) 208 209 reqBody := map[string]interface{}{} 210 err = json.Unmarshal(b, &reqBody) 211 require.NoError(t, err) 212 213 url := reqBody["url"].(string) 214 require.Truef(t, strings.HasSuffix(url, tt.wantURL), "expected %q to end with %q", url, tt.wantURL) 215 })) 216 defer srv.Close() 217 218 ctx := testctx.NewWithCfg(config.Project{ 219 ProjectName: "projectname", 220 Env: []string{ 221 "GORELEASER_TEST_GITLAB_URLS_DOWNLOAD=https://gitlab.mycompany.com", 222 }, 223 Release: config.Release{ 224 GitLab: config.Repo{ 225 Owner: "test", 226 Name: "test", 227 }, 228 }, 229 GitLabURLs: config.GitLabURLs{ 230 API: srv.URL, 231 Download: tt.downloadURL, 232 UsePackageRegistry: tt.usePackageRegistry, 233 }, 234 }, testctx.WithVersion("1.0.0")) 235 236 tmpFile, err := os.CreateTemp(t.TempDir(), "") 237 require.NoError(t, err) 238 239 client, err := newGitLab(ctx, ctx.Token) 240 require.NoError(t, err) 241 242 err = client.Upload(ctx, "1234", &artifact.Artifact{Name: "test", Path: "some-path"}, tmpFile) 243 if tt.wantErr { 244 require.Error(t, err) 245 return 246 } 247 require.NoError(t, err) 248 }) 249 } 250 } 251 252 func TestGitLabCreateReleaseUnknownHost(t *testing.T) { 253 ctx := testctx.NewWithCfg(config.Project{ 254 Release: config.Release{ 255 GitLab: config.Repo{ 256 Owner: "owner", 257 Name: "name", 258 }, 259 }, 260 GitLabURLs: config.GitLabURLs{ 261 API: "http://goreleaser-notexists", 262 }, 263 }) 264 client, err := newGitLab(ctx, "test-token") 265 require.NoError(t, err) 266 267 _, err = client.CreateRelease(ctx, "body") 268 require.Error(t, err) 269 } 270 271 func TestGitLabCreateReleaseReleaseNotExists(t *testing.T) { 272 notExistsStatusCodes := []int{http.StatusNotFound, http.StatusForbidden} 273 274 for _, tt := range notExistsStatusCodes { 275 t.Run(strconv.Itoa(tt), func(t *testing.T) { 276 totalRequests := 0 277 createdRelease := false 278 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 279 defer r.Body.Close() 280 totalRequests++ 281 282 if !strings.Contains(r.URL.Path, "releases") { 283 w.WriteHeader(http.StatusOK) 284 fmt.Fprint(w, "{}") 285 return 286 } 287 288 // Check if release exists 289 if r.Method == http.MethodGet { 290 w.WriteHeader(tt) 291 fmt.Fprint(w, "{}") 292 return 293 } 294 295 // Create release if it doesn't exist 296 if r.Method == http.MethodPost { 297 createdRelease = true 298 w.WriteHeader(http.StatusOK) 299 fmt.Fprint(w, "{}") 300 return 301 } 302 303 require.FailNow(t, "should not reach here") 304 })) 305 defer srv.Close() 306 307 ctx := testctx.NewWithCfg(config.Project{ 308 GitLabURLs: config.GitLabURLs{ 309 API: srv.URL, 310 }, 311 }) 312 client, err := newGitLab(ctx, "test-token") 313 require.NoError(t, err) 314 315 _, err = client.CreateRelease(ctx, "body") 316 require.NoError(t, err) 317 require.True(t, createdRelease) 318 require.Equal(t, 2, totalRequests) 319 }) 320 } 321 } 322 323 func TestGitLabCreateReleaseReleaseExists(t *testing.T) { 324 totalRequests := 0 325 createdRelease := false 326 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 327 defer r.Body.Close() 328 totalRequests++ 329 330 if !strings.Contains(r.URL.Path, "releases") { 331 w.WriteHeader(http.StatusOK) 332 fmt.Fprint(w, "{}") 333 return 334 } 335 336 // Check if release exists 337 if r.Method == http.MethodGet { 338 w.WriteHeader(200) 339 require.NoError(t, json.NewEncoder(w).Encode(map[string]string{ 340 "description": "original description", 341 })) 342 return 343 } 344 345 // Update release 346 if r.Method == http.MethodPut { 347 createdRelease = true 348 var resBody map[string]string 349 require.NoError(t, json.NewDecoder(r.Body).Decode(&resBody)) 350 require.Equal(t, "original description", resBody["description"]) 351 w.WriteHeader(http.StatusOK) 352 fmt.Fprint(w, "{}") 353 return 354 } 355 356 require.FailNow(t, "should not reach here") 357 })) 358 defer srv.Close() 359 360 ctx := testctx.NewWithCfg(config.Project{ 361 GitLabURLs: config.GitLabURLs{ 362 API: srv.URL, 363 }, 364 Release: config.Release{ 365 ReleaseNotesMode: config.ReleaseNotesModeKeepExisting, 366 }, 367 }) 368 client, err := newGitLab(ctx, "test-token") 369 require.NoError(t, err) 370 371 _, err = client.CreateRelease(ctx, "body") 372 require.NoError(t, err) 373 require.True(t, createdRelease) 374 require.Equal(t, 2, totalRequests) 375 } 376 377 func TestGitLabCreateReleaseUnknownHTTPError(t *testing.T) { 378 totalRequests := 0 379 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 380 totalRequests++ 381 defer r.Body.Close() 382 383 w.WriteHeader(http.StatusUnprocessableEntity) 384 fmt.Fprint(w, "{}") 385 })) 386 defer srv.Close() 387 388 ctx := testctx.NewWithCfg(config.Project{ 389 GitLabURLs: config.GitLabURLs{ 390 API: srv.URL, 391 }, 392 }) 393 client, err := newGitLab(ctx, "test-token") 394 require.NoError(t, err) 395 396 _, err = client.CreateRelease(ctx, "body") 397 require.Error(t, err) 398 require.Equal(t, 1, totalRequests) 399 } 400 401 func TestGitLabGetDefaultBranch(t *testing.T) { 402 totalRequests := 0 403 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 404 totalRequests++ 405 defer r.Body.Close() 406 407 // Assume the request to create a branch was good 408 w.WriteHeader(http.StatusOK) 409 fmt.Fprint(w, "{}") 410 })) 411 defer srv.Close() 412 413 ctx := testctx.NewWithCfg(config.Project{ 414 GitLabURLs: config.GitLabURLs{ 415 API: srv.URL, 416 }, 417 }) 418 client, err := newGitLab(ctx, "test-token") 419 require.NoError(t, err) 420 repo := Repo{ 421 Owner: "someone", 422 Name: "something", 423 Branch: "somebranch", 424 } 425 426 _, err = client.getDefaultBranch(ctx, repo) 427 require.NoError(t, err) 428 require.Equal(t, 1, totalRequests) 429 } 430 431 func TestGitLabGetDefaultBranchErr(t *testing.T) { 432 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 433 defer r.Body.Close() 434 435 // Assume the request to create a branch was good 436 w.WriteHeader(http.StatusNotImplemented) 437 fmt.Fprint(w, "{}") 438 })) 439 defer srv.Close() 440 441 ctx := testctx.NewWithCfg(config.Project{ 442 GitLabURLs: config.GitLabURLs{ 443 API: srv.URL, 444 }, 445 }) 446 client, err := newGitLab(ctx, "test-token") 447 require.NoError(t, err) 448 repo := Repo{ 449 Owner: "someone", 450 Name: "something", 451 Branch: "somebranch", 452 } 453 454 _, err = client.getDefaultBranch(ctx, repo) 455 require.Error(t, err) 456 } 457 458 func TestGitLabChangelog(t *testing.T) { 459 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 460 if strings.HasSuffix(r.URL.Path, "projects/someone/something/repository/compare") { 461 r, err := os.Open("testdata/gitlab/compare.json") 462 require.NoError(t, err) 463 _, err = io.Copy(w, r) 464 require.NoError(t, err) 465 return 466 } 467 defer r.Body.Close() 468 })) 469 defer srv.Close() 470 471 ctx := testctx.NewWithCfg(config.Project{ 472 GitLabURLs: config.GitLabURLs{ 473 API: srv.URL, 474 }, 475 }) 476 client, err := newGitLab(ctx, "test-token") 477 require.NoError(t, err) 478 repo := Repo{ 479 Owner: "someone", 480 Name: "something", 481 Branch: "somebranch", 482 } 483 484 log, err := client.Changelog(ctx, repo, "v1.0.0", "v1.1.0") 485 require.NoError(t, err) 486 require.Equal(t, "6dcb09b5: Fix all the bugs (Joey User <joey@user.edu>)", log) 487 } 488 489 func TestGitLabCreateFile(t *testing.T) { 490 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 491 // Handle the test where we know the branch 492 if strings.HasSuffix(r.URL.Path, "projects/someone/something/repository/files/newfile.txt") { 493 _, err := io.Copy(w, strings.NewReader(`{ "file_path": "newfile.txt", "branch": "somebranch" }`)) 494 require.NoError(t, err) 495 return 496 } 497 // Handle the test where we detect the branch 498 if strings.HasSuffix(r.URL.Path, "projects/someone/something/repository/files/newfile-in-default.txt") { 499 _, err := io.Copy(w, strings.NewReader(`{ "file_path": "newfile.txt", "branch": "main" }`)) 500 require.NoError(t, err) 501 return 502 } 503 // Handle the case with a projectID 504 if strings.HasSuffix(r.URL.Path, "projects/123456789/repository/files/newfile-projectID.txt") { 505 _, err := io.Copy(w, strings.NewReader(`{ "file_path": "newfile-projectID.txt", "branch": "main" }`)) 506 require.NoError(t, err) 507 return 508 } 509 // File of doooom...gets created, but 404s when getting fetched 510 if strings.HasSuffix(r.URL.Path, "projects/someone/something/repository/files/doomed-file-404.txt") { 511 if r.Method == "PUT" { 512 _, err := io.Copy(w, strings.NewReader(`{ "file_path": "doomed-file-404.txt", "branch": "main" }`)) 513 require.NoError(t, err) 514 } else { 515 w.WriteHeader(http.StatusNotFound) 516 } 517 return 518 } 519 520 defer r.Body.Close() 521 })) 522 defer srv.Close() 523 524 ctx := testctx.NewWithCfg(config.Project{ 525 GitLabURLs: config.GitLabURLs{ 526 API: srv.URL, 527 }, 528 }) 529 530 client, err := newGitLab(ctx, "test-token") 531 require.NoError(t, err) 532 533 // Test using an arbitrary branch 534 repo := Repo{ 535 Owner: "someone", 536 Name: "something", 537 Branch: "somebranch", 538 } 539 540 err = client.CreateFile(ctx, config.CommitAuthor{Name: repo.Owner}, repo, []byte("Hello there"), "newfile.txt", "test: test commit") 541 require.NoError(t, err) 542 543 // Test detecting the default branch 544 repo = Repo{ 545 Owner: "someone", 546 Name: "something", 547 // Note there is no branch here, gonna try and guess it! 548 } 549 550 err = client.CreateFile(ctx, config.CommitAuthor{Name: repo.Owner}, repo, []byte("Hello there"), "newfile-in-default.txt", "test: test commit") 551 require.NoError(t, err) 552 553 // Test using projectID 554 repo = Repo{ 555 Name: "123456789", 556 Branch: "main", 557 } 558 559 err = client.CreateFile(ctx, config.CommitAuthor{Name: repo.Owner}, repo, []byte("Hello there"), "newfile-projectID.txt", "test: test commit") 560 require.NoError(t, err) 561 562 // Test a doomed file. This is a file that is 'successfully' created, but returns a 404 when trying to fetch 563 repo = Repo{ 564 Owner: "someone", 565 Name: "something", 566 Branch: "doomed", 567 } 568 569 err = client.CreateFile(ctx, config.CommitAuthor{Name: repo.Owner}, repo, []byte("Hello there"), "doomed-file-404.txt", "test: test commit") 570 require.Error(t, err) 571 } 572 573 func TestGitLabCloseMilestone(t *testing.T) { 574 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 575 if strings.HasSuffix(r.URL.Path, "projects/someone/something/milestones") { 576 r, err := os.Open("testdata/gitlab/milestones.json") 577 require.NoError(t, err) 578 _, err = io.Copy(w, r) 579 require.NoError(t, err) 580 return 581 } else if strings.HasSuffix(r.URL.Path, "projects/someone/something/milestones/12") { 582 r, err := os.Open("testdata/gitlab/milestone.json") 583 require.NoError(t, err) 584 _, err = io.Copy(w, r) 585 require.NoError(t, err) 586 return 587 } 588 defer r.Body.Close() 589 })) 590 defer srv.Close() 591 592 ctx := testctx.NewWithCfg(config.Project{ 593 GitLabURLs: config.GitLabURLs{ 594 API: srv.URL, 595 }, 596 }) 597 client, err := newGitLab(ctx, "test-token") 598 require.NoError(t, err) 599 600 repo := Repo{ 601 Owner: "someone", 602 Name: "something", 603 } 604 605 err = client.CloseMilestone(ctx, repo, "10.0") 606 require.NoError(t, err) 607 608 // Be sure to error on missing milestones 609 err = client.CloseMilestone(ctx, repo, "never-will-exist") 610 require.Error(t, err) 611 } 612 613 func TestGitLabCheckUseJobToken(t *testing.T) { 614 tests := []struct { 615 useJobToken bool 616 token string 617 ciToken string 618 want bool 619 desc string 620 name string 621 }{ 622 { 623 useJobToken: true, 624 token: "real-ci-token", 625 ciToken: "real-ci-token", 626 desc: "token and CI_JOB_TOKEN match so should return true", 627 want: true, 628 name: "UseJobToken-tokens-equal", 629 }, 630 { 631 useJobToken: true, 632 token: "some-random-token", 633 ciToken: "real-ci-token", 634 desc: "token and CI_JOB_TOKEN do NOT match so should return false", 635 want: false, 636 name: "UseJobToken-tokens-diff", 637 }, 638 { 639 useJobToken: false, 640 token: "real-ci-token", 641 ciToken: "real-ci-token", 642 desc: "token and CI_JOB_TOKEN match, however UseJobToken is set to false, so return false", 643 want: false, 644 name: "NoUseJobToken-tokens-equal", 645 }, 646 { 647 useJobToken: false, 648 token: "real-ci-token", 649 ciToken: "real-ci-token", 650 desc: "token and CI_JOB_TOKEN do not match, and UseJobToken is set to false, should return false", 651 want: false, 652 name: "NoUseJobToken-tokens-diff", 653 }, 654 } 655 for _, tt := range tests { 656 t.Run(tt.name, func(t *testing.T) { 657 t.Setenv("CI_JOB_TOKEN", tt.ciToken) 658 ctx := testctx.NewWithCfg(config.Project{ 659 GitLabURLs: config.GitLabURLs{ 660 UseJobToken: tt.useJobToken, 661 }, 662 }) 663 got := checkUseJobToken(*ctx, tt.token) 664 require.Equal(t, tt.want, got, tt.desc) 665 }) 666 } 667 }