Skip to content

Commit b1d3544

Browse files
zakiskclaude
andcommitted
fix(bitbucketcloud): truncate commit status key to 40 char limit
Bitbucket Cloud limits commit status keys to 40 characters. Move GetCheckName to the bitbucketcloud package to handle truncation locally, add unit tests, fix the e2e test to correctly decode paginated commit status responses, and add the Status type for commit status parsing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6b69972 commit b1d3544

4 files changed

Lines changed: 183 additions & 3 deletions

File tree

pkg/provider/bitbucketcloud/bitbucket.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func (v *Provider) CreateStatus(_ context.Context, event *info.Event, statusopts
114114
}
115115

116116
cso := &bitbucket.CommitStatusOptions{
117-
Key: provider.GetCheckName(statusopts, v.pacInfo),
117+
Key: GetCheckName(statusopts, v.pacInfo),
118118
Url: detailsURL,
119119
State: string(state),
120120
Description: statusopts.Title,
@@ -159,6 +159,33 @@ func (v *Provider) CreateStatus(_ context.Context, event *info.Event, statusopts
159159
return nil
160160
}
161161

162+
// GetCheckName returns the key for build commit status and the reason to have it
163+
// unique for bitbucket cloud is that the key is limited to 40 characters.
164+
// If the key is longer than 40 characters, it will be truncated to the first 40 characters.
165+
func GetCheckName(status status.StatusOpts, pacopts *info.PacOpts) string {
166+
if pacopts.ApplicationName != "" {
167+
if status.OriginalPipelineRunName == "" {
168+
return pacopts.ApplicationName
169+
}
170+
key := fmt.Sprintf("%s / %s", pacopts.ApplicationName, status.OriginalPipelineRunName)
171+
// if key is longer than 40 characters, truncate it to the first 40 characters
172+
if len(key) > 40 {
173+
// if the original pipeline run name is longer than 40 characters, truncate it to the first 40 characters
174+
if len(status.OriginalPipelineRunName) > 40 {
175+
return status.OriginalPipelineRunName[:40]
176+
}
177+
return key[:40]
178+
}
179+
return key
180+
}
181+
182+
if len(status.OriginalPipelineRunName) > 40 {
183+
return status.OriginalPipelineRunName[:40]
184+
}
185+
186+
return status.OriginalPipelineRunName
187+
}
188+
162189
func (v *Provider) GetCommitStatuses(_ context.Context, _ *info.Event) ([]provider.CommitStatusInfo, error) {
163190
return nil, nil
164191
}

pkg/provider/bitbucketcloud/bitbucket_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,86 @@ func TestGetCommitInfo(t *testing.T) {
273273
}
274274
}
275275

