Skip to content

Commit f93d72c

Browse files
authored
GitLab reviews may not have the updated_at field set (#18450) (#18461)
Fallback to created_at if that the case and to time.Now() if it is also missing. Fixes: #18434 Co-authored-by: Loïc Dachary <[email protected]> Conflicts: services/migrations/gitlab.go trivial context conflict because var reviews became reviews := in 1.17
1 parent 2f22337 commit f93d72c

File tree

3 files changed

+181
-33
lines changed

3 files changed

+181
-33
lines changed

services/migrations/gitlab.go

+32-24
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ func init() {
3232
}
3333

3434
// GitlabDownloaderFactory defines a gitlab downloader factory
35-
type GitlabDownloaderFactory struct {
36-
}
35+
type GitlabDownloaderFactory struct{}
3736

3837
// New returns a Downloader related to this factory according MigrateOptions
3938
func (f *GitlabDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
@@ -184,16 +183,17 @@ func (g *GitlabDownloader) GetTopics() ([]string, error) {
184183

185184
// GetMilestones returns milestones
186185
func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) {
187-
var perPage = g.maxPerPage
188-
var state = "all"
189-
var milestones = make([]*base.Milestone, 0, perPage)
186+
perPage := g.maxPerPage
187+
state := "all"
188+
milestones := make([]*base.Milestone, 0, perPage)
190189
for i := 1; ; i++ {
191190
ms, _, err := g.client.Milestones.ListMilestones(g.repoID, &gitlab.ListMilestonesOptions{
192191
State: &state,
193192
ListOptions: gitlab.ListOptions{
194193
Page: i,
195194
PerPage: perPage,
196-
}}, nil, gitlab.WithContext(g.ctx))
195+
},
196+
}, nil, gitlab.WithContext(g.ctx))
197197
if err != nil {
198198
return nil, err
199199
}
@@ -203,7 +203,7 @@ func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) {
203203
if m.Description != "" {
204204
desc = m.Description
205205
}
206-
var state = "open"
206+
state := "open"
207207
var closedAt *time.Time
208208
if m.State != "" {
209209
state = m.State
@@ -255,8 +255,8 @@ func (g *GitlabDownloader) normalizeColor(val string) string {
255255

256256
// GetLabels returns labels
257257
func (g *GitlabDownloader) GetLabels() ([]*base.Label, error) {
258-
var perPage = g.maxPerPage
259-
var labels = make([]*base.Label, 0, perPage)
258+
perPage := g.maxPerPage
259+
labels := make([]*base.Label, 0, perPage)
260260
for i := 1; ; i++ {
261261
ls, _, err := g.client.Labels.ListLabels(g.repoID, &gitlab.ListLabelsOptions{ListOptions: gitlab.ListOptions{
262262
Page: i,
@@ -327,8 +327,8 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea
327327

328328
// GetReleases returns releases
329329
func (g *GitlabDownloader) GetReleases() ([]*base.Release, error) {
330-
var perPage = g.maxPerPage
331-
var releases = make([]*base.Release, 0, perPage)
330+
perPage := g.maxPerPage
331+
releases := make([]*base.Release, 0, perPage)
332332
for i := 1; ; i++ {
333333
ls, _, err := g.client.Releases.ListReleases(g.repoID, &gitlab.ListReleasesOptions{
334334
Page: i,
@@ -381,15 +381,15 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er
381381
},
382382
}
383383

384-
var allIssues = make([]*base.Issue, 0, perPage)
384+
allIssues := make([]*base.Issue, 0, perPage)
385385

386386
issues, _, err := g.client.Issues.ListProjectIssues(g.repoID, opt, nil, gitlab.WithContext(g.ctx))
387387
if err != nil {
388388
return nil, false, fmt.Errorf("error while listing issues: %v", err)
389389
}
390390
for _, issue := range issues {
391391

392-
var labels = make([]*base.Label, 0, len(issue.Labels))
392+
labels := make([]*base.Label, 0, len(issue.Labels))
393393
for _, l := range issue.Labels {
394394
labels = append(labels, &base.Label{
395395
Name: l,
@@ -402,7 +402,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er
402402
}
403403

404404
var reactions []*base.Reaction
405-
var awardPage = 1
405+
awardPage := 1
406406
for {
407407
awards, _, err := g.client.AwardEmoji.ListIssueAwardEmoji(g.repoID, issue.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx))
408408
if err != nil {
@@ -456,9 +456,9 @@ func (g *GitlabDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Com
456456
return nil, false, fmt.Errorf("unexpected context: %+v", opts.Context)
457457
}
458458

459-
var allComments = make([]*base.Comment, 0, g.maxPerPage)
459+
allComments := make([]*base.Comment, 0, g.maxPerPage)
460460

461-
var page = 1
461+
page := 1
462462

463463
for {
464464
var comments []*gitlab.Discussion
@@ -503,7 +503,6 @@ func (g *GitlabDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Com
503503
Created: *c.CreatedAt,
504504
})
505505
}
506-
507506
}
508507
if resp.NextPage == 0 {
509508
break
@@ -526,15 +525,15 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
526525
},
527526
}
528527

529-
var allPRs = make([]*base.PullRequest, 0, perPage)
528+
allPRs := make([]*base.PullRequest, 0, perPage)
530529

531530
prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil, gitlab.WithContext(g.ctx))
532531
if err != nil {
533532
return nil, false, fmt.Errorf("error while listing merge requests: %v", err)
534533
}
535534
for _, pr := range prs {
536535

537-
var labels = make([]*base.Label, 0, len(pr.Labels))
536+
labels := make([]*base.Label, 0, len(pr.Labels))
538537
for _, l := range pr.Labels {
539538
labels = append(labels, &base.Label{
540539
Name: l,
@@ -547,12 +546,12 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
547546
pr.State = "closed"
548547
}
549548

550-
var mergeTime = pr.MergedAt
549+
mergeTime := pr.MergedAt
551550
if merged && pr.MergedAt == nil {
552551
mergeTime = pr.UpdatedAt
553552
}
554553

555-
var closeTime = pr.ClosedAt
554+
closeTime := pr.ClosedAt
556555
if merged && pr.ClosedAt == nil {
557556
closeTime = pr.UpdatedAt
558557
}
@@ -568,7 +567,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
568567
}
569568

570569
var reactions []*base.Reaction
571-
var awardPage = 1
570+
awardPage := 1
572571
for {
573572
awards, _, err := g.client.AwardEmoji.ListMergeRequestAwardEmoji(g.repoID, pr.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx))
574573
if err != nil {
@@ -641,13 +640,22 @@ func (g *GitlabDownloader) GetReviews(context base.IssueContext) ([]*base.Review
641640
return nil, err
642641
}
643642

644-
var reviews = make([]*base.Review, 0, len(approvals.ApprovedBy))
643+
var createdAt time.Time
644+
if approvals.CreatedAt != nil {
645+
createdAt = *approvals.CreatedAt
646+
} else if approvals.UpdatedAt != nil {
647+
createdAt = *approvals.UpdatedAt
648+
} else {
649+
createdAt = time.Now()
650+
}
651+
652+
reviews := make([]*base.Review, 0, len(approvals.ApprovedBy))
645653
for _, user := range approvals.ApprovedBy {
646654
reviews = append(reviews, &base.Review{
647655
IssueIndex: context.LocalID(),
648656
ReviewerID: int64(user.User.ID),
649657
ReviewerName: user.User.Username,
650-
CreatedAt: *approvals.UpdatedAt,
658+
CreatedAt: createdAt,
651659
// All we get are approvals
652660
State: base.ReviewStateApproved,
653661
})

services/migrations/gitlab_test.go

+140
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ import (
88
"context"
99
"fmt"
1010
"net/http"
11+
"net/http/httptest"
1112
"os"
13+
"strconv"
1214
"testing"
1315
"time"
1416

1517
base "code.gitea.io/gitea/modules/migration"
1618

1719
"github.com/stretchr/testify/assert"
20+
"github.com/xanzy/go-gitlab"
1821
)
1922

2023
func TestGitlabDownloadRepo(t *testing.T) {
@@ -310,12 +313,14 @@ func TestGitlabDownloadRepo(t *testing.T) {
310313
assert.NoError(t, err)
311314
assertReviewsEqual(t, []*base.Review{
312315
{
316+
IssueIndex: 1,
313317
ReviewerID: 4102996,
314318
ReviewerName: "zeripath",
315319
CreatedAt: time.Date(2019, 11, 28, 16, 2, 8, 377000000, time.UTC),
316320
State: "APPROVED",
317321
},
318322
{
323+
IssueIndex: 1,
319324
ReviewerID: 527793,
320325
ReviewerName: "axifive",
321326
CreatedAt: time.Date(2019, 11, 28, 16, 2, 8, 377000000, time.UTC),
@@ -327,10 +332,145 @@ func TestGitlabDownloadRepo(t *testing.T) {
327332
assert.NoError(t, err)
328333
assertReviewsEqual(t, []*base.Review{
329334
{
335+
IssueIndex: 2,
330336
ReviewerID: 4575606,
331337
ReviewerName: "real6543",
332338
CreatedAt: time.Date(2020, 4, 19, 19, 24, 21, 108000000, time.UTC),
333339
State: "APPROVED",
334340
},
335341
}, rvs)
336342
}
343+
344+
func gitlabClientMockSetup(t *testing.T) (*http.ServeMux, *httptest.Server, *gitlab.Client) {
345+
// mux is the HTTP request multiplexer used with the test server.
346+
mux := http.NewServeMux()
347+
348+
// server is a test HTTP server used to provide mock API responses.
349+
server := httptest.NewServer(mux)
350+
351+
// client is the Gitlab client being tested.
352+
client, err := gitlab.NewClient("", gitlab.WithBaseURL(server.URL))
353+
if err != nil {
354+
server.Close()
355+
t.Fatalf("Failed to create client: %v", err)
356+
}
357+
358+
return mux, server, client
359+
}
360+
361+
func gitlabClientMockTeardown(server *httptest.Server) {
362+
server.Close()
363+
}
364+
365+
type reviewTestCase struct {
366+
repoID, prID, reviewerID int
367+
reviewerName string
368+
createdAt, updatedAt *time.Time
369+
expectedCreatedAt time.Time
370+
}
371+
372+
func convertTestCase(t reviewTestCase) (func(w http.ResponseWriter, r *http.Request), base.Review) {
373+
var updatedAtField string
374+
if t.updatedAt == nil {
375+
updatedAtField = ""
376+
} else {
377+
updatedAtField = `"updated_at": "` + t.updatedAt.Format(time.RFC3339) + `",`
378+
}
379+
380+
var createdAtField string
381+
if t.createdAt == nil {
382+
createdAtField = ""
383+
} else {
384+
createdAtField = `"created_at": "` + t.createdAt.Format(time.RFC3339) + `",`
385+
}
386+
387+
handler := func(w http.ResponseWriter, r *http.Request) {
388+
fmt.Fprint(w, `
389+
{
390+
"id": 5,
391+
"iid": `+strconv.Itoa(t.prID)+`,
392+
"project_id": `+strconv.Itoa(t.repoID)+`,
393+
"title": "Approvals API",
394+
"description": "Test",
395+
"state": "opened",
396+
`+createdAtField+`
397+
`+updatedAtField+`
398+
"merge_status": "cannot_be_merged",
399+
"approvals_required": 2,
400+
"approvals_left": 1,
401+
"approved_by": [
402+
{
403+
"user": {
404+
"name": "Administrator",
405+
"username": "`+t.reviewerName+`",
406+
"id": `+strconv.Itoa(t.reviewerID)+`,
407+
"state": "active",
408+
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
409+
"web_url": "http://localhost:3000/root"
410+
}
411+
}
412+
]
413+
}`)
414+
}
415+
review := base.Review{
416+
IssueIndex: int64(t.prID),
417+
ReviewerID: int64(t.reviewerID),
418+
ReviewerName: t.reviewerName,
419+
CreatedAt: t.expectedCreatedAt,
420+
State: "APPROVED",
421+
}
422+
423+
return handler, review
424+
}
425+
426+
func TestGitlabGetReviews(t *testing.T) {
427+
mux, server, client := gitlabClientMockSetup(t)
428+
defer gitlabClientMockTeardown(server)
429+
430+
repoID := 1324
431+
432+
downloader := &GitlabDownloader{
433+
ctx: context.Background(),
434+
client: client,
435+
repoID: repoID,
436+
}
437+
438+
createdAt := time.Date(2020, 4, 19, 19, 24, 21, 0, time.UTC)
439+
440+
for _, testCase := range []reviewTestCase{
441+
{
442+
repoID: repoID,
443+
prID: 1,
444+
reviewerID: 801,
445+
reviewerName: "someone1",
446+
createdAt: nil,
447+
updatedAt: &createdAt,
448+
expectedCreatedAt: createdAt,
449+
},
450+
{
451+
repoID: repoID,
452+
prID: 2,
453+
reviewerID: 802,
454+
reviewerName: "someone2",
455+
createdAt: &createdAt,
456+
updatedAt: nil,
457+
expectedCreatedAt: createdAt,
458+
},
459+
{
460+
repoID: repoID,
461+
prID: 3,
462+
reviewerID: 803,
463+
reviewerName: "someone3",
464+
createdAt: nil,
465+
updatedAt: nil,
466+
expectedCreatedAt: time.Now(),
467+
},
468+
} {
469+
mock, review := convertTestCase(testCase)
470+
mux.HandleFunc(fmt.Sprintf("/api/v4/projects/%d/merge_requests/%d/approvals", testCase.repoID, testCase.prID), mock)
471+
472+
rvs, err := downloader.GetReviews(base.BasicIssueContext(testCase.prID))
473+
assert.NoError(t, err)
474+
assertReviewsEqual(t, []*base.Review{&review}, rvs)
475+
}
476+
}

services/migrations/main_test.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -223,15 +223,15 @@ func assertRepositoryEqual(t *testing.T, expected, actual *base.Repository) {
223223
}
224224

225225
func assertReviewEqual(t *testing.T, expected, actual *base.Review) {
226-
assert.Equal(t, expected.ID, actual.ID)
227-
assert.Equal(t, expected.IssueIndex, actual.IssueIndex)
228-
assert.Equal(t, expected.ReviewerID, actual.ReviewerID)
229-
assert.Equal(t, expected.ReviewerName, actual.ReviewerName)
230-
assert.Equal(t, expected.Official, actual.Official)
231-
assert.Equal(t, expected.CommitID, actual.CommitID)
232-
assert.Equal(t, expected.Content, actual.Content)
233-
assertTimeEqual(t, expected.CreatedAt, actual.CreatedAt)
234-
assert.Equal(t, expected.State, actual.State)
226+
assert.Equal(t, expected.ID, actual.ID, "ID")
227+
assert.Equal(t, expected.IssueIndex, actual.IssueIndex, "IsssueIndex")
228+
assert.Equal(t, expected.ReviewerID, actual.ReviewerID, "ReviewerID")
229+
assert.Equal(t, expected.ReviewerName, actual.ReviewerName, "ReviewerName")
230+
assert.Equal(t, expected.Official, actual.Official, "Official")
231+
assert.Equal(t, expected.CommitID, actual.CommitID, "CommitID")
232+
assert.Equal(t, expected.Content, actual.Content, "Content")
233+
assert.WithinDuration(t, expected.CreatedAt, actual.CreatedAt, 10*time.Second)
234+
assert.Equal(t, expected.State, actual.State, "State")
235235
assertReviewCommentsEqual(t, expected.Comments, actual.Comments)
236236
}
237237

0 commit comments

Comments
 (0)