From dd136858f1ea40ad3c94191d647487fa4f31926c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 18 Oct 2024 20:33:49 +0200 Subject: Adding upstream version 9.0.0. Signed-off-by: Daniel Baumann --- services/migrations/gitlab_test.go | 646 +++++++++++++++++++++++++++++++++++++ 1 file changed, 646 insertions(+) create mode 100644 services/migrations/gitlab_test.go (limited to 'services/migrations/gitlab_test.go') diff --git a/services/migrations/gitlab_test.go b/services/migrations/gitlab_test.go new file mode 100644 index 0000000..39edba3 --- /dev/null +++ b/services/migrations/gitlab_test.go @@ -0,0 +1,646 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package migrations + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "os" + "strconv" + "testing" + "time" + + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/json" + base "code.gitea.io/gitea/modules/migration" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/xanzy/go-gitlab" +) + +func TestGitlabDownloadRepo(t *testing.T) { + // If a GitLab access token is provided, this test will make HTTP requests to the live gitlab.com instance. + // When doing so, the responses from gitlab.com will be saved as test data files. + // If no access token is available, those cached responses will be used instead. + gitlabPersonalAccessToken := os.Getenv("GITLAB_READ_TOKEN") + fixturePath := "./testdata/gitlab/full_download" + server := unittest.NewMockWebServer(t, "https://gitlab.com", fixturePath, gitlabPersonalAccessToken != "") + defer server.Close() + + downloader, err := NewGitlabDownloader(context.Background(), server.URL, "forgejo/test_repo", "", "", gitlabPersonalAccessToken) + if err != nil { + t.Fatalf("NewGitlabDownloader is nil: %v", err) + } + repo, err := downloader.GetRepoInfo() + require.NoError(t, err) + // Repo Owner is blank in Gitlab Group repos + assertRepositoryEqual(t, &base.Repository{ + Name: "test_repo", + Owner: "", + Description: "Test repository for testing migration from gitlab to forgejo", + CloneURL: server.URL + "/forgejo/test_repo.git", + OriginalURL: server.URL + "/forgejo/test_repo", + DefaultBranch: "master", + }, repo) + + topics, err := downloader.GetTopics() + require.NoError(t, err) + assert.Len(t, topics, 2) + assert.EqualValues(t, []string{"migration", "test"}, topics) + + milestones, err := downloader.GetMilestones() + require.NoError(t, err) + assertMilestonesEqual(t, []*base.Milestone{ + { + Title: "1.0.0", + Created: time.Date(2024, 9, 3, 13, 53, 8, 516000000, time.UTC), + Updated: timePtr(time.Date(2024, 9, 3, 20, 3, 57, 786000000, time.UTC)), + Closed: timePtr(time.Date(2024, 9, 3, 20, 3, 57, 786000000, time.UTC)), + State: "closed", + }, + { + Title: "1.1.0", + Created: time.Date(2024, 9, 3, 13, 52, 48, 414000000, time.UTC), + Updated: timePtr(time.Date(2024, 9, 3, 14, 52, 14, 93000000, time.UTC)), + State: "active", + }, + }, milestones) + + labels, err := downloader.GetLabels() + require.NoError(t, err) + assertLabelsEqual(t, []*base.Label{ + { + Name: "bug", + Color: "d9534f", + }, + { + Name: "confirmed", + Color: "d9534f", + }, + { + Name: "critical", + Color: "d9534f", + }, + { + Name: "discussion", + Color: "428bca", + }, + { + Name: "documentation", + Color: "f0ad4e", + }, + { + Name: "duplicate", + Color: "7f8c8d", + }, + { + Name: "enhancement", + Color: "5cb85c", + }, + { + Name: "suggestion", + Color: "428bca", + }, + { + Name: "support", + Color: "f0ad4e", + }, + { + Name: "test-scope/label0", + Color: "6699cc", + Description: "scoped label", + Exclusive: true, + }, + { + Name: "test-scope/label1", + Color: "dc143c", + Exclusive: true, + }, + }, labels) + + releases, err := downloader.GetReleases() + require.NoError(t, err) + assertReleasesEqual(t, []*base.Release{ + { + TagName: "v0.9.99", + TargetCommitish: "0720a3ec57c1f843568298117b874319e7deee75", + Name: "First Release", + Body: "A test release", + Created: time.Date(2024, 9, 3, 15, 1, 1, 513000000, time.UTC), + PublisherID: 548513, + PublisherName: "mkobel", + }, + }, releases) + + issues, isEnd, err := downloader.GetIssues(1, 2) + require.NoError(t, err) + assert.False(t, isEnd) + assertIssuesEqual(t, []*base.Issue{ + { + Number: 1, + Title: "Please add an animated gif icon to the merge button", + Content: "I just want the merge button to hurt my eyes a little. :stuck_out_tongue_closed_eyes:", + Milestone: "1.0.0", + PosterID: 548513, + PosterName: "mkobel", + State: "closed", + Created: time.Date(2024, 9, 3, 14, 42, 34, 924000000, time.UTC), + Updated: time.Date(2024, 9, 3, 14, 48, 43, 756000000, time.UTC), + Labels: []*base.Label{ + { + Name: "bug", + }, + { + Name: "discussion", + }, + }, + Reactions: []*base.Reaction{ + { + UserID: 548513, + UserName: "mkobel", + Content: "thumbsup", + }, + { + UserID: 548513, + UserName: "mkobel", + Content: "open_mouth", + }, + }, + Closed: timePtr(time.Date(2024, 9, 3, 14, 43, 10, 708000000, time.UTC)), + }, + { + Number: 2, + Title: "Test issue", + Content: "This is test issue 2, do not touch!", + Milestone: "1.0.0", + PosterID: 548513, + PosterName: "mkobel", + State: "closed", + Created: time.Date(2024, 9, 3, 14, 42, 35, 371000000, time.UTC), + Updated: time.Date(2024, 9, 3, 20, 3, 43, 536000000, time.UTC), + Labels: []*base.Label{ + { + Name: "duplicate", + }, + }, + Reactions: []*base.Reaction{ + { + UserID: 548513, + UserName: "mkobel", + Content: "thumbsup", + }, + { + UserID: 548513, + UserName: "mkobel", + Content: "thumbsdown", + }, + { + UserID: 548513, + UserName: "mkobel", + Content: "laughing", + }, + { + UserID: 548513, + UserName: "mkobel", + Content: "tada", + }, + { + UserID: 548513, + UserName: "mkobel", + Content: "confused", + }, + { + UserID: 548513, + UserName: "mkobel", + Content: "hearts", + }, + }, + Closed: timePtr(time.Date(2024, 9, 3, 14, 43, 10, 906000000, time.UTC)), + }, + }, issues) + + comments, _, err := downloader.GetComments(&base.Issue{ + Number: 2, + ForeignIndex: 2, + Context: gitlabIssueContext{IsMergeRequest: false}, + }) + require.NoError(t, err) + assertCommentsEqual(t, []*base.Comment{ + { + IssueIndex: 2, + PosterID: 548513, + PosterName: "mkobel", + Created: time.Date(2024, 9, 3, 14, 45, 20, 848000000, time.UTC), + Content: "This is a comment", + Reactions: nil, + }, + { + IssueIndex: 2, + PosterID: 548513, + PosterName: "mkobel", + Created: time.Date(2024, 9, 3, 14, 45, 30, 59000000, time.UTC), + Content: "A second comment", + Reactions: nil, + }, + { + IssueIndex: 2, + PosterID: 548513, + PosterName: "mkobel", + Created: time.Date(2024, 9, 3, 14, 43, 10, 947000000, time.UTC), + Content: "", + Reactions: nil, + CommentType: "close", + }, + }, comments) + + prs, _, err := downloader.GetPullRequests(1, 1) + require.NoError(t, err) + assertPullRequestsEqual(t, []*base.PullRequest{ + { + Number: 3, + Title: "Test branch", + Content: "do not merge this PR", + Milestone: "1.1.0", + PosterID: 2005797, + PosterName: "oliverpool", + State: "opened", + Created: time.Date(2024, 9, 3, 7, 57, 19, 866000000, time.UTC), + Labels: []*base.Label{ + { + Name: "test-scope/label0", + }, + { + Name: "test-scope/label1", + }, + }, + Reactions: []*base.Reaction{{ + UserID: 548513, + UserName: "mkobel", + Content: "thumbsup", + }, { + UserID: 548513, + UserName: "mkobel", + Content: "tada", + }}, + PatchURL: server.URL + "/forgejo/test_repo/-/merge_requests/1.patch", + Head: base.PullRequestBranch{ + Ref: "feat/test", + CloneURL: server.URL + "/forgejo/test_repo/-/merge_requests/1", + SHA: "9f733b96b98a4175276edf6a2e1231489c3bdd23", + RepoName: "test_repo", + OwnerName: "oliverpool", + }, + Base: base.PullRequestBranch{ + Ref: "master", + SHA: "c59c9b451acca9d106cc19d61d87afe3fbbb8b83", + OwnerName: "oliverpool", + RepoName: "test_repo", + }, + Closed: nil, + Merged: false, + MergedTime: nil, + MergeCommitSHA: "", + ForeignIndex: 2, + Context: gitlabIssueContext{IsMergeRequest: true}, + }, + }, prs) + + rvs, err := downloader.GetReviews(&base.PullRequest{Number: 1, ForeignIndex: 1}) + require.NoError(t, err) + assertReviewsEqual(t, []*base.Review{ + { + IssueIndex: 1, + ReviewerID: 548513, + ReviewerName: "mkobel", + CreatedAt: time.Date(2024, 9, 3, 7, 57, 19, 86600000, time.UTC), + State: "APPROVED", + }, + }, rvs) +} + +func TestGitlabSkippedIssueNumber(t *testing.T) { + // If a GitLab access token is provided, this test will make HTTP requests to the live gitlab.com instance. + // When doing so, the responses from gitlab.com will be saved as test data files. + // If no access token is available, those cached responses will be used instead. + gitlabPersonalAccessToken := os.Getenv("GITLAB_READ_TOKEN") + fixturePath := "./testdata/gitlab/skipped_issue_number" + server := unittest.NewMockWebServer(t, "https://gitlab.com", fixturePath, gitlabPersonalAccessToken != "") + defer server.Close() + + downloader, err := NewGitlabDownloader(context.Background(), server.URL, "troyengel/archbuild", "", "", gitlabPersonalAccessToken) + if err != nil { + t.Fatalf("NewGitlabDownloader is nil: %v", err) + } + repo, err := downloader.GetRepoInfo() + require.NoError(t, err) + assertRepositoryEqual(t, &base.Repository{ + Name: "archbuild", + Owner: "troyengel", + Description: "Arch packaging and build files", + CloneURL: server.URL + "/troyengel/archbuild.git", + OriginalURL: server.URL + "/troyengel/archbuild", + DefaultBranch: "master", + }, repo) + + issues, isEnd, err := downloader.GetIssues(1, 10) + require.NoError(t, err) + assert.True(t, isEnd) + + // the only issue in this repository has number 2 + assert.Len(t, issues, 1) + assert.EqualValues(t, 2, issues[0].Number) + assert.EqualValues(t, "vpn unlimited errors", issues[0].Title) + + prs, _, err := downloader.GetPullRequests(1, 10) + require.NoError(t, err) + // the only merge request in this repository has number 1, + // but we offset it by the maximum issue number so it becomes + // pull request 3 in Forgejo + assert.Len(t, prs, 1) + assert.EqualValues(t, 3, prs[0].Number) + assert.EqualValues(t, "Review", prs[0].Title) +} + +func gitlabClientMockSetup(t *testing.T) (*http.ServeMux, *httptest.Server, *gitlab.Client) { + // mux is the HTTP request multiplexer used with the test server. + mux := http.NewServeMux() + + // server is a test HTTP server used to provide mock API responses. + server := httptest.NewServer(mux) + + // client is the Gitlab client being tested. + client, err := gitlab.NewClient("", gitlab.WithBaseURL(server.URL)) + if err != nil { + server.Close() + t.Fatalf("Failed to create client: %v", err) + } + + return mux, server, client +} + +func gitlabClientMockTeardown(server *httptest.Server) { + server.Close() +} + +type reviewTestCase struct { + repoID, prID, reviewerID int + reviewerName string + createdAt, updatedAt *time.Time + expectedCreatedAt time.Time +} + +func convertTestCase(t reviewTestCase) (func(w http.ResponseWriter, r *http.Request), base.Review) { + var updatedAtField string + if t.updatedAt == nil { + updatedAtField = "" + } else { + updatedAtField = `"updated_at": "` + t.updatedAt.Format(time.RFC3339) + `",` + } + + var createdAtField string + if t.createdAt == nil { + createdAtField = "" + } else { + createdAtField = `"created_at": "` + t.createdAt.Format(time.RFC3339) + `",` + } + + handler := func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, ` +{ + "id": 5, + "iid": `+strconv.Itoa(t.prID)+`, + "project_id": `+strconv.Itoa(t.repoID)+`, + "title": "Approvals API", + "description": "Test", + "state": "opened", + `+createdAtField+` + `+updatedAtField+` + "merge_status": "cannot_be_merged", + "approvals_required": 2, + "approvals_left": 1, + "approved_by": [ + { + "user": { + "name": "Administrator", + "username": "`+t.reviewerName+`", + "id": `+strconv.Itoa(t.reviewerID)+`, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon", + "web_url": "http://localhost:3000/root" + } + } + ] +}`) + } + review := base.Review{ + IssueIndex: int64(t.prID), + ReviewerID: int64(t.reviewerID), + ReviewerName: t.reviewerName, + CreatedAt: t.expectedCreatedAt, + State: "APPROVED", + } + + return handler, review +} + +func TestGitlabGetReviews(t *testing.T) { + mux, server, client := gitlabClientMockSetup(t) + defer gitlabClientMockTeardown(server) + + repoID := 1324 + + downloader := &GitlabDownloader{ + ctx: context.Background(), + client: client, + repoID: repoID, + } + + createdAt := time.Date(2020, 4, 19, 19, 24, 21, 0, time.UTC) + + for _, testCase := range []reviewTestCase{ + { + repoID: repoID, + prID: 1, + reviewerID: 801, + reviewerName: "someone1", + createdAt: nil, + updatedAt: &createdAt, + expectedCreatedAt: createdAt, + }, + { + repoID: repoID, + prID: 2, + reviewerID: 802, + reviewerName: "someone2", + createdAt: &createdAt, + updatedAt: nil, + expectedCreatedAt: createdAt, + }, + { + repoID: repoID, + prID: 3, + reviewerID: 803, + reviewerName: "someone3", + createdAt: nil, + updatedAt: nil, + expectedCreatedAt: time.Now(), + }, + } { + mock, review := convertTestCase(testCase) + mux.HandleFunc(fmt.Sprintf("/api/v4/projects/%d/merge_requests/%d/approvals", testCase.repoID, testCase.prID), mock) + + id := int64(testCase.prID) + rvs, err := downloader.GetReviews(&base.Issue{Number: id, ForeignIndex: id}) + require.NoError(t, err) + assertReviewsEqual(t, []*base.Review{&review}, rvs) + } +} + +func TestAwardsToReactions(t *testing.T) { + downloader := &GitlabDownloader{} + // yes gitlab can have duplicated reactions (https://gitlab.com/jaywink/socialhome/-/issues/24) + testResponse := ` +[ + { + "name": "thumbsup", + "user": { + "id": 1241334, + "username": "lafriks" + } + }, + { + "name": "thumbsup", + "user": { + "id": 1241334, + "username": "lafriks" + } + }, + { + "name": "thumbsup", + "user": { + "id": 4575606, + "username": "real6543" + } + } +] +` + var awards []*gitlab.AwardEmoji + require.NoError(t, json.Unmarshal([]byte(testResponse), &awards)) + + reactions := downloader.awardsToReactions(awards) + assert.EqualValues(t, []*base.Reaction{ + { + UserName: "lafriks", + UserID: 1241334, + Content: "thumbsup", + }, + { + UserName: "real6543", + UserID: 4575606, + Content: "thumbsup", + }, + }, reactions) +} + +func TestNoteToComment(t *testing.T) { + downloader := &GitlabDownloader{} + + now := time.Now() + makeTestNote := func(id int, body string, system bool) gitlab.Note { + return gitlab.Note{ + ID: id, + Author: struct { + ID int `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Name string `json:"name"` + State string `json:"state"` + AvatarURL string `json:"avatar_url"` + WebURL string `json:"web_url"` + }{ + ID: 72, + Email: "test@example.com", + Username: "test", + }, + Body: body, + CreatedAt: &now, + System: system, + } + } + notes := []gitlab.Note{ + makeTestNote(1, "This is a regular comment", false), + makeTestNote(2, "enabled an automatic merge for abcd1234", true), + makeTestNote(3, "changed target branch from `master` to `main`", true), + makeTestNote(4, "canceled the automatic merge", true), + } + comments := []base.Comment{{ + IssueIndex: 17, + Index: 1, + PosterID: 72, + PosterName: "test", + PosterEmail: "test@example.com", + CommentType: "", + Content: "This is a regular comment", + Created: now, + Meta: map[string]any{}, + }, { + IssueIndex: 17, + Index: 2, + PosterID: 72, + PosterName: "test", + PosterEmail: "test@example.com", + CommentType: "pull_scheduled_merge", + Content: "enabled an automatic merge for abcd1234", + Created: now, + Meta: map[string]any{}, + }, { + IssueIndex: 17, + Index: 3, + PosterID: 72, + PosterName: "test", + PosterEmail: "test@example.com", + CommentType: "change_target_branch", + Content: "changed target branch from `master` to `main`", + Created: now, + Meta: map[string]any{ + "OldRef": "master", + "NewRef": "main", + }, + }, { + IssueIndex: 17, + Index: 4, + PosterID: 72, + PosterName: "test", + PosterEmail: "test@example.com", + CommentType: "pull_cancel_scheduled_merge", + Content: "canceled the automatic merge", + Created: now, + Meta: map[string]any{}, + }} + + for i, note := range notes { + actualComment := *downloader.convertNoteToComment(17, ¬e) + assert.EqualValues(t, actualComment, comments[i]) + } +} + +func TestGitlabIIDResolver(t *testing.T) { + r := gitlabIIDResolver{} + r.recordIssueIID(1) + r.recordIssueIID(2) + r.recordIssueIID(3) + r.recordIssueIID(2) + assert.EqualValues(t, 4, r.generatePullRequestNumber(1)) + assert.EqualValues(t, 13, r.generatePullRequestNumber(10)) + + assert.Panics(t, func() { + r := gitlabIIDResolver{} + r.recordIssueIID(1) + assert.EqualValues(t, 2, r.generatePullRequestNumber(1)) + r.recordIssueIID(3) // the generation procedure has been started, it shouldn't accept any new issue IID, so it panics + }) +} -- cgit v1.2.3