diff --git a/README.md b/README.md index 59779c82..1e6a9101 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,11 @@ Currently supported providers are: [GitHub](#github), [Bitbucket Server](#bitbuc - [Update Webhook](#update-webhook) - [Delete Webhook](#delete-webhook) - [Set Commit Status](#set-commit-status) - - [Create Pull Request](#create-pull-request) + - [Get Commit Status](#get-commit-status) + - [Create Pull Request](#create-pull-request) - [List Open Pull Requests](#list-open-pull-requests) - - [Add Pull Request Comment](#add-pull-request-comment) - - [List Pull Request Comments](#list-pull-request-comments) + - [Add Pull Request Comment](#add-pull-request-comment) + - [List Pull Request Comments](#list-pull-request-comments) - [Get Latest Commit](#get-latest-commit) - [Get Commit By SHA](#get-commit-by-sha) - [Get List of Modified Files](#get-list-of-modified-files) @@ -291,6 +292,21 @@ detailsURL := "https://acme.jfrog.io/ui/xray-scan-results-url" err := client.SetCommitStatus(ctx, commitStatus, owner, repository, ref, title, description, detailsURL) ``` +#### Get Commit Status + +```go +// Go context +ctx := context.Background() +// Organization or username +owner := "jfrog" +// VCS repository +repository := "jfrog-cli" +// Commit tag on GitHub and GitLab, commit on Bitbucket +ref := "5c05522fecf8d93a11752ff255c99fcb0f0557cd" + +commitStatuses, err := client.GetCommitStatus(ctx, owner, repository, ref) +``` + ##### Create Pull Request ```go diff --git a/vcsclient/azurerepos.go b/vcsclient/azurerepos.go index cd7097ef..05190f20 100644 --- a/vcsclient/azurerepos.go +++ b/vcsclient/azurerepos.go @@ -4,18 +4,17 @@ import ( "context" "errors" "fmt" + "github.com/jfrog/froggit-go/vcsutils" + "github.com/jfrog/gofrog/datastructures" + "github.com/microsoft/azure-devops-go-api/azuredevops" + "github.com/microsoft/azure-devops-go-api/azuredevops/git" + "github.com/mitchellh/mapstructure" "io" "net/http" "os" "sort" "strings" - - "github.com/jfrog/gofrog/datastructures" - "github.com/microsoft/azure-devops-go-api/azuredevops" - "github.com/microsoft/azure-devops-go-api/azuredevops/git" - "github.com/mitchellh/mapstructure" - - "github.com/jfrog/froggit-go/vcsutils" + "time" ) // Azure Devops API version 6 @@ -360,7 +359,56 @@ func (client *AzureReposClient) DeleteWebhook(ctx context.Context, owner, reposi // SetCommitStatus on Azure Repos func (client *AzureReposClient) SetCommitStatus(ctx context.Context, commitStatus CommitStatus, owner, repository, ref, title, description, detailsURL string) error { - return getUnsupportedInAzureError("set commit status") + azureReposGitClient, err := client.buildAzureReposClient(ctx) + if err != nil { + return err + } + statusState := git.GitStatusState(mapStatusToString(commitStatus)) + commitStatusArgs := git.CreateCommitStatusArgs{ + GitCommitStatusToCreate: &git.GitStatus{ + Description: &description, + State: &statusState, + TargetUrl: &detailsURL, + Context: &git.GitStatusContext{ + Name: &owner, + Genre: &title, + }, + }, + CommitId: &ref, + RepositoryId: &repository, + Project: &repository, + } + _, err = azureReposGitClient.CreateCommitStatus(ctx, commitStatusArgs) + return err +} + +// GetCommitStatuses on Azure Repos +func (client *AzureReposClient) GetCommitStatuses(ctx context.Context, owner, repository, ref string) (status []CommitStatusInfo, err error) { + azureReposGitClient, err := client.buildAzureReposClient(ctx) + if err != nil { + return nil, err + } + commitStatusArgs := git.GetStatusesArgs{ + CommitId: &ref, + RepositoryId: &repository, + Project: &repository, + } + resGitStatus, err := azureReposGitClient.GetStatuses(ctx, commitStatusArgs) + if err != nil { + return nil, err + } + results := make([]CommitStatusInfo, 0) + for _, singleStatus := range *resGitStatus { + results = append(results, CommitStatusInfo{ + State: CommitStatusAsStringToStatus(string(*singleStatus.State)), + Description: *singleStatus.Description, + DetailsUrl: *singleStatus.TargetUrl, + Creator: *singleStatus.CreatedBy.DisplayName, + LastUpdatedAt: extractTimeFromAzuredevopsTime(singleStatus.UpdatedDate), + CreatedAt: extractTimeFromAzuredevopsTime(singleStatus.CreationDate), + }) + } + return results, err } // DownloadFileFromRepo on Azure Repos @@ -453,3 +501,21 @@ func remapFields[T any](src any, tagName string) (T, error) { } return dst, nil } + +// mapStatusToString maps commit status enum to string, specific for azure. +func mapStatusToString(status CommitStatus) string { + conversionMap := map[CommitStatus]string{ + Pass: "Succeeded", + Fail: "Failed", + Error: "Error", + InProgress: "Pending", + } + return conversionMap[status] +} + +func extractTimeFromAzuredevopsTime(rawStatus *azuredevops.Time) time.Time { + if rawStatus == nil { + return time.Time{} + } + return extractTimeWithFallback(&rawStatus.Time) +} diff --git a/vcsclient/azurerepos_test.go b/vcsclient/azurerepos_test.go index 1fc6d121..b92a5d87 100644 --- a/vcsclient/azurerepos_test.go +++ b/vcsclient/azurerepos_test.go @@ -352,9 +352,16 @@ func TestAzureReposClient_DeleteWebhook(t *testing.T) { func TestAzureReposClient_SetCommitStatus(t *testing.T) { ctx := context.Background() - client, cleanUp := createServerAndClient(t, vcsutils.AzureRepos, true, "", "unsupportedTest", createAzureReposHandler) + commitHash := "86d6919952702f9ab03bc95b45687f145a663de0" + expectedUri := "/_apis/ResourceAreas/commitStatus" + client, cleanUp := createServerAndClient(t, vcsutils.AzureRepos, true, nil, expectedUri, createAzureReposHandler) defer cleanUp() - err := client.SetCommitStatus(ctx, 1, owner, repo1, "", "", "", "") + err := client.SetCommitStatus(ctx, 1, owner, repo1, commitHash, "", "", "") + assert.NoError(t, err) + + badClient, badClientCleanup := createBadAzureReposClient(t, []byte{}) + defer badClientCleanup() + err = badClient.SetCommitStatus(ctx, 1, owner, repo1, commitHash, "", "", "") assert.Error(t, err) } @@ -443,6 +450,36 @@ func TestAzureReposClient_GetModifiedFiles(t *testing.T) { }) } +func TestAzureReposClient_GetCommitStatus(t *testing.T) { + ctx := context.Background() + commitHash := "86d6919952702f9ab03bc95b45687f145a663de0" + expectedUri := "/_apis/ResourceAreas/commitStatus" + t.Run("Valid response", func(t *testing.T) { + response, err := os.ReadFile(filepath.Join("testdata", "azurerepos", "commits_statuses.json")) + assert.NoError(t, err) + client, cleanUp := createServerAndClient(t, vcsutils.AzureRepos, true, response, expectedUri, createAzureReposHandler) + defer cleanUp() + commitStatuses, err := client.GetCommitStatuses(ctx, owner, repo1, commitHash) + assert.NoError(t, err) + assert.True(t, len(commitStatuses) == 3) + assert.True(t, commitStatuses[0].State == Pass) + assert.True(t, commitStatuses[1].State == InProgress) + assert.True(t, commitStatuses[2].State == Fail) + }) + t.Run("Empty response", func(t *testing.T) { + client, cleanUp := createServerAndClient(t, vcsutils.AzureRepos, true, nil, expectedUri, createAzureReposHandler) + defer cleanUp() + _, err := client.GetCommitStatuses(ctx, owner, repo1, commitHash) + assert.NoError(t, err) + }) + t.Run("Bad client", func(t *testing.T) { + badClient, badClientCleanup := createBadAzureReposClient(t, []byte{}) + defer badClientCleanup() + _, err := badClient.GetCommitStatuses(ctx, owner, repo1, "") + assert.Error(t, err) + }) +} + func createAzureReposHandler(t *testing.T, expectedURI string, response []byte, expectedStatusCode int) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { base64Token := base64.StdEncoding.EncodeToString([]byte(":" + token)) diff --git a/vcsclient/bitbucketcloud.go b/vcsclient/bitbucketcloud.go index b4676231..e4354668 100644 --- a/vcsclient/bitbucketcloud.go +++ b/vcsclient/bitbucketcloud.go @@ -215,6 +215,25 @@ func (client *BitbucketCloudClient) SetCommitStatus(ctx context.Context, commitS return err } +// GetCommitStatuses on Bitbucket cloud +func (client *BitbucketCloudClient) GetCommitStatuses(ctx context.Context, owner, repository, ref string) (status []CommitStatusInfo, err error) { + bitbucketClient := client.buildBitbucketCloudClient(ctx) + commitOptions := &bitbucket.CommitsOptions{ + Owner: owner, + RepoSlug: repository, + Revision: ref, + } + rawStatuses, err := bitbucketClient.Repositories.Commits.GetCommitStatuses(commitOptions) + if err != nil { + return nil, err + } + results, err := bitbucketParseCommitStatuses(rawStatuses) + if err != nil { + return nil, err + } + return results, err +} + // DownloadRepository on Bitbucket cloud func (client *BitbucketCloudClient) DownloadRepository(ctx context.Context, owner, repository, branch, localPath string) error { diff --git a/vcsclient/bitbucketcloud_test.go b/vcsclient/bitbucketcloud_test.go index 5361662b..6cc8a654 100644 --- a/vcsclient/bitbucketcloud_test.go +++ b/vcsclient/bitbucketcloud_test.go @@ -490,6 +490,29 @@ func TestBitbucketCloudClient_GetModifiedFiles(t *testing.T) { }) } +func TestBitbucketCloudClient_GetCommitStatus(t *testing.T) { + ctx := context.Background() + t.Run("empty response", func(t *testing.T) { + client, cleanUp := createServerAndClient(t, vcsutils.BitbucketCloud, true, nil, "/repositories/owner/repo/commit/ref/statuses", createBitbucketCloudHandler) + defer cleanUp() + _, err := client.GetCommitStatuses(ctx, "owner", "repo", "ref") + assert.NoError(t, err) + }) + + t.Run("non empty response", func(t *testing.T) { + response, err := os.ReadFile(filepath.Join("testdata", "bitbucketcloud", "commits_statuses.json")) + assert.NoError(t, err) + client, cleanUp := createServerAndClient(t, vcsutils.BitbucketCloud, true, response, "/repositories/owner/repo/commit/ref/statuses", createBitbucketCloudHandler) + defer cleanUp() + commitStatuses, err := client.GetCommitStatuses(ctx, "owner", "repo", "ref") + assert.NoError(t, err) + assert.True(t, len(commitStatuses) == 3) + assert.True(t, commitStatuses[0].State == InProgress) + assert.True(t, commitStatuses[1].State == Pass) + assert.True(t, commitStatuses[2].State == Fail) + }) +} + func createBitbucketCloudHandler(t *testing.T, expectedURI string, response []byte, expectedStatusCode int) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(expectedStatusCode) diff --git a/vcsclient/bitbucketcommon.go b/vcsclient/bitbucketcommon.go index 5ba8718a..563518a6 100644 --- a/vcsclient/bitbucketcommon.go +++ b/vcsclient/bitbucketcommon.go @@ -2,6 +2,8 @@ package vcsclient import ( "errors" + "github.com/mitchellh/mapstructure" + "time" ) var errLabelsNotSupported = errors.New("labels are not supported on Bitbucket") @@ -21,3 +23,43 @@ func getBitbucketCommitState(commitState CommitStatus) string { } return "" } + +// bitbucketParseCommitStatuses parse raw response into CommitStatusInfo slice +// The response is the same for BitBucket cloud and server +func bitbucketParseCommitStatuses(rawStatuses interface{}) ([]CommitStatusInfo, error) { + results := make([]CommitStatusInfo, 0) + statuses := struct { + Statuses []struct { + Title string `mapstructure:"key"` + Url string `mapstructure:"url"` + State string `mapstructure:"state"` + Description string `mapstructure:"description"` + Creator string `mapstructure:"name"` + LastUpdatedAt string `mapstructure:"updated_on"` + CreatedAt string `mapstructure:"created_at"` + } `mapstructure:"values"` + }{} + err := mapstructure.Decode(rawStatuses, &statuses) + if err != nil { + return nil, err + } + for _, commitStatus := range statuses.Statuses { + lastUpdatedAt, err := time.Parse(time.RFC3339, commitStatus.LastUpdatedAt) + if err != nil { + return nil, err + } + createdAt, err := time.Parse(time.RFC3339, commitStatus.LastUpdatedAt) + if err != nil { + return nil, err + } + results = append(results, CommitStatusInfo{ + State: CommitStatusAsStringToStatus(commitStatus.State), + Description: commitStatus.Description, + DetailsUrl: commitStatus.Url, + Creator: commitStatus.Creator, + LastUpdatedAt: lastUpdatedAt, + CreatedAt: createdAt, + }) + } + return results, err +} diff --git a/vcsclient/bitbucketserver.go b/vcsclient/bitbucketserver.go index a175a9b4..9d362ef3 100644 --- a/vcsclient/bitbucketserver.go +++ b/vcsclient/bitbucketserver.go @@ -256,6 +256,19 @@ func (client *BitbucketServerClient) SetCommitStatus(ctx context.Context, commit return err } +// GetCommitStatuses on Bitbucket server +func (client *BitbucketServerClient) GetCommitStatuses(ctx context.Context, owner, repository, ref string) (status []CommitStatusInfo, err error) { + bitbucketClient, err := client.buildBitbucketClient(ctx) + if err != nil { + return nil, err + } + response, err := bitbucketClient.GetCommitStatus(ref) + if err != nil { + return nil, err + } + return bitbucketParseCommitStatuses(response.Values) +} + // DownloadRepository on Bitbucket server func (client *BitbucketServerClient) DownloadRepository(ctx context.Context, owner, repository, branch, localPath string) error { bitbucketClient, err := client.buildBitbucketClient(ctx) diff --git a/vcsclient/bitbucketserver_test.go b/vcsclient/bitbucketserver_test.go index ddde8286..2b32b4a6 100644 --- a/vcsclient/bitbucketserver_test.go +++ b/vcsclient/bitbucketserver_test.go @@ -665,6 +665,44 @@ func createBitbucketServerWithBodyHandler(t *testing.T, expectedURI string, resp assert.NoError(t, err) } } +func TestBitbucketServer_TestGetCommitStatus(t *testing.T) { + ctx := context.Background() + ref := "9caf1c431fb783b669f0f909bd018b40f2ea3808" + t.Run("Empty response", func(t *testing.T) { + client, cleanUp := createServerAndClient(t, vcsutils.BitbucketServer, false, nil, + fmt.Sprintf("/rest/build-status/1.0/commits/%s", ref), createBitbucketServerHandler) + defer cleanUp() + _, err := client.GetCommitStatuses(ctx, owner, repo1, ref) + assert.NoError(t, err) + }) + t.Run("Valid response", func(t *testing.T) { + response, err := os.ReadFile(filepath.Join("testdata", "bitbucketserver", "commits_statuses.json")) + assert.NoError(t, err) + client, cleanUp := createServerAndClient(t, vcsutils.BitbucketServer, false, response, + fmt.Sprintf("/rest/build-status/1.0/commits/%s", ref), createBitbucketServerHandler) + defer cleanUp() + commitStatuses, err := client.GetCommitStatuses(ctx, owner, repo1, ref) + assert.NoError(t, err) + assert.True(t, len(commitStatuses) == 3) + assert.True(t, commitStatuses[0].State == InProgress) + assert.True(t, commitStatuses[1].State == Pass) + assert.True(t, commitStatuses[2].State == Fail) + }) + t.Run("Decode failure", func(t *testing.T) { + response, err := os.ReadFile(filepath.Join("testdata", "bitbucketserver", "commits_statuses_bad_decode.json")) + assert.NoError(t, err) + client, cleanUp := createServerAndClient(t, vcsutils.BitbucketServer, false, response, + fmt.Sprintf("/rest/build-status/1.0/commits/%s", ref), createBitbucketServerHandler) + defer cleanUp() + _, err = client.GetCommitStatuses(ctx, owner, repo1, ref) + assert.Error(t, err) + }) + t.Run("bad client", func(t *testing.T) { + client := createBadBitbucketServerClient(t) + _, err := client.GetCommitStatuses(ctx, owner, repo1, ref) + assert.Error(t, err) + }) +} func createBadBitbucketServerClient(t *testing.T) VcsClient { client, err := NewClientBuilder(vcsutils.BitbucketServer).ApiEndpoint("https://bad^endpoint").Build() diff --git a/vcsclient/github.go b/vcsclient/github.go index a069566a..d00d708f 100644 --- a/vcsclient/github.go +++ b/vcsclient/github.go @@ -190,6 +190,30 @@ func (client *GitHubClient) SetCommitStatus(ctx context.Context, commitStatus Co return err } +// GetCommitStatuses on GitHub +func (client *GitHubClient) GetCommitStatuses(ctx context.Context, owner, repository, ref string) (status []CommitStatusInfo, err error) { + ghClient, err := client.buildGithubClient(ctx) + if err != nil { + return nil, err + } + statuses, _, err := ghClient.Repositories.GetCombinedStatus(ctx, owner, repository, ref, nil) + if err != nil { + return nil, err + } + results := make([]CommitStatusInfo, 0) + for _, singleStatus := range statuses.Statuses { + results = append(results, CommitStatusInfo{ + State: CommitStatusAsStringToStatus(*singleStatus.State), + Description: singleStatus.GetDescription(), + DetailsUrl: singleStatus.GetTargetURL(), + Creator: singleStatus.GetCreator().GetName(), + LastUpdatedAt: singleStatus.GetUpdatedAt(), + CreatedAt: singleStatus.GetCreatedAt(), + }) + } + return results, err +} + // DownloadRepository on GitHub func (client *GitHubClient) DownloadRepository(ctx context.Context, owner, repository, branch, localPath string) error { ghClient, err := client.buildGithubClient(ctx) diff --git a/vcsclient/github_test.go b/vcsclient/github_test.go index a6f226ac..c3170498 100644 --- a/vcsclient/github_test.go +++ b/vcsclient/github_test.go @@ -689,6 +689,49 @@ func TestGitHubClient_GetModifiedFiles(t *testing.T) { }) } +func TestGitHubClient_TestGetCommitStatus(t *testing.T) { + ctx := context.Background() + ref := "5fbf81b31ff7a3b06bd362d1891e2f01bdb2be69" + t.Run("Empty response", func(t *testing.T) { + client, cleanUp := createServerAndClient(t, vcsutils.GitHub, false, nil, fmt.Sprintf("/repos/jfrog/%s/commits/%s/status", repo1, ref), createGitHubHandler) + defer cleanUp() + _, err := client.GetCommitStatuses(ctx, owner, repo1, ref) + assert.NoError(t, err) + }) + t.Run("Full response", func(t *testing.T) { + response, err := os.ReadFile(filepath.Join("testdata", "github", "commits_statuses.json")) + assert.NoError(t, err) + client, cleanUp := createServerAndClient(t, vcsutils.GitHub, false, response, + fmt.Sprintf("/repos/jfrog/%s/commits/%s/status", repo1, ref), + createGitHubHandler) + defer cleanUp() + commitStatuses, err := client.GetCommitStatuses(ctx, owner, repo1, ref) + assert.NoError(t, err) + assert.True(t, len(commitStatuses) == 4) + assert.True(t, commitStatuses[0].State == Pass) + assert.True(t, commitStatuses[1].State == InProgress) + assert.True(t, commitStatuses[2].State == Fail) + assert.True(t, commitStatuses[3].State == Error) + }) + t.Run("Bad response format", func(t *testing.T) { + response, err := os.ReadFile(filepath.Join("testdata", "github", "commits_statuses_bad_json.json")) + assert.NoError(t, err) + client, cleanUp := createServerAndClient(t, vcsutils.GitHub, false, response, + fmt.Sprintf("/repos/jfrog/%s/commits/%s/status", repo1, ref), + createGitHubHandler) + defer cleanUp() + _, err = client.GetCommitStatuses(ctx, owner, repo1, ref) + assert.Error(t, err) + _, err = createBadGitHubClient(t).GetCommitStatuses(ctx, owner, repo1, ref) + assert.Error(t, err) + }) + t.Run("Bad client", func(t *testing.T) { + client := createBadGitHubClient(t) + _, err := client.GetCommitStatuses(ctx, owner, repo1, ref) + assert.Error(t, err) + }) +} + func createBadGitHubClient(t *testing.T) VcsClient { client, err := NewClientBuilder(vcsutils.GitHub).ApiEndpoint("https://bad^endpoint").Build() require.NoError(t, err) diff --git a/vcsclient/gitlab.go b/vcsclient/gitlab.go index 517ddd93..baad0622 100644 --- a/vcsclient/gitlab.go +++ b/vcsclient/gitlab.go @@ -6,14 +6,13 @@ import ( "encoding/base64" "errors" "fmt" + "github.com/jfrog/froggit-go/vcsutils" "github.com/jfrog/gofrog/datastructures" + "github.com/xanzy/go-gitlab" "net/http" "sort" "strconv" "strings" - - "github.com/jfrog/froggit-go/vcsutils" - "github.com/xanzy/go-gitlab" ) // GitLabClient API version 4 @@ -177,6 +176,26 @@ func (client *GitLabClient) SetCommitStatus(ctx context.Context, commitStatus Co return err } +// GetCommitStatuses on GitLab +func (client *GitLabClient) GetCommitStatuses(ctx context.Context, owner, repository, ref string) (status []CommitStatusInfo, err error) { + statuses, _, err := client.glClient.Commits.GetCommitStatuses(repository, ref, nil, gitlab.WithContext(ctx)) + if err != nil { + return nil, err + } + results := make([]CommitStatusInfo, 0) + for _, singleStatus := range statuses { + results = append(results, CommitStatusInfo{ + State: CommitStatusAsStringToStatus(singleStatus.Status), + Description: singleStatus.Description, + DetailsUrl: singleStatus.TargetURL, + Creator: singleStatus.Author.Name, + LastUpdatedAt: extractTimeWithFallback(singleStatus.FinishedAt), + CreatedAt: extractTimeWithFallback(singleStatus.CreatedAt), + }) + } + return results, nil +} + // DownloadRepository on GitLab func (client *GitLabClient) DownloadRepository(ctx context.Context, owner, repository, branch, localPath string) error { format := "tar.gz" diff --git a/vcsclient/gitlab_test.go b/vcsclient/gitlab_test.go index 6e83fc94..b32fbf9d 100644 --- a/vcsclient/gitlab_test.go +++ b/vcsclient/gitlab_test.go @@ -628,3 +628,44 @@ func createGitLabWithBodyHandler(t *testing.T, expectedURI string, response []by assert.NoError(t, err) } } +func TestGitLabClient_TestGetCommitStatus(t *testing.T) { + ctx := context.Background() + ref := "5fbf81b31ff7a3b06bd362d1891e2f01bdb2be69" + t.Run("Empty response", func(t *testing.T) { + client, cleanUp := createServerAndClient(t, vcsutils.GitLab, false, []CommitStatusInfo{}, + fmt.Sprintf("/api/v4/projects/%s/repository/commits/%s/statuses", repo1, ref), + createGitLabHandler) + defer cleanUp() + _, err := client.GetCommitStatuses(ctx, owner, repo1, ref) + assert.NoError(t, err) + }) + t.Run("Valid response", func(t *testing.T) { + response, err := os.ReadFile(filepath.Join("testdata", "gitlab", "commits_statuses.json")) + assert.NoError(t, err) + client, cleanUp := createServerAndClient(t, vcsutils.GitLab, false, response, + fmt.Sprintf("/api/v4/projects/%s/repository/commits/%s/statuses", repo1, ref), + createGitLabHandler) + defer cleanUp() + commitStatuses, err := client.GetCommitStatuses(ctx, owner, repo1, ref) + assert.True(t, len(commitStatuses) == 3) + assert.True(t, commitStatuses[0].State == Pass) + assert.True(t, commitStatuses[1].State == InProgress) + assert.True(t, commitStatuses[2].State == Fail) + assert.NoError(t, err) + }) + t.Run("Invalid response format", func(t *testing.T) { + response, err := os.ReadFile(filepath.Join("testdata", "github", "commits_statuses_bad_json.json")) + assert.NoError(t, err) + client, cleanUp := createServerAndClient(t, vcsutils.GitLab, false, response, + fmt.Sprintf("/api/v4/projects/%s/repository/commits/%s/statuses", repo1, ref), + createGitLabHandler) + defer cleanUp() + _, err = client.GetCommitStatuses(ctx, owner, repo1, ref) + assert.Error(t, err) + }) + t.Run("Bad client", func(t *testing.T) { + client := createBadGitHubClient(t) + _, err := client.GetCommitStatuses(ctx, owner, repo1, ref) + assert.Error(t, err) + }) +} diff --git a/vcsclient/testdata/azurerepos/commits_statuses.json b/vcsclient/testdata/azurerepos/commits_statuses.json new file mode 100644 index 00000000..29d843a8 --- /dev/null +++ b/vcsclient/testdata/azurerepos/commits_statuses.json @@ -0,0 +1,56 @@ +{ + "count": 1, + "value": [ + { + "state": "succeeded", + "description": "The build is successful", + "context": { + "name": "Build123", + "genre": "continuous-integration" + }, + "creationDate": "2016-01-27T09:33:07Z", + "createdBy": { + "id": "278d5cd2-584d-4b63-824a-2ba458937249", + "displayName": "Norman Paulk", + "uniqueName": "Fabrikamfiber16", + "url": "https://dev.azure.com/fabrikam/_apis/Identities/278d5cd2-584d-4b63-824a-2ba458937249", + "imageUrl": "https://dev.azure.com/fabrikam/_api/_common/identityImage?id=278d5cd2-584d-4b63-824a-2ba458937249" + }, + "targetUrl": "https://ci.fabrikam.com/my-project/build/123 " + }, + { + "state": "pending", + "description": "The build is pending", + "context": { + "name": "Build123", + "genre": "continuous-integration" + }, + "creationDate": "2016-01-27T09:33:07Z", + "createdBy": { + "id": "278d5cd2-584d-4b63-824a-2ba458937249", + "displayName": "Norman Paulk", + "uniqueName": "Fabrikamfiber16", + "url": "https://dev.azure.com/fabrikam/_apis/Identities/278d5cd2-584d-4b63-824a-2ba458937249", + "imageUrl": "https://dev.azure.com/fabrikam/_api/_common/identityImage?id=278d5cd2-584d-4b63-824a-2ba458937249" + }, + "targetUrl": "https://ci.fabrikam.com/my-project/build/123 " + }, + { + "state": "failed", + "description": "The build has failed", + "context": { + "name": "Build123", + "genre": "continuous-integration" + }, + "creationDate": "2016-01-27T09:33:07Z", + "createdBy": { + "id": "278d5cd2-584d-4b63-824a-2ba458937249", + "displayName": "Norman Paulk", + "uniqueName": "Fabrikamfiber16", + "url": "https://dev.azure.com/fabrikam/_apis/Identities/278d5cd2-584d-4b63-824a-2ba458937249", + "imageUrl": "https://dev.azure.com/fabrikam/_api/_common/identityImage?id=278d5cd2-584d-4b63-824a-2ba458937249" + }, + "targetUrl": "https://ci.fabrikam.com/my-project/build/123 " + } + ] +} \ No newline at end of file diff --git a/vcsclient/testdata/azurerepos/resourcesResponse.json b/vcsclient/testdata/azurerepos/resourcesResponse.json index d702595d..2425cb53 100644 --- a/vcsclient/testdata/azurerepos/resourcesResponse.json +++ b/vcsclient/testdata/azurerepos/resourcesResponse.json @@ -79,6 +79,16 @@ "minVersion": "3.2", "maxVersion": "7.1", "releasedVersion": "0.0" + }, + { + "id": "428dd4fb-fda5-4722-af02-9313b80305da", + "area": "Location", + "resourceName": "ResourceAreas", + "routeTemplate": "_apis/{resource}/commitStatus", + "resourceVersion": 1, + "minVersion": "3.2", + "maxVersion": "7.1", + "releasedVersion": "0.0" } ], "count": 2 diff --git a/vcsclient/testdata/bitbucketcloud/commits_statuses.json b/vcsclient/testdata/bitbucketcloud/commits_statuses.json new file mode 100644 index 00000000..b8e7edb6 --- /dev/null +++ b/vcsclient/testdata/bitbucketcloud/commits_statuses.json @@ -0,0 +1,37 @@ +{ + "values": [ + { + "uuid": "", + "key": "", + "refname": "", + "url": "", + "state": "INPROGRESS", + "name": "", + "description": "", + "created_on": "2016-01-27T09:33:07Z", + "updated_on": "2016-01-27T09:33:07Z" + }, + { + "uuid": "", + "key": "", + "refname": "", + "url": "", + "state": "SUCCESSFUL", + "name": "", + "description": "", + "created_on": "2016-01-27T09:33:07Z", + "updated_on": "2016-01-27T09:33:07Z" + }, + { + "uuid": "", + "key": "", + "refname": "", + "url": "", + "state": "FAILED", + "name": "", + "description": "", + "created_on": "2016-01-27T09:33:07Z", + "updated_on": "2016-01-27T09:33:07Z" + } + ] +} \ No newline at end of file diff --git a/vcsclient/testdata/bitbucketcloud/get_commit_status_response.json b/vcsclient/testdata/bitbucketcloud/get_commit_status_response.json new file mode 100644 index 00000000..0bd07543 --- /dev/null +++ b/vcsclient/testdata/bitbucketcloud/get_commit_status_response.json @@ -0,0 +1,70 @@ +{ + "values": [ + { + "links": { + "self": { + "href": "", + "name": "" + }, + "commit": { + "href": "", + "name": "" + } + }, + "uuid": "", + "key": "", + "refname": "", + "url": "", + "state": "FAILED", + "name": "", + "description": "", + "created_on": "", + "updated_on": "" + }, + { + "links": { + "self": { + "href": "", + "name": "" + }, + "commit": { + "href": "", + "name": "" + } + }, + "uuid": "", + "key": "", + "refname": "", + "url": "", + "state": "PENDING", + "name": "", + "description": "", + "created_on": "", + "updated_on": "" + }, + { + "links": { + "self": { + "href": "", + "name": "" + }, + "commit": { + "href": "", + "name": "" + } + }, + "uuid": "", + "key": "", + "refname": "", + "url": "", + "state": "SUCCESS", + "name": "", + "description": "", + "created_on": "", + "updated_on": "" + } + ], + "page": 1, + "pagelen": 10, + "size": 2 +} \ No newline at end of file diff --git a/vcsclient/testdata/bitbucketserver/commits_statuses.json b/vcsclient/testdata/bitbucketserver/commits_statuses.json new file mode 100644 index 00000000..11645c53 --- /dev/null +++ b/vcsclient/testdata/bitbucketserver/commits_statuses.json @@ -0,0 +1,37 @@ +{ + "values": [ + { + "uuid": "", + "key": "", + "refname": "", + "url": "", + "state": "INPROGRESS", + "name": "", + "description": "this is pending", + "created_on": "2016-01-27T09:33:07Z", + "updated_on": "2016-01-27T09:33:07Z" + }, + { + "uuid": "", + "key": "", + "refname": "", + "url": "", + "state": "SUCCESSFUL", + "name": "", + "description": "this is successful", + "created_on": "2016-01-27T09:33:07Z", + "updated_on": "2016-01-27T09:33:07Z" + }, + { + "uuid": "", + "key": "", + "refname": "", + "url": "", + "state": "FAILED", + "name": "", + "description": "this is a failure", + "created_on": "2016-01-27T09:33:07Z", + "updated_on": "2016-01-27T09:33:07Z" + } + ] +} \ No newline at end of file diff --git a/vcsclient/testdata/bitbucketserver/commits_statuses_bad_decode.json b/vcsclient/testdata/bitbucketserver/commits_statuses_bad_decode.json new file mode 100644 index 00000000..dfdf2455 --- /dev/null +++ b/vcsclient/testdata/bitbucketserver/commits_statuses_bad_decode.json @@ -0,0 +1,18 @@ +{ + "values": [ + { + "key": "Title 1", + "url": "https://example.com/page1", + "state": "active", + "description": "Description 1", + "name": "Creator 1" + }, + { + "key": "Title 2", + "url": "https://example.com/page2", + "state": "inactive", + "description": "Description 2", + "name": {"invalid_key": "invalid_value"} + } + ] +} diff --git a/vcsclient/testdata/github/commits_statuses.json b/vcsclient/testdata/github/commits_statuses.json new file mode 100644 index 00000000..9aa65cb1 --- /dev/null +++ b/vcsclient/testdata/github/commits_statuses.json @@ -0,0 +1,125 @@ +{ + "statuses": [ + { + "url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "avatar_url": "https://github.com/images/error/hubot_happy.gif", + "id": 1, + "node_id": "MDY6U3RhdHVzMQ==", + "state": "success", + "description": "Build has completed successfully", + "target_url": "https://ci.example.com/1000/output", + "context": "continuous-integration/jenkins", + "created_at": "2012-07-20T01:19:13Z", + "updated_at": "2012-07-20T01:19:13Z" + }, + { + "url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "avatar_url": "https://github.com/images/error/other_user_happy.gif", + "id": 2, + "node_id": "MDY6U3RhdHVzMg==", + "state": "pending", + "description": "Build is pending", + "target_url": "https://ci.example.com/2000/output", + "context": "security/brakeman", + "created_at": "2012-08-20T01:19:13Z", + "updated_at": "2012-08-20T01:19:13Z" + } , + { + "url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "avatar_url": "https://github.com/images/error/other_user_happy.gif", + "id": 2, + "node_id": "MDY6U3RhdHVzMg==", + "state": "failure", + "description": "Build has failed", + "target_url": "https://ci.example.com/2000/output", + "context": "security/brakeman", + "created_at": "2012-08-20T01:19:13Z", + "updated_at": "2012-08-20T01:19:13Z" + }, + { + "url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "avatar_url": "https://github.com/images/error/hubot_happy.gif", + "id": 1, + "node_id": "MDY6U3RhdHVzMQ==", + "state": "someNewState", + "description": "this should return failed state as it is unknown", + "target_url": "https://ci.example.com/1000/output", + "context": "continuous-integration/jenkins", + "created_at": "2012-07-20T01:19:13Z", + "updated_at": "2012-07-20T01:19:13Z" + } + ], + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "total_count": 2, + "repository": { + "id": 1296269, + "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", + "name": "Hello-World", + "full_name": "octocat/Hello-World", + "owner": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/octocat/Hello-World", + "description": "This your first repo!", + "fork": false, + "url": "https://api.github.com/repos/octocat/Hello-World", + "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", + "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", + "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", + "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", + "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", + "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", + "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", + "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", + "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", + "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", + "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments", + "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", + "events_url": "https://api.github.com/repos/octocat/Hello-World/events", + "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", + "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", + "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", + "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", + "git_url": "git:github.com/octocat/Hello-World.git", + "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", + "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", + "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", + "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", + "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", + "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", + "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", + "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", + "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", + "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", + "ssh_url": "git@github.com:octocat/Hello-World.git", + "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", + "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", + "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", + "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", + "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", + "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", + "hooks_url": "http://api.github.com/repos/octocat/Hello-World/hooks" + }, + "commit_url": "https://api.github.com/repos/octocat/Hello-World/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "url": "https://api.github.com/repos/octocat/Hello-World/6dcb09b5b57875f334f61aebed695e2e4193db5e/status" +} \ No newline at end of file diff --git a/vcsclient/testdata/github/commits_statuses_bad_json.json b/vcsclient/testdata/github/commits_statuses_bad_json.json new file mode 100644 index 00000000..e56ba462 --- /dev/null +++ b/vcsclient/testdata/github/commits_statuses_bad_json.json @@ -0,0 +1,124 @@ + "statuses": [ + { + "url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "avatar_url": "https://github.com/images/error/hubot_happy.gif", + "id": 1, + "node_id": "MDY6U3RhdHVzMQ==", + "state": "success", + "description": "Build has completed successfully", + "target_url": "https://ci.example.com/1000/output", + "context": "continuous-integration/jenkins", + "created_at": "2012-07-20T01:19:13Z", + "updated_at": "2012-07-20T01:19:13Z" + }, + { + "url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "avatar_url": "https://github.com/images/error/other_user_happy.gif", + "id": 2, + "node_id": "MDY6U3RhdHVzMg==", + "state": "pending", + "description": "Build is pending", + "target_url": "https://ci.example.com/2000/output", + "context": "security/brakeman", + "created_at": "2012-08-20T01:19:13Z", + "updated_at": "2012-08-20T01:19:13Z" + } , + { + "url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "avatar_url": "https://github.com/images/error/other_user_happy.gif", + "id": 2, + "node_id": "MDY6U3RhdHVzMg==", + "state": "failure", + "description": "Build has failed", + "target_url": "https://ci.example.com/2000/output", + "context": "security/brakeman", + "created_at": "2012-08-20T01:19:13Z", + "updated_at": "2012-08-20T01:19:13Z" + }, + { + "url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "avatar_url": "https://github.com/images/error/hubot_happy.gif", + "id": 1, + "node_id": "MDY6U3RhdHVzMQ==", + "state": "someNewState", + "description": "this should return failed state as it is unknown", + "target_url": "https://ci.example.com/1000/output", + "context": "continuous-integration/jenkins", + "created_at": "2012-07-20T01:19:13Z", + "updated_at": "2012-07-20T01:19:13Z" + } + ], + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "total_count": 2, + "repository": { + "id": 1296269, + "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", + "name": "Hello-World", + "full_name": "octocat/Hello-World", + "owner": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/octocat/Hello-World", + "description": "This your first repo!", + "fork": false, + "url": "https://api.github.com/repos/octocat/Hello-World", + "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", + "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", + "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", + "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", + "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", + "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", + "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", + "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", + "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", + "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", + "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments", + "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", + "events_url": "https://api.github.com/repos/octocat/Hello-World/events", + "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", + "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", + "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", + "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", + "git_url": "git:github.com/octocat/Hello-World.git", + "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", + "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", + "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", + "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", + "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", + "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", + "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", + "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", + "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", + "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", + "ssh_url": "git@github.com:octocat/Hello-World.git", + "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", + "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", + "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", + "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", + "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", + "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", + "hooks_url": "http://api.github.com/repos/octocat/Hello-World/hooks" + }, + "commit_url": "https://api.github.com/repos/octocat/Hello-World/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "url": "https://api.github.com/repos/octocat/Hello-World/6dcb09b5b57875f334f61aebed695e2e4193db5e/status" +} \ No newline at end of file diff --git a/vcsclient/testdata/gitlab/commits_statuses.json b/vcsclient/testdata/gitlab/commits_statuses.json new file mode 100644 index 00000000..763df63b --- /dev/null +++ b/vcsclient/testdata/gitlab/commits_statuses.json @@ -0,0 +1,65 @@ +[ + { + "status" : "success", + "created_at" : "2016-01-19T08:40:25.934Z", + "started_at" : null, + "name" : "bundler:audit", + "allow_failure" : true, + "author" : { + "username" : "janedoe", + "state" : "active", + "web_url" : "https://gitlab.example.com/janedoe", + "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/jane-doe-400-400.png", + "id" : 28, + "name" : "Jane Doe" + }, + "description" : "this is my description", + "sha" : "18f3e63d05582537db6d183d9d557be09e1f90c8", + "target_url" : "https://gitlab.example.com/janedoe/gitlab-foss/builds/91", + "finished_at" : null, + "id" : 91, + "ref" : "master" + }, + { + "started_at" : null, + "name" : "test", + "allow_failure" : false, + "status" : "pending", + "created_at" : "2016-01-19T08:40:25.832Z", + "target_url" : "https://gitlab.example.com/janedoe/gitlab-foss/builds/90", + "id" : 90, + "finished_at" : null, + "ref" : "master", + "sha" : "18f3e63d05582537db6d183d9d557be09e1f90c8", + "author" : { + "id" : 28, + "name" : "Jane Doe", + "username" : "janedoe", + "web_url" : "https://gitlab.example.com/janedoe", + "state" : "active", + "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/jane-doe-400-400.png" + }, + "description" : null + }, + { + "started_at" : null, + "name" : "test", + "allow_failure" : false, + "status" : "failure", + "created_at" : "2016-01-19T08:40:25.832Z", + "target_url" : "https://gitlab.example.com/janedoe/gitlab-foss/builds/90", + "id" : 90, + "finished_at" : null, + "ref" : "master", + "sha" : "18f3e63d05582537db6d183d9d557be09e1f90c8", + "author" : { + "id" : 28, + "name" : "Jane Doe", + "username" : "janedoe", + "web_url" : "https://gitlab.example.com/janedoe", + "state" : "active", + "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/jane-doe-400-400.png" + }, + "description" : null + } +] \ No newline at end of file diff --git a/vcsclient/vcsclient.go b/vcsclient/vcsclient.go index 74c6c586..07ac6f1f 100644 --- a/vcsclient/vcsclient.go +++ b/vcsclient/vcsclient.go @@ -61,6 +61,22 @@ type RepositoryEnvironmentInfo struct { Reviewers []string } +// CommitStatusInfo status which is then reflected in pull requests involving those commits +// State - One of success, pending, failure, or error +// Description - Description of the commit status +// DetailsUrl - The URL for component status link +// Creator - The creator of the status +// CreatedAt - Date of status creation +// LastUpdatedAt - Date of status last update time. +type CommitStatusInfo struct { + State CommitStatus + Description string + DetailsUrl string + Creator string + CreatedAt time.Time + LastUpdatedAt time.Time +} + // VcsClient is a base class of all Vcs clients - GitHub, GitLab, Bitbucket server and cloud clients type VcsClient interface { // TestConnection Returns nil if connection and authorization established successfully @@ -109,6 +125,12 @@ type VcsClient interface { // detailsUrl - The URL for component status link SetCommitStatus(ctx context.Context, commitStatus CommitStatus, owner, repository, ref, title, description, detailsURL string) error + // GetCommitStatuses Gets all statuses for a specific commit + // owner - User or organization + // repository - VCS repository name + // ref - SHA, a branch name, or a tag name. + GetCommitStatuses(ctx context.Context, owner, repository, ref string) (status []CommitStatusInfo, err error) + // DownloadRepository Downloads and extracts a VCS repository // owner - User or organization // repository - VCS repository name @@ -290,3 +312,25 @@ func validateParametersNotBlank(paramNameValueMap map[string]string) error { } return nil } + +// CommitStatusAsStringToStatus maps status as string to CommitStatus +// Handles all the different statuses for every VCS provider +func CommitStatusAsStringToStatus(rawStatus string) CommitStatus { + switch strings.ToLower(rawStatus) { + case "success", "succeeded", "successful": + return Pass + case "fail", "failure", "failed": + return Fail + case "pending", "inprogress": + return InProgress + default: + return Error + } +} + +func extractTimeWithFallback(timeObject *time.Time) time.Time { + if timeObject == nil { + return time.Time{} + } + return timeObject.UTC() +}