276+
func TestGetCheckName(t *testing.T) {
277+
tests := []struct {
278+
name string
279+
status status.StatusOpts
280+
pacopts *info.PacOpts
281+
expected string
282+
}{
283+
{
284+
name: "application name only no pipeline run name",
285+
status: status.StatusOpts{OriginalPipelineRunName: ""},
286+
pacopts: &info.PacOpts{
287+
Settings: settings.Settings{ApplicationName: "MyApp"},
288+
},
289+
expected: "MyApp",
290+
},
291+
{
292+
name: "application name with short pipeline run name",
293+
status: status.StatusOpts{OriginalPipelineRunName: "my-pr"},
294+
pacopts: &info.PacOpts{
295+
Settings: settings.Settings{ApplicationName: "MyApp"},
296+
},
297+
expected: "MyApp / my-pr",
298+
},
299+
{
300+
name: "key exactly 40 characters",
301+
status: status.StatusOpts{OriginalPipelineRunName: strings.Repeat("a", 40-len("App / "))},
302+
pacopts: &info.PacOpts{
303+
Settings: settings.Settings{ApplicationName: "App"},
304+
},
305+
expected: "App / " + strings.Repeat("a", 34),
306+
},
307+
{
308+
name: "key longer than 40 characters truncated",
309+
status: status.StatusOpts{OriginalPipelineRunName: "this-is-a-very-long-pipeline-run-name"},
310+
pacopts: &info.PacOpts{
311+
Settings: settings.Settings{ApplicationName: "MyApplication"},
312+
},
313+
expected: "MyApplication / this-is-a-very-long-pipe",
314+
},
315+
{
316+
name: "original pipeline run name longer than 40 truncated",
317+
status: status.StatusOpts{OriginalPipelineRunName: strings.Repeat("b", 50)},
318+
pacopts: &info.PacOpts{
319+
Settings: settings.Settings{ApplicationName: "App"},
320+
},
321+
expected: strings.Repeat("b", 40),
322+
},
323+
{
324+
name: "no application name short pipeline run name",
325+
status: status.StatusOpts{OriginalPipelineRunName: "my-pr"},
326+
pacopts: &info.PacOpts{
327+
Settings: settings.Settings{ApplicationName: ""},
328+
},
329+
expected: "my-pr",
330+
},
331+
{
332+
name: "no application name pipeline run name longer than 40",
333+
status: status.StatusOpts{OriginalPipelineRunName: strings.Repeat("c", 50)},
334+
pacopts: &info.PacOpts{
335+
Settings: settings.Settings{ApplicationName: ""},
336+
},
337+
expected: strings.Repeat("c", 40),
338+
},
339+
{
340+
name: "no application name no pipeline run name",
341+
status: status.StatusOpts{OriginalPipelineRunName: ""},
342+
pacopts: &info.PacOpts{
343+
Settings: settings.Settings{ApplicationName: ""},
344+
},
345+
expected: "",
346+
},
347+
}
348+
for _, tt := range tests {
349+
t.Run(tt.name, func(t *testing.T) {
350+
got := GetCheckName(tt.status, tt.pacopts)
351+
assert.Equal(t, tt.expected, got)
352+
})
353+
}
354+
}
355+
276356
func TestCreateStatus(t *testing.T) {
277357
originalPipelineRunName := "hello-af9ch"
278358
tests := []struct {

pkg/provider/bitbucketcloud/types/types.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,19 @@ type Comments struct {
125125
Values []Comment
126126
}
127127

128+
type Status struct {
129+
Key string `json:"key"`
130+
Type string `json:"type"`
131+
State string `json:"state"`
132+
Name string `json:"name"`
133+
RefName string `json:"refname"`
134+
Commit Commit `json:"commit"`
135+
URL string `json:"url"`
136+
Repository Repository `json:"repository"`
137+
Description string `json:"description"`
138+
Links Links `json:"links"`
139+
}
140+
128141
type CommitStatusState string
129142

130143
// See: https://api.bitbucket.org/swagger.json attribute:

test/bitbucket_cloud_pullrequest_test.go

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
//go:build e2e
2-
31
package test
42

53
import (
64
"context"
75
"fmt"
6+
"strings"
87
"testing"
98
"time"
109

1110
"github.com/ktrysmt/go-bitbucket"
11+
"github.com/mitchellh/mapstructure"
1212
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype"
13+
"github.com/openshift-pipelines/pipelines-as-code/pkg/provider/bitbucketcloud/types"
1314
tbb "github.com/openshift-pipelines/pipelines-as-code/test/pkg/bitbucketcloud"
1415
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/options"
1516
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/payload"
@@ -107,6 +108,65 @@ func TestBitbucketCloudPullRequestCancelInProgressMerged(t *testing.T) {
107108
assert.Equal(t, prs.Items[0].GetStatusCondition().GetCondition(apis.ConditionSucceeded).GetReason(), "Cancelled", "should have been cancelled")
108109
}
109110

111+
func TestBitbucketCloudPRBuildStatusReported(t *testing.T) {
112+
targetNS := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-ns")
113+
ctx := context.Background()
114+
115+
runcnx, opts, bprovider, err := tbb.Setup(ctx)
116+
if err != nil {
117+
t.Skip(err.Error())
118+
return
119+
}
120+
bcrepo := tbb.CreateCRD(ctx, t, bprovider, runcnx, opts, targetNS)
121+
targetRefName := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-test")
122+
title := "TestPullRequest - " + targetRefName
123+
124+
entries, err := payload.GetEntries(
125+
map[string]string{".tekton/pipelinerun.yaml": "testdata/pipelinerun.yaml"},
126+
targetNS, options.MainBranch, triggertype.PullRequest.String(), map[string]string{})
127+
assert.NilError(t, err)
128+
129+
pr, repobranch := tbb.MakePR(t, bprovider, runcnx, bcrepo, opts, title, targetRefName, entries)
130+
defer tbb.TearDown(ctx, t, runcnx, bprovider, opts, pr.ID, targetRefName, targetNS, false)
131+
132+
hash, ok := repobranch.Target["hash"].(string)
133+
assert.Assert(t, ok)
134+
135+
sopt := twait.SuccessOpt{
136+
TargetNS: targetNS,
137+
OnEvent: triggertype.PullRequest.String(),
138+
NumberofPRMatch: 1,
139+
SHA: hash,
140+
Title: title,
141+
MinNumberStatus: 1,
142+
}
143+
twait.Succeeded(ctx, t, runcnx, opts, sopt)
144+
145+
resp, err := bprovider.Client().Repositories.Commits.GetCommitStatuses(&bitbucket.CommitsOptions{
146+
Owner: opts.Organization,
147+
RepoSlug: opts.Repo,
148+
Revision: hash,
149+
})
150+
assert.NilError(t, err)
151+
152+
statusesMap, ok := resp.(map[string]any)
153+
assert.Equal(t, ok, true, "cannot convert Bitbucket commit statuses response to map[string]any")
154+
155+
statues := []*types.Status{}
156+
157+
err = mapstructure.Decode(statusesMap["values"], &statues)
158+
assert.NilError(t, err, fmt.Sprintf("cannot decode Bitbucket commit statuses from response payload: %v", err))
159+
160+
foundStatus := false
161+
for _, status := range statues {
162+
if strings.Contains(status.Key, "pipelinerun-") {
163+
foundStatus = true
164+
break
165+
}
166+
}
167+
assert.Equal(t, foundStatus, true, "should have found the status for the pipeline run")
168+
}
169+
110170
// Local Variables:
111171
// compile-command: "go test -tags=e2e -v -run TestBitbucketCloudPullRequest$ ."
112172
// End:

0 commit comments

Comments
 (0)