diff --git a/github/actions_concurrency_groups.go b/github/actions_concurrency_groups.go new file mode 100644 index 00000000000..42a8e7b7ba2 --- /dev/null +++ b/github/actions_concurrency_groups.go @@ -0,0 +1,132 @@ +// Copyright 2026 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/url" +) + +// ConcurrencyGroup represents a GitHub Actions concurrency group. +type ConcurrencyGroup struct { + GroupName string `json:"group_name"` + GroupURL string `json:"group_url"` + TotalCount *int `json:"total_count,omitempty"` + LastAcquiredAt *Timestamp `json:"last_acquired_at,omitempty"` + GroupMembers []*ConcurrencyGroupMember `json:"group_members,omitzero"` +} + +// ConcurrencyGroupMember represents a workflow run or job that is holding or waiting for a concurrency group. +type ConcurrencyGroupMember struct { + RunID int64 `json:"run_id"` + RunName string `json:"run_name"` + RunURL string `json:"run_url"` + RunHTMLURL string `json:"run_html_url"` + JobID *int64 `json:"job_id,omitempty"` + JobName *string `json:"job_name,omitempty"` + JobURL *string `json:"job_url,omitempty"` + JobHTMLURL *string `json:"job_html_url,omitempty"` + Status string `json:"status"` + Position *int `json:"position,omitempty"` + PositionURL *string `json:"position_url,omitempty"` +} + +// ConcurrencyGroupList represents a list of GitHub Actions concurrency groups. +type ConcurrencyGroupList struct { + TotalCount int `json:"total_count"` + ConcurrencyGroups []*ConcurrencyGroup `json:"concurrency_groups"` +} + +// GetConcurrencyGroupOptions specifies the optional parameters to the +// ActionsService.GetConcurrencyGroup method. +type GetConcurrencyGroupOptions struct { + // AheadOfRun filters the results to the items ahead of this workflow run ID, plus the run itself. + AheadOfRun int64 `url:"ahead_of_run,omitempty"` + + // AheadOfJob filters the results to the items ahead of this job ID, plus the job itself. + AheadOfJob int64 `url:"ahead_of_job,omitempty"` +} + +// ListConcurrencyGroups lists the active concurrency groups for a repository. +// +// GitHub API docs: https://docs.github.com/rest/actions/concurrency-groups?apiVersion=2022-11-28#list-concurrency-groups-for-a-repository +// +//meta:operation GET /repos/{owner}/{repo}/actions/concurrency_groups +func (s *ActionsService) ListConcurrencyGroups(ctx context.Context, owner, repo string, opts *ListCursorOptions) (*ConcurrencyGroupList, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/actions/concurrency_groups", owner, repo) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest(ctx, "GET", u, nil) + if err != nil { + return nil, nil, err + } + + var groups *ConcurrencyGroupList + resp, err := s.client.Do(req, &groups) + if err != nil { + return nil, resp, err + } + + return groups, resp, nil +} + +// GetConcurrencyGroup gets a concurrency group for a repository, including the items in its queue. +// +// Note: the concurrency group name is URL path escaped for you. See: https://pkg.go.dev/net/url#PathEscape . +// +// GitHub API docs: https://docs.github.com/rest/actions/concurrency-groups?apiVersion=2022-11-28#get-a-concurrency-group-for-a-repository +// +//meta:operation GET /repos/{owner}/{repo}/actions/concurrency_groups/{concurrency_group_name} +func (s *ActionsService) GetConcurrencyGroup(ctx context.Context, owner, repo, groupName string, opts *GetConcurrencyGroupOptions) (*ConcurrencyGroup, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/actions/concurrency_groups/%v", owner, repo, url.PathEscape(groupName)) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest(ctx, "GET", u, nil) + if err != nil { + return nil, nil, err + } + + var group *ConcurrencyGroup + resp, err := s.client.Do(req, &group) + if err != nil { + return nil, resp, err + } + + return group, resp, nil +} + +// ListWorkflowRunConcurrencyGroups lists the concurrency groups associated with a workflow run or its jobs. +// +// GitHub API docs: https://docs.github.com/rest/actions/concurrency-groups?apiVersion=2022-11-28#list-concurrency-groups-for-a-workflow-run +// +//meta:operation GET /repos/{owner}/{repo}/actions/runs/{run_id}/concurrency_groups +func (s *ActionsService) ListWorkflowRunConcurrencyGroups(ctx context.Context, owner, repo string, runID int64, opts *ListCursorOptions) (*ConcurrencyGroupList, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/concurrency_groups", owner, repo, runID) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest(ctx, "GET", u, nil) + if err != nil { + return nil, nil, err + } + + var groups *ConcurrencyGroupList + resp, err := s.client.Do(req, &groups) + if err != nil { + return nil, resp, err + } + + return groups, resp, nil +} diff --git a/github/actions_concurrency_groups_test.go b/github/actions_concurrency_groups_test.go new file mode 100644 index 00000000000..6589c9ef722 --- /dev/null +++ b/github/actions_concurrency_groups_test.go @@ -0,0 +1,289 @@ +// Copyright 2026 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "net/http" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestActionsService_ListConcurrencyGroups(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/repos/o/r/actions/concurrency_groups", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "after": "abc", + "per_page": "2", + }) + fmt.Fprint(w, `{ + "total_count": 2, + "concurrency_groups": [ + { + "group_name": "deploy-prod", + "group_url": "https://api.github.com/repos/o/r/actions/concurrency_groups/deploy-prod", + "last_acquired_at": `+referenceTimeStr+` + }, + { + "group_name": "ci-build", + "group_url": "https://api.github.com/repos/o/r/actions/concurrency_groups/ci-build", + "last_acquired_at": null + } + ] + }`) + }) + + ctx := t.Context() + opts := &ListCursorOptions{After: "abc", PerPage: 2} + groups, _, err := client.Actions.ListConcurrencyGroups(ctx, "o", "r", opts) + if err != nil { + t.Errorf("Actions.ListConcurrencyGroups returned error: %v", err) + } + + want := &ConcurrencyGroupList{ + TotalCount: 2, + ConcurrencyGroups: []*ConcurrencyGroup{ + { + GroupName: "deploy-prod", + GroupURL: "https://api.github.com/repos/o/r/actions/concurrency_groups/deploy-prod", + LastAcquiredAt: &referenceTimestamp, + }, + { + GroupName: "ci-build", + GroupURL: "https://api.github.com/repos/o/r/actions/concurrency_groups/ci-build", + }, + }, + } + if !cmp.Equal(groups, want) { + t.Errorf("Actions.ListConcurrencyGroups returned %+v, want %+v", groups, want) + } + + const methodName = "ListConcurrencyGroups" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Actions.ListConcurrencyGroups(ctx, "\n", "\n", opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Actions.ListConcurrencyGroups(ctx, "o", "r", opts) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestActionsService_GetConcurrencyGroup(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/repos/o/r/actions/concurrency_groups/deploy-prod", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "ahead_of_job": "2", + }) + fmt.Fprint(w, `{ + "group_name": "deploy-prod", + "group_url": "https://api.github.com/repos/o/r/actions/concurrency_groups/deploy-prod", + "total_count": 2, + "group_members": [ + { + "run_id": 1, + "run_name": "Deploy", + "run_url": "https://api.github.com/repos/o/r/actions/runs/1", + "run_html_url": "https://github.com/o/r/actions/runs/1", + "status": "in_progress" + }, + { + "run_id": 3, + "run_name": "Deploy hotfix", + "run_url": "https://api.github.com/repos/o/r/actions/runs/3", + "run_html_url": "https://github.com/o/r/actions/runs/3", + "job_id": 2, + "job_name": "deploy", + "job_url": "https://api.github.com/repos/o/r/actions/jobs/2", + "job_html_url": "https://github.com/o/r/actions/runs/3/job/2", + "status": "pending" + } + ] + }`) + }) + + ctx := t.Context() + opts := &GetConcurrencyGroupOptions{AheadOfJob: 2} + group, _, err := client.Actions.GetConcurrencyGroup(ctx, "o", "r", "deploy-prod", opts) + if err != nil { + t.Errorf("Actions.GetConcurrencyGroup returned error: %v", err) + } + + want := &ConcurrencyGroup{ + GroupName: "deploy-prod", + GroupURL: "https://api.github.com/repos/o/r/actions/concurrency_groups/deploy-prod", + TotalCount: new(2), + GroupMembers: []*ConcurrencyGroupMember{ + { + RunID: 1, + RunName: "Deploy", + RunURL: "https://api.github.com/repos/o/r/actions/runs/1", + RunHTMLURL: "https://github.com/o/r/actions/runs/1", + Status: "in_progress", + }, + { + RunID: 3, + RunName: "Deploy hotfix", + RunURL: "https://api.github.com/repos/o/r/actions/runs/3", + RunHTMLURL: "https://github.com/o/r/actions/runs/3", + JobID: new(int64(2)), + JobName: new("deploy"), + JobURL: new("https://api.github.com/repos/o/r/actions/jobs/2"), + JobHTMLURL: new("https://github.com/o/r/actions/runs/3/job/2"), + Status: "pending", + }, + }, + } + if !cmp.Equal(group, want) { + t.Errorf("Actions.GetConcurrencyGroup returned %+v, want %+v", group, want) + } + + const methodName = "GetConcurrencyGroup" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Actions.GetConcurrencyGroup(ctx, "\n", "\n", "\n", opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Actions.GetConcurrencyGroup(ctx, "o", "r", "deploy-prod", opts) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestActionsService_GetConcurrencyGroup_EscapeGroupName(t *testing.T) { + t.Parallel() + for _, tt := range []struct { + groupName string + escapedGroupName string + }{ + {groupName: "deploy-prod", escapedGroupName: "deploy-prod"}, + {groupName: "CI-refs/heads/main", escapedGroupName: "CI-refs%2Fheads%2Fmain"}, + } { + t.Run(tt.groupName, func(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + if got, want := r.URL.EscapedPath(), "/repos/o/r/actions/concurrency_groups/"+tt.escapedGroupName; got != want { + t.Errorf("Request path = %q, want %q", got, want) + } + if got, want := r.URL.Path, "/repos/o/r/actions/concurrency_groups/"+tt.groupName; got != want { + t.Errorf("Decoded request path = %q, want %q", got, want) + } + fmt.Fprint(w, `{}`) + }) + + ctx := t.Context() + _, _, err := client.Actions.GetConcurrencyGroup(ctx, "o", "r", tt.groupName, nil) + if err != nil { + t.Fatalf("Actions.GetConcurrencyGroup returned error: %v", err) + } + }) + } +} + +func TestActionsService_ListWorkflowRunConcurrencyGroups(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/repos/o/r/actions/runs/1/concurrency_groups", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "before": "xyz", + "per_page": "2", + }) + fmt.Fprint(w, `{ + "total_count": 2, + "concurrency_groups": [ + { + "group_name": "deploy-prod", + "group_url": "https://api.github.com/repos/o/r/actions/concurrency_groups/deploy-prod", + "group_members": [ + { + "run_id": 1, + "run_name": "Deploy", + "run_url": "https://api.github.com/repos/o/r/actions/runs/1", + "run_html_url": "https://github.com/o/r/actions/runs/1", + "status": "in_progress", + "position": 0, + "position_url": "https://api.github.com/repos/o/r/actions/concurrency_groups/deploy-prod?ahead_of_run=1" + } + ] + }, + { + "group_name": "ci-build", + "group_url": "https://api.github.com/repos/o/r/actions/concurrency_groups/ci-build", + "group_members": [] + } + ] + }`) + }) + + ctx := t.Context() + opts := &ListCursorOptions{Before: "xyz", PerPage: 2} + groups, _, err := client.Actions.ListWorkflowRunConcurrencyGroups(ctx, "o", "r", 1, opts) + if err != nil { + t.Errorf("Actions.ListWorkflowRunConcurrencyGroups returned error: %v", err) + } + + want := &ConcurrencyGroupList{ + TotalCount: 2, + ConcurrencyGroups: []*ConcurrencyGroup{ + { + GroupName: "deploy-prod", + GroupURL: "https://api.github.com/repos/o/r/actions/concurrency_groups/deploy-prod", + GroupMembers: []*ConcurrencyGroupMember{ + { + RunID: 1, + RunName: "Deploy", + RunURL: "https://api.github.com/repos/o/r/actions/runs/1", + RunHTMLURL: "https://github.com/o/r/actions/runs/1", + Status: "in_progress", + Position: new(0), + PositionURL: new("https://api.github.com/repos/o/r/actions/concurrency_groups/deploy-prod?ahead_of_run=1"), + }, + }, + }, + { + GroupName: "ci-build", + GroupURL: "https://api.github.com/repos/o/r/actions/concurrency_groups/ci-build", + GroupMembers: []*ConcurrencyGroupMember{}, + }, + }, + } + if !cmp.Equal(groups, want) { + t.Errorf("Actions.ListWorkflowRunConcurrencyGroups returned %+v, want %+v", groups, want) + } + + const methodName = "ListWorkflowRunConcurrencyGroups" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Actions.ListWorkflowRunConcurrencyGroups(ctx, "\n", "\n", -1, opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Actions.ListWorkflowRunConcurrencyGroups(ctx, "o", "r", 1, opts) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} diff --git a/github/github-accessors.go b/github/github-accessors.go index 952d3d8fea0..7a55b999a8c 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -7814,6 +7814,150 @@ func (c *CommunityHealthMetrics) GetUpdatedAt() Timestamp { return *c.UpdatedAt } +// GetGroupMembers returns the GroupMembers slice if it's non-nil, nil otherwise. +func (c *ConcurrencyGroup) GetGroupMembers() []*ConcurrencyGroupMember { + if c == nil || c.GroupMembers == nil { + return nil + } + return c.GroupMembers +} + +// GetGroupName returns the GroupName field. +func (c *ConcurrencyGroup) GetGroupName() string { + if c == nil { + return "" + } + return c.GroupName +} + +// GetGroupURL returns the GroupURL field. +func (c *ConcurrencyGroup) GetGroupURL() string { + if c == nil { + return "" + } + return c.GroupURL +} + +// GetLastAcquiredAt returns the LastAcquiredAt field if it's non-nil, zero value otherwise. +func (c *ConcurrencyGroup) GetLastAcquiredAt() Timestamp { + if c == nil || c.LastAcquiredAt == nil { + return Timestamp{} + } + return *c.LastAcquiredAt +} + +// GetTotalCount returns the TotalCount field if it's non-nil, zero value otherwise. +func (c *ConcurrencyGroup) GetTotalCount() int { + if c == nil || c.TotalCount == nil { + return 0 + } + return *c.TotalCount +} + +// GetConcurrencyGroups returns the ConcurrencyGroups slice if it's non-nil, nil otherwise. +func (c *ConcurrencyGroupList) GetConcurrencyGroups() []*ConcurrencyGroup { + if c == nil || c.ConcurrencyGroups == nil { + return nil + } + return c.ConcurrencyGroups +} + +// GetTotalCount returns the TotalCount field. +func (c *ConcurrencyGroupList) GetTotalCount() int { + if c == nil { + return 0 + } + return c.TotalCount +} + +// GetJobHTMLURL returns the JobHTMLURL field if it's non-nil, zero value otherwise. +func (c *ConcurrencyGroupMember) GetJobHTMLURL() string { + if c == nil || c.JobHTMLURL == nil { + return "" + } + return *c.JobHTMLURL +} + +// GetJobID returns the JobID field if it's non-nil, zero value otherwise. +func (c *ConcurrencyGroupMember) GetJobID() int64 { + if c == nil || c.JobID == nil { + return 0 + } + return *c.JobID +} + +// GetJobName returns the JobName field if it's non-nil, zero value otherwise. +func (c *ConcurrencyGroupMember) GetJobName() string { + if c == nil || c.JobName == nil { + return "" + } + return *c.JobName +} + +// GetJobURL returns the JobURL field if it's non-nil, zero value otherwise. +func (c *ConcurrencyGroupMember) GetJobURL() string { + if c == nil || c.JobURL == nil { + return "" + } + return *c.JobURL +} + +// GetPosition returns the Position field if it's non-nil, zero value otherwise. +func (c *ConcurrencyGroupMember) GetPosition() int { + if c == nil || c.Position == nil { + return 0 + } + return *c.Position +} + +// GetPositionURL returns the PositionURL field if it's non-nil, zero value otherwise. +func (c *ConcurrencyGroupMember) GetPositionURL() string { + if c == nil || c.PositionURL == nil { + return "" + } + return *c.PositionURL +} + +// GetRunHTMLURL returns the RunHTMLURL field. +func (c *ConcurrencyGroupMember) GetRunHTMLURL() string { + if c == nil { + return "" + } + return c.RunHTMLURL +} + +// GetRunID returns the RunID field. +func (c *ConcurrencyGroupMember) GetRunID() int64 { + if c == nil { + return 0 + } + return c.RunID +} + +// GetRunName returns the RunName field. +func (c *ConcurrencyGroupMember) GetRunName() string { + if c == nil { + return "" + } + return c.RunName +} + +// GetRunURL returns the RunURL field. +func (c *ConcurrencyGroupMember) GetRunURL() string { + if c == nil { + return "" + } + return c.RunURL +} + +// GetStatus returns the Status field. +func (c *ConcurrencyGroupMember) GetStatus() string { + if c == nil { + return "" + } + return c.Status +} + // GetNodes returns the Nodes slice if it's non-nil, nil otherwise. func (c *ConfigApplyEvents) GetNodes() []*ConfigApplyEventsNode { if c == nil || c.Nodes == nil { @@ -19430,6 +19574,22 @@ func (g *GetCodeownersErrorsOptions) GetRef() string { return g.Ref } +// GetAheadOfJob returns the AheadOfJob field. +func (g *GetConcurrencyGroupOptions) GetAheadOfJob() int64 { + if g == nil { + return 0 + } + return g.AheadOfJob +} + +// GetAheadOfRun returns the AheadOfRun field. +func (g *GetConcurrencyGroupOptions) GetAheadOfRun() int64 { + if g == nil { + return 0 + } + return g.AheadOfRun +} + // GetFields returns the Fields slice if it's non-nil, nil otherwise. func (g *GetProjectItemOptions) GetFields() []int64 { if g == nil || g.Fields == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 7e9ad6671c9..b509778d1ca 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -9876,6 +9876,180 @@ func TestCommunityHealthMetrics_GetUpdatedAt(tt *testing.T) { c.GetUpdatedAt() } +func TestConcurrencyGroup_GetGroupMembers(tt *testing.T) { + tt.Parallel() + zeroValue := []*ConcurrencyGroupMember{} + c := &ConcurrencyGroup{GroupMembers: zeroValue} + c.GetGroupMembers() + c = &ConcurrencyGroup{} + c.GetGroupMembers() + c = nil + c.GetGroupMembers() +} + +func TestConcurrencyGroup_GetGroupName(tt *testing.T) { + tt.Parallel() + c := &ConcurrencyGroup{} + c.GetGroupName() + c = nil + c.GetGroupName() +} + +func TestConcurrencyGroup_GetGroupURL(tt *testing.T) { + tt.Parallel() + c := &ConcurrencyGroup{} + c.GetGroupURL() + c = nil + c.GetGroupURL() +} + +func TestConcurrencyGroup_GetLastAcquiredAt(tt *testing.T) { + tt.Parallel() + var zeroValue Timestamp + c := &ConcurrencyGroup{LastAcquiredAt: &zeroValue} + c.GetLastAcquiredAt() + c = &ConcurrencyGroup{} + c.GetLastAcquiredAt() + c = nil + c.GetLastAcquiredAt() +} + +func TestConcurrencyGroup_GetTotalCount(tt *testing.T) { + tt.Parallel() + var zeroValue int + c := &ConcurrencyGroup{TotalCount: &zeroValue} + c.GetTotalCount() + c = &ConcurrencyGroup{} + c.GetTotalCount() + c = nil + c.GetTotalCount() +} + +func TestConcurrencyGroupList_GetConcurrencyGroups(tt *testing.T) { + tt.Parallel() + zeroValue := []*ConcurrencyGroup{} + c := &ConcurrencyGroupList{ConcurrencyGroups: zeroValue} + c.GetConcurrencyGroups() + c = &ConcurrencyGroupList{} + c.GetConcurrencyGroups() + c = nil + c.GetConcurrencyGroups() +} + +func TestConcurrencyGroupList_GetTotalCount(tt *testing.T) { + tt.Parallel() + c := &ConcurrencyGroupList{} + c.GetTotalCount() + c = nil + c.GetTotalCount() +} + +func TestConcurrencyGroupMember_GetJobHTMLURL(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &ConcurrencyGroupMember{JobHTMLURL: &zeroValue} + c.GetJobHTMLURL() + c = &ConcurrencyGroupMember{} + c.GetJobHTMLURL() + c = nil + c.GetJobHTMLURL() +} + +func TestConcurrencyGroupMember_GetJobID(tt *testing.T) { + tt.Parallel() + var zeroValue int64 + c := &ConcurrencyGroupMember{JobID: &zeroValue} + c.GetJobID() + c = &ConcurrencyGroupMember{} + c.GetJobID() + c = nil + c.GetJobID() +} + +func TestConcurrencyGroupMember_GetJobName(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &ConcurrencyGroupMember{JobName: &zeroValue} + c.GetJobName() + c = &ConcurrencyGroupMember{} + c.GetJobName() + c = nil + c.GetJobName() +} + +func TestConcurrencyGroupMember_GetJobURL(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &ConcurrencyGroupMember{JobURL: &zeroValue} + c.GetJobURL() + c = &ConcurrencyGroupMember{} + c.GetJobURL() + c = nil + c.GetJobURL() +} + +func TestConcurrencyGroupMember_GetPosition(tt *testing.T) { + tt.Parallel() + var zeroValue int + c := &ConcurrencyGroupMember{Position: &zeroValue} + c.GetPosition() + c = &ConcurrencyGroupMember{} + c.GetPosition() + c = nil + c.GetPosition() +} + +func TestConcurrencyGroupMember_GetPositionURL(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &ConcurrencyGroupMember{PositionURL: &zeroValue} + c.GetPositionURL() + c = &ConcurrencyGroupMember{} + c.GetPositionURL() + c = nil + c.GetPositionURL() +} + +func TestConcurrencyGroupMember_GetRunHTMLURL(tt *testing.T) { + tt.Parallel() + c := &ConcurrencyGroupMember{} + c.GetRunHTMLURL() + c = nil + c.GetRunHTMLURL() +} + +func TestConcurrencyGroupMember_GetRunID(tt *testing.T) { + tt.Parallel() + c := &ConcurrencyGroupMember{} + c.GetRunID() + c = nil + c.GetRunID() +} + +func TestConcurrencyGroupMember_GetRunName(tt *testing.T) { + tt.Parallel() + c := &ConcurrencyGroupMember{} + c.GetRunName() + c = nil + c.GetRunName() +} + +func TestConcurrencyGroupMember_GetRunURL(tt *testing.T) { + tt.Parallel() + c := &ConcurrencyGroupMember{} + c.GetRunURL() + c = nil + c.GetRunURL() +} + +func TestConcurrencyGroupMember_GetStatus(tt *testing.T) { + tt.Parallel() + c := &ConcurrencyGroupMember{} + c.GetStatus() + c = nil + c.GetStatus() +} + func TestConfigApplyEvents_GetNodes(tt *testing.T) { tt.Parallel() zeroValue := []*ConfigApplyEventsNode{} @@ -24288,6 +24462,22 @@ func TestGetCodeownersErrorsOptions_GetRef(tt *testing.T) { g.GetRef() } +func TestGetConcurrencyGroupOptions_GetAheadOfJob(tt *testing.T) { + tt.Parallel() + g := &GetConcurrencyGroupOptions{} + g.GetAheadOfJob() + g = nil + g.GetAheadOfJob() +} + +func TestGetConcurrencyGroupOptions_GetAheadOfRun(tt *testing.T) { + tt.Parallel() + g := &GetConcurrencyGroupOptions{} + g.GetAheadOfRun() + g = nil + g.GetAheadOfRun() +} + func TestGetProjectItemOptions_GetFields(tt *testing.T) { tt.Parallel() zeroValue := []int64{} diff --git a/github/github-iterators.go b/github/github-iterators.go index 3413ef22f63..957e6b18178 100644 --- a/github/github-iterators.go +++ b/github/github-iterators.go @@ -119,6 +119,41 @@ func (s *ActionsService) ListCachesIter(ctx context.Context, owner string, repo } } +// ListConcurrencyGroupsIter returns an iterator that paginates through all results of ListConcurrencyGroups. +func (s *ActionsService) ListConcurrencyGroupsIter(ctx context.Context, owner string, repo string, opts *ListCursorOptions) iter.Seq2[*ConcurrencyGroup, error] { + return func(yield func(*ConcurrencyGroup, error) bool) { + // Create a copy of opts to avoid mutating the caller's struct + if opts == nil { + opts = &ListCursorOptions{} + } else { + opts = new(*opts) + } + + for { + results, resp, err := s.ListConcurrencyGroups(ctx, owner, repo, opts) + if err != nil { + yield(nil, err) + return + } + + var iterItems []*ConcurrencyGroup + if results != nil { + iterItems = results.ConcurrencyGroups + } + for _, item := range iterItems { + if !yield(item, nil) { + return + } + } + + if resp.After == "" { + break + } + opts.After = resp.After + } + } +} + // ListEnabledOrgsInEnterpriseIter returns an iterator that paginates through all results of ListEnabledOrgsInEnterprise. func (s *ActionsService) ListEnabledOrgsInEnterpriseIter(ctx context.Context, owner string, opts *ListOptions) iter.Seq2[*Organization, error] { return func(yield func(*Organization, error) bool) { @@ -1029,6 +1064,41 @@ func (s *ActionsService) ListWorkflowRunArtifactsIter(ctx context.Context, owner } } +// ListWorkflowRunConcurrencyGroupsIter returns an iterator that paginates through all results of ListWorkflowRunConcurrencyGroups. +func (s *ActionsService) ListWorkflowRunConcurrencyGroupsIter(ctx context.Context, owner string, repo string, runID int64, opts *ListCursorOptions) iter.Seq2[*ConcurrencyGroup, error] { + return func(yield func(*ConcurrencyGroup, error) bool) { + // Create a copy of opts to avoid mutating the caller's struct + if opts == nil { + opts = &ListCursorOptions{} + } else { + opts = new(*opts) + } + + for { + results, resp, err := s.ListWorkflowRunConcurrencyGroups(ctx, owner, repo, runID, opts) + if err != nil { + yield(nil, err) + return + } + + var iterItems []*ConcurrencyGroup + if results != nil { + iterItems = results.ConcurrencyGroups + } + for _, item := range iterItems { + if !yield(item, nil) { + return + } + } + + if resp.After == "" { + break + } + opts.After = resp.After + } + } +} + // ListWorkflowRunsByFileNameIter returns an iterator that paginates through all results of ListWorkflowRunsByFileName. func (s *ActionsService) ListWorkflowRunsByFileNameIter(ctx context.Context, owner string, repo string, workflowFileName string, opts *ListWorkflowRunsOptions) iter.Seq2[*WorkflowRun, error] { return func(yield func(*WorkflowRun, error) bool) { diff --git a/github/github-iterators_test.go b/github/github-iterators_test.go index ebbf921315d..ef1b2edc538 100644 --- a/github/github-iterators_test.go +++ b/github/github-iterators_test.go @@ -231,6 +231,78 @@ func TestActionsService_ListCachesIter(t *testing.T) { } } +func TestActionsService_ListConcurrencyGroupsIter(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + var callNum int + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + callNum++ + switch callNum { + case 1: + w.Header().Set("Link", `; rel="next"`) + fmt.Fprint(w, `{"concurrency_groups": [{},{},{}]}`) + case 2: + fmt.Fprint(w, `{"concurrency_groups": [{},{},{},{}]}`) + case 3: + fmt.Fprint(w, `{"concurrency_groups": [{},{}]}`) + case 4: + w.WriteHeader(http.StatusNotFound) + case 5: + fmt.Fprint(w, `{"concurrency_groups": [{},{}]}`) + } + }) + + iter := client.Actions.ListConcurrencyGroupsIter(t.Context(), "", "", nil) + var gotItems int + for _, err := range iter { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } + if want := 7; gotItems != want { + t.Errorf("client.Actions.ListConcurrencyGroupsIter call 1 got %v items; want %v", gotItems, want) + } + + opts := &ListCursorOptions{} + iter = client.Actions.ListConcurrencyGroupsIter(t.Context(), "", "", opts) + gotItems = 0 + for _, err := range iter { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } + if want := 2; gotItems != want { + t.Errorf("client.Actions.ListConcurrencyGroupsIter call 2 got %v items; want %v", gotItems, want) + } + + iter = client.Actions.ListConcurrencyGroupsIter(t.Context(), "", "", nil) + gotItems = 0 + for _, err := range iter { + gotItems++ + if err == nil { + t.Error("expected error; got nil") + } + } + if gotItems != 1 { + t.Errorf("client.Actions.ListConcurrencyGroupsIter call 3 got %v items; want 1 (an error)", gotItems) + } + + iter = client.Actions.ListConcurrencyGroupsIter(t.Context(), "", "", nil) + gotItems = 0 + iter(func(item *ConcurrencyGroup, err error) bool { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + return false + }) + if gotItems != 1 { + t.Errorf("client.Actions.ListConcurrencyGroupsIter call 4 got %v items; want 1 (an error)", gotItems) + } +} + func TestActionsService_ListEnabledOrgsInEnterpriseIter(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -2103,6 +2175,78 @@ func TestActionsService_ListWorkflowRunArtifactsIter(t *testing.T) { } } +func TestActionsService_ListWorkflowRunConcurrencyGroupsIter(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + var callNum int + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + callNum++ + switch callNum { + case 1: + w.Header().Set("Link", `; rel="next"`) + fmt.Fprint(w, `{"concurrency_groups": [{},{},{}]}`) + case 2: + fmt.Fprint(w, `{"concurrency_groups": [{},{},{},{}]}`) + case 3: + fmt.Fprint(w, `{"concurrency_groups": [{},{}]}`) + case 4: + w.WriteHeader(http.StatusNotFound) + case 5: + fmt.Fprint(w, `{"concurrency_groups": [{},{}]}`) + } + }) + + iter := client.Actions.ListWorkflowRunConcurrencyGroupsIter(t.Context(), "", "", 0, nil) + var gotItems int + for _, err := range iter { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } + if want := 7; gotItems != want { + t.Errorf("client.Actions.ListWorkflowRunConcurrencyGroupsIter call 1 got %v items; want %v", gotItems, want) + } + + opts := &ListCursorOptions{} + iter = client.Actions.ListWorkflowRunConcurrencyGroupsIter(t.Context(), "", "", 0, opts) + gotItems = 0 + for _, err := range iter { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } + if want := 2; gotItems != want { + t.Errorf("client.Actions.ListWorkflowRunConcurrencyGroupsIter call 2 got %v items; want %v", gotItems, want) + } + + iter = client.Actions.ListWorkflowRunConcurrencyGroupsIter(t.Context(), "", "", 0, nil) + gotItems = 0 + for _, err := range iter { + gotItems++ + if err == nil { + t.Error("expected error; got nil") + } + } + if gotItems != 1 { + t.Errorf("client.Actions.ListWorkflowRunConcurrencyGroupsIter call 3 got %v items; want 1 (an error)", gotItems) + } + + iter = client.Actions.ListWorkflowRunConcurrencyGroupsIter(t.Context(), "", "", 0, nil) + gotItems = 0 + iter(func(item *ConcurrencyGroup, err error) bool { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + return false + }) + if gotItems != 1 { + t.Errorf("client.Actions.ListWorkflowRunConcurrencyGroupsIter call 4 got %v items; want 1 (an error)", gotItems) + } +} + func TestActionsService_ListWorkflowRunsByFileNameIter(t *testing.T) { t.Parallel() client, mux, _ := setup(t)