Skip to content

Commit 213f283

Browse files
SergKMykolaMarusenko
authored andcommitted
feat: extend PullRequest model with description, draft, and commit_sha fields
Add three new optional fields to the PullRequest model available from all provider List endpoints without extra API calls. Update OpenAPI spec, regenerate models, and implement field mapping in GitHub, GitLab, and Bitbucket converters with consistent nil-for-empty-string semantics. Signed-off-by: Sergiy Kulanov <sergiy_kulanov@epam.com>
1 parent b81eaf5 commit 213f283

File tree

9 files changed

+456
-67
lines changed

9 files changed

+456
-67
lines changed

internal/api/oapi.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,15 @@ components:
513513
updated_at:
514514
type: string
515515
format: date-time
516+
description:
517+
type: string
518+
description: Pull request body text
519+
draft:
520+
type: boolean
521+
description: Whether this pull request is a draft
522+
commit_sha:
523+
type: string
524+
description: Head commit SHA of the source branch
516525
required:
517526
- id
518527
- number

internal/models/models_gen.go

Lines changed: 11 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/services/bitbucket/bitbucket.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,11 @@ type bitbucketPRResponse struct {
173173
}
174174

175175
type bitbucketPR struct {
176-
ID int `json:"id"`
177-
Title string `json:"title"`
178-
State string `json:"state"`
176+
ID int `json:"id"`
177+
Title string `json:"title"`
178+
State string `json:"state"`
179+
Description string `json:"description"`
180+
Draft bool `json:"draft"`
179181

180182
Author struct {
181183
DisplayName string `json:"display_name"`
@@ -191,6 +193,9 @@ type bitbucketPR struct {
191193
Branch struct {
192194
Name string `json:"name"`
193195
} `json:"branch"`
196+
Commit struct {
197+
Hash string `json:"hash"`
198+
} `json:"commit"`
194199
} `json:"source"`
195200

196201
Destination struct {
@@ -288,7 +293,7 @@ func (b *BitbucketService) ListPullRequests(
288293
author.AvatarUrl = &avatarURL
289294
}
290295

291-
result = append(result, models.PullRequest{
296+
prModel := models.PullRequest{
292297
Id: strconv.Itoa(pr.ID),
293298
Number: pr.ID,
294299
Title: pr.Title,
@@ -299,7 +304,18 @@ func (b *BitbucketService) ListPullRequests(
299304
Author: author,
300305
CreatedAt: createdAt,
301306
UpdatedAt: updatedAt,
302-
})
307+
Draft: &pr.Draft,
308+
}
309+
310+
if pr.Description != "" {
311+
prModel.Description = &pr.Description
312+
}
313+
314+
if pr.Source.Commit.Hash != "" {
315+
prModel.CommitSha = &pr.Source.Commit.Hash
316+
}
317+
318+
result = append(result, prModel)
303319
}
304320

305321
total := bbResp.Size

internal/services/bitbucket/bitbucket_pullrequests_test.go

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"github.com/stretchr/testify/require"
1717
)
1818

19-
func Test_convertBitbucketPRState(t *testing.T) {
19+
func TestConvertBitbucketPRState(t *testing.T) {
2020
tests := []struct {
2121
name string
2222
state string
@@ -62,7 +62,7 @@ func Test_convertBitbucketPRState(t *testing.T) {
6262
}
6363
}
6464

65-
func Test_bitbucketPRResponse_JSONDeserialization(t *testing.T) {
65+
func TestBitbucketPRResponseJSONDeserialization(t *testing.T) {
6666
tests := []struct {
6767
name string
6868
input string
@@ -166,11 +166,13 @@ func Test_bitbucketPRResponse_JSONDeserialization(t *testing.T) {
166166
}
167167
}
168168

169-
func Test_bitbucketPR_FieldMapping(t *testing.T) {
169+
func TestBitbucketPRFieldMapping(t *testing.T) {
170170
input := `{
171171
"id": 99,
172172
"title": "Fix bug in parser",
173173
"state": "MERGED",
174+
"description": "Fixes the parser edge case",
175+
"draft": true,
174176
"author": {
175177
"display_name": "Jane Smith",
176178
"uuid": "{jane-uuid}",
@@ -181,7 +183,8 @@ func Test_bitbucketPR_FieldMapping(t *testing.T) {
181183
}
182184
},
183185
"source": {
184-
"branch": {"name": "bugfix/parser"}
186+
"branch": {"name": "bugfix/parser"},
187+
"commit": {"hash": "abc123def"}
185188
},
186189
"destination": {
187190
"branch": {"name": "develop"}
@@ -206,7 +209,10 @@ func Test_bitbucketPR_FieldMapping(t *testing.T) {
206209
assert.Equal(t, "Jane Smith", pr.Author.DisplayName)
207210
assert.Equal(t, "{jane-uuid}", pr.Author.UUID)
208211
assert.Equal(t, "https://bitbucket.org/jane-avatar.png", pr.Author.Links.Avatar.Href)
212+
assert.Equal(t, "Fixes the parser edge case", pr.Description)
213+
assert.True(t, pr.Draft)
209214
assert.Equal(t, "bugfix/parser", pr.Source.Branch.Name)
215+
assert.Equal(t, "abc123def", pr.Source.Commit.Hash)
210216
assert.Equal(t, "develop", pr.Destination.Branch.Name)
211217
assert.Equal(t, "https://bitbucket.org/owner/repo/pull-requests/99", pr.Links.HTML.Href)
212218
assert.Equal(t, "2026-02-01T08:15:30.000000+00:00", pr.CreatedOn)
@@ -229,6 +235,9 @@ func newTestBitbucketPR(id int, title, state, sourceBranch, createdOn, updatedOn
229235
Branch struct {
230236
Name string `json:"name"`
231237
} `json:"branch"`
238+
Commit struct {
239+
Hash string `json:"hash"`
240+
} `json:"commit"`
232241
}{Branch: struct {
233242
Name string `json:"name"`
234243
}{Name: sourceBranch}},
@@ -258,7 +267,7 @@ func (t *redirectTransport) RoundTrip(req *http.Request) (*http.Response, error)
258267
return t.wrapped.RoundTrip(req)
259268
}
260269

261-
func TestBitbucketService_ListPullRequests(t *testing.T) {
270+
func TestBitbucketServiceListPullRequests(t *testing.T) {
262271
tests := []struct {
263272
name string
264273
state string
@@ -432,9 +441,12 @@ func TestBitbucketService_ListPullRequests(t *testing.T) {
432441
}
433442
}
434443

435-
func TestBitbucketService_ListPullRequests_FieldMapping(t *testing.T) {
444+
func TestBitbucketServiceListPullRequestsFieldMapping(t *testing.T) {
436445
bbPR := newTestBitbucketPR(42, "Add new feature", "OPEN", "feature/awesome",
437446
"2026-01-15T10:30:00.123456+00:00", "2026-01-16T14:00:00.654321+00:00")
447+
bbPR.Description = "Adds an awesome new feature"
448+
bbPR.Draft = true
449+
bbPR.Source.Commit.Hash = "deadbeef123"
438450
bbPR.Author.DisplayName = "John Doe"
439451
bbPR.Author.UUID = "{user-uuid}"
440452
bbPR.Author.Links.Avatar.Href = "https://avatar.example.com/john.png"
@@ -495,6 +507,14 @@ func TestBitbucketService_ListPullRequests_FieldMapping(t *testing.T) {
495507
require.NotNil(t, pr.Author.AvatarUrl)
496508
assert.Equal(t, "https://avatar.example.com/john.png", *pr.Author.AvatarUrl)
497509

510+
// New fields
511+
require.NotNil(t, pr.Description)
512+
assert.Equal(t, "Adds an awesome new feature", *pr.Description)
513+
require.NotNil(t, pr.Draft)
514+
assert.True(t, *pr.Draft)
515+
require.NotNil(t, pr.CommitSha)
516+
assert.Equal(t, "deadbeef123", *pr.CommitSha)
517+
498518
// Timestamps parsed with RFC3339Nano (microsecond precision)
499519
assert.Equal(t, 2026, pr.CreatedAt.Year())
500520
assert.Equal(t, 1, int(pr.CreatedAt.Month()))
@@ -511,7 +531,7 @@ func TestBitbucketService_ListPullRequests_FieldMapping(t *testing.T) {
511531
assert.Equal(t, perPage, *result.Pagination.PerPage)
512532
}
513533

514-
func TestBitbucketService_ListPullRequests_EmptyAuthorAvatar(t *testing.T) {
534+
func TestBitbucketServiceListPullRequestsEmptyAuthorAvatar(t *testing.T) {
515535
bbPR := newTestBitbucketPR(5, "PR without avatar", "OPEN", "fix",
516536
"2026-01-01T00:00:00.000000+00:00", "2026-01-01T00:00:00.000000+00:00")
517537
bbPR.Author.DisplayName = "No Avatar User"
@@ -560,7 +580,55 @@ func TestBitbucketService_ListPullRequests_EmptyAuthorAvatar(t *testing.T) {
560580
assert.Nil(t, pr.Author.AvatarUrl, "avatar_url should be nil when empty")
561581
}
562582

563-
func TestBitbucketService_ListPullRequests_InvalidToken(t *testing.T) {
583+
func TestBitbucketServiceListPullRequestsEmptyNewFields(t *testing.T) {
584+
bbPR := newTestBitbucketPR(10, "PR with empty fields", "OPEN", "feature",
585+
"2026-01-01T00:00:00.000000+00:00", "2026-01-01T00:00:00.000000+00:00")
586+
// bbPR has empty Description, Draft=false, and no commit hash by default
587+
588+
prResponse := bitbucketPRResponse{
589+
Size: 1,
590+
Page: 1,
591+
Pagelen: 20,
592+
Values: []bitbucketPR{bbPR},
593+
}
594+
595+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
596+
w.Header().Set("Content-Type", "application/json")
597+
w.WriteHeader(http.StatusOK)
598+
599+
body, _ := json.Marshal(prResponse)
600+
_, _ = w.Write(body)
601+
}))
602+
defer server.Close()
603+
604+
svc := &BitbucketService{
605+
httpClient: resty.New().SetTransport(&redirectTransport{
606+
target: server.URL,
607+
wrapped: http.DefaultTransport,
608+
}),
609+
}
610+
token := testBitbucketToken()
611+
612+
result, err := svc.ListPullRequests(
613+
context.Background(),
614+
"owner",
615+
"repo",
616+
krci.GitServerSettings{Token: token},
617+
models.PullRequestListOptions{State: "open", Page: 1, PerPage: 20},
618+
)
619+
620+
require.NoError(t, err)
621+
require.NotNil(t, result)
622+
require.Len(t, result.Data, 1)
623+
624+
pr := result.Data[0]
625+
assert.Nil(t, pr.Description, "empty description should be nil")
626+
require.NotNil(t, pr.Draft, "draft should not be nil even when false")
627+
assert.False(t, *pr.Draft, "draft should be false")
628+
assert.Nil(t, pr.CommitSha, "empty commit hash should be nil")
629+
}
630+
631+
func TestBitbucketServiceListPullRequestsInvalidToken(t *testing.T) {
564632
svc := NewBitbucketProvider()
565633

566634
_, err := svc.ListPullRequests(
@@ -575,7 +643,7 @@ func TestBitbucketService_ListPullRequests_InvalidToken(t *testing.T) {
575643
assert.Contains(t, err.Error(), "failed to decode bitbucket token")
576644
}
577645

578-
func TestBitbucketService_ListPullRequests_InvalidTimestamp(t *testing.T) {
646+
func TestBitbucketServiceListPullRequestsInvalidTimestamp(t *testing.T) {
579647
prResponse := bitbucketPRResponse{
580648
Size: 1,
581649
Page: 1,
@@ -615,7 +683,7 @@ func TestBitbucketService_ListPullRequests_InvalidTimestamp(t *testing.T) {
615683
assert.Contains(t, err.Error(), "failed to parse created_on time")
616684
}
617685

618-
func TestBitbucketService_ListPullRequests_PaginationParams(t *testing.T) {
686+
func TestBitbucketServiceListPullRequestsPaginationParams(t *testing.T) {
619687
var capturedReq *http.Request
620688

621689
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -653,7 +721,7 @@ func TestBitbucketService_ListPullRequests_PaginationParams(t *testing.T) {
653721
assert.Equal(t, "10", capturedReq.URL.Query().Get("pagelen"))
654722
}
655723

656-
func TestBitbucketService_ListPullRequests_SupersededState(t *testing.T) {
724+
func TestBitbucketServiceListPullRequestsSupersededState(t *testing.T) {
657725
prResponse := bitbucketPRResponse{
658726
Size: 2,
659727
Page: 1,

internal/services/github/github.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,15 @@ func convertGitHubPullRequest(pr *github.PullRequest) models.PullRequest {
510510
Url: pr.GetHTMLURL(),
511511
CreatedAt: pr.GetCreatedAt().Time,
512512
UpdatedAt: pr.GetUpdatedAt().Time,
513+
Draft: pr.Draft,
514+
}
515+
516+
if pr.GetBody() != "" {
517+
prModel.Description = pr.Body
518+
}
519+
520+
if pr.Head != nil && pr.Head.GetSHA() != "" {
521+
prModel.CommitSha = pr.Head.SHA
513522
}
514523

515524
if pr.User != nil {

0 commit comments

Comments
 (0)