From 775f0f6b50673104a05a028b2d6f27ae6bfad7f2 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 17 Sep 2025 14:31:10 +0000 Subject: [PATCH 01/39] Add support for projects V2 --- github/github.go | 2 + github/projects.go | 120 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 github/projects.go diff --git a/github/github.go b/github/github.go index b1e7b3ddaf1..f434fdfaa16 100644 --- a/github/github.go +++ b/github/github.go @@ -218,6 +218,7 @@ type Client struct { Meta *MetaService Migrations *MigrationService Organizations *OrganizationsService + Projects *ProjectsService PullRequests *PullRequestsService RateLimit *RateLimitService Reactions *ReactionsService @@ -456,6 +457,7 @@ func (c *Client) initialize() { c.Meta = (*MetaService)(&c.common) c.Migrations = (*MigrationService)(&c.common) c.Organizations = (*OrganizationsService)(&c.common) + c.Projects = (*ProjectsService)(&c.common) c.PullRequests = (*PullRequestsService)(&c.common) c.RateLimit = (*RateLimitService)(&c.common) c.Reactions = (*ReactionsService)(&c.common) diff --git a/github/projects.go b/github/projects.go new file mode 100644 index 00000000000..09978427eb8 --- /dev/null +++ b/github/projects.go @@ -0,0 +1,120 @@ +// Copyright 2013 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" +) + +// ProjectsService handles communication with the project V2 +// methods of the GitHub API. +// +// GitHub API docs: https://docs.github.com/rest/projects/projects +type ProjectsService service + +func (p ProjectV2) String() string { return Stringify(p) } + +// ListProjectsOptions specifies optional parameters to list organization projects. +type ListProjectsOptions struct { + // Q is an optional query string to filter/search projects (when supported). + Q string `url:"q,omitempty"` + ListOptions + ListCursorOptions +} + +// ListOrganizationProjects lists Projects V2 for an organization. +// +// GitHub API docs: https://docs.github.com/rest/projects/projects#list-organization-projects +// +//meta:operation GET /orgs/{org}/projectsV2 +func (s *ProjectsService) ListOrganizationProjects(ctx context.Context, org string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2", org) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + req.Header.Set("Accept", mediaTypeProjectsPreview) + + var projects []*ProjectV2 + resp, err := s.client.Do(ctx, req, &projects) + if err != nil { + return nil, resp, err + } + return projects, resp, nil +} + +// GetByOrg gets a Projects V2 project for an organization by ID. +// +// GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-organization +// +//meta:operation GET /orgs/{org}/projectsV2/{project_id} +func (s *ProjectsService) GetByOrg(ctx context.Context, org string, projectID int64) (*ProjectV2, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v", org, projectID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + req.Header.Set("Accept", mediaTypeProjectsPreview) + + project := new(ProjectV2) + resp, err := s.client.Do(ctx, req, project) + if err != nil { + return nil, resp, err + } + return project, resp, nil +} + +// ListByUser lists Projects V2 for a user. +// +// GitHub API docs: https://docs.github.com/en/rest/projects/projects#list-projects-for-user +// +//meta:operation GET /users/{username}/projectsV2 +func (s *ProjectsService) ListByUser(ctx context.Context, username string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { + u := fmt.Sprintf("users/%v/projectsV2", username) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + req.Header.Set("Accept", mediaTypeProjectsPreview) + + var projects []*ProjectV2 + resp, err := s.client.Do(ctx, req, &projects) + if err != nil { + return nil, resp, err + } + return projects, resp, nil +} + +// GetUserProject gets a Projects V2 project for a user by ID. +// +// GitHub API docs: https://docs.github.com/en/rest/projects/projects#get-project-for-user +// +//meta:operation GET /users/{username}/projectsV2/{project_id} +func (s *ProjectsService) GetUserProject(ctx context.Context, username string, projectID int64) (*ProjectV2, *Response, error) { + u := fmt.Sprintf("users/%v/projectsV2/%v", username, projectID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + req.Header.Set("Accept", mediaTypeProjectsPreview) + + project := new(ProjectV2) + resp, err := s.client.Do(ctx, req, project) + if err != nil { + return nil, resp, err + } + return project, resp, nil +} From 74712bd3d1b6af699dc3af74208fee2e1d229c68 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 17 Sep 2025 14:35:27 +0000 Subject: [PATCH 02/39] Add test --- github/projects_test.go | 165 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 github/projects_test.go diff --git a/github/projects_test.go b/github/projects_test.go new file mode 100644 index 00000000000..2a305d83b0e --- /dev/null +++ b/github/projects_test.go @@ -0,0 +1,165 @@ +package github + +import ( + "context" + "fmt" + "net/http" + "testing" +) + +func TestProjectsService_ListOrganizationProjects(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/projectsV2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + // Expect query params q, page, per_page when provided + testFormValues(t, r, values{"q": "alpha", "page": "2", "per_page": "1"}) + fmt.Fprint(w, `[{"id":1,"title":"T1","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + }) + + opts := &ListProjectsOptions{Q: "alpha", ListOptions: ListOptions{Page: 2, PerPage: 1}} + ctx := context.Background() + projects, _, err := client.Projects.ListOrganizationProjects(ctx, "o", opts) + if err != nil { + t.Fatalf("Projects.ListOrganizationProjects returned error: %v", err) + } + if len(projects) != 1 || projects[0].GetID() != 1 || projects[0].GetTitle() != "T1" { + t.Fatalf("Projects.ListOrganizationProjects returned %+v", projects) + } + + const methodName = "ListOrganizationProjects" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Projects.ListOrganizationProjects(ctx, "\n", opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.ListOrganizationProjects(ctx, "o", opts) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestProjectsService_GetOrganizationProject(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/projectsV2/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + fmt.Fprint(w, `{"id":1,"title":"OrgProj","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}`) + }) + + ctx := context.Background() + project, _, err := client.Projects.GetByOrg(ctx, "o", 1) + if err != nil { + t.Fatalf("Projects.GetByOrg returned error: %v", err) + } + if project.GetID() != 1 || project.GetTitle() != "OrgProj" { + t.Fatalf("Projects.GetByOrg returned %+v", project) + } + + const methodName = "GetByOrg" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.GetByOrg(ctx, "o", 1) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestProjectsService_ListUserProjects(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/users/u/projectsV2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + testFormValues(t, r, values{"q": "beta", "page": "1", "per_page": "2"}) + fmt.Fprint(w, `[{"id":2,"title":"UProj","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + }) + + opts := &ListProjectsOptions{Q: "beta", ListOptions: ListOptions{Page: 1, PerPage: 2}} + ctx := context.Background() + projects, _, err := client.Projects.ListByUser(ctx, "u", opts) + if err != nil { + t.Fatalf("Projects.ListByUser returned error: %v", err) + } + if len(projects) != 1 || projects[0].GetID() != 2 || projects[0].GetTitle() != "UProj" { + t.Fatalf("Projects.ListByUser returned %+v", projects) + } + + const methodName = "ListByUser" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Projects.ListByUser(ctx, "\n", opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.ListByUser(ctx, "u", opts) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestProjectsService_GetUserProject(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/users/u/projectsV2/2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + fmt.Fprint(w, `{"id":2,"title":"UProj","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}`) + }) + + ctx := context.Background() + project, _, err := client.Projects.GetUserProject(ctx, "u", 2) + if err != nil { + t.Fatalf("Projects.GetUserProject returned error: %v", err) + } + if project.GetID() != 2 || project.GetTitle() != "UProj" { + t.Fatalf("Projects.GetUserProject returned %+v", project) + } + + const methodName = "GetUserProject" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.GetUserProject(ctx, "u", 2) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +// Marshal test ensures V2 fields marshal correctly. +func TestProjectV2_Marshal(t *testing.T) { + t.Parallel() + testJSONMarshal(t, &ProjectV2{}, "{}") + + p := &ProjectV2{ + ID: Ptr(int64(10)), + Title: Ptr("Title"), + Description: Ptr("Desc"), + Public: Ptr(true), + CreatedAt: &Timestamp{referenceTime}, + UpdatedAt: &Timestamp{referenceTime}, + } + + want := `{ + "id": 10, + "title": "Title", + "description": "Desc", + "public": true, + "created_at": ` + referenceTimeStr + `, + "updated_at": ` + referenceTimeStr + ` + }` + + testJSONMarshal(t, p, want) +} From 3a8cf0c0852698f4d715ece629aff8bb57b1c0b2 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Mon, 22 Sep 2025 13:49:18 +0000 Subject: [PATCH 03/39] Address feedback --- github/projects.go | 15 +++++++++++---- github/projects_test.go | 6 +++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/github/projects.go b/github/projects.go index 09978427eb8..264a800b5b3 100644 --- a/github/projects.go +++ b/github/projects.go @@ -18,12 +18,19 @@ type ProjectsService service func (p ProjectV2) String() string { return Stringify(p) } -// ListProjectsOptions specifies optional parameters to list organization projects. +// ListProjectsOptions specifies optional parameters to list projects for user / organization. type ListProjectsOptions struct { + // A cursor, as given in the Link header. If specified, the query only searches for events before this cursor. + Before string `url:"before,omitempty"` + + // A cursor, as given in the Link header. If specified, the query only searches for events after this cursor. + After string `url:"after,omitempty"` + + // For paginated result sets, the number of results to include per page. + PerPage int `url:"per_page,omitempty"` + // Q is an optional query string to filter/search projects (when supported). - Q string `url:"q,omitempty"` - ListOptions - ListCursorOptions + Query string `url:"q,omitempty"` } // ListOrganizationProjects lists Projects V2 for an organization. diff --git a/github/projects_test.go b/github/projects_test.go index 2a305d83b0e..017b89e7577 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -19,7 +19,7 @@ func TestProjectsService_ListOrganizationProjects(t *testing.T) { fmt.Fprint(w, `[{"id":1,"title":"T1","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) }) - opts := &ListProjectsOptions{Q: "alpha", ListOptions: ListOptions{Page: 2, PerPage: 1}} + opts := &ListProjectsOptions{Query: "alpha", After: "2", Before: "1"} ctx := context.Background() projects, _, err := client.Projects.ListOrganizationProjects(ctx, "o", opts) if err != nil { @@ -80,11 +80,11 @@ func TestProjectsService_ListUserProjects(t *testing.T) { mux.HandleFunc("/users/u/projectsV2", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testHeader(t, r, "Accept", mediaTypeProjectsPreview) - testFormValues(t, r, values{"q": "beta", "page": "1", "per_page": "2"}) + testFormValues(t, r, values{"q": "beta", "before": "1", "after": "2", "per_page": "2"}) fmt.Fprint(w, `[{"id":2,"title":"UProj","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) }) - opts := &ListProjectsOptions{Q: "beta", ListOptions: ListOptions{Page: 1, PerPage: 2}} + opts := &ListProjectsOptions{Query: "beta", Before: "1", After: "2", PerPage: 2} ctx := context.Background() projects, _, err := client.Projects.ListByUser(ctx, "u", opts) if err != nil { From e93ed36abae785f1434f1e50b4f5ac60182bb7de Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Mon, 22 Sep 2025 19:01:59 +0000 Subject: [PATCH 04/39] Remove header and rename functions --- github/projects.go | 50 ++++++++++++++++------------------- github/projects_test.go | 58 +++++++++++++++++++---------------------- 2 files changed, 50 insertions(+), 58 deletions(-) diff --git a/github/projects.go b/github/projects.go index 264a800b5b3..2631968826e 100644 --- a/github/projects.go +++ b/github/projects.go @@ -38,7 +38,7 @@ type ListProjectsOptions struct { // GitHub API docs: https://docs.github.com/rest/projects/projects#list-organization-projects // //meta:operation GET /orgs/{org}/projectsV2 -func (s *ProjectsService) ListOrganizationProjects(ctx context.Context, org string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { +func (s *ProjectsService) ListProjectsForOrganization(ctx context.Context, org string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2", org) u, err := addOptions(u, opts) if err != nil { @@ -49,7 +49,6 @@ func (s *ProjectsService) ListOrganizationProjects(ctx context.Context, org stri if err != nil { return nil, nil, err } - req.Header.Set("Accept", mediaTypeProjectsPreview) var projects []*ProjectV2 resp, err := s.client.Do(ctx, req, &projects) @@ -59,33 +58,12 @@ func (s *ProjectsService) ListOrganizationProjects(ctx context.Context, org stri return projects, resp, nil } -// GetByOrg gets a Projects V2 project for an organization by ID. -// -// GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-organization -// -//meta:operation GET /orgs/{org}/projectsV2/{project_id} -func (s *ProjectsService) GetByOrg(ctx context.Context, org string, projectID int64) (*ProjectV2, *Response, error) { - u := fmt.Sprintf("orgs/%v/projectsV2/%v", org, projectID) - req, err := s.client.NewRequest("GET", u, nil) - if err != nil { - return nil, nil, err - } - req.Header.Set("Accept", mediaTypeProjectsPreview) - - project := new(ProjectV2) - resp, err := s.client.Do(ctx, req, project) - if err != nil { - return nil, resp, err - } - return project, resp, nil -} - // ListByUser lists Projects V2 for a user. // // GitHub API docs: https://docs.github.com/en/rest/projects/projects#list-projects-for-user // //meta:operation GET /users/{username}/projectsV2 -func (s *ProjectsService) ListByUser(ctx context.Context, username string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { +func (s *ProjectsService) ListProjectsForUser(ctx context.Context, username string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2", username) u, err := addOptions(u, opts) if err != nil { @@ -95,7 +73,6 @@ func (s *ProjectsService) ListByUser(ctx context.Context, username string, opts if err != nil { return nil, nil, err } - req.Header.Set("Accept", mediaTypeProjectsPreview) var projects []*ProjectV2 resp, err := s.client.Do(ctx, req, &projects) @@ -110,13 +87,32 @@ func (s *ProjectsService) ListByUser(ctx context.Context, username string, opts // GitHub API docs: https://docs.github.com/en/rest/projects/projects#get-project-for-user // //meta:operation GET /users/{username}/projectsV2/{project_id} -func (s *ProjectsService) GetUserProject(ctx context.Context, username string, projectID int64) (*ProjectV2, *Response, error) { +func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string, projectID int64) (*ProjectV2, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v", username, projectID) req, err := s.client.NewRequest("GET", u, nil) if err != nil { return nil, nil, err } - req.Header.Set("Accept", mediaTypeProjectsPreview) + + project := new(ProjectV2) + resp, err := s.client.Do(ctx, req, project) + if err != nil { + return nil, resp, err + } + return project, resp, nil +} + +// GetByOrg gets a Projects V2 project for an organization by ID. +// +// GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-organization +// +//meta:operation GET /orgs/{org}/projectsV2/{project_id} +func (s *ProjectsService) GetProjectForOrg(ctx context.Context, org string, projectID int64) (*ProjectV2, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v", org, projectID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } project := new(ProjectV2) resp, err := s.client.Do(ctx, req, project) diff --git a/github/projects_test.go b/github/projects_test.go index 017b89e7577..056a47406c5 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -7,36 +7,35 @@ import ( "testing" ) -func TestProjectsService_ListOrganizationProjects(t *testing.T) { +func TestProjectsService_ListProjectsForOrganizations(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/orgs/o/projectsV2", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - testHeader(t, r, "Accept", mediaTypeProjectsPreview) - // Expect query params q, page, per_page when provided - testFormValues(t, r, values{"q": "alpha", "page": "2", "per_page": "1"}) + // Expect query params q, after, before when provided + testFormValues(t, r, values{"q": "alpha", "after": "2", "before": "1"}) fmt.Fprint(w, `[{"id":1,"title":"T1","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) }) opts := &ListProjectsOptions{Query: "alpha", After: "2", Before: "1"} ctx := context.Background() - projects, _, err := client.Projects.ListOrganizationProjects(ctx, "o", opts) + projects, _, err := client.Projects.ListProjectsForOrganization(ctx, "o", opts) if err != nil { - t.Fatalf("Projects.ListOrganizationProjects returned error: %v", err) + t.Fatalf("Projects.ListProjectsForOrganization returned error: %v", err) } if len(projects) != 1 || projects[0].GetID() != 1 || projects[0].GetTitle() != "T1" { - t.Fatalf("Projects.ListOrganizationProjects returned %+v", projects) + t.Fatalf("Projects.ListProjectsForOrganization returned %+v", projects) } - const methodName = "ListOrganizationProjects" + const methodName = "ListProjectsForOrganization" testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Projects.ListOrganizationProjects(ctx, "\n", opts) + _, _, err = client.Projects.ListProjectsForOrganization(ctx, "\n", opts) return err }) testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.ListOrganizationProjects(ctx, "o", opts) + got, resp, err := client.Projects.ListProjectsForOrganization(ctx, "o", opts) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -44,28 +43,27 @@ func TestProjectsService_ListOrganizationProjects(t *testing.T) { }) } -func TestProjectsService_GetOrganizationProject(t *testing.T) { +func TestProjectsService_GetProjectForOrg(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/orgs/o/projectsV2/1", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - testHeader(t, r, "Accept", mediaTypeProjectsPreview) fmt.Fprint(w, `{"id":1,"title":"OrgProj","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}`) }) ctx := context.Background() - project, _, err := client.Projects.GetByOrg(ctx, "o", 1) + project, _, err := client.Projects.GetProjectForOrg(ctx, "o", 1) if err != nil { - t.Fatalf("Projects.GetByOrg returned error: %v", err) + t.Fatalf("Projects.GetProjectForOrg returned error: %v", err) } if project.GetID() != 1 || project.GetTitle() != "OrgProj" { - t.Fatalf("Projects.GetByOrg returned %+v", project) + t.Fatalf("Projects.GetProjectForOrg returned %+v", project) } - const methodName = "GetByOrg" + const methodName = "GetProjectForOrg" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.GetByOrg(ctx, "o", 1) + got, resp, err := client.Projects.GetProjectForOrg(ctx, "o", 1) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -79,29 +77,28 @@ func TestProjectsService_ListUserProjects(t *testing.T) { mux.HandleFunc("/users/u/projectsV2", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - testHeader(t, r, "Accept", mediaTypeProjectsPreview) testFormValues(t, r, values{"q": "beta", "before": "1", "after": "2", "per_page": "2"}) fmt.Fprint(w, `[{"id":2,"title":"UProj","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) }) opts := &ListProjectsOptions{Query: "beta", Before: "1", After: "2", PerPage: 2} ctx := context.Background() - projects, _, err := client.Projects.ListByUser(ctx, "u", opts) + projects, _, err := client.Projects.ListProjectsForUser(ctx, "u", opts) if err != nil { - t.Fatalf("Projects.ListByUser returned error: %v", err) + t.Fatalf("Projects.ListProjectsForUser returned error: %v", err) } if len(projects) != 1 || projects[0].GetID() != 2 || projects[0].GetTitle() != "UProj" { - t.Fatalf("Projects.ListByUser returned %+v", projects) + t.Fatalf("Projects.ListProjectsForUser returned %+v", projects) } - const methodName = "ListByUser" + const methodName = "ListProjectsForUser" testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Projects.ListByUser(ctx, "\n", opts) + _, _, err = client.Projects.ListProjectsForUser(ctx, "\n", opts) return err }) testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.ListByUser(ctx, "u", opts) + got, resp, err := client.Projects.ListProjectsForUser(ctx, "u", opts) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -109,28 +106,27 @@ func TestProjectsService_ListUserProjects(t *testing.T) { }) } -func TestProjectsService_GetUserProject(t *testing.T) { +func TestProjectsService_GetProjectForUser(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/users/u/projectsV2/2", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - testHeader(t, r, "Accept", mediaTypeProjectsPreview) fmt.Fprint(w, `{"id":2,"title":"UProj","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}`) }) ctx := context.Background() - project, _, err := client.Projects.GetUserProject(ctx, "u", 2) + project, _, err := client.Projects.GetProjectForUser(ctx, "u", 2) if err != nil { - t.Fatalf("Projects.GetUserProject returned error: %v", err) + t.Fatalf("Projects.GetProjectForUser returned error: %v", err) } if project.GetID() != 2 || project.GetTitle() != "UProj" { - t.Fatalf("Projects.GetUserProject returned %+v", project) + t.Fatalf("Projects.GetProjectForUser returned %+v", project) } - const methodName = "GetUserProject" + const methodName = "GetProjectForUser" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.GetUserProject(ctx, "u", 2) + got, resp, err := client.Projects.GetProjectForUser(ctx, "u", 2) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } From 99852cff4cf7c605d9a8b2d568a43fe76500fdae Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Mon, 22 Sep 2025 19:56:32 +0000 Subject: [PATCH 05/39] Update comments --- github/projects.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/github/projects.go b/github/projects.go index 2631968826e..c341bee33ac 100644 --- a/github/projects.go +++ b/github/projects.go @@ -33,7 +33,7 @@ type ListProjectsOptions struct { Query string `url:"q,omitempty"` } -// ListOrganizationProjects lists Projects V2 for an organization. +// ListProjectsForOrganization lists Projects V2 for an organization. // // GitHub API docs: https://docs.github.com/rest/projects/projects#list-organization-projects // @@ -58,7 +58,7 @@ func (s *ProjectsService) ListProjectsForOrganization(ctx context.Context, org s return projects, resp, nil } -// ListByUser lists Projects V2 for a user. +// ListProjectsForUser lists Projects V2 for a user. // // GitHub API docs: https://docs.github.com/en/rest/projects/projects#list-projects-for-user // @@ -82,7 +82,7 @@ func (s *ProjectsService) ListProjectsForUser(ctx context.Context, username stri return projects, resp, nil } -// GetUserProject gets a Projects V2 project for a user by ID. +// GetProjectForUser gets a Projects V2 project for a user by ID. // // GitHub API docs: https://docs.github.com/en/rest/projects/projects#get-project-for-user // @@ -102,7 +102,7 @@ func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string return project, resp, nil } -// GetByOrg gets a Projects V2 project for an organization by ID. +// GetProjectForOrg gets a Projects V2 project for an organization by ID. // // GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-organization // From 76642151d013908d077440ecf20ffe91f5b78470 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Mon, 22 Sep 2025 20:03:17 +0000 Subject: [PATCH 06/39] Copyright update --- github/projects.go | 2 +- github/projects_test.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/github/projects.go b/github/projects.go index c341bee33ac..52960a1d4db 100644 --- a/github/projects.go +++ b/github/projects.go @@ -1,4 +1,4 @@ -// Copyright 2013 The go-github AUTHORS. All rights reserved. +// Copyright 2025 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. diff --git a/github/projects_test.go b/github/projects_test.go index 056a47406c5..a0cd13aed91 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -1,3 +1,8 @@ +// Copyright 2025 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 ( From 2b9476576779c6315d3270717e50ae951bd3d498 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Tue, 23 Sep 2025 08:13:19 +0000 Subject: [PATCH 07/39] Update comments --- github/projects.go | 53 +++++++++-------- github/projects_test.go | 128 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 157 insertions(+), 24 deletions(-) diff --git a/github/projects.go b/github/projects.go index 52960a1d4db..be14f1b2ad0 100644 --- a/github/projects.go +++ b/github/projects.go @@ -19,6 +19,13 @@ type ProjectsService service func (p ProjectV2) String() string { return Stringify(p) } // ListProjectsOptions specifies optional parameters to list projects for user / organization. +// +// Note: Pagination is powered by before/after cursor-style pagination. After the initial call, +// inspect the returned *Response. Use resp.After as the opts.After value to request +// the next page, and resp.Before as the opts.Before value to request the previous +// page. Set either Before or After for a request; if both are +// supplied GitHub API will return an error. PerPage controls the number of items +// per page (max 100 per GitHub API docs). type ListProjectsOptions struct { // A cursor, as given in the Link header. If specified, the query only searches for events before this cursor. Before string `url:"before,omitempty"` @@ -29,13 +36,13 @@ type ListProjectsOptions struct { // For paginated result sets, the number of results to include per page. PerPage int `url:"per_page,omitempty"` - // Q is an optional query string to filter/search projects (when supported). + // Q is an optional query string to limit results to projects of the specified type. Query string `url:"q,omitempty"` } // ListProjectsForOrganization lists Projects V2 for an organization. // -// GitHub API docs: https://docs.github.com/rest/projects/projects#list-organization-projects +// GitHub API docs: https://docs.github.com/rest/projects/projects#list-projects-for-organization // //meta:operation GET /orgs/{org}/projectsV2 func (s *ProjectsService) ListProjectsForOrganization(ctx context.Context, org string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { @@ -58,9 +65,29 @@ func (s *ProjectsService) ListProjectsForOrganization(ctx context.Context, org s return projects, resp, nil } +// GetProjectForOrg gets a Projects V2 project for an organization by ID. +// +// GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-organization +// +//meta:operation GET /orgs/{org}/projectsV2/{project_id} +func (s *ProjectsService) GetProjectForOrg(ctx context.Context, org string, projectID int64) (*ProjectV2, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v", org, projectID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + project := new(ProjectV2) + resp, err := s.client.Do(ctx, req, project) + if err != nil { + return nil, resp, err + } + return project, resp, nil +} + // ListProjectsForUser lists Projects V2 for a user. // -// GitHub API docs: https://docs.github.com/en/rest/projects/projects#list-projects-for-user +// GitHub API docs: https://docs.github.com/rest/projects/projects#list-projects-for-user // //meta:operation GET /users/{username}/projectsV2 func (s *ProjectsService) ListProjectsForUser(ctx context.Context, username string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { @@ -101,23 +128,3 @@ func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string } return project, resp, nil } - -// GetProjectForOrg gets a Projects V2 project for an organization by ID. -// -// GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-organization -// -//meta:operation GET /orgs/{org}/projectsV2/{project_id} -func (s *ProjectsService) GetProjectForOrg(ctx context.Context, org string, projectID int64) (*ProjectV2, *Response, error) { - u := fmt.Sprintf("orgs/%v/projectsV2/%v", org, projectID) - req, err := s.client.NewRequest("GET", u, nil) - if err != nil { - return nil, nil, err - } - - project := new(ProjectV2) - resp, err := s.client.Do(ctx, req, project) - if err != nil { - return nil, resp, err - } - return project, resp, nil -} diff --git a/github/projects_test.go b/github/projects_test.go index a0cd13aed91..f12a44aa2b6 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -16,9 +16,15 @@ func TestProjectsService_ListProjectsForOrganizations(t *testing.T) { t.Parallel() client, mux, _ := setup(t) + // Combined handler: supports initial test case and dual before/after validation scenario. mux.HandleFunc("/orgs/o/projectsV2", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - // Expect query params q, after, before when provided + q := r.URL.Query() + if q.Get("before") == "b" && q.Get("after") == "a" { + fmt.Fprint(w, `[]`) + return + } + // default expectation for main part of test testFormValues(t, r, values{"q": "alpha", "after": "2", "before": "1"}) fmt.Fprint(w, `[{"id":1,"title":"T1","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) }) @@ -46,6 +52,12 @@ func TestProjectsService_ListProjectsForOrganizations(t *testing.T) { } return resp, err }) + + // still allow both set (no validation enforced) – ensure it does not error + ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) + if _, _, err = client.Projects.ListProjectsForOrganization(ctxBypass, "o", &ListProjectsOptions{Before: "b", After: "a"}); err != nil { + t.Fatalf("unexpected error when both before/after set: %v", err) + } } func TestProjectsService_GetProjectForOrg(t *testing.T) { @@ -80,14 +92,21 @@ func TestProjectsService_ListUserProjects(t *testing.T) { t.Parallel() client, mux, _ := setup(t) + // Combined handler: supports initial test case and dual before/after scenario. mux.HandleFunc("/users/u/projectsV2", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") + q := r.URL.Query() + if q.Get("before") == "b" && q.Get("after") == "a" { + fmt.Fprint(w, `[]`) + return + } testFormValues(t, r, values{"q": "beta", "before": "1", "after": "2", "per_page": "2"}) fmt.Fprint(w, `[{"id":2,"title":"UProj","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) }) opts := &ListProjectsOptions{Query: "beta", Before: "1", After: "2", PerPage: 2} ctx := context.Background() + var ctxBypass context.Context projects, _, err := client.Projects.ListProjectsForUser(ctx, "u", opts) if err != nil { t.Fatalf("Projects.ListProjectsForUser returned error: %v", err) @@ -109,6 +128,12 @@ func TestProjectsService_ListUserProjects(t *testing.T) { } return resp, err }) + + // still allow both set (no validation enforced) – ensure it does not error + ctxBypass = context.WithValue(context.Background(), BypassRateLimitCheck, true) + if _, _, err = client.Projects.ListProjectsForUser(ctxBypass, "u", &ListProjectsOptions{Before: "b", After: "a"}); err != nil { + t.Fatalf("unexpected error when both before/after set: %v", err) + } } func TestProjectsService_GetProjectForUser(t *testing.T) { @@ -139,6 +164,107 @@ func TestProjectsService_GetProjectForUser(t *testing.T) { }) } +// TestProjectsService_ListProjectsForOrganization_pagination clarifies how callers should +// use resp.After to request the next page and resp.Before for previous pages when supported. +func TestProjectsService_ListProjectsForOrganization_pagination(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + // First page returns a Link header with rel="next" containing an after cursor (after=cursor2) + mux.HandleFunc("/orgs/o/projectsV2", func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + after := q.Get("after") + before := q.Get("before") + if after == "" && before == "" { + // first request + w.Header().Set("Link", "; rel=\"next\"") + fmt.Fprint(w, `[{"id":1,"title":"P1","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + return + } + if after == "cursor2" { + // second request simulates a previous link + w.Header().Set("Link", "; rel=\"prev\"") + fmt.Fprint(w, `[{"id":2,"title":"P2","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + return + } + // unexpected state + http.Error(w, "unexpected query", http.StatusBadRequest) + }) + + ctx := context.Background() + first, resp, err := client.Projects.ListProjectsForOrganization(ctx, "o", nil) + if err != nil { + t.Fatalf("first page error: %v", err) + } + if len(first) != 1 || first[0].GetID() != 1 { + t.Fatalf("unexpected first page %+v", first) + } + if resp.After != "cursor2" { + t.Fatalf("expected resp.After=cursor2 got %q", resp.After) + } + + // Use resp.After as opts.After for next page + opts := &ListProjectsOptions{After: resp.After} + second, resp2, err := client.Projects.ListProjectsForOrganization(ctx, "o", opts) + if err != nil { + t.Fatalf("second page error: %v", err) + } + if len(second) != 1 || second[0].GetID() != 2 { + t.Fatalf("unexpected second page %+v", second) + } + if resp2.Before != "cursor2" { + t.Fatalf("expected resp2.Before=cursor2 got %q", resp2.Before) + } +} + +// TestProjectsService_ListProjectsForUser_pagination mirrors the org pagination test +// but exercises the user endpoint to ensure Before/After cursor handling works identically. +func TestProjectsService_ListProjectsForUser_pagination(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/users/u/projectsV2", func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + after := q.Get("after") + before := q.Get("before") + if after == "" && before == "" { // first page + w.Header().Set("Link", "; rel=\"next\"") + fmt.Fprint(w, `[{"id":10,"title":"UP1","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + return + } + if after == "ucursor2" { // second page provides prev + w.Header().Set("Link", "; rel=\"prev\"") + fmt.Fprint(w, `[{"id":11,"title":"UP2","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + return + } + http.Error(w, "unexpected query", http.StatusBadRequest) + }) + + ctx := context.Background() + first, resp, err := client.Projects.ListProjectsForUser(ctx, "u", nil) + if err != nil { + t.Fatalf("first page error: %v", err) + } + if len(first) != 1 || first[0].GetID() != 10 { + t.Fatalf("unexpected first page %+v", first) + } + if resp.After != "ucursor2" { + t.Fatalf("expected resp.After=ucursor2 got %q", resp.After) + } + + opts := &ListProjectsOptions{After: resp.After} + second, resp2, err := client.Projects.ListProjectsForUser(ctx, "u", opts) + if err != nil { + t.Fatalf("second page error: %v", err) + } + if len(second) != 1 || second[0].GetID() != 11 { + t.Fatalf("unexpected second page %+v", second) + } + if resp2.Before != "ucursor2" { + t.Fatalf("expected resp2.Before=ucursor2 got %q", resp2.Before) + } +} + // Marshal test ensures V2 fields marshal correctly. func TestProjectV2_Marshal(t *testing.T) { t.Parallel() From a4b0a56bdf47c8e28aebf52bd3a09ebf321e4fdc Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Tue, 23 Sep 2025 10:37:26 +0200 Subject: [PATCH 08/39] Generate docs --- github/github-stringify_test.go | 33 ++++++++++++++++++++ github/projects.go | 14 ++++----- openapi_operations.yaml | 55 +++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 7 deletions(-) diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go index b246f0bc35a..79998b56960 100644 --- a/github/github-stringify_test.go +++ b/github/github-stringify_test.go @@ -1477,6 +1477,39 @@ func TestPreReceiveHook_String(t *testing.T) { } } +func TestProjectV2_String(t *testing.T) { + t.Parallel() + v := ProjectV2{ + ID: Ptr(int64(0)), + NodeID: Ptr(""), + Owner: &User{}, + Creator: &User{}, + Title: Ptr(""), + Description: Ptr(""), + Public: Ptr(false), + ClosedAt: &Timestamp{}, + CreatedAt: &Timestamp{}, + UpdatedAt: &Timestamp{}, + DeletedAt: &Timestamp{}, + Number: Ptr(0), + ShortDescription: Ptr(""), + DeletedBy: &User{}, + URL: Ptr(""), + HTMLURL: Ptr(""), + ColumnsURL: Ptr(""), + OwnerURL: Ptr(""), + Name: Ptr(""), + Body: Ptr(""), + State: Ptr(""), + OrganizationPermission: Ptr(""), + Private: Ptr(false), + } + want := `github.ProjectV2{ID:0, NodeID:"", Owner:github.User{}, Creator:github.User{}, Title:"", Description:"", Public:false, ClosedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, DeletedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, Number:0, ShortDescription:"", DeletedBy:github.User{}, URL:"", HTMLURL:"", ColumnsURL:"", OwnerURL:"", Name:"", Body:"", State:"", OrganizationPermission:"", Private:false}` + if got := v.String(); got != want { + t.Errorf("ProjectV2.String = %v, want %v", got, want) + } +} + func TestPullRequest_String(t *testing.T) { t.Parallel() v := PullRequest{ diff --git a/github/projects.go b/github/projects.go index be14f1b2ad0..b2aab5e5399 100644 --- a/github/projects.go +++ b/github/projects.go @@ -69,9 +69,9 @@ func (s *ProjectsService) ListProjectsForOrganization(ctx context.Context, org s // // GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-organization // -//meta:operation GET /orgs/{org}/projectsV2/{project_id} -func (s *ProjectsService) GetProjectForOrg(ctx context.Context, org string, projectID int64) (*ProjectV2, *Response, error) { - u := fmt.Sprintf("orgs/%v/projectsV2/%v", org, projectID) +//meta:operation GET /orgs/{org}/projectsV2/{project_number} +func (s *ProjectsService) GetProjectForOrg(ctx context.Context, org string, projectNumber int64) (*ProjectV2, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v", org, projectNumber) req, err := s.client.NewRequest("GET", u, nil) if err != nil { return nil, nil, err @@ -111,11 +111,11 @@ func (s *ProjectsService) ListProjectsForUser(ctx context.Context, username stri // GetProjectForUser gets a Projects V2 project for a user by ID. // -// GitHub API docs: https://docs.github.com/en/rest/projects/projects#get-project-for-user +// GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-user // -//meta:operation GET /users/{username}/projectsV2/{project_id} -func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string, projectID int64) (*ProjectV2, *Response, error) { - u := fmt.Sprintf("users/%v/projectsV2/%v", username, projectID) +//meta:operation GET /users/{username}/projectsV2/{project_number} +func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string, projectNumber int64) (*ProjectV2, *Response, error) { + u := fmt.Sprintf("users/%v/projectsV2/%v", username, projectNumber) req, err := s.client.NewRequest("GET", u, nil) if err != nil { return nil, nil, err diff --git a/openapi_operations.yaml b/openapi_operations.yaml index d817be5af5a..3f433cc0a33 100644 --- a/openapi_operations.yaml +++ b/openapi_operations.yaml @@ -2877,6 +2877,61 @@ openapi_operations: - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - descriptions/ghes-3.17/ghes-3.17.json + - name: GET /orgs/{org}/projectsV2 + documentation_url: https://docs.github.com/rest/projects/projects#list-projects-for-organization + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /users/{username}/projectsV2 + documentation_url: https://docs.github.com/rest/projects/projects#list-projects-for-user + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /orgs/{org}/projectsV2/{project_number} + documentation_url: https://docs.github.com/rest/projects/projects#get-project-for-organization + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /users/{username}/projectsV2/{project_number} + documentation_url: https://docs.github.com/rest/projects/projects#get-project-for-user + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /orgs/{org}/projectsV2/{project_number}/fields + documentation_url: https://docs.github.com/rest/projects/fields#list-project-fields-for-organization + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /orgs/{org}/projectsV2/{project_number}/fields/{field_id} + documentation_url: https://docs.github.com/rest/projects/fields#get-project-field-for-organization + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /orgs/{org}/projectsV2/{project_number}/items + documentation_url: https://docs.github.com/rest/projects/items#list-items-for-an-organization-owned-project + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: POST /orgs/{org}/projectsV2/{project_number}/items + documentation_url: https://docs.github.com/rest/projects/items#add-item-to-organization-owned-project + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: DELETE /orgs/{org}/projectsV2/{project_number}/items/{item_id} + documentation_url: https://docs.github.com/rest/projects/items#delete-project-item-for-organization + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /orgs/{org}/projectsV2/{project_number}/items/{item_id} + documentation_url: https://docs.github.com/rest/projects/items#get-an-item-for-an-organization-owned-project + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: PATCH /orgs/{org}/projectsV2/{project_number}/items/{item_id} + documentation_url: https://docs.github.com/rest/projects/items#update-project-item-for-organization + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json - name: GET /orgs/{org}/properties/schema documentation_url: https://docs.github.com/rest/orgs/custom-properties#get-all-custom-properties-for-an-organization openapi_files: From 696102bd7b89fc0fa87766b79bf95618d2569195 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Tue, 23 Sep 2025 11:30:01 +0200 Subject: [PATCH 09/39] Add list projects option --- github/github-accessors.go | 16 +++ github/github-accessors_test.go | 22 ++++ github/projects.go | 51 ++++++++ github/projects_test.go | 201 ++++++++++++++++++++++++++++++++ 4 files changed, 290 insertions(+) diff --git a/github/github-accessors.go b/github/github-accessors.go index b470ba80255..55334fddea2 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -18638,6 +18638,22 @@ func (p *ProjectV2Event) GetSender() *User { return p.Sender } +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (p *ProjectV2Field) GetCreatedAt() Timestamp { + if p == nil || p.CreatedAt == nil { + return Timestamp{} + } + return *p.CreatedAt +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (p *ProjectV2Field) GetUpdatedAt() Timestamp { + if p == nil || p.UpdatedAt == nil { + return Timestamp{} + } + return *p.UpdatedAt +} + // GetArchivedAt returns the ArchivedAt field if it's non-nil, zero value otherwise. func (p *ProjectV2Item) GetArchivedAt() Timestamp { if p == nil || p.ArchivedAt == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index d61b00e5f69..fd2d06717b0 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -24180,6 +24180,28 @@ func TestProjectV2Event_GetSender(tt *testing.T) { p.GetSender() } +func TestProjectV2Field_GetCreatedAt(tt *testing.T) { + tt.Parallel() + var zeroValue Timestamp + p := &ProjectV2Field{CreatedAt: &zeroValue} + p.GetCreatedAt() + p = &ProjectV2Field{} + p.GetCreatedAt() + p = nil + p.GetCreatedAt() +} + +func TestProjectV2Field_GetUpdatedAt(tt *testing.T) { + tt.Parallel() + var zeroValue Timestamp + p := &ProjectV2Field{UpdatedAt: &zeroValue} + p.GetUpdatedAt() + p = &ProjectV2Field{} + p.GetUpdatedAt() + p = nil + p.GetUpdatedAt() +} + func TestProjectV2Item_GetArchivedAt(tt *testing.T) { tt.Parallel() var zeroValue Timestamp diff --git a/github/projects.go b/github/projects.go index b2aab5e5399..be932a597a2 100644 --- a/github/projects.go +++ b/github/projects.go @@ -40,6 +40,32 @@ type ListProjectsOptions struct { Query string `url:"q,omitempty"` } +// ProjectV2FieldOption represents an option for a project field of type single_select or multi_select. +// It defines the available choices that can be selected for dropdown-style fields. +// +// GitHub API docs: https://docs.github.com/rest/projects/fields +type ProjectV2FieldOption struct { + ID string `json:"id,omitempty"` // The unique identifier for this option. + Name string `json:"name,omitempty"` // The display name of the option. + Color string `json:"color,omitempty"` // The color associated with this option (e.g., "blue", "red"). + Description string `json:"description,omitempty"` // An optional description for this option. +} + +// ProjectV2Field represents a field in a GitHub Projects V2 project. +// Fields define the structure and data types for project items. +// +// GitHub API docs: https://docs.github.com/rest/projects/fields +type ProjectV2Field struct { + ID string `json:"id,omitempty"` // The unique identifier for this field. + NodeID string `json:"node_id,omitempty"` // The GraphQL node ID for this field. + Name string `json:"name,omitempty"` // The display name of the field. + DataType string `json:"dataType,omitempty"` // The data type of the field (e.g., "text", "number", "date", "single_select", "multi_select"). + ProjectURL string `json:"url,omitempty"` // The API URL for this field. + Options []*ProjectV2FieldOption `json:"options,omitempty"` // Available options for single_select and multi_select fields. + CreatedAt *Timestamp `json:"created_at,omitempty"` // The time when this field was created. + UpdatedAt *Timestamp `json:"updated_at,omitempty"` // The time when this field was last updated. +} + // ListProjectsForOrganization lists Projects V2 for an organization. // // GitHub API docs: https://docs.github.com/rest/projects/projects#list-projects-for-organization @@ -128,3 +154,28 @@ func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string } return project, resp, nil } + +// ListProjectFieldsForOrganization lists Projects V2 for an organization. +// +// GitHub API docs: https://docs.github.com/rest/projects/fields#list-project-fields-for-organization +// +//meta:operation GET /orgs/{org}/projectsV2/{project_number}/fields +func (s *ProjectsService) ListProjectFieldsForOrganization(ctx context.Context, org string, projectNumber int64, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v/fields", org, projectNumber) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var fields []*ProjectV2Field + resp, err := s.client.Do(ctx, req, &fields) + if err != nil { + return nil, resp, err + } + return fields, resp, nil +} diff --git a/github/projects_test.go b/github/projects_test.go index f12a44aa2b6..8fcb20f43ad 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -265,6 +265,162 @@ func TestProjectsService_ListProjectsForUser_pagination(t *testing.T) { } } +func TestProjectsService_ListProjectFieldsForOrganization(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + // Combined handler: supports initial test case and dual before/after validation scenario. + mux.HandleFunc("/orgs/o/projectsV2/1/fields", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + q := r.URL.Query() + if q.Get("before") == "b" && q.Get("after") == "a" { + fmt.Fprint(w, `[]`) + return + } + // default expectation for main part of test + testFormValues(t, r, values{"q": "text", "after": "2", "before": "1"}) + fmt.Fprint(w, `[ + { + "id": "field1", + "node_id": "node_1", + "name": "Status", + "dataType": "single_select", + "url": "https://api.github.com/projects/1/fields/field1", + "options": [ + { + "id": "option1", + "name": "Todo", + "color": "blue", + "description": "Tasks to be done" + }, + { + "id": "option2", + "name": "In Progress", + "color": "yellow" + } + ], + "created_at": "2011-01-02T15:04:05Z", + "updated_at": "2012-01-02T15:04:05Z" + }, + { + "id": "field2", + "node_id": "node_2", + "name": "Priority", + "dataType": "text", + "url": "https://api.github.com/projects/1/fields/field2", + "created_at": "2011-01-02T15:04:05Z", + "updated_at": "2012-01-02T15:04:05Z" + } + ]`) + }) + + opts := &ListProjectsOptions{Query: "text", After: "2", Before: "1"} + ctx := context.Background() + fields, _, err := client.Projects.ListProjectFieldsForOrganization(ctx, "o", 1, opts) + if err != nil { + t.Fatalf("Projects.ListProjectFieldsForOrganization returned error: %v", err) + } + + if len(fields) != 2 { + t.Fatalf("Projects.ListProjectFieldsForOrganization returned %d fields, want 2", len(fields)) + } + + // Validate first field (with options) + field1 := fields[0] + if field1.ID != "field1" || field1.Name != "Status" || field1.DataType != "single_select" { + t.Errorf("First field: got ID=%s, Name=%s, DataType=%s; want field1, Status, single_select", + field1.ID, field1.Name, field1.DataType) + } + if len(field1.Options) != 2 { + t.Errorf("First field options: got %d, want 2", len(field1.Options)) + } + if field1.Options[0].Name != "Todo" || field1.Options[1].Name != "In Progress" { + t.Errorf("First field option names: got %s, %s; want Todo, In Progress", + field1.Options[0].Name, field1.Options[1].Name) + } + + // Validate second field (without options) + field2 := fields[1] + if field2.ID != "field2" || field2.Name != "Priority" || field2.DataType != "text" { + t.Errorf("Second field: got ID=%s, Name=%s, DataType=%s; want field2, Priority, text", + field2.ID, field2.Name, field2.DataType) + } + if len(field2.Options) != 0 { + t.Errorf("Second field options: got %d, want 0", len(field2.Options)) + } + + const methodName = "ListProjectFieldsForOrganization" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Projects.ListProjectFieldsForOrganization(ctx, "\n", 1, opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.ListProjectFieldsForOrganization(ctx, "o", 1, opts) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) + + // still allow both set (no validation enforced) – ensure it does not error + ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) + if _, _, err = client.Projects.ListProjectFieldsForOrganization(ctxBypass, "o", 1, &ListProjectsOptions{Before: "b", After: "a"}); err != nil { + t.Fatalf("unexpected error when both before/after set: %v", err) + } +} + +func TestProjectsService_ListProjectFieldsForOrganization_pagination(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + // First page returns a Link header with rel="next" containing an after cursor + mux.HandleFunc("/orgs/o/projectsV2/1/fields", func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + after := q.Get("after") + before := q.Get("before") + if after == "" && before == "" { + // first request + w.Header().Set("Link", "; rel=\"next\"") + fmt.Fprint(w, `[{"id":"field1","name":"Status","dataType":"single_select","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + return + } + if after == "cursor2" { + // second request simulates a previous link + w.Header().Set("Link", "; rel=\"prev\"") + fmt.Fprint(w, `[{"id":"field2","name":"Priority","dataType":"text","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + return + } + // unexpected state + http.Error(w, "unexpected query", http.StatusBadRequest) + }) + + ctx := context.Background() + first, resp, err := client.Projects.ListProjectFieldsForOrganization(ctx, "o", 1, nil) + if err != nil { + t.Fatalf("first page error: %v", err) + } + if len(first) != 1 || first[0].ID != "field1" { + t.Fatalf("unexpected first page %+v", first) + } + if resp.After != "cursor2" { + t.Fatalf("expected resp.After=cursor2 got %q", resp.After) + } + + // Use resp.After as opts.After for next page + opts := &ListProjectsOptions{After: resp.After} + second, resp2, err := client.Projects.ListProjectFieldsForOrganization(ctx, "o", 1, opts) + if err != nil { + t.Fatalf("second page error: %v", err) + } + if len(second) != 1 || second[0].ID != "field2" { + t.Fatalf("unexpected second page %+v", second) + } + if resp2.Before != "cursor2" { + t.Fatalf("expected resp2.Before=cursor2 got %q", resp2.Before) + } +} + // Marshal test ensures V2 fields marshal correctly. func TestProjectV2_Marshal(t *testing.T) { t.Parallel() @@ -290,3 +446,48 @@ func TestProjectV2_Marshal(t *testing.T) { testJSONMarshal(t, p, want) } + +// Marshal test ensures V2 field structures marshal correctly. +func TestProjectV2Field_Marshal(t *testing.T) { + t.Parallel() + testJSONMarshal(t, &ProjectV2Field{}, "{}") + testJSONMarshal(t, &ProjectV2FieldOption{}, "{}") + + field := &ProjectV2Field{ + ID: "field1", + NodeID: "node_1", + Name: "Status", + DataType: "single_select", + ProjectURL: "https://api.github.com/projects/1/fields/field1", + Options: []*ProjectV2FieldOption{ + { + ID: "option1", + Name: "Todo", + Color: "blue", + Description: "Tasks to be done", + }, + }, + CreatedAt: &Timestamp{referenceTime}, + UpdatedAt: &Timestamp{referenceTime}, + } + + want := `{ + "id": "field1", + "node_id": "node_1", + "name": "Status", + "dataType": "single_select", + "url": "https://api.github.com/projects/1/fields/field1", + "options": [ + { + "id": "option1", + "name": "Todo", + "color": "blue", + "description": "Tasks to be done" + } + ], + "created_at": ` + referenceTimeStr + `, + "updated_at": ` + referenceTimeStr + ` + }` + + testJSONMarshal(t, field, want) +} From 469e03a0a0719f8dc26a406bc2f2b8d9f31e7fdf Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Tue, 23 Sep 2025 12:16:06 +0200 Subject: [PATCH 10/39] Generate openapi docs --- openapi_operations.yaml | 189 +++++++++++++++++++++++++++++++++------- 1 file changed, 158 insertions(+), 31 deletions(-) diff --git a/openapi_operations.yaml b/openapi_operations.yaml index 3f433cc0a33..053c19325b6 100644 --- a/openapi_operations.yaml +++ b/openapi_operations.yaml @@ -27,7 +27,7 @@ operation_overrides: documentation_url: https://docs.github.com/rest/pages/pages#request-a-github-pages-build - name: GET /repos/{owner}/{repo}/pages/builds/{build_id} documentation_url: https://docs.github.com/rest/pages/pages#get-github-pages-build -openapi_commit: 30ab35c5db4a05519ceed2e41292cdb7af182f1c +openapi_commit: 44dd20c107ca46d1dbcaf4aa522d9039e96e631c openapi_operations: - name: GET / documentation_url: https://docs.github.com/rest/meta/meta#github-api-root @@ -476,6 +476,14 @@ openapi_operations: documentation_url: https://docs.github.com/enterprise-server@3.17/rest/enterprise-admin/admin-stats#get-users-statistics openapi_files: - descriptions/ghes-3.17/ghes-3.17.json + - name: POST /enterprises/{enterprise}/access-restrictions/disable + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/enterprise-admin/enterprises#disable-access-restrictions-for-an-enterprise + openapi_files: + - descriptions/ghec/ghec.json + - name: POST /enterprises/{enterprise}/access-restrictions/enable + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/enterprise-admin/enterprises#enable-access-restrictions-for-an-enterprise + openapi_files: + - descriptions/ghec/ghec.json - name: GET /enterprises/{enterprise}/actions/cache/usage documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/actions/cache#get-github-actions-cache-usage-for-an-enterprise openapi_files: @@ -827,6 +835,10 @@ openapi_operations: documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/enterprise-admin/bypass-requests#list-push-rule-bypass-requests-within-an-enterprise openapi_files: - descriptions/ghec/ghec.json + - name: GET /enterprises/{enterprise}/bypass-requests/secret-scanning + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/secret-scanning/delegated-bypass#list-bypass-requests-for-secret-scanning-for-an-enterprise + openapi_files: + - descriptions/ghec/ghec.json - name: GET /enterprises/{enterprise}/code-scanning/alerts documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/code-scanning/code-scanning#list-code-scanning-alerts-for-an-enterprise openapi_files: @@ -904,6 +916,22 @@ openapi_operations: documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/copilot/copilot-user-management#list-all-copilot-seat-assignments-for-an-enterprise openapi_files: - descriptions/ghec/ghec.json + - name: DELETE /enterprises/{enterprise}/copilot/billing/selected_enterprise_teams + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/copilot/copilot-user-management#remove-enterprise-teams-from-the-copilot-subscription-for-an-enterprise + openapi_files: + - descriptions/ghec/ghec.json + - name: POST /enterprises/{enterprise}/copilot/billing/selected_enterprise_teams + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/copilot/copilot-user-management#add-enterprise-teams-to-the-copilot-subscription-for-an-enterprise + openapi_files: + - descriptions/ghec/ghec.json + - name: DELETE /enterprises/{enterprise}/copilot/billing/selected_users + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/copilot/copilot-user-management#remove-users-from-the-copilot-subscription-for-an-enterprise + openapi_files: + - descriptions/ghec/ghec.json + - name: POST /enterprises/{enterprise}/copilot/billing/selected_users + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/copilot/copilot-user-management#add-users-to-the-copilot-subscription-for-an-enterprise + openapi_files: + - descriptions/ghec/ghec.json - name: GET /enterprises/{enterprise}/copilot/metrics documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/copilot/copilot-metrics#get-copilot-metrics-for-an-enterprise openapi_files: @@ -1061,6 +1089,61 @@ openapi_operations: documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/copilot/copilot-metrics#get-copilot-metrics-for-an-enterprise-team openapi_files: - descriptions/ghec/ghec.json + - name: GET /enterprises/{enterprise}/teams + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-teams#list-enterprise-teams + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: POST /enterprises/{enterprise}/teams + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-teams#create-an-enterprise-team + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /enterprises/{enterprise}/teams/{enterprise-team}/memberships + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-team-members#list-members-in-an-enterprise-team + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: POST /enterprises/{enterprise}/teams/{enterprise-team}/memberships/add + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-team-members#bulk-add-team-members + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: POST /enterprises/{enterprise}/teams/{enterprise-team}/memberships/remove + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-team-members#bulk-remove-team-members + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: DELETE /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username} + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-team-members#remove-team-membership + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username} + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-team-members#get-enterprise-team-membership + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: PUT /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username} + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-team-members#add-team-member + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: DELETE /enterprises/{enterprise}/teams/{team_slug} + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-teams#delete-an-enterprise-team + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /enterprises/{enterprise}/teams/{team_slug} + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-teams#get-an-enterprise-team + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: PATCH /enterprises/{enterprise}/teams/{team_slug} + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-teams#update-an-enterprise-team + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json - name: POST /enterprises/{enterprise}/{security_product}/{enablement} documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/enterprise-admin/code-security-and-analysis#enable-or-disable-a-security-feature openapi_files: @@ -1939,8 +2022,18 @@ openapi_operations: openapi_files: - descriptions/ghec/ghec.json - descriptions/ghes-3.17/ghes-3.17.json + - name: POST /orgs/{org}/artifacts/metadata/storage-record + documentation_url: https://docs.github.com/rest/orgs/artifact-metadata#create-artifact-metadata-storage-record + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /orgs/{org}/artifacts/{subject_digest}/metadata/storage-records + documentation_url: https://docs.github.com/rest/orgs/artifact-metadata#list-artifact-storage-records + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json - name: POST /orgs/{org}/attestations/bulk-list - documentation_url: https://docs.github.com/rest/orgs/orgs#list-attestations-by-bulk-subject-digests + documentation_url: https://docs.github.com/rest/orgs/attestations#list-attestations-by-bulk-subject-digests openapi_files: - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json @@ -1960,7 +2053,7 @@ openapi_operations: - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - name: GET /orgs/{org}/attestations/{subject_digest} - documentation_url: https://docs.github.com/rest/orgs/orgs#list-attestations + documentation_url: https://docs.github.com/rest/orgs/attestations#list-attestations openapi_files: - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json @@ -2882,21 +2975,11 @@ openapi_operations: openapi_files: - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - - name: GET /users/{username}/projectsV2 - documentation_url: https://docs.github.com/rest/projects/projects#list-projects-for-user - openapi_files: - - descriptions/api.github.com/api.github.com.json - - descriptions/ghec/ghec.json - name: GET /orgs/{org}/projectsV2/{project_number} documentation_url: https://docs.github.com/rest/projects/projects#get-project-for-organization openapi_files: - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - - name: GET /users/{username}/projectsV2/{project_number} - documentation_url: https://docs.github.com/rest/projects/projects#get-project-for-user - openapi_files: - - descriptions/api.github.com/api.github.com.json - - descriptions/ghec/ghec.json - name: GET /orgs/{org}/projectsV2/{project_number}/fields documentation_url: https://docs.github.com/rest/projects/fields#list-project-fields-for-organization openapi_files: @@ -3404,29 +3487,25 @@ openapi_operations: - descriptions/ghec/ghec.json - descriptions/ghes-3.17/ghes-3.17.json - name: DELETE /projects/columns/cards/{card_id} - documentation_url: https://docs.github.com/rest/projects-classic/cards#delete-a-project-card + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/projects-classic/cards#delete-a-project-card openapi_files: - - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - - descriptions/ghes-3.17/ghes-3.17.json + - descriptions/ghes-3.16/ghes-3.16.json - name: GET /projects/columns/cards/{card_id} - documentation_url: https://docs.github.com/rest/projects-classic/cards#get-a-project-card + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/projects-classic/cards#get-a-project-card openapi_files: - - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - - descriptions/ghes-3.17/ghes-3.17.json + - descriptions/ghes-3.16/ghes-3.16.json - name: PATCH /projects/columns/cards/{card_id} - documentation_url: https://docs.github.com/rest/projects-classic/cards#update-an-existing-project-card + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/projects-classic/cards#update-an-existing-project-card openapi_files: - - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - - descriptions/ghes-3.17/ghes-3.17.json + - descriptions/ghes-3.16/ghes-3.16.json - name: POST /projects/columns/cards/{card_id}/moves - documentation_url: https://docs.github.com/rest/projects-classic/cards#move-a-project-card + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/projects-classic/cards#move-a-project-card openapi_files: - - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - - descriptions/ghes-3.17/ghes-3.17.json + - descriptions/ghes-3.16/ghes-3.16.json - name: DELETE /projects/columns/{column_id} documentation_url: https://docs.github.com/rest/projects-classic/columns#delete-a-project-column openapi_files: @@ -3446,17 +3525,15 @@ openapi_operations: - descriptions/ghec/ghec.json - descriptions/ghes-3.17/ghes-3.17.json - name: GET /projects/columns/{column_id}/cards - documentation_url: https://docs.github.com/rest/projects-classic/cards#list-project-cards + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/projects-classic/cards#list-project-cards openapi_files: - - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - - descriptions/ghes-3.17/ghes-3.17.json + - descriptions/ghes-3.16/ghes-3.16.json - name: POST /projects/columns/{column_id}/cards - documentation_url: https://docs.github.com/rest/projects-classic/cards#create-a-project-card + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/projects-classic/cards#create-a-project-card openapi_files: - - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - - descriptions/ghes-3.17/ghes-3.17.json + - descriptions/ghes-3.16/ghes-3.16.json - name: POST /projects/columns/{column_id}/moves documentation_url: https://docs.github.com/rest/projects-classic/columns#move-a-project-column openapi_files: @@ -5440,6 +5517,11 @@ openapi_operations: - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - descriptions/ghes-3.17/ghes-3.17.json + - name: GET /repos/{owner}/{repo}/issues/{issue_number}/parent + documentation_url: https://docs.github.com/rest/issues/sub-issues#get-parent-issue + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json - name: GET /repos/{owner}/{repo}/issues/{issue_number}/reactions documentation_url: https://docs.github.com/rest/reactions/reactions#list-reactions-for-an-issue openapi_files: @@ -7501,6 +7583,51 @@ openapi_operations: - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - descriptions/ghes-3.17/ghes-3.17.json + - name: GET /users/{username}/projectsV2 + documentation_url: https://docs.github.com/rest/projects/projects#list-projects-for-user + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /users/{username}/projectsV2/{project_number} + documentation_url: https://docs.github.com/rest/projects/projects#get-project-for-user + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /users/{username}/projectsV2/{project_number}/fields + documentation_url: https://docs.github.com/rest/projects/fields#list-project-fields-for-user + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /users/{username}/projectsV2/{project_number}/fields/{field_id} + documentation_url: https://docs.github.com/rest/projects/fields#get-project-field-for-user + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /users/{username}/projectsV2/{project_number}/items + documentation_url: https://docs.github.com/rest/projects/items#list-items-for-a-user-owned-project + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: POST /users/{username}/projectsV2/{project_number}/items + documentation_url: https://docs.github.com/rest/projects/items#add-item-to-user-owned-project + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: DELETE /users/{username}/projectsV2/{project_number}/items/{item_id} + documentation_url: https://docs.github.com/rest/projects/items#delete-project-item-for-user + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /users/{username}/projectsV2/{project_number}/items/{item_id} + documentation_url: https://docs.github.com/rest/projects/items#get-an-item-for-a-user-owned-project + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: PATCH /users/{username}/projectsV2/{project_number}/items/{item_id} + documentation_url: https://docs.github.com/rest/projects/items#update-project-item-for-user + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json - name: GET /users/{username}/received_events documentation_url: https://docs.github.com/rest/activity/events#list-events-received-by-the-authenticated-user openapi_files: From 2dbe5b45ea6c776a5255f8a8cefd71b6cc613e6c Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Tue, 23 Sep 2025 12:19:38 +0200 Subject: [PATCH 11/39] Results of generate --- github/orgs_attestations.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/orgs_attestations.go b/github/orgs_attestations.go index d0ac123e2cf..1a7a1d5c966 100644 --- a/github/orgs_attestations.go +++ b/github/orgs_attestations.go @@ -14,7 +14,7 @@ import ( // with a given subject digest that are associated with repositories // owned by an organization. // -// GitHub API docs: https://docs.github.com/rest/orgs/orgs#list-attestations +// GitHub API docs: https://docs.github.com/rest/orgs/attestations#list-attestations // //meta:operation GET /orgs/{org}/attestations/{subject_digest} func (s *OrganizationsService) ListAttestations(ctx context.Context, org, subjectDigest string, opts *ListOptions) (*AttestationsResponse, *Response, error) { From 053a9e171d07fc56c390218fd7b0dd4ca6dc109e Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Tue, 23 Sep 2025 19:22:26 +0200 Subject: [PATCH 12/39] Update github/projects.go Co-authored-by: Glenn Lewis <6598971+gmlewis@users.noreply.github.com> --- github/projects.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/projects.go b/github/projects.go index be932a597a2..afebf1dab1a 100644 --- a/github/projects.go +++ b/github/projects.go @@ -60,7 +60,7 @@ type ProjectV2Field struct { NodeID string `json:"node_id,omitempty"` // The GraphQL node ID for this field. Name string `json:"name,omitempty"` // The display name of the field. DataType string `json:"dataType,omitempty"` // The data type of the field (e.g., "text", "number", "date", "single_select", "multi_select"). - ProjectURL string `json:"url,omitempty"` // The API URL for this field. + URL string `json:"url,omitempty"` // The API URL for this field. Options []*ProjectV2FieldOption `json:"options,omitempty"` // Available options for single_select and multi_select fields. CreatedAt *Timestamp `json:"created_at,omitempty"` // The time when this field was created. UpdatedAt *Timestamp `json:"updated_at,omitempty"` // The time when this field was last updated. From 680a0d1217eeb13d8e09f6c4f4b4678619e437e7 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Tue, 23 Sep 2025 19:23:52 +0200 Subject: [PATCH 13/39] Rename url in test --- github/projects_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/github/projects_test.go b/github/projects_test.go index 8fcb20f43ad..ccbdc65601a 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -454,11 +454,11 @@ func TestProjectV2Field_Marshal(t *testing.T) { testJSONMarshal(t, &ProjectV2FieldOption{}, "{}") field := &ProjectV2Field{ - ID: "field1", - NodeID: "node_1", - Name: "Status", - DataType: "single_select", - ProjectURL: "https://api.github.com/projects/1/fields/field1", + ID: "field1", + NodeID: "node_1", + Name: "Status", + DataType: "single_select", + URL: "https://api.github.com/projects/1/fields/field1", Options: []*ProjectV2FieldOption{ { ID: "option1", From d8696b44026369d53f5cfbe533dbe2908caeb560 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Tue, 23 Sep 2025 19:30:47 +0200 Subject: [PATCH 14/39] Generate again --- github/projects.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/github/projects.go b/github/projects.go index afebf1dab1a..4359db07ef6 100644 --- a/github/projects.go +++ b/github/projects.go @@ -56,14 +56,14 @@ type ProjectV2FieldOption struct { // // GitHub API docs: https://docs.github.com/rest/projects/fields type ProjectV2Field struct { - ID string `json:"id,omitempty"` // The unique identifier for this field. - NodeID string `json:"node_id,omitempty"` // The GraphQL node ID for this field. - Name string `json:"name,omitempty"` // The display name of the field. - DataType string `json:"dataType,omitempty"` // The data type of the field (e.g., "text", "number", "date", "single_select", "multi_select"). - URL string `json:"url,omitempty"` // The API URL for this field. - Options []*ProjectV2FieldOption `json:"options,omitempty"` // Available options for single_select and multi_select fields. - CreatedAt *Timestamp `json:"created_at,omitempty"` // The time when this field was created. - UpdatedAt *Timestamp `json:"updated_at,omitempty"` // The time when this field was last updated. + ID string `json:"id,omitempty"` // The unique identifier for this field. + NodeID string `json:"node_id,omitempty"` // The GraphQL node ID for this field. + Name string `json:"name,omitempty"` // The display name of the field. + DataType string `json:"dataType,omitempty"` // The data type of the field (e.g., "text", "number", "date", "single_select", "multi_select"). + URL string `json:"url,omitempty"` // The API URL for this field. + Options []*ProjectV2FieldOption `json:"options,omitempty"` // Available options for single_select and multi_select fields. + CreatedAt *Timestamp `json:"created_at,omitempty"` // The time when this field was created. + UpdatedAt *Timestamp `json:"updated_at,omitempty"` // The time when this field was last updated. } // ListProjectsForOrganization lists Projects V2 for an organization. From 060aaae6143e3e9cbfc8a386ff1c53baaf78dca0 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 24 Sep 2025 09:58:38 +0200 Subject: [PATCH 15/39] Shorten functions for orgs --- github/projects.go | 8 +++---- github/projects_test.go | 46 ++++++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/github/projects.go b/github/projects.go index 4359db07ef6..9d559e298fb 100644 --- a/github/projects.go +++ b/github/projects.go @@ -66,12 +66,12 @@ type ProjectV2Field struct { UpdatedAt *Timestamp `json:"updated_at,omitempty"` // The time when this field was last updated. } -// ListProjectsForOrganization lists Projects V2 for an organization. +// ListProjectsForOrg lists Projects V2 for an organization. // // GitHub API docs: https://docs.github.com/rest/projects/projects#list-projects-for-organization // //meta:operation GET /orgs/{org}/projectsV2 -func (s *ProjectsService) ListProjectsForOrganization(ctx context.Context, org string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { +func (s *ProjectsService) ListProjectsForOrg(ctx context.Context, org string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2", org) u, err := addOptions(u, opts) if err != nil { @@ -155,12 +155,12 @@ func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string return project, resp, nil } -// ListProjectFieldsForOrganization lists Projects V2 for an organization. +// ListProjectFieldsForOrg lists Projects V2 for an organization. // // GitHub API docs: https://docs.github.com/rest/projects/fields#list-project-fields-for-organization // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/fields -func (s *ProjectsService) ListProjectFieldsForOrganization(ctx context.Context, org string, projectNumber int64, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { +func (s *ProjectsService) ListProjectFieldsForOrg(ctx context.Context, org string, projectNumber int64, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/fields", org, projectNumber) u, err := addOptions(u, opts) if err != nil { diff --git a/github/projects_test.go b/github/projects_test.go index ccbdc65601a..43e7dc3353e 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -12,7 +12,7 @@ import ( "testing" ) -func TestProjectsService_ListProjectsForOrganizations(t *testing.T) { +func TestProjectsService_ListProjectsForOrg(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -31,22 +31,22 @@ func TestProjectsService_ListProjectsForOrganizations(t *testing.T) { opts := &ListProjectsOptions{Query: "alpha", After: "2", Before: "1"} ctx := context.Background() - projects, _, err := client.Projects.ListProjectsForOrganization(ctx, "o", opts) + projects, _, err := client.Projects.ListProjectsForOrg(ctx, "o", opts) if err != nil { - t.Fatalf("Projects.ListProjectsForOrganization returned error: %v", err) + t.Fatalf("Projects.ListProjectsForOrg returned error: %v", err) } if len(projects) != 1 || projects[0].GetID() != 1 || projects[0].GetTitle() != "T1" { - t.Fatalf("Projects.ListProjectsForOrganization returned %+v", projects) + t.Fatalf("Projects.ListProjectsForOrg returned %+v", projects) } - const methodName = "ListProjectsForOrganization" + const methodName = "ListProjectsForOrg" testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Projects.ListProjectsForOrganization(ctx, "\n", opts) + _, _, err = client.Projects.ListProjectsForOrg(ctx, "\n", opts) return err }) testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.ListProjectsForOrganization(ctx, "o", opts) + got, resp, err := client.Projects.ListProjectsForOrg(ctx, "o", opts) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -55,7 +55,7 @@ func TestProjectsService_ListProjectsForOrganizations(t *testing.T) { // still allow both set (no validation enforced) – ensure it does not error ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectsForOrganization(ctxBypass, "o", &ListProjectsOptions{Before: "b", After: "a"}); err != nil { + if _, _, err = client.Projects.ListProjectsForOrg(ctxBypass, "o", &ListProjectsOptions{Before: "b", After: "a"}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } @@ -164,9 +164,9 @@ func TestProjectsService_GetProjectForUser(t *testing.T) { }) } -// TestProjectsService_ListProjectsForOrganization_pagination clarifies how callers should +// TestProjectsService_ListProjectsForOrg_pagination clarifies how callers should // use resp.After to request the next page and resp.Before for previous pages when supported. -func TestProjectsService_ListProjectsForOrganization_pagination(t *testing.T) { +func TestProjectsService_ListProjectsForOrg_pagination(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -192,7 +192,7 @@ func TestProjectsService_ListProjectsForOrganization_pagination(t *testing.T) { }) ctx := context.Background() - first, resp, err := client.Projects.ListProjectsForOrganization(ctx, "o", nil) + first, resp, err := client.Projects.ListProjectsForOrg(ctx, "o", nil) if err != nil { t.Fatalf("first page error: %v", err) } @@ -205,7 +205,7 @@ func TestProjectsService_ListProjectsForOrganization_pagination(t *testing.T) { // Use resp.After as opts.After for next page opts := &ListProjectsOptions{After: resp.After} - second, resp2, err := client.Projects.ListProjectsForOrganization(ctx, "o", opts) + second, resp2, err := client.Projects.ListProjectsForOrg(ctx, "o", opts) if err != nil { t.Fatalf("second page error: %v", err) } @@ -265,7 +265,7 @@ func TestProjectsService_ListProjectsForUser_pagination(t *testing.T) { } } -func TestProjectsService_ListProjectFieldsForOrganization(t *testing.T) { +func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -316,13 +316,13 @@ func TestProjectsService_ListProjectFieldsForOrganization(t *testing.T) { opts := &ListProjectsOptions{Query: "text", After: "2", Before: "1"} ctx := context.Background() - fields, _, err := client.Projects.ListProjectFieldsForOrganization(ctx, "o", 1, opts) + fields, _, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) if err != nil { - t.Fatalf("Projects.ListProjectFieldsForOrganization returned error: %v", err) + t.Fatalf("Projects.ListProjectFieldsForOrg returned error: %v", err) } if len(fields) != 2 { - t.Fatalf("Projects.ListProjectFieldsForOrganization returned %d fields, want 2", len(fields)) + t.Fatalf("Projects.ListProjectFieldsForOrg returned %d fields, want 2", len(fields)) } // Validate first field (with options) @@ -349,14 +349,14 @@ func TestProjectsService_ListProjectFieldsForOrganization(t *testing.T) { t.Errorf("Second field options: got %d, want 0", len(field2.Options)) } - const methodName = "ListProjectFieldsForOrganization" + const methodName = "ListProjectFieldsForOrg" testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Projects.ListProjectFieldsForOrganization(ctx, "\n", 1, opts) + _, _, err = client.Projects.ListProjectFieldsForOrg(ctx, "\n", 1, opts) return err }) testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.ListProjectFieldsForOrganization(ctx, "o", 1, opts) + got, resp, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -365,12 +365,12 @@ func TestProjectsService_ListProjectFieldsForOrganization(t *testing.T) { // still allow both set (no validation enforced) – ensure it does not error ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectFieldsForOrganization(ctxBypass, "o", 1, &ListProjectsOptions{Before: "b", After: "a"}); err != nil { + if _, _, err = client.Projects.ListProjectFieldsForOrg(ctxBypass, "o", 1, &ListProjectsOptions{Before: "b", After: "a"}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } -func TestProjectsService_ListProjectFieldsForOrganization_pagination(t *testing.T) { +func TestProjectsService_ListProjectFieldsForOrg_pagination(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -396,7 +396,7 @@ func TestProjectsService_ListProjectFieldsForOrganization_pagination(t *testing. }) ctx := context.Background() - first, resp, err := client.Projects.ListProjectFieldsForOrganization(ctx, "o", 1, nil) + first, resp, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, nil) if err != nil { t.Fatalf("first page error: %v", err) } @@ -409,7 +409,7 @@ func TestProjectsService_ListProjectFieldsForOrganization_pagination(t *testing. // Use resp.After as opts.After for next page opts := &ListProjectsOptions{After: resp.After} - second, resp2, err := client.Projects.ListProjectFieldsForOrganization(ctx, "o", 1, opts) + second, resp2, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) if err != nil { t.Fatalf("second page error: %v", err) } From 98c28fa2018d2c34f3c44f984003b54cd24de3fc Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 24 Sep 2025 11:17:49 +0200 Subject: [PATCH 16/39] Implemented all endpoints for project items --- github/event_types.go | 21 +- github/github-accessors.go | 48 ++++ github/github-accessors_test.go | 66 +++++ github/projects.go | 235 ++++++++++++++- github/projects_test.go | 491 ++++++++++++++++++-------------- 5 files changed, 629 insertions(+), 232 deletions(-) diff --git a/github/event_types.go b/github/event_types.go index 480ed8dfb96..a4dc7daaa63 100644 --- a/github/event_types.go +++ b/github/event_types.go @@ -1179,15 +1179,18 @@ type FieldValue struct { // ProjectV2Item represents an item belonging to a project. type ProjectV2Item struct { - ID *int64 `json:"id,omitempty"` - NodeID *string `json:"node_id,omitempty"` - ProjectNodeID *string `json:"project_node_id,omitempty"` - ContentNodeID *string `json:"content_node_id,omitempty"` - ContentType *string `json:"content_type,omitempty"` - Creator *User `json:"creator,omitempty"` - CreatedAt *Timestamp `json:"created_at,omitempty"` - UpdatedAt *Timestamp `json:"updated_at,omitempty"` - ArchivedAt *Timestamp `json:"archived_at,omitempty"` + ID *int64 `json:"id,omitempty"` + NodeID *string `json:"node_id,omitempty"` + ProjectNodeID *string `json:"project_node_id,omitempty"` + ContentNodeID *string `json:"content_node_id,omitempty"` + ProjectURL *string `json:"project_url,omitempty"` + ContentType *string `json:"content_type,omitempty"` + Creator *User `json:"creator,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + ArchivedAt *Timestamp `json:"archived_at,omitempty"` + ItemURL *string `json:"item_url,omitempty"` + Fields []*ProjectV2Field `json:"fields,omitempty"` } // PublicEvent is triggered when a private repository is open sourced. diff --git a/github/github-accessors.go b/github/github-accessors.go index 55334fddea2..fdbfe6a4984 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -15198,6 +15198,14 @@ func (n *NetworkSettingsResource) GetSubnetID() string { return *n.SubnetID } +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (n *NewProjectV2Field) GetID() int64 { + if n == nil || n.ID == nil { + return 0 + } + return *n.ID +} + // GetBase returns the Base field if it's non-nil, zero value otherwise. func (n *NewPullRequest) GetBase() string { if n == nil || n.Base == nil { @@ -18646,6 +18654,14 @@ func (p *ProjectV2Field) GetCreatedAt() Timestamp { return *p.CreatedAt } +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *ProjectV2Field) GetID() int64 { + if p == nil || p.ID == nil { + return 0 + } + return *p.ID +} + // GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. func (p *ProjectV2Field) GetUpdatedAt() Timestamp { if p == nil || p.UpdatedAt == nil { @@ -18654,6 +18670,14 @@ func (p *ProjectV2Field) GetUpdatedAt() Timestamp { return *p.UpdatedAt } +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *ProjectV2FieldOption) GetID() int64 { + if p == nil || p.ID == nil { + return 0 + } + return *p.ID +} + // GetArchivedAt returns the ArchivedAt field if it's non-nil, zero value otherwise. func (p *ProjectV2Item) GetArchivedAt() Timestamp { if p == nil || p.ArchivedAt == nil { @@ -18702,6 +18726,14 @@ func (p *ProjectV2Item) GetID() int64 { return *p.ID } +// GetItemURL returns the ItemURL field if it's non-nil, zero value otherwise. +func (p *ProjectV2Item) GetItemURL() string { + if p == nil || p.ItemURL == nil { + return "" + } + return *p.ItemURL +} + // GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. func (p *ProjectV2Item) GetNodeID() string { if p == nil || p.NodeID == nil { @@ -18718,6 +18750,14 @@ func (p *ProjectV2Item) GetProjectNodeID() string { return *p.ProjectNodeID } +// GetProjectURL returns the ProjectURL field if it's non-nil, zero value otherwise. +func (p *ProjectV2Item) GetProjectURL() string { + if p == nil || p.ProjectURL == nil { + return "" + } + return *p.ProjectURL +} + // GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. func (p *ProjectV2Item) GetUpdatedAt() Timestamp { if p == nil || p.UpdatedAt == nil { @@ -28334,6 +28374,14 @@ func (u *UpdateEnterpriseRunnerGroupRequest) GetVisibility() string { return *u.Visibility } +// GetArchived returns the Archived field if it's non-nil, zero value otherwise. +func (u *UpdateProjectItemOptions) GetArchived() bool { + if u == nil || u.Archived == nil { + return false + } + return *u.Archived +} + // GetForce returns the Force field if it's non-nil, zero value otherwise. func (u *UpdateRef) GetForce() bool { if u == nil || u.Force == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index fd2d06717b0..dab5705bb43 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -19699,6 +19699,17 @@ func TestNetworkSettingsResource_GetSubnetID(tt *testing.T) { n.GetSubnetID() } +func TestNewProjectV2Field_GetID(tt *testing.T) { + tt.Parallel() + var zeroValue int64 + n := &NewProjectV2Field{ID: &zeroValue} + n.GetID() + n = &NewProjectV2Field{} + n.GetID() + n = nil + n.GetID() +} + func TestNewPullRequest_GetBase(tt *testing.T) { tt.Parallel() var zeroValue string @@ -24191,6 +24202,17 @@ func TestProjectV2Field_GetCreatedAt(tt *testing.T) { p.GetCreatedAt() } +func TestProjectV2Field_GetID(tt *testing.T) { + tt.Parallel() + var zeroValue int64 + p := &ProjectV2Field{ID: &zeroValue} + p.GetID() + p = &ProjectV2Field{} + p.GetID() + p = nil + p.GetID() +} + func TestProjectV2Field_GetUpdatedAt(tt *testing.T) { tt.Parallel() var zeroValue Timestamp @@ -24202,6 +24224,17 @@ func TestProjectV2Field_GetUpdatedAt(tt *testing.T) { p.GetUpdatedAt() } +func TestProjectV2FieldOption_GetID(tt *testing.T) { + tt.Parallel() + var zeroValue int64 + p := &ProjectV2FieldOption{ID: &zeroValue} + p.GetID() + p = &ProjectV2FieldOption{} + p.GetID() + p = nil + p.GetID() +} + func TestProjectV2Item_GetArchivedAt(tt *testing.T) { tt.Parallel() var zeroValue Timestamp @@ -24265,6 +24298,17 @@ func TestProjectV2Item_GetID(tt *testing.T) { p.GetID() } +func TestProjectV2Item_GetItemURL(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &ProjectV2Item{ItemURL: &zeroValue} + p.GetItemURL() + p = &ProjectV2Item{} + p.GetItemURL() + p = nil + p.GetItemURL() +} + func TestProjectV2Item_GetNodeID(tt *testing.T) { tt.Parallel() var zeroValue string @@ -24287,6 +24331,17 @@ func TestProjectV2Item_GetProjectNodeID(tt *testing.T) { p.GetProjectNodeID() } +func TestProjectV2Item_GetProjectURL(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &ProjectV2Item{ProjectURL: &zeroValue} + p.GetProjectURL() + p = &ProjectV2Item{} + p.GetProjectURL() + p = nil + p.GetProjectURL() +} + func TestProjectV2Item_GetUpdatedAt(tt *testing.T) { tt.Parallel() var zeroValue Timestamp @@ -36477,6 +36532,17 @@ func TestUpdateEnterpriseRunnerGroupRequest_GetVisibility(tt *testing.T) { u.GetVisibility() } +func TestUpdateProjectItemOptions_GetArchived(tt *testing.T) { + tt.Parallel() + var zeroValue bool + u := &UpdateProjectItemOptions{Archived: &zeroValue} + u.GetArchived() + u = &UpdateProjectItemOptions{} + u.GetArchived() + u = nil + u.GetArchived() +} + func TestUpdateRef_GetForce(tt *testing.T) { tt.Parallel() var zeroValue bool diff --git a/github/projects.go b/github/projects.go index 9d559e298fb..178d18b54d1 100644 --- a/github/projects.go +++ b/github/projects.go @@ -45,7 +45,7 @@ type ListProjectsOptions struct { // // GitHub API docs: https://docs.github.com/rest/projects/fields type ProjectV2FieldOption struct { - ID string `json:"id,omitempty"` // The unique identifier for this option. + ID *int64 `json:"id,omitempty"` // The unique identifier for this option. Name string `json:"name,omitempty"` // The display name of the option. Color string `json:"color,omitempty"` // The color associated with this option (e.g., "blue", "red"). Description string `json:"description,omitempty"` // An optional description for this option. @@ -56,7 +56,7 @@ type ProjectV2FieldOption struct { // // GitHub API docs: https://docs.github.com/rest/projects/fields type ProjectV2Field struct { - ID string `json:"id,omitempty"` // The unique identifier for this field. + ID *int64 `json:"id,omitempty"` // The unique identifier for this field. NodeID string `json:"node_id,omitempty"` // The GraphQL node ID for this field. Name string `json:"name,omitempty"` // The display name of the field. DataType string `json:"dataType,omitempty"` // The data type of the field (e.g., "text", "number", "date", "single_select", "multi_select"). @@ -66,6 +66,12 @@ type ProjectV2Field struct { UpdatedAt *Timestamp `json:"updated_at,omitempty"` // The time when this field was last updated. } +// NewProjectV2Field represents a new field to be added to a GitHub Projects V2. +type NewProjectV2Field struct { + ID *int64 `json:"id,omitempty"` // The unique identifier for this field. + Value any `json:"value,omitempty"` // The value of the field. +} + // ListProjectsForOrg lists Projects V2 for an organization. // // GitHub API docs: https://docs.github.com/rest/projects/projects#list-projects-for-organization @@ -179,3 +185,228 @@ func (s *ProjectsService) ListProjectFieldsForOrg(ctx context.Context, org strin } return fields, resp, nil } + +// ListProjectItemsOptions specifies optional parameters when listing project items. +// Note: Pagination uses before/after cursor-style pagination similar to ListProjectsOptions. +// "Fields" can be used to restrict which field values are returned (by their numeric IDs). +type ListProjectItemsOptions struct { + // Embed ListProjectsOptions to reuse pagination and query parameters. + ListProjectsOptions + // Fields restricts which field values are returned by numeric field IDs. + Fields []int64 `url:"fields,omitempty,comma"` +} + +// GetProjectItemOptions specifies optional parameters when getting a project item. +type GetProjectItemOptions struct { + // Fields restricts which field values are returned by numeric field IDs. + Fields []int64 `url:"fields,omitempty,comma"` +} + +// AddProjectItemOptions represents the payload to add an item (issue or pull request) +// to a project. The Type must be either "Issue" or "PullRequest" (as per API docs) and +// ID is the numerical ID of that issue or pull request. +type AddProjectItemOptions struct { + Type string `json:"type,omitempty"` + ID int64 `json:"id,omitempty"` +} + +// UpdateProjectItemOptions represents fields that can be modified for a project item. +// Currently the REST API allows archiving/unarchiving an item (archived boolean). +// This struct can be expanded in the future as the API grows. +type UpdateProjectItemOptions struct { + // Archived indicates whether the item should be archived (true) or unarchived (false). + Archived *bool `json:"archived,omitempty"` + // Fields allows updating field values for the item. Each entry supplies a field ID and a value. + Fields []*NewProjectV2Field `json:"fields,omitempty"` +} + +// ListProjectItemsForOrg lists items for an organization owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#list-items-for-an-organization-owned-project +// +//meta:operation GET /orgs/{org}/projectsV2/{project_number}/items +func (s *ProjectsService) ListProjectItemsForOrg(ctx context.Context, org string, projectNumber int64, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v/items", org, projectNumber) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var items []*ProjectV2Item + resp, err := s.client.Do(ctx, req, &items) + if err != nil { + return nil, resp, err + } + return items, resp, nil +} + +// AddProjectItemForOrg adds an issue or pull request item to an organization owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#add-item-to-organization-owned-project +// +//meta:operation POST /orgs/{org}/projectsV2/{project_number}/items +func (s *ProjectsService) AddProjectItemForOrg(ctx context.Context, org string, projectNumber int64, opts *AddProjectItemOptions) (*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v/items", org, projectNumber) + req, err := s.client.NewRequest("POST", u, opts) + if err != nil { + return nil, nil, err + } + + item := new(ProjectV2Item) + resp, err := s.client.Do(ctx, req, item) + if err != nil { + return nil, resp, err + } + return item, resp, nil +} + +// GetProjectItemForOrg gets a single item from an organization owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#get-an-item-for-an-organization-owned-project +// +//meta:operation GET /orgs/{org}/projectsV2/{project_number}/items/{item_id} +func (s *ProjectsService) GetProjectItemForOrg(ctx context.Context, org string, projectNumber, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) + req, err := s.client.NewRequest("GET", u, opts) + if err != nil { + return nil, nil, err + } + item := new(ProjectV2Item) + resp, err := s.client.Do(ctx, req, item) + if err != nil { + return nil, resp, err + } + return item, resp, nil +} + +// UpdateProjectItemForOrg updates an item in an organization owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#update-project-item-for-organization +// +//meta:operation PATCH /orgs/{org}/projectsV2/{project_number}/items/{item_id} +func (s *ProjectsService) UpdateProjectItemForOrg(ctx context.Context, org string, projectNumber, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) + req, err := s.client.NewRequest("PATCH", u, opts) + if err != nil { + return nil, nil, err + } + item := new(ProjectV2Item) + resp, err := s.client.Do(ctx, req, item) + if err != nil { + return nil, resp, err + } + return item, resp, nil +} + +// DeleteProjectItemForOrg deletes an item from an organization owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#delete-project-item-for-organization +// +//meta:operation DELETE /orgs/{org}/projectsV2/{project_number}/items/{item_id} +func (s *ProjectsService) DeleteProjectItemForOrg(ctx context.Context, org string, projectNumber, itemID int64) (*Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// ListProjectItemsForUser lists items for a user owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#list-items-for-a-user-owned-project +// +//meta:operation GET /users/{username}/projectsV2/{project_number}/items +func (s *ProjectsService) ListProjectItemsForUser(ctx context.Context, username string, projectNumber int64, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("users/%v/projectsV2/%v/items", username, projectNumber) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + var items []*ProjectV2Item + resp, err := s.client.Do(ctx, req, &items) + if err != nil { + return nil, resp, err + } + return items, resp, nil +} + +// AddProjectItemForUser adds an issue or pull request item to a user owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#add-item-to-user-owned-project +// +//meta:operation POST /users/{username}/projectsV2/{project_number}/items +func (s *ProjectsService) AddProjectItemForUser(ctx context.Context, username string, projectNumber int64, opts *AddProjectItemOptions) (*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("users/%v/projectsV2/%v/items", username, projectNumber) + req, err := s.client.NewRequest("POST", u, opts) + if err != nil { + return nil, nil, err + } + item := new(ProjectV2Item) + resp, err := s.client.Do(ctx, req, item) + if err != nil { + return nil, resp, err + } + return item, resp, nil +} + +// GetProjectItemForUser gets a single item from a user owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#get-an-item-for-a-user-owned-project +// +//meta:operation GET /users/{username}/projectsV2/{project_number}/items/{item_id} +func (s *ProjectsService) GetProjectItemForUser(ctx context.Context, username string, projectNumber, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) + req, err := s.client.NewRequest("GET", u, opts) + if err != nil { + return nil, nil, err + } + item := new(ProjectV2Item) + resp, err := s.client.Do(ctx, req, item) + if err != nil { + return nil, resp, err + } + return item, resp, nil +} + +// UpdateProjectItemForUser updates an item in a user owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#update-project-item-for-user +// +//meta:operation PATCH /users/{username}/projectsV2/{project_number}/items/{item_id} +func (s *ProjectsService) UpdateProjectItemForUser(ctx context.Context, username string, projectNumber, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) + req, err := s.client.NewRequest("PATCH", u, opts) + if err != nil { + return nil, nil, err + } + item := new(ProjectV2Item) + resp, err := s.client.Do(ctx, req, item) + if err != nil { + return nil, resp, err + } + return item, resp, nil +} + +// DeleteProjectItemForUser deletes an item from a user owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#delete-project-item-for-user +// +//meta:operation DELETE /users/{username}/projectsV2/{project_number}/items/{item_id} +func (s *ProjectsService) DeleteProjectItemForUser(ctx context.Context, username string, projectNumber, itemID int64) (*Response, error) { + u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} diff --git a/github/projects_test.go b/github/projects_test.go index 43e7dc3353e..be2b9070017 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -8,6 +8,7 @@ package github import ( "context" "fmt" + "io" "net/http" "testing" ) @@ -88,158 +89,95 @@ func TestProjectsService_GetProjectForOrg(t *testing.T) { }) } -func TestProjectsService_ListUserProjects(t *testing.T) { +// TestProjectsService_ListProjectFieldsForOrg lists fields for an org project. +func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { t.Parallel() client, mux, _ := setup(t) - // Combined handler: supports initial test case and dual before/after scenario. - mux.HandleFunc("/users/u/projectsV2", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/orgs/o/projectsV2/1/fields", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") q := r.URL.Query() - if q.Get("before") == "b" && q.Get("after") == "a" { + if q.Get("before") == "b" && q.Get("after") == "a" { // bypass scenario fmt.Fprint(w, `[]`) return } - testFormValues(t, r, values{"q": "beta", "before": "1", "after": "2", "per_page": "2"}) - fmt.Fprint(w, `[{"id":2,"title":"UProj","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + testFormValues(t, r, values{"after": "2", "before": "1", "q": "text"}) + fmt.Fprint(w, `[ + { + "id": 1, + "node_id": "node_1", + "name": "Status", + "dataType": "single_select", + "url": "https://api.github.com/projects/1/fields/field1", + "options": [ + {"id": 1, "name": "Todo", "color": "blue", "description": "Tasks to be done"}, + {"id": 2, "name": "In Progress", "color": "yellow"} + ], + "created_at": "2011-01-02T15:04:05Z", + "updated_at": "2012-01-02T15:04:05Z" + }, + { + "id": 2, + "node_id": "node_2", + "name": "Priority", + "dataType": "text", + "url": "https://api.github.com/projects/1/fields/field2", + "created_at": "2011-01-02T15:04:05Z", + "updated_at": "2012-01-02T15:04:05Z" + } + ]`) }) - opts := &ListProjectsOptions{Query: "beta", Before: "1", After: "2", PerPage: 2} + opts := &ListProjectsOptions{Query: "text", After: "2", Before: "1"} ctx := context.Background() - var ctxBypass context.Context - projects, _, err := client.Projects.ListProjectsForUser(ctx, "u", opts) + fields, _, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) if err != nil { - t.Fatalf("Projects.ListProjectsForUser returned error: %v", err) + t.Fatalf("Projects.ListProjectFieldsForOrg returned error: %v", err) + } + if len(fields) != 2 { + t.Fatalf("Projects.ListProjectFieldsForOrg returned %d fields, want 2", len(fields)) } - if len(projects) != 1 || projects[0].GetID() != 2 || projects[0].GetTitle() != "UProj" { - t.Fatalf("Projects.ListProjectsForUser returned %+v", projects) + if fields[0].ID == nil || *fields[0].ID != 1 || fields[1].ID == nil || *fields[1].ID != 2 { + t.Fatalf("unexpected field IDs: %+v", fields) } - const methodName = "ListProjectsForUser" + const methodName = "ListProjectFieldsForOrg" testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Projects.ListProjectsForUser(ctx, "\n", opts) + _, _, err = client.Projects.ListProjectFieldsForOrg(ctx, "\n", 1, opts) return err }) - testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.ListProjectsForUser(ctx, "u", opts) + got, resp, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } return resp, err }) - - // still allow both set (no validation enforced) – ensure it does not error - ctxBypass = context.WithValue(context.Background(), BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectsForUser(ctxBypass, "u", &ListProjectsOptions{Before: "b", After: "a"}); err != nil { + ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) + if _, _, err = client.Projects.ListProjectFieldsForOrg(ctxBypass, "o", 1, &ListProjectsOptions{Before: "b", After: "a"}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } -func TestProjectsService_GetProjectForUser(t *testing.T) { - t.Parallel() - client, mux, _ := setup(t) - - mux.HandleFunc("/users/u/projectsV2/2", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - fmt.Fprint(w, `{"id":2,"title":"UProj","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}`) - }) - - ctx := context.Background() - project, _, err := client.Projects.GetProjectForUser(ctx, "u", 2) - if err != nil { - t.Fatalf("Projects.GetProjectForUser returned error: %v", err) - } - if project.GetID() != 2 || project.GetTitle() != "UProj" { - t.Fatalf("Projects.GetProjectForUser returned %+v", project) - } - - const methodName = "GetProjectForUser" - testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.GetProjectForUser(ctx, "u", 2) - if got != nil { - t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) - } - return resp, err - }) -} - -// TestProjectsService_ListProjectsForOrg_pagination clarifies how callers should -// use resp.After to request the next page and resp.Before for previous pages when supported. -func TestProjectsService_ListProjectsForOrg_pagination(t *testing.T) { - t.Parallel() - client, mux, _ := setup(t) - - // First page returns a Link header with rel="next" containing an after cursor (after=cursor2) - mux.HandleFunc("/orgs/o/projectsV2", func(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query() - after := q.Get("after") - before := q.Get("before") - if after == "" && before == "" { - // first request - w.Header().Set("Link", "; rel=\"next\"") - fmt.Fprint(w, `[{"id":1,"title":"P1","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) - return - } - if after == "cursor2" { - // second request simulates a previous link - w.Header().Set("Link", "; rel=\"prev\"") - fmt.Fprint(w, `[{"id":2,"title":"P2","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) - return - } - // unexpected state - http.Error(w, "unexpected query", http.StatusBadRequest) - }) - - ctx := context.Background() - first, resp, err := client.Projects.ListProjectsForOrg(ctx, "o", nil) - if err != nil { - t.Fatalf("first page error: %v", err) - } - if len(first) != 1 || first[0].GetID() != 1 { - t.Fatalf("unexpected first page %+v", first) - } - if resp.After != "cursor2" { - t.Fatalf("expected resp.After=cursor2 got %q", resp.After) - } - - // Use resp.After as opts.After for next page - opts := &ListProjectsOptions{After: resp.After} - second, resp2, err := client.Projects.ListProjectsForOrg(ctx, "o", opts) - if err != nil { - t.Fatalf("second page error: %v", err) - } - if len(second) != 1 || second[0].GetID() != 2 { - t.Fatalf("unexpected second page %+v", second) - } - if resp2.Before != "cursor2" { - t.Fatalf("expected resp2.Before=cursor2 got %q", resp2.Before) - } -} - -// TestProjectsService_ListProjectsForUser_pagination mirrors the org pagination test -// but exercises the user endpoint to ensure Before/After cursor handling works identically. func TestProjectsService_ListProjectsForUser_pagination(t *testing.T) { t.Parallel() client, mux, _ := setup(t) - mux.HandleFunc("/users/u/projectsV2", func(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() after := q.Get("after") before := q.Get("before") - if after == "" && before == "" { // first page + if after == "" && before == "" { w.Header().Set("Link", "; rel=\"next\"") fmt.Fprint(w, `[{"id":10,"title":"UP1","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) return } - if after == "ucursor2" { // second page provides prev + if after == "ucursor2" { w.Header().Set("Link", "; rel=\"prev\"") fmt.Fprint(w, `[{"id":11,"title":"UP2","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) return } http.Error(w, "unexpected query", http.StatusBadRequest) }) - ctx := context.Background() first, resp, err := client.Projects.ListProjectsForUser(ctx, "u", nil) if err != nil { @@ -251,7 +189,6 @@ func TestProjectsService_ListProjectsForUser_pagination(t *testing.T) { if resp.After != "ucursor2" { t.Fatalf("expected resp.After=ucursor2 got %q", resp.After) } - opts := &ListProjectsOptions{After: resp.After} second, resp2, err := client.Projects.ListProjectsForUser(ctx, "u", opts) if err != nil { @@ -265,111 +202,6 @@ func TestProjectsService_ListProjectsForUser_pagination(t *testing.T) { } } -func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { - t.Parallel() - client, mux, _ := setup(t) - - // Combined handler: supports initial test case and dual before/after validation scenario. - mux.HandleFunc("/orgs/o/projectsV2/1/fields", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - q := r.URL.Query() - if q.Get("before") == "b" && q.Get("after") == "a" { - fmt.Fprint(w, `[]`) - return - } - // default expectation for main part of test - testFormValues(t, r, values{"q": "text", "after": "2", "before": "1"}) - fmt.Fprint(w, `[ - { - "id": "field1", - "node_id": "node_1", - "name": "Status", - "dataType": "single_select", - "url": "https://api.github.com/projects/1/fields/field1", - "options": [ - { - "id": "option1", - "name": "Todo", - "color": "blue", - "description": "Tasks to be done" - }, - { - "id": "option2", - "name": "In Progress", - "color": "yellow" - } - ], - "created_at": "2011-01-02T15:04:05Z", - "updated_at": "2012-01-02T15:04:05Z" - }, - { - "id": "field2", - "node_id": "node_2", - "name": "Priority", - "dataType": "text", - "url": "https://api.github.com/projects/1/fields/field2", - "created_at": "2011-01-02T15:04:05Z", - "updated_at": "2012-01-02T15:04:05Z" - } - ]`) - }) - - opts := &ListProjectsOptions{Query: "text", After: "2", Before: "1"} - ctx := context.Background() - fields, _, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) - if err != nil { - t.Fatalf("Projects.ListProjectFieldsForOrg returned error: %v", err) - } - - if len(fields) != 2 { - t.Fatalf("Projects.ListProjectFieldsForOrg returned %d fields, want 2", len(fields)) - } - - // Validate first field (with options) - field1 := fields[0] - if field1.ID != "field1" || field1.Name != "Status" || field1.DataType != "single_select" { - t.Errorf("First field: got ID=%s, Name=%s, DataType=%s; want field1, Status, single_select", - field1.ID, field1.Name, field1.DataType) - } - if len(field1.Options) != 2 { - t.Errorf("First field options: got %d, want 2", len(field1.Options)) - } - if field1.Options[0].Name != "Todo" || field1.Options[1].Name != "In Progress" { - t.Errorf("First field option names: got %s, %s; want Todo, In Progress", - field1.Options[0].Name, field1.Options[1].Name) - } - - // Validate second field (without options) - field2 := fields[1] - if field2.ID != "field2" || field2.Name != "Priority" || field2.DataType != "text" { - t.Errorf("Second field: got ID=%s, Name=%s, DataType=%s; want field2, Priority, text", - field2.ID, field2.Name, field2.DataType) - } - if len(field2.Options) != 0 { - t.Errorf("Second field options: got %d, want 0", len(field2.Options)) - } - - const methodName = "ListProjectFieldsForOrg" - testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Projects.ListProjectFieldsForOrg(ctx, "\n", 1, opts) - return err - }) - - testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) - if got != nil { - t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) - } - return resp, err - }) - - // still allow both set (no validation enforced) – ensure it does not error - ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectFieldsForOrg(ctxBypass, "o", 1, &ListProjectsOptions{Before: "b", After: "a"}); err != nil { - t.Fatalf("unexpected error when both before/after set: %v", err) - } -} - func TestProjectsService_ListProjectFieldsForOrg_pagination(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -382,13 +214,13 @@ func TestProjectsService_ListProjectFieldsForOrg_pagination(t *testing.T) { if after == "" && before == "" { // first request w.Header().Set("Link", "; rel=\"next\"") - fmt.Fprint(w, `[{"id":"field1","name":"Status","dataType":"single_select","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + fmt.Fprint(w, `[{"id":1,"name":"Status","dataType":"single_select","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) return } if after == "cursor2" { // second request simulates a previous link w.Header().Set("Link", "; rel=\"prev\"") - fmt.Fprint(w, `[{"id":"field2","name":"Priority","dataType":"text","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + fmt.Fprint(w, `[{"id":2,"name":"Priority","dataType":"text","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) return } // unexpected state @@ -400,7 +232,7 @@ func TestProjectsService_ListProjectFieldsForOrg_pagination(t *testing.T) { if err != nil { t.Fatalf("first page error: %v", err) } - if len(first) != 1 || first[0].ID != "field1" { + if len(first) != 1 || first[0].ID == nil || *first[0].ID != 1 { t.Fatalf("unexpected first page %+v", first) } if resp.After != "cursor2" { @@ -413,7 +245,7 @@ func TestProjectsService_ListProjectFieldsForOrg_pagination(t *testing.T) { if err != nil { t.Fatalf("second page error: %v", err) } - if len(second) != 1 || second[0].ID != "field2" { + if len(second) != 1 || second[0].ID == nil || *second[0].ID != 2 { t.Fatalf("unexpected second page %+v", second) } if resp2.Before != "cursor2" { @@ -454,14 +286,14 @@ func TestProjectV2Field_Marshal(t *testing.T) { testJSONMarshal(t, &ProjectV2FieldOption{}, "{}") field := &ProjectV2Field{ - ID: "field1", + ID: Ptr(int64(2)), NodeID: "node_1", Name: "Status", DataType: "single_select", URL: "https://api.github.com/projects/1/fields/field1", Options: []*ProjectV2FieldOption{ { - ID: "option1", + ID: Ptr(int64(1)), Name: "Todo", Color: "blue", Description: "Tasks to be done", @@ -472,14 +304,14 @@ func TestProjectV2Field_Marshal(t *testing.T) { } want := `{ - "id": "field1", + "id": 2, "node_id": "node_1", "name": "Status", "dataType": "single_select", "url": "https://api.github.com/projects/1/fields/field1", "options": [ { - "id": "option1", + "id": 1, "name": "Todo", "color": "blue", "description": "Tasks to be done" @@ -491,3 +323,220 @@ func TestProjectV2Field_Marshal(t *testing.T) { testJSONMarshal(t, field, want) } + +func TestProjectsService_ListProjectItemsForOrg(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/projectsV2/1/items", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + q := r.URL.Query() + if q.Get("before") == "b" && q.Get("after") == "a" { // bypass scenario + fmt.Fprint(w, `[]`) + return + } + testFormValues(t, r, values{"after": "2", "before": "1", "per_page": "50", "fields": "10,11", "q": "status:open"}) + fmt.Fprint(w, `[{"id":17,"node_id":"PVTI_node"}]`) + }) + + opts := &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{After: "2", Before: "1", PerPage: 50, Query: "status:open"}, Fields: []int64{10, 11}} + ctx := context.Background() + items, _, err := client.Projects.ListProjectItemsForOrg(ctx, "o", 1, opts) + if err != nil { + t.Fatalf("Projects.ListProjectItemsForOrg returned error: %v", err) + } + if len(items) != 1 || items[0].GetID() != 17 { + t.Fatalf("Projects.ListProjectItemsForOrg returned %+v", items) + } + + const methodName = "ListProjectItemsForOrg" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Projects.ListProjectItemsForOrg(ctx, "\n", 1, opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.ListProjectItemsForOrg(ctx, "o", 1, opts) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) + + ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) + if _, _, err = client.Projects.ListProjectItemsForOrg(ctxBypass, "o", 1, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{Before: "b", After: "a"}}); err != nil { + t.Fatalf("unexpected error when both before/after set: %v", err) + } +} + +func TestProjectsService_AddProjectItemForOrg(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/projectsV2/1/items", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + b, _ := io.ReadAll(r.Body) + body := string(b) + if body != `{"type":"Issue","id":99}`+"\n" { // encoder adds newline + t.Fatalf("unexpected body: %s", body) + } + fmt.Fprint(w, `{"id":99,"node_id":"PVTI_new"}`) + }) + + ctx := context.Background() + item, _, err := client.Projects.AddProjectItemForOrg(ctx, "o", 1, &AddProjectItemOptions{Type: "Issue", ID: 99}) + if err != nil { + t.Fatalf("Projects.AddProjectItemForOrg returned error: %v", err) + } + if item.GetID() != 99 { + t.Fatalf("unexpected item: %+v", item) + } +} + +func TestProjectsService_GetProjectItemForOrg(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":17,"node_id":"PVTI_node"}`) + }) + ctx := context.Background() + opts := &GetProjectItemOptions{} + item, _, err := client.Projects.GetProjectItemForOrg(ctx, "o", 1, 17, opts) + if err != nil { + t.Fatalf("GetProjectItemForOrg error: %v", err) + } + if item.GetID() != 17 { + t.Fatalf("unexpected item: %+v", item) + } +} + +func TestProjectsService_UpdateProjectItemForOrg(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + b, _ := io.ReadAll(r.Body) + body := string(b) + if body != `{"archived":true}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } + fmt.Fprint(w, `{"id":17}`) + }) + archived := true + ctx := context.Background() + item, _, err := client.Projects.UpdateProjectItemForOrg(ctx, "o", 1, 17, &UpdateProjectItemOptions{Archived: &archived}) + if err != nil { + t.Fatalf("UpdateProjectItemForOrg error: %v", err) + } + if item.GetID() != 17 { + t.Fatalf("unexpected item: %+v", item) + } +} + +func TestProjectsService_DeleteProjectItemForOrg(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + ctx := context.Background() + if _, err := client.Projects.DeleteProjectItemForOrg(ctx, "o", 1, 17); err != nil { + t.Fatalf("DeleteProjectItemForOrg error: %v", err) + } +} + +func TestProjectsService_ListProjectItemsForUser(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"per_page": "20", "q": "type:issue"}) + fmt.Fprint(w, `[{"id":7,"node_id":"PVTI_user"}]`) + }) + ctx := context.Background() + items, _, err := client.Projects.ListProjectItemsForUser(ctx, "u", 2, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{PerPage: 20, Query: "type:issue"}}) + if err != nil { + t.Fatalf("ListProjectItemsForUser error: %v", err) + } + if len(items) != 1 || items[0].GetID() != 7 { + t.Fatalf("unexpected items: %+v", items) + } +} + +func TestProjectsService_AddProjectItemForUser(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + b, _ := io.ReadAll(r.Body) + body := string(b) + if body != `{"type":"PullRequest","id":123}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } + fmt.Fprint(w, `{"id":123,"node_id":"PVTI_new_user"}`) + }) + ctx := context.Background() + item, _, err := client.Projects.AddProjectItemForUser(ctx, "u", 2, &AddProjectItemOptions{Type: "PullRequest", ID: 123}) + if err != nil { + t.Fatalf("AddProjectItemForUser error: %v", err) + } + if item.GetID() != 123 { + t.Fatalf("unexpected item: %+v", item) + } +} + +func TestProjectsService_GetProjectItemForUser(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":55,"node_id":"PVTI_user_item"}`) + }) + ctx := context.Background() + opts := &GetProjectItemOptions{} + item, _, err := client.Projects.GetProjectItemForUser(ctx, "u", 2, 55, opts) + if err != nil { + t.Fatalf("GetProjectItemForUser error: %v", err) + } + if item.GetID() != 55 { + t.Fatalf("unexpected item: %+v", item) + } +} + +func TestProjectsService_UpdateProjectItemForUser(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + b, _ := io.ReadAll(r.Body) + body := string(b) + if body != `{"archived":false}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } + fmt.Fprint(w, `{"id":55}`) + }) + archived := false + ctx := context.Background() + item, _, err := client.Projects.UpdateProjectItemForUser(ctx, "u", 2, 55, &UpdateProjectItemOptions{Archived: &archived}) + if err != nil { + t.Fatalf("UpdateProjectItemForUser error: %v", err) + } + if item.GetID() != 55 { + t.Fatalf("unexpected item: %+v", item) + } +} + +func TestProjectsService_DeleteProjectItemForUser(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + ctx := context.Background() + if _, err := client.Projects.DeleteProjectItemForUser(ctx, "u", 2, 55); err != nil { + t.Fatalf("DeleteProjectItemForUser error: %v", err) + } +} From a4b1014c1d2eca39cd425480ad5b44f644f62246 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 24 Sep 2025 12:39:19 +0200 Subject: [PATCH 17/39] Remove comments --- github/projects_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/github/projects_test.go b/github/projects_test.go index be2b9070017..1c4cd627b9e 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -89,7 +89,6 @@ func TestProjectsService_GetProjectForOrg(t *testing.T) { }) } -// TestProjectsService_ListProjectFieldsForOrg lists fields for an org project. func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { t.Parallel() client, mux, _ := setup(t) From 4e935dd6e67affd6b77107432f698c90bac38c53 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 24 Sep 2025 18:29:21 +0200 Subject: [PATCH 18/39] Add tests --- github/projects_test.go | 74 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/github/projects_test.go b/github/projects_test.go index 1c4cd627b9e..6659949b478 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -89,6 +89,34 @@ func TestProjectsService_GetProjectForOrg(t *testing.T) { }) } +func TestProjectsService_GetProjectForUser(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/users/u/projectsV2/3", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":3,"title":"UserProj","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}`) + }) + + ctx := context.Background() + project, _, err := client.Projects.GetProjectForUser(ctx, "u", 3) + if err != nil { + t.Fatalf("Projects.GetProjectForUser returned error: %v", err) + } + if project.GetID() != 3 || project.GetTitle() != "UserProj" { + t.Fatalf("Projects.GetProjectForUser returned %+v", project) + } + + const methodName = "GetProjectForUser" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.GetProjectForUser(ctx, "u", 3) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -252,6 +280,52 @@ func TestProjectsService_ListProjectFieldsForOrg_pagination(t *testing.T) { } } +func TestProjectsService_ListProjectsForOrg_pagination(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/projectsV2", func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + after := q.Get("after") + before := q.Get("before") + if after == "" && before == "" { + w.Header().Set("Link", "; rel=\"next\"") + fmt.Fprint(w, `[{"id":20,"title":"OP1","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + return + } + if after == "ocursor2" { + w.Header().Set("Link", "; rel=\"prev\"") + fmt.Fprint(w, `[{"id":21,"title":"OP2","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + return + } + http.Error(w, "unexpected query", http.StatusBadRequest) + }) + + ctx := context.Background() + first, resp, err := client.Projects.ListProjectsForOrg(ctx, "o", nil) + if err != nil { + t.Fatalf("first page error: %v", err) + } + if len(first) != 1 || first[0].GetID() != 20 { + t.Fatalf("unexpected first page %+v", first) + } + if resp.After != "ocursor2" { + t.Fatalf("expected resp.After=ocursor2 got %q", resp.After) + } + + opts := &ListProjectsOptions{After: resp.After} + second, resp2, err := client.Projects.ListProjectsForOrg(ctx, "o", opts) + if err != nil { + t.Fatalf("second page error: %v", err) + } + if len(second) != 1 || second[0].GetID() != 21 { + t.Fatalf("unexpected second page %+v", second) + } + if resp2.Before != "ocursor2" { + t.Fatalf("expected resp2.Before=ocursor2 got %q", resp2.Before) + } +} + // Marshal test ensures V2 fields marshal correctly. func TestProjectV2_Marshal(t *testing.T) { t.Parallel() From 0c5356d08b9b16a591925f05cf6d6caa387e2e87 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 24 Sep 2025 19:49:07 +0200 Subject: [PATCH 19/39] More tests --- github/projects_test.go | 184 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/github/projects_test.go b/github/projects_test.go index 6659949b478..91427ef1b4e 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -229,6 +229,29 @@ func TestProjectsService_ListProjectsForUser_pagination(t *testing.T) { } } +func TestProjectsService_ListProjectsForUser_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[]`) + }) + ctx := context.Background() + const methodName = "ListProjectsForUser" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.ListProjectsForUser(ctx, "u", nil) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) + // bad options (bad username) should error + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Projects.ListProjectsForUser(ctx, "\n", nil) + return err + }) +} + func TestProjectsService_ListProjectFieldsForOrg_pagination(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -466,6 +489,25 @@ func TestProjectsService_AddProjectItemForOrg(t *testing.T) { } } +func TestProjectsService_AddProjectItemForOrg_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/orgs/o/projectsV2/1/items", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"id":1}`) + }) + ctx := context.Background() + const methodName = "AddProjectItemForOrg" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.AddProjectItemForOrg(ctx, "o", 1, &AddProjectItemOptions{Type: "Issue", ID: 1}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + func TestProjectsService_GetProjectItemForOrg(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -484,6 +526,24 @@ func TestProjectsService_GetProjectItemForOrg(t *testing.T) { } } +func TestProjectsService_GetProjectItemForOrg_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":17}`) + }) + ctx := context.Background() + const methodName = "GetProjectItemForOrg" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.GetProjectItemForOrg(ctx, "o", 1, 17, &GetProjectItemOptions{}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + func TestProjectsService_UpdateProjectItemForOrg(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -507,6 +567,25 @@ func TestProjectsService_UpdateProjectItemForOrg(t *testing.T) { } } +func TestProjectsService_UpdateProjectItemForOrg_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + fmt.Fprint(w, `{"id":17}`) + }) + archived := true + ctx := context.Background() + const methodName = "UpdateProjectItemForOrg" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.UpdateProjectItemForOrg(ctx, "o", 1, 17, &UpdateProjectItemOptions{Archived: &archived}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + func TestProjectsService_DeleteProjectItemForOrg(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -520,6 +599,20 @@ func TestProjectsService_DeleteProjectItemForOrg(t *testing.T) { } } +func TestProjectsService_DeleteProjectItemForOrg_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + ctx := context.Background() + const methodName = "DeleteProjectItemForOrg" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.Projects.DeleteProjectItemForOrg(ctx, "o", 1, 17) + }) +} + func TestProjectsService_ListProjectItemsForUser(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -538,6 +631,28 @@ func TestProjectsService_ListProjectItemsForUser(t *testing.T) { } } +func TestProjectsService_ListProjectItemsForUser_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[]`) + }) + ctx := context.Background() + const methodName = "ListProjectItemsForUser" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.ListProjectItemsForUser(ctx, "u", 2, nil) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Projects.ListProjectItemsForUser(ctx, "\n", 2, nil) + return err + }) +} + func TestProjectsService_AddProjectItemForUser(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -560,6 +675,24 @@ func TestProjectsService_AddProjectItemForUser(t *testing.T) { } } +func TestProjectsService_AddProjectItemForUser_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, `{"id":5}`) + }) + ctx := context.Background() + const methodName = "AddProjectItemForUser" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.AddProjectItemForUser(ctx, "u", 2, &AddProjectItemOptions{Type: "Issue", ID: 5}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + func TestProjectsService_GetProjectItemForUser(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -578,6 +711,24 @@ func TestProjectsService_GetProjectItemForUser(t *testing.T) { } } +func TestProjectsService_GetProjectItemForUser_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":55}`) + }) + ctx := context.Background() + const methodName = "GetProjectItemForUser" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.GetProjectItemForUser(ctx, "u", 2, 55, &GetProjectItemOptions{}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + func TestProjectsService_UpdateProjectItemForUser(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -601,6 +752,25 @@ func TestProjectsService_UpdateProjectItemForUser(t *testing.T) { } } +func TestProjectsService_UpdateProjectItemForUser_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + fmt.Fprint(w, `{"id":55}`) + }) + archived := false + ctx := context.Background() + const methodName = "UpdateProjectItemForUser" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.UpdateProjectItemForUser(ctx, "u", 2, 55, &UpdateProjectItemOptions{Archived: &archived}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + func TestProjectsService_DeleteProjectItemForUser(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -613,3 +783,17 @@ func TestProjectsService_DeleteProjectItemForUser(t *testing.T) { t.Fatalf("DeleteProjectItemForUser error: %v", err) } } + +func TestProjectsService_DeleteProjectItemForUser_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + ctx := context.Background() + const methodName = "DeleteProjectItemForUser" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.Projects.DeleteProjectItemForUser(ctx, "u", 2, 55) + }) +} From 551885facf9a3673af74691aa71089ed89ededf9 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Mon, 27 Oct 2025 21:33:55 +0000 Subject: [PATCH 20/39] fixes all conflicts between upstream/master and add-project-items branch --- github/github-accessors.go | 8 -------- github/github-accessors_test.go | 4 ++-- github/projects.go | 29 +++++++++++++---------------- github/projects_test.go | 12 ++++++------ 4 files changed, 21 insertions(+), 32 deletions(-) diff --git a/github/github-accessors.go b/github/github-accessors.go index 9e53f471f73..7af3fde62e7 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -15526,14 +15526,6 @@ func (n *NetworkSettingsResource) GetSubnetID() string { return *n.SubnetID } -// GetID returns the ID field if it's non-nil, zero value otherwise. -func (n *NewProjectV2Field) GetID() int64 { - if n == nil || n.ID == nil { - return 0 - } - return *n.ID -} - // GetBase returns the Base field if it's non-nil, zero value otherwise. func (n *NewPullRequest) GetBase() string { if n == nil || n.Base == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 904fa1c7bc1..1ffcf5d34c0 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -20141,9 +20141,9 @@ func TestNetworkSettingsResource_GetSubnetID(tt *testing.T) { func TestNewProjectV2Field_GetID(tt *testing.T) { tt.Parallel() var zeroValue int64 - n := &NewProjectV2Field{ID: &zeroValue} + n := &ProjectV2Field{ID: &zeroValue} n.GetID() - n = &NewProjectV2Field{} + n = &ProjectV2Field{} n.GetID() n = nil n.GetID() diff --git a/github/projects.go b/github/projects.go index a6b84de9b1b..e829d132172 100644 --- a/github/projects.go +++ b/github/projects.go @@ -79,13 +79,10 @@ type ListProjectsOptions struct { // // GitHub API docs: https://docs.github.com/rest/projects/fields type ProjectV2FieldOption struct { - ID string `json:"id,omitempty"` - // The display name of the option. - Name string `json:"name,omitempty"` - // The color associated with this option (e.g., "blue", "red"). - Color string `json:"color,omitempty"` - // An optional description for this option. - Description string `json:"description,omitempty"` + ID *int64 `json:"id,omitempty"` // The unique identifier for this option. + Name string `json:"name,omitempty"` // The display name of the option. + Color string `json:"color,omitempty"` // The color associated with this option (e.g., "blue", "red"). + Description string `json:"description,omitempty"` // An optional description for this option. } // ProjectV2Field represents a field in a GitHub Projects V2 project. @@ -93,14 +90,14 @@ type ProjectV2FieldOption struct { // // GitHub API docs: https://docs.github.com/rest/projects/fields type ProjectV2Field struct { - ID *int64 `json:"id,omitempty"` - NodeID string `json:"node_id,omitempty"` - Name string `json:"name,omitempty"` - DataType string `json:"dataType,omitempty"` - URL string `json:"url,omitempty"` - Options []*any `json:"options,omitempty"` - CreatedAt *Timestamp `json:"created_at,omitempty"` - UpdatedAt *Timestamp `json:"updated_at,omitempty"` + ID *int64 `json:"id,omitempty"` + NodeID string `json:"node_id,omitempty"` + Name string `json:"name,omitempty"` + DataType string `json:"dataType,omitempty"` + URL string `json:"url,omitempty"` + Options []*ProjectV2FieldOption `json:"options,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` } // ListProjectsForOrg lists Projects V2 for an organization. @@ -248,7 +245,7 @@ type UpdateProjectItemOptions struct { // Archived indicates whether the item should be archived (true) or unarchived (false). Archived *bool `json:"archived,omitempty"` // Fields allows updating field values for the item. Each entry supplies a field ID and a value. - Fields []*NewProjectV2Field `json:"fields,omitempty"` + Fields []*ProjectV2Field `json:"fields,omitempty"` } // ListProjectItemsForOrg lists items for an organization owned project. diff --git a/github/projects_test.go b/github/projects_test.go index d6b3ddca78d..d0151cbd350 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -203,7 +203,7 @@ func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { ]`) }) - opts := &ListProjectsOptions{Query: "text", After: "2", Before: "1"} + opts := &ListProjectsOptions{Query: "text", ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: "2", Before: "1"}} ctx := context.Background() fields, _, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) if err != nil { @@ -229,7 +229,7 @@ func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { return resp, err }) ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectFieldsForOrg(ctxBypass, "o", 1, &ListProjectsOptions{Before: "b", After: "a"}); err != nil { + if _, _, err = client.Projects.ListProjectFieldsForOrg(ctxBypass, "o", 1, &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: "b", After: "a"}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } @@ -384,7 +384,7 @@ func TestProjectsService_ListProjectsForOrg_pagination(t *testing.T) { t.Fatalf("expected resp.After=ocursor2 got %q", resp.After) } - opts := &ListProjectsOptions{After: resp.After} + opts := &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: resp.After}} second, resp2, err := client.Projects.ListProjectsForOrg(ctx, "o", opts) if err != nil { t.Fatalf("second page error: %v", err) @@ -483,7 +483,7 @@ func TestProjectsService_ListProjectItemsForOrg(t *testing.T) { fmt.Fprint(w, `[{"id":17,"node_id":"PVTI_node"}]`) }) - opts := &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{After: "2", Before: "1", PerPage: 50, Query: "status:open"}, Fields: []int64{10, 11}} + opts := &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: "2", Before: "1", PerPage: 50}, Query: "status:open"}, Fields: []int64{10, 11}} ctx := context.Background() items, _, err := client.Projects.ListProjectItemsForOrg(ctx, "o", 1, opts) if err != nil { @@ -508,7 +508,7 @@ func TestProjectsService_ListProjectItemsForOrg(t *testing.T) { }) ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectItemsForOrg(ctxBypass, "o", 1, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{Before: "b", After: "a"}}); err != nil { + if _, _, err = client.Projects.ListProjectItemsForOrg(ctxBypass, "o", 1, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: "b", After: "a"}}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } @@ -670,7 +670,7 @@ func TestProjectsService_ListProjectItemsForUser(t *testing.T) { fmt.Fprint(w, `[{"id":7,"node_id":"PVTI_user"}]`) }) ctx := context.Background() - items, _, err := client.Projects.ListProjectItemsForUser(ctx, "u", 2, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{PerPage: 20, Query: "type:issue"}}) + items, _, err := client.Projects.ListProjectItemsForUser(ctx, "u", 2, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{PerPage: 20}, Query: "type:issue"}}) if err != nil { t.Fatalf("ListProjectItemsForUser error: %v", err) } From f4685c8df50d8bb1830312dcf1bcd5b33bcfea9c Mon Sep 17 00:00:00 2001 From: Jonathan Date: Mon, 27 Oct 2025 21:48:30 +0000 Subject: [PATCH 21/39] fixes additional conflicts between upstream and fork with openapi_operations.yaml --- openapi_operations.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openapi_operations.yaml b/openapi_operations.yaml index 2a43f1857c9..21db61d04b4 100644 --- a/openapi_operations.yaml +++ b/openapi_operations.yaml @@ -27,11 +27,7 @@ operation_overrides: documentation_url: https://docs.github.com/rest/pages/pages#request-a-github-pages-build - name: GET /repos/{owner}/{repo}/pages/builds/{build_id} documentation_url: https://docs.github.com/rest/pages/pages#get-github-pages-build -<<<<<<< HEAD -openapi_commit: 44dd20c107ca46d1dbcaf4aa522d9039e96e631c -======= openapi_commit: dee4dc2b1ab40c75c2ae562e4300503b632f4424 ->>>>>>> upstream/master openapi_operations: - name: GET / documentation_url: https://docs.github.com/rest/meta/meta#github-api-root From 8f6f69971359759b33e015a0edc6a9fdb6a2d5b8 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Mon, 27 Oct 2025 23:01:06 +0000 Subject: [PATCH 22/39] adds missing endpoint for users project fields --- github/projects.go | 25 +++++++++++++++ github/projects_test.go | 69 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/github/projects.go b/github/projects.go index e829d132172..2fedcabc207 100644 --- a/github/projects.go +++ b/github/projects.go @@ -214,6 +214,31 @@ func (s *ProjectsService) ListProjectFieldsForOrg(ctx context.Context, org strin return fields, resp, nil } +// ListProjectFieldsForUser lists Projects V2 for a user. +// +// GitHub API docs: https://docs.github.com/rest/projects/fields#list-project-fields-for-user +// +//meta:operation GET /users/{user}/projectsV2/{project_number}/fields +func (s *ProjectsService) ListProjectFieldsForUser(ctx context.Context, user string, projectNumber int, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { + u := fmt.Sprintf("users/%v/projectsV2/%v/fields", user, projectNumber) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var fields []*ProjectV2Field + resp, err := s.client.Do(ctx, req, &fields) + if err != nil { + return nil, resp, err + } + return fields, resp, nil +} + // ListProjectItemsOptions specifies optional parameters when listing project items. // Note: Pagination uses before/after cursor-style pagination similar to ListProjectsOptions. // "Fields" can be used to restrict which field values are returned (by their numeric IDs). diff --git a/github/projects_test.go b/github/projects_test.go index d0151cbd350..c68b6aea8ca 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -234,6 +234,75 @@ func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { } } +func TestProjectsService_ListProjectFieldsForUser(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/users/u/projectsV2/1/fields", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + q := r.URL.Query() + if q.Get("before") == "b" && q.Get("after") == "a" { // bypass scenario + fmt.Fprint(w, `[]`) + return + } + testFormValues(t, r, values{"after": "2", "before": "1", "q": "text"}) + fmt.Fprint(w, `[ + { + "id": 1, + "node_id": "node_1", + "name": "Status", + "dataType": "single_select", + "url": "https://api.github.com/projects/1/fields/field1", + "options": [ + {"id": 1, "name": "Todo", "color": "blue", "description": "Tasks to be done"}, + {"id": 2, "name": "In Progress", "color": "yellow"} + ], + "created_at": "2011-01-02T15:04:05Z", + "updated_at": "2012-01-02T15:04:05Z" + }, + { + "id": 2, + "node_id": "node_2", + "name": "Priority", + "dataType": "text", + "url": "https://api.github.com/projects/1/fields/field2", + "created_at": "2011-01-02T15:04:05Z", + "updated_at": "2012-01-02T15:04:05Z" + } + ]`) + }) + + opts := &ListProjectsOptions{Query: "text", ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: "2", Before: "1"}} + ctx := context.Background() + fields, _, err := client.Projects.ListProjectFieldsForUser(ctx, "u", 1, opts) + if err != nil { + t.Fatalf("Projects.ListProjectFieldsForUser returned error: %v", err) + } + if len(fields) != 2 { + t.Fatalf("Projects.ListProjectFieldsForUser returned %d fields, want 2", len(fields)) + } + if fields[0].ID == nil || *fields[0].ID != 1 || fields[1].ID == nil || *fields[1].ID != 2 { + t.Fatalf("unexpected field IDs: %+v", fields) + } + + const methodName = "ListProjectFieldsForUser" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Projects.ListProjectFieldsForUser(ctx, "\n", 1, opts) + return err + }) + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.ListProjectFieldsForUser(ctx, "u", 1, opts) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) + ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) + if _, _, err = client.Projects.ListProjectFieldsForUser(ctxBypass, "u", 1, &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: "b", After: "a"}}); err != nil { + t.Fatalf("unexpected error when both before/after set: %v", err) + } +} + func TestProjectsService_ListProjectsForUser_pagination(t *testing.T) { t.Parallel() client, mux, _ := setup(t) From 9d94d0e7f262aa33b8fe8ef13dd54924defd7b60 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 28 Oct 2025 16:45:04 +0000 Subject: [PATCH 23/39] ensures projectNumber is in64 across the board --- github/event_types_test.go | 2 +- github/github-accessors.go | 2 +- github/github-accessors_test.go | 2 +- github/github-stringify_test.go | 2 +- github/projects.go | 18 +++++++++--------- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/github/event_types_test.go b/github/event_types_test.go index 9ad223dca69..98d38456e99 100644 --- a/github/event_types_test.go +++ b/github/event_types_test.go @@ -15673,7 +15673,7 @@ func TestProjectV2Event_Marshal(t *testing.T) { CreatedAt: &Timestamp{referenceTime}, UpdatedAt: &Timestamp{referenceTime}, DeletedAt: &Timestamp{referenceTime}, - Number: Ptr(1), + Number: Ptr(int64(1)), ShortDescription: Ptr("sd"), DeletedBy: &User{ Login: Ptr("l"), diff --git a/github/github-accessors.go b/github/github-accessors.go index 7af3fde62e7..7f43265b32a 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -18991,7 +18991,7 @@ func (p *ProjectV2) GetNodeID() string { } // GetNumber returns the Number field if it's non-nil, zero value otherwise. -func (p *ProjectV2) GetNumber() int { +func (p *ProjectV2) GetNumber() int64 { if p == nil || p.Number == nil { return 0 } diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 1ffcf5d34c0..3d1b53f7830 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -24677,7 +24677,7 @@ func TestProjectV2_GetNodeID(tt *testing.T) { func TestProjectV2_GetNumber(tt *testing.T) { tt.Parallel() - var zeroValue int + var zeroValue int64 p := &ProjectV2{Number: &zeroValue} p.GetNumber() p = &ProjectV2{} diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go index c068952d51c..5656a81b5d4 100644 --- a/github/github-stringify_test.go +++ b/github/github-stringify_test.go @@ -1554,7 +1554,7 @@ func TestProjectV2_String(t *testing.T) { CreatedAt: &Timestamp{}, UpdatedAt: &Timestamp{}, DeletedAt: &Timestamp{}, - Number: Ptr(0), + Number: Ptr(int64(0)), ShortDescription: Ptr(""), DeletedBy: &User{}, URL: Ptr(""), diff --git a/github/projects.go b/github/projects.go index 2fedcabc207..6681d0e73bd 100644 --- a/github/projects.go +++ b/github/projects.go @@ -29,7 +29,7 @@ type ProjectV2 struct { CreatedAt *Timestamp `json:"created_at,omitempty"` UpdatedAt *Timestamp `json:"updated_at,omitempty"` DeletedAt *Timestamp `json:"deleted_at,omitempty"` - Number *int `json:"number,omitempty"` + Number *int64 `json:"number,omitempty"` ShortDescription *string `json:"short_description,omitempty"` DeletedBy *User `json:"deleted_by,omitempty"` @@ -174,7 +174,7 @@ func (s *ProjectsService) ListProjectsForUser(ctx context.Context, username stri // GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-user // //meta:operation GET /users/{username}/projectsV2/{project_number} -func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string, projectNumber int64) (*ProjectV2, *Response, error) { +func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string, projectNumber int) (*ProjectV2, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v", username, projectNumber) req, err := s.client.NewRequest("GET", u, nil) if err != nil { @@ -194,7 +194,7 @@ func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string // GitHub API docs: https://docs.github.com/rest/projects/fields#list-project-fields-for-organization // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/fields -func (s *ProjectsService) ListProjectFieldsForOrg(ctx context.Context, org string, projectNumber int, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { +func (s *ProjectsService) ListProjectFieldsForOrg(ctx context.Context, org string, projectNumber int64, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/fields", org, projectNumber) u, err := addOptions(u, opts) if err != nil { @@ -219,7 +219,7 @@ func (s *ProjectsService) ListProjectFieldsForOrg(ctx context.Context, org strin // GitHub API docs: https://docs.github.com/rest/projects/fields#list-project-fields-for-user // //meta:operation GET /users/{user}/projectsV2/{project_number}/fields -func (s *ProjectsService) ListProjectFieldsForUser(ctx context.Context, user string, projectNumber int, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { +func (s *ProjectsService) ListProjectFieldsForUser(ctx context.Context, user string, projectNumber int64, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/fields", user, projectNumber) u, err := addOptions(u, opts) if err != nil { @@ -323,7 +323,7 @@ func (s *ProjectsService) AddProjectItemForOrg(ctx context.Context, org string, // GitHub API docs: https://docs.github.com/rest/projects/items#get-an-item-for-an-organization-owned-project // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) GetProjectItemForOrg(ctx context.Context, org string, projectNumber, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) GetProjectItemForOrg(ctx context.Context, org string, projectNumber int64, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) req, err := s.client.NewRequest("GET", u, opts) if err != nil { @@ -342,7 +342,7 @@ func (s *ProjectsService) GetProjectItemForOrg(ctx context.Context, org string, // GitHub API docs: https://docs.github.com/rest/projects/items#update-project-item-for-organization // //meta:operation PATCH /orgs/{org}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) UpdateProjectItemForOrg(ctx context.Context, org string, projectNumber, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) UpdateProjectItemForOrg(ctx context.Context, org string, projectNumber int64, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) req, err := s.client.NewRequest("PATCH", u, opts) if err != nil { @@ -417,7 +417,7 @@ func (s *ProjectsService) AddProjectItemForUser(ctx context.Context, username st // GitHub API docs: https://docs.github.com/rest/projects/items#get-an-item-for-a-user-owned-project // //meta:operation GET /users/{username}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) GetProjectItemForUser(ctx context.Context, username string, projectNumber, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) GetProjectItemForUser(ctx context.Context, username string, projectNumber int64, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) req, err := s.client.NewRequest("GET", u, opts) if err != nil { @@ -436,7 +436,7 @@ func (s *ProjectsService) GetProjectItemForUser(ctx context.Context, username st // GitHub API docs: https://docs.github.com/rest/projects/items#update-project-item-for-user // //meta:operation PATCH /users/{username}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) UpdateProjectItemForUser(ctx context.Context, username string, projectNumber, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) UpdateProjectItemForUser(ctx context.Context, username string, projectNumber int64, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) req, err := s.client.NewRequest("PATCH", u, opts) if err != nil { @@ -455,7 +455,7 @@ func (s *ProjectsService) UpdateProjectItemForUser(ctx context.Context, username // GitHub API docs: https://docs.github.com/rest/projects/items#delete-project-item-for-user // //meta:operation DELETE /users/{username}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) DeleteProjectItemForUser(ctx context.Context, username string, projectNumber, itemID int64) (*Response, error) { +func (s *ProjectsService) DeleteProjectItemForUser(ctx context.Context, username string, projectNumber int64, itemID int64) (*Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) req, err := s.client.NewRequest("DELETE", u, nil) if err != nil { From 16de153544465dac6c42eb21c2bf1c96910fdcd3 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 28 Oct 2025 17:58:32 +0000 Subject: [PATCH 24/39] updates from generate.sh --- github/github-accessors.go | 48 +++++++++++++++++++++ github/github-accessors_test.go | 74 ++++++++++++++++++++++++++++----- github/projects.go | 2 +- 3 files changed, 112 insertions(+), 12 deletions(-) diff --git a/github/github-accessors.go b/github/github-accessors.go index 7f43265b32a..4630a047e71 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -28942,6 +28942,54 @@ func (u *UpdateEnterpriseRunnerGroupRequest) GetVisibility() string { return *u.Visibility } +// GetEncryptedValue returns the EncryptedValue field if it's non-nil, zero value otherwise. +func (u *UpdateOrganizationPrivateRegistry) GetEncryptedValue() string { + if u == nil || u.EncryptedValue == nil { + return "" + } + return *u.EncryptedValue +} + +// GetKeyID returns the KeyID field if it's non-nil, zero value otherwise. +func (u *UpdateOrganizationPrivateRegistry) GetKeyID() string { + if u == nil || u.KeyID == nil { + return "" + } + return *u.KeyID +} + +// GetRegistryType returns the RegistryType field if it's non-nil, zero value otherwise. +func (u *UpdateOrganizationPrivateRegistry) GetRegistryType() string { + if u == nil || u.RegistryType == nil { + return "" + } + return *u.RegistryType +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (u *UpdateOrganizationPrivateRegistry) GetURL() string { + if u == nil || u.URL == nil { + return "" + } + return *u.URL +} + +// GetUsername returns the Username field if it's non-nil, zero value otherwise. +func (u *UpdateOrganizationPrivateRegistry) GetUsername() string { + if u == nil || u.Username == nil { + return "" + } + return *u.Username +} + +// GetVisibility returns the Visibility field. +func (u *UpdateOrganizationPrivateRegistry) GetVisibility() *PrivateRegistryVisibility { + if u == nil { + return nil + } + return u.Visibility +} + // GetArchived returns the Archived field if it's non-nil, zero value otherwise. func (u *UpdateProjectItemOptions) GetArchived() bool { if u == nil || u.Archived == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 3d1b53f7830..2974aeaed00 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -20138,17 +20138,6 @@ func TestNetworkSettingsResource_GetSubnetID(tt *testing.T) { n.GetSubnetID() } -func TestNewProjectV2Field_GetID(tt *testing.T) { - tt.Parallel() - var zeroValue int64 - n := &ProjectV2Field{ID: &zeroValue} - n.GetID() - n = &ProjectV2Field{} - n.GetID() - n = nil - n.GetID() -} - func TestNewPullRequest_GetBase(tt *testing.T) { tt.Parallel() var zeroValue string @@ -37294,6 +37283,69 @@ func TestUpdateEnterpriseRunnerGroupRequest_GetVisibility(tt *testing.T) { u.GetVisibility() } +func TestUpdateOrganizationPrivateRegistry_GetEncryptedValue(tt *testing.T) { + tt.Parallel() + var zeroValue string + u := &UpdateOrganizationPrivateRegistry{EncryptedValue: &zeroValue} + u.GetEncryptedValue() + u = &UpdateOrganizationPrivateRegistry{} + u.GetEncryptedValue() + u = nil + u.GetEncryptedValue() +} + +func TestUpdateOrganizationPrivateRegistry_GetKeyID(tt *testing.T) { + tt.Parallel() + var zeroValue string + u := &UpdateOrganizationPrivateRegistry{KeyID: &zeroValue} + u.GetKeyID() + u = &UpdateOrganizationPrivateRegistry{} + u.GetKeyID() + u = nil + u.GetKeyID() +} + +func TestUpdateOrganizationPrivateRegistry_GetRegistryType(tt *testing.T) { + tt.Parallel() + var zeroValue string + u := &UpdateOrganizationPrivateRegistry{RegistryType: &zeroValue} + u.GetRegistryType() + u = &UpdateOrganizationPrivateRegistry{} + u.GetRegistryType() + u = nil + u.GetRegistryType() +} + +func TestUpdateOrganizationPrivateRegistry_GetURL(tt *testing.T) { + tt.Parallel() + var zeroValue string + u := &UpdateOrganizationPrivateRegistry{URL: &zeroValue} + u.GetURL() + u = &UpdateOrganizationPrivateRegistry{} + u.GetURL() + u = nil + u.GetURL() +} + +func TestUpdateOrganizationPrivateRegistry_GetUsername(tt *testing.T) { + tt.Parallel() + var zeroValue string + u := &UpdateOrganizationPrivateRegistry{Username: &zeroValue} + u.GetUsername() + u = &UpdateOrganizationPrivateRegistry{} + u.GetUsername() + u = nil + u.GetUsername() +} + +func TestUpdateOrganizationPrivateRegistry_GetVisibility(tt *testing.T) { + tt.Parallel() + u := &UpdateOrganizationPrivateRegistry{} + u.GetVisibility() + u = nil + u.GetVisibility() +} + func TestUpdateProjectItemOptions_GetArchived(tt *testing.T) { tt.Parallel() var zeroValue bool diff --git a/github/projects.go b/github/projects.go index 6681d0e73bd..610a38adfdd 100644 --- a/github/projects.go +++ b/github/projects.go @@ -218,7 +218,7 @@ func (s *ProjectsService) ListProjectFieldsForOrg(ctx context.Context, org strin // // GitHub API docs: https://docs.github.com/rest/projects/fields#list-project-fields-for-user // -//meta:operation GET /users/{user}/projectsV2/{project_number}/fields +//meta:operation GET /users/{username}/projectsV2/{project_number}/fields func (s *ProjectsService) ListProjectFieldsForUser(ctx context.Context, user string, projectNumber int64, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/fields", user, projectNumber) u, err := addOptions(u, opts) From 8a4bbd8cd08f0cc727e182a527122551f9d0e91d Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 28 Oct 2025 19:09:48 +0000 Subject: [PATCH 25/39] additional updates to project number --- github/projects.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/projects.go b/github/projects.go index 610a38adfdd..25e3a0b307e 100644 --- a/github/projects.go +++ b/github/projects.go @@ -174,7 +174,7 @@ func (s *ProjectsService) ListProjectsForUser(ctx context.Context, username stri // GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-user // //meta:operation GET /users/{username}/projectsV2/{project_number} -func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string, projectNumber int) (*ProjectV2, *Response, error) { +func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string, projectNumber int64) (*ProjectV2, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v", username, projectNumber) req, err := s.client.NewRequest("GET", u, nil) if err != nil { From 6a7c464e05e496943ddfab5b8df431205a17ce3e Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 28 Oct 2025 20:21:09 +0000 Subject: [PATCH 26/39] fixes additional linting errors --- github/projects.go | 10 +++---- github/projects_test.go | 58 ++++++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/github/projects.go b/github/projects.go index 25e3a0b307e..5c39726c39e 100644 --- a/github/projects.go +++ b/github/projects.go @@ -323,7 +323,7 @@ func (s *ProjectsService) AddProjectItemForOrg(ctx context.Context, org string, // GitHub API docs: https://docs.github.com/rest/projects/items#get-an-item-for-an-organization-owned-project // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) GetProjectItemForOrg(ctx context.Context, org string, projectNumber int64, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) GetProjectItemForOrg(ctx context.Context, org string, projectNumber, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) req, err := s.client.NewRequest("GET", u, opts) if err != nil { @@ -342,7 +342,7 @@ func (s *ProjectsService) GetProjectItemForOrg(ctx context.Context, org string, // GitHub API docs: https://docs.github.com/rest/projects/items#update-project-item-for-organization // //meta:operation PATCH /orgs/{org}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) UpdateProjectItemForOrg(ctx context.Context, org string, projectNumber int64, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) UpdateProjectItemForOrg(ctx context.Context, org string, projectNumber, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) req, err := s.client.NewRequest("PATCH", u, opts) if err != nil { @@ -417,7 +417,7 @@ func (s *ProjectsService) AddProjectItemForUser(ctx context.Context, username st // GitHub API docs: https://docs.github.com/rest/projects/items#get-an-item-for-a-user-owned-project // //meta:operation GET /users/{username}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) GetProjectItemForUser(ctx context.Context, username string, projectNumber int64, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) GetProjectItemForUser(ctx context.Context, username string, projectNumber, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) req, err := s.client.NewRequest("GET", u, opts) if err != nil { @@ -436,7 +436,7 @@ func (s *ProjectsService) GetProjectItemForUser(ctx context.Context, username st // GitHub API docs: https://docs.github.com/rest/projects/items#update-project-item-for-user // //meta:operation PATCH /users/{username}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) UpdateProjectItemForUser(ctx context.Context, username string, projectNumber int64, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) UpdateProjectItemForUser(ctx context.Context, username string, projectNumber, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) req, err := s.client.NewRequest("PATCH", u, opts) if err != nil { @@ -455,7 +455,7 @@ func (s *ProjectsService) UpdateProjectItemForUser(ctx context.Context, username // GitHub API docs: https://docs.github.com/rest/projects/items#delete-project-item-for-user // //meta:operation DELETE /users/{username}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) DeleteProjectItemForUser(ctx context.Context, username string, projectNumber int64, itemID int64) (*Response, error) { +func (s *ProjectsService) DeleteProjectItemForUser(ctx context.Context, username string, projectNumber, itemID int64) (*Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) req, err := s.client.NewRequest("DELETE", u, nil) if err != nil { diff --git a/github/projects_test.go b/github/projects_test.go index c68b6aea8ca..949cfcceb66 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -146,7 +146,7 @@ func TestProjectsService_GetProjectForUser(t *testing.T) { fmt.Fprint(w, `{"id":3,"title":"UserProj","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}`) }) - ctx := context.Background() + ctx := t.Context() project, _, err := client.Projects.GetProjectForUser(ctx, "u", 3) if err != nil { t.Fatalf("Projects.GetProjectForUser returned error: %v", err) @@ -204,7 +204,7 @@ func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { }) opts := &ListProjectsOptions{Query: "text", ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: "2", Before: "1"}} - ctx := context.Background() + ctx := t.Context() fields, _, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) if err != nil { t.Fatalf("Projects.ListProjectFieldsForOrg returned error: %v", err) @@ -228,7 +228,7 @@ func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { } return resp, err }) - ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) + ctxBypass := context.WithValue(ctx, BypassRateLimitCheck, true) if _, _, err = client.Projects.ListProjectFieldsForOrg(ctxBypass, "o", 1, &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: "b", After: "a"}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } @@ -273,7 +273,7 @@ func TestProjectsService_ListProjectFieldsForUser(t *testing.T) { }) opts := &ListProjectsOptions{Query: "text", ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: "2", Before: "1"}} - ctx := context.Background() + ctx := t.Context() fields, _, err := client.Projects.ListProjectFieldsForUser(ctx, "u", 1, opts) if err != nil { t.Fatalf("Projects.ListProjectFieldsForUser returned error: %v", err) @@ -297,7 +297,7 @@ func TestProjectsService_ListProjectFieldsForUser(t *testing.T) { } return resp, err }) - ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) + ctxBypass := context.WithValue(ctx, BypassRateLimitCheck, true) if _, _, err = client.Projects.ListProjectFieldsForUser(ctxBypass, "u", 1, &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: "b", After: "a"}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } @@ -322,7 +322,7 @@ func TestProjectsService_ListProjectsForUser_pagination(t *testing.T) { } http.Error(w, "unexpected query", http.StatusBadRequest) }) - ctx := context.Background() + ctx := t.Context() first, resp, err := client.Projects.ListProjectsForUser(ctx, "u", nil) if err != nil { t.Fatalf("first page error: %v", err) @@ -354,7 +354,7 @@ func TestProjectsService_ListProjectsForUser_error(t *testing.T) { testMethod(t, r, "GET") fmt.Fprint(w, `[]`) }) - ctx := context.Background() + ctx := t.Context() const methodName = "ListProjectsForUser" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.ListProjectsForUser(ctx, "u", nil) @@ -395,7 +395,7 @@ func TestProjectsService_ListProjectFieldsForOrg_pagination(t *testing.T) { http.Error(w, "unexpected query", http.StatusBadRequest) }) - ctx := context.Background() + ctx := t.Context() first, resp, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, nil) if err != nil { t.Fatalf("first page error: %v", err) @@ -441,7 +441,7 @@ func TestProjectsService_ListProjectsForOrg_pagination(t *testing.T) { http.Error(w, "unexpected query", http.StatusBadRequest) }) - ctx := context.Background() + ctx := t.Context() first, resp, err := client.Projects.ListProjectsForOrg(ctx, "o", nil) if err != nil { t.Fatalf("first page error: %v", err) @@ -553,7 +553,7 @@ func TestProjectsService_ListProjectItemsForOrg(t *testing.T) { }) opts := &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: "2", Before: "1", PerPage: 50}, Query: "status:open"}, Fields: []int64{10, 11}} - ctx := context.Background() + ctx := t.Context() items, _, err := client.Projects.ListProjectItemsForOrg(ctx, "o", 1, opts) if err != nil { t.Fatalf("Projects.ListProjectItemsForOrg returned error: %v", err) @@ -576,7 +576,7 @@ func TestProjectsService_ListProjectItemsForOrg(t *testing.T) { return resp, err }) - ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) + ctxBypass := context.WithValue(ctx, BypassRateLimitCheck, true) if _, _, err = client.Projects.ListProjectItemsForOrg(ctxBypass, "o", 1, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: "b", After: "a"}}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } @@ -596,7 +596,7 @@ func TestProjectsService_AddProjectItemForOrg(t *testing.T) { fmt.Fprint(w, `{"id":99,"node_id":"PVTI_new"}`) }) - ctx := context.Background() + ctx := t.Context() item, _, err := client.Projects.AddProjectItemForOrg(ctx, "o", 1, &AddProjectItemOptions{Type: "Issue", ID: 99}) if err != nil { t.Fatalf("Projects.AddProjectItemForOrg returned error: %v", err) @@ -614,7 +614,7 @@ func TestProjectsService_AddProjectItemForOrg_error(t *testing.T) { w.WriteHeader(http.StatusCreated) fmt.Fprint(w, `{"id":1}`) }) - ctx := context.Background() + ctx := t.Context() const methodName = "AddProjectItemForOrg" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.AddProjectItemForOrg(ctx, "o", 1, &AddProjectItemOptions{Type: "Issue", ID: 1}) @@ -632,7 +632,7 @@ func TestProjectsService_GetProjectItemForOrg(t *testing.T) { testMethod(t, r, "GET") fmt.Fprint(w, `{"id":17,"node_id":"PVTI_node"}`) }) - ctx := context.Background() + ctx := t.Context() opts := &GetProjectItemOptions{} item, _, err := client.Projects.GetProjectItemForOrg(ctx, "o", 1, 17, opts) if err != nil { @@ -650,7 +650,7 @@ func TestProjectsService_GetProjectItemForOrg_error(t *testing.T) { testMethod(t, r, "GET") fmt.Fprint(w, `{"id":17}`) }) - ctx := context.Background() + ctx := t.Context() const methodName = "GetProjectItemForOrg" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.GetProjectItemForOrg(ctx, "o", 1, 17, &GetProjectItemOptions{}) @@ -674,7 +674,7 @@ func TestProjectsService_UpdateProjectItemForOrg(t *testing.T) { fmt.Fprint(w, `{"id":17}`) }) archived := true - ctx := context.Background() + ctx := t.Context() item, _, err := client.Projects.UpdateProjectItemForOrg(ctx, "o", 1, 17, &UpdateProjectItemOptions{Archived: &archived}) if err != nil { t.Fatalf("UpdateProjectItemForOrg error: %v", err) @@ -692,7 +692,7 @@ func TestProjectsService_UpdateProjectItemForOrg_error(t *testing.T) { fmt.Fprint(w, `{"id":17}`) }) archived := true - ctx := context.Background() + ctx := t.Context() const methodName = "UpdateProjectItemForOrg" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.UpdateProjectItemForOrg(ctx, "o", 1, 17, &UpdateProjectItemOptions{Archived: &archived}) @@ -710,7 +710,7 @@ func TestProjectsService_DeleteProjectItemForOrg(t *testing.T) { testMethod(t, r, "DELETE") w.WriteHeader(http.StatusNoContent) }) - ctx := context.Background() + ctx := t.Context() if _, err := client.Projects.DeleteProjectItemForOrg(ctx, "o", 1, 17); err != nil { t.Fatalf("DeleteProjectItemForOrg error: %v", err) } @@ -723,7 +723,7 @@ func TestProjectsService_DeleteProjectItemForOrg_error(t *testing.T) { testMethod(t, r, "DELETE") w.WriteHeader(http.StatusNoContent) }) - ctx := context.Background() + ctx := t.Context() const methodName = "DeleteProjectItemForOrg" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { return client.Projects.DeleteProjectItemForOrg(ctx, "o", 1, 17) @@ -738,7 +738,7 @@ func TestProjectsService_ListProjectItemsForUser(t *testing.T) { testFormValues(t, r, values{"per_page": "20", "q": "type:issue"}) fmt.Fprint(w, `[{"id":7,"node_id":"PVTI_user"}]`) }) - ctx := context.Background() + ctx := t.Context() items, _, err := client.Projects.ListProjectItemsForUser(ctx, "u", 2, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{PerPage: 20}, Query: "type:issue"}}) if err != nil { t.Fatalf("ListProjectItemsForUser error: %v", err) @@ -755,7 +755,7 @@ func TestProjectsService_ListProjectItemsForUser_error(t *testing.T) { testMethod(t, r, "GET") fmt.Fprint(w, `[]`) }) - ctx := context.Background() + ctx := t.Context() const methodName = "ListProjectItemsForUser" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.ListProjectItemsForUser(ctx, "u", 2, nil) @@ -782,7 +782,7 @@ func TestProjectsService_AddProjectItemForUser(t *testing.T) { } fmt.Fprint(w, `{"id":123,"node_id":"PVTI_new_user"}`) }) - ctx := context.Background() + ctx := t.Context() item, _, err := client.Projects.AddProjectItemForUser(ctx, "u", 2, &AddProjectItemOptions{Type: "PullRequest", ID: 123}) if err != nil { t.Fatalf("AddProjectItemForUser error: %v", err) @@ -799,7 +799,7 @@ func TestProjectsService_AddProjectItemForUser_error(t *testing.T) { testMethod(t, r, "POST") fmt.Fprint(w, `{"id":5}`) }) - ctx := context.Background() + ctx := t.Context() const methodName = "AddProjectItemForUser" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.AddProjectItemForUser(ctx, "u", 2, &AddProjectItemOptions{Type: "Issue", ID: 5}) @@ -817,7 +817,7 @@ func TestProjectsService_GetProjectItemForUser(t *testing.T) { testMethod(t, r, "GET") fmt.Fprint(w, `{"id":55,"node_id":"PVTI_user_item"}`) }) - ctx := context.Background() + ctx := t.Context() opts := &GetProjectItemOptions{} item, _, err := client.Projects.GetProjectItemForUser(ctx, "u", 2, 55, opts) if err != nil { @@ -835,7 +835,7 @@ func TestProjectsService_GetProjectItemForUser_error(t *testing.T) { testMethod(t, r, "GET") fmt.Fprint(w, `{"id":55}`) }) - ctx := context.Background() + ctx := t.Context() const methodName = "GetProjectItemForUser" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.GetProjectItemForUser(ctx, "u", 2, 55, &GetProjectItemOptions{}) @@ -859,7 +859,7 @@ func TestProjectsService_UpdateProjectItemForUser(t *testing.T) { fmt.Fprint(w, `{"id":55}`) }) archived := false - ctx := context.Background() + ctx := t.Context() item, _, err := client.Projects.UpdateProjectItemForUser(ctx, "u", 2, 55, &UpdateProjectItemOptions{Archived: &archived}) if err != nil { t.Fatalf("UpdateProjectItemForUser error: %v", err) @@ -877,7 +877,7 @@ func TestProjectsService_UpdateProjectItemForUser_error(t *testing.T) { fmt.Fprint(w, `{"id":55}`) }) archived := false - ctx := context.Background() + ctx := t.Context() const methodName = "UpdateProjectItemForUser" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.UpdateProjectItemForUser(ctx, "u", 2, 55, &UpdateProjectItemOptions{Archived: &archived}) @@ -895,7 +895,7 @@ func TestProjectsService_DeleteProjectItemForUser(t *testing.T) { testMethod(t, r, "DELETE") w.WriteHeader(http.StatusNoContent) }) - ctx := context.Background() + ctx := t.Context() if _, err := client.Projects.DeleteProjectItemForUser(ctx, "u", 2, 55); err != nil { t.Fatalf("DeleteProjectItemForUser error: %v", err) } @@ -908,7 +908,7 @@ func TestProjectsService_DeleteProjectItemForUser_error(t *testing.T) { testMethod(t, r, "DELETE") w.WriteHeader(http.StatusNoContent) }) - ctx := context.Background() + ctx := t.Context() const methodName = "DeleteProjectItemForUser" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { return client.Projects.DeleteProjectItemForUser(ctx, "u", 2, 55) From 7e69a24b2d370049ead31d5f9274571eb6ab8e26 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 28 Oct 2025 20:48:05 +0000 Subject: [PATCH 27/39] Implement methods for retrieving fields by project and field ID for both organizations and users --- github/projects.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/github/projects.go b/github/projects.go index 5c39726c39e..cb7598fee99 100644 --- a/github/projects.go +++ b/github/projects.go @@ -239,6 +239,46 @@ func (s *ProjectsService) ListProjectFieldsForUser(ctx context.Context, user str return fields, resp, nil } +// GetProjectFieldForOrg gets a single project field from an organization owned project. +// +// GitHub API docs: https://docs.github.com/en/rest/projects/fields?apiVersion=2022-11-28#get-project-field-for-organization +// +//meta:operation GET /orgs/{org}/projectsV2/{project_number}/fields/{field_id} +func (s *ProjectsService) GetProjectFieldForOrg(ctx context.Context, org string, projectNumber, fieldID int64) (*ProjectV2Field, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v/fields/%v", org, projectNumber, fieldID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + field := new(ProjectV2Field) + resp, err := s.client.Do(ctx, req, field) + if err != nil { + return nil, resp, err + } + return field, resp, nil +} + +// GetProjectFieldForUser gets a single project field from a user owned project. +// +// GitHub API docs: https://docs.github.com/en/rest/projects/fields?apiVersion=2022-11-28#get-project-field-for-user +// +//meta:operation GET /users/{username}/projectsV2/{project_number}/fields/{field_id} +func (s *ProjectsService) GetProjectFieldForUser(ctx context.Context, user string, projectNumber, fieldID int64) (*ProjectV2Field, *Response, error) { + u := fmt.Sprintf("users/%v/projectsV2/%v/fields/%v", user, projectNumber, fieldID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + field := new(ProjectV2Field) + resp, err := s.client.Do(ctx, req, field) + if err != nil { + return nil, resp, err + } + return field, resp, nil +} + // ListProjectItemsOptions specifies optional parameters when listing project items. // Note: Pagination uses before/after cursor-style pagination similar to ListProjectsOptions. // "Fields" can be used to restrict which field values are returned (by their numeric IDs). From 9191d8596ad78c795722e31a369b28c4f671fcba Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 28 Oct 2025 20:57:05 +0000 Subject: [PATCH 28/39] adding generated updates --- github/projects.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/github/projects.go b/github/projects.go index cb7598fee99..2f514b7d621 100644 --- a/github/projects.go +++ b/github/projects.go @@ -241,7 +241,7 @@ func (s *ProjectsService) ListProjectFieldsForUser(ctx context.Context, user str // GetProjectFieldForOrg gets a single project field from an organization owned project. // -// GitHub API docs: https://docs.github.com/en/rest/projects/fields?apiVersion=2022-11-28#get-project-field-for-organization +// GitHub API docs: https://docs.github.com/rest/projects/fields#get-project-field-for-organization // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/fields/{field_id} func (s *ProjectsService) GetProjectFieldForOrg(ctx context.Context, org string, projectNumber, fieldID int64) (*ProjectV2Field, *Response, error) { @@ -261,7 +261,7 @@ func (s *ProjectsService) GetProjectFieldForOrg(ctx context.Context, org string, // GetProjectFieldForUser gets a single project field from a user owned project. // -// GitHub API docs: https://docs.github.com/en/rest/projects/fields?apiVersion=2022-11-28#get-project-field-for-user +// GitHub API docs: https://docs.github.com/rest/projects/fields#get-project-field-for-user // //meta:operation GET /users/{username}/projectsV2/{project_number}/fields/{field_id} func (s *ProjectsService) GetProjectFieldForUser(ctx context.Context, user string, projectNumber, fieldID int64) (*ProjectV2Field, *Response, error) { From 521e0ba47c065c8763d4ecadeacd99b78290f20a Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 28 Oct 2025 21:28:18 +0000 Subject: [PATCH 29/39] add additional tests to improve coverage for individual field retrieval API --- github/projects_test.go | 82 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/github/projects_test.go b/github/projects_test.go index 949cfcceb66..525b1011f5f 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -303,6 +303,88 @@ func TestProjectsService_ListProjectFieldsForUser(t *testing.T) { } } +func TestProjectsService_GetProjectFieldForOrg(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/projectsV2/1/fields/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, ` + { + "id": 1, + "node_id": "node_1", + "name": "Status", + "dataType": "single_select", + "url": "https://api.github.com/projects/1/fields/field1", + "options": [ + {"id": 1, "name": "Todo", "color": "blue", "description": "Tasks to be done"}, + {"id": 2, "name": "In Progress", "color": "yellow"} + ], + "created_at": "2011-01-02T15:04:05Z", + "updated_at": "2012-01-02T15:04:05Z" + }`) + }) + + ctx := t.Context() + field, _, err := client.Projects.GetProjectFieldForOrg(ctx, "o", 1, 1) + if err != nil { + t.Fatalf("Projects.GetProjectFieldForOrg returned error: %v", err) + } + if field == nil || field.ID == nil || *field.ID != 1 { + t.Fatalf("unexpected field: %+v", field) + } + + const methodName = "GetProjectFieldForOrg" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.GetProjectFieldForOrg(ctx, "o", 1, 1) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestProjectsService_GetProjectFieldForUser(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/users/u/projectsV2/1/fields/3", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, ` + { + "id": 3, + "node_id": "node_3", + "name": "Status", + "dataType": "single_select", + "url": "https://api.github.com/projects/1/fields/field3", + "options": [ + {"id": 1, "name": "Done", "color": "red", "description": "Done task"}, + {"id": 2, "name": "In Progress", "color": "yellow"} + ], + "created_at": "2011-01-02T15:04:05Z", + "updated_at": "2012-01-02T15:04:05Z" + }`) + }) + + ctx := t.Context() + field, _, err := client.Projects.GetProjectFieldForUser(ctx, "u", 1, 3) + if err != nil { + t.Fatalf("Projects.GetProjectFieldForUser returned error: %v", err) + } + if field == nil || field.ID == nil || *field.ID != 3 { + t.Fatalf("unexpected field: %+v", field) + } + + const methodName = "GetProjectFieldForUser" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.GetProjectFieldForUser(ctx, "u", 1, 3) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + func TestProjectsService_ListProjectsForUser_pagination(t *testing.T) { t.Parallel() client, mux, _ := setup(t) From 25561d31fc04b30c7c527ff4025da4a676e1ed10 Mon Sep 17 00:00:00 2001 From: Jonathan Otalora Date: Wed, 29 Oct 2025 13:12:21 -0600 Subject: [PATCH 30/39] reverts projectNumber from int64 to int --- github/event_types_test.go | 2 +- github/github-accessors.go | 2 +- github/github-accessors_test.go | 2 +- github/github-stringify_test.go | 2 +- github/projects.go | 18 +++++++++--------- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/github/event_types_test.go b/github/event_types_test.go index 98d38456e99..9ad223dca69 100644 --- a/github/event_types_test.go +++ b/github/event_types_test.go @@ -15673,7 +15673,7 @@ func TestProjectV2Event_Marshal(t *testing.T) { CreatedAt: &Timestamp{referenceTime}, UpdatedAt: &Timestamp{referenceTime}, DeletedAt: &Timestamp{referenceTime}, - Number: Ptr(int64(1)), + Number: Ptr(1), ShortDescription: Ptr("sd"), DeletedBy: &User{ Login: Ptr("l"), diff --git a/github/github-accessors.go b/github/github-accessors.go index 4630a047e71..c0b360726c6 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -18991,7 +18991,7 @@ func (p *ProjectV2) GetNodeID() string { } // GetNumber returns the Number field if it's non-nil, zero value otherwise. -func (p *ProjectV2) GetNumber() int64 { +func (p *ProjectV2) GetNumber() int { if p == nil || p.Number == nil { return 0 } diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 2974aeaed00..bdbbdb019ff 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -24666,7 +24666,7 @@ func TestProjectV2_GetNodeID(tt *testing.T) { func TestProjectV2_GetNumber(tt *testing.T) { tt.Parallel() - var zeroValue int64 + var zeroValue int p := &ProjectV2{Number: &zeroValue} p.GetNumber() p = &ProjectV2{} diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go index 5656a81b5d4..c068952d51c 100644 --- a/github/github-stringify_test.go +++ b/github/github-stringify_test.go @@ -1554,7 +1554,7 @@ func TestProjectV2_String(t *testing.T) { CreatedAt: &Timestamp{}, UpdatedAt: &Timestamp{}, DeletedAt: &Timestamp{}, - Number: Ptr(int64(0)), + Number: Ptr(0), ShortDescription: Ptr(""), DeletedBy: &User{}, URL: Ptr(""), diff --git a/github/projects.go b/github/projects.go index 2f514b7d621..f9ee0cb8a25 100644 --- a/github/projects.go +++ b/github/projects.go @@ -29,7 +29,7 @@ type ProjectV2 struct { CreatedAt *Timestamp `json:"created_at,omitempty"` UpdatedAt *Timestamp `json:"updated_at,omitempty"` DeletedAt *Timestamp `json:"deleted_at,omitempty"` - Number *int64 `json:"number,omitempty"` + Number *int `json:"number,omitempty"` ShortDescription *string `json:"short_description,omitempty"` DeletedBy *User `json:"deleted_by,omitempty"` @@ -130,7 +130,7 @@ func (s *ProjectsService) ListProjectsForOrg(ctx context.Context, org string, op // GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-organization // //meta:operation GET /orgs/{org}/projectsV2/{project_number} -func (s *ProjectsService) GetProjectForOrg(ctx context.Context, org string, projectNumber int64) (*ProjectV2, *Response, error) { +func (s *ProjectsService) GetProjectForOrg(ctx context.Context, org string, projectNumber int) (*ProjectV2, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v", org, projectNumber) req, err := s.client.NewRequest("GET", u, nil) if err != nil { @@ -174,7 +174,7 @@ func (s *ProjectsService) ListProjectsForUser(ctx context.Context, username stri // GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-user // //meta:operation GET /users/{username}/projectsV2/{project_number} -func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string, projectNumber int64) (*ProjectV2, *Response, error) { +func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string, projectNumber int) (*ProjectV2, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v", username, projectNumber) req, err := s.client.NewRequest("GET", u, nil) if err != nil { @@ -194,7 +194,7 @@ func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string // GitHub API docs: https://docs.github.com/rest/projects/fields#list-project-fields-for-organization // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/fields -func (s *ProjectsService) ListProjectFieldsForOrg(ctx context.Context, org string, projectNumber int64, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { +func (s *ProjectsService) ListProjectFieldsForOrg(ctx context.Context, org string, projectNumber int, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/fields", org, projectNumber) u, err := addOptions(u, opts) if err != nil { @@ -219,7 +219,7 @@ func (s *ProjectsService) ListProjectFieldsForOrg(ctx context.Context, org strin // GitHub API docs: https://docs.github.com/rest/projects/fields#list-project-fields-for-user // //meta:operation GET /users/{username}/projectsV2/{project_number}/fields -func (s *ProjectsService) ListProjectFieldsForUser(ctx context.Context, user string, projectNumber int64, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { +func (s *ProjectsService) ListProjectFieldsForUser(ctx context.Context, user string, projectNumber int, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/fields", user, projectNumber) u, err := addOptions(u, opts) if err != nil { @@ -318,7 +318,7 @@ type UpdateProjectItemOptions struct { // GitHub API docs: https://docs.github.com/rest/projects/items#list-items-for-an-organization-owned-project // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/items -func (s *ProjectsService) ListProjectItemsForOrg(ctx context.Context, org string, projectNumber int64, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { +func (s *ProjectsService) ListProjectItemsForOrg(ctx context.Context, org string, projectNumber int, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items", org, projectNumber) u, err := addOptions(u, opts) if err != nil { @@ -343,7 +343,7 @@ func (s *ProjectsService) ListProjectItemsForOrg(ctx context.Context, org string // GitHub API docs: https://docs.github.com/rest/projects/items#add-item-to-organization-owned-project // //meta:operation POST /orgs/{org}/projectsV2/{project_number}/items -func (s *ProjectsService) AddProjectItemForOrg(ctx context.Context, org string, projectNumber int64, opts *AddProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) AddProjectItemForOrg(ctx context.Context, org string, projectNumber int, opts *AddProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items", org, projectNumber) req, err := s.client.NewRequest("POST", u, opts) if err != nil { @@ -415,7 +415,7 @@ func (s *ProjectsService) DeleteProjectItemForOrg(ctx context.Context, org strin // GitHub API docs: https://docs.github.com/rest/projects/items#list-items-for-a-user-owned-project // //meta:operation GET /users/{username}/projectsV2/{project_number}/items -func (s *ProjectsService) ListProjectItemsForUser(ctx context.Context, username string, projectNumber int64, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { +func (s *ProjectsService) ListProjectItemsForUser(ctx context.Context, username string, projectNumber int, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items", username, projectNumber) u, err := addOptions(u, opts) if err != nil { @@ -438,7 +438,7 @@ func (s *ProjectsService) ListProjectItemsForUser(ctx context.Context, username // GitHub API docs: https://docs.github.com/rest/projects/items#add-item-to-user-owned-project // //meta:operation POST /users/{username}/projectsV2/{project_number}/items -func (s *ProjectsService) AddProjectItemForUser(ctx context.Context, username string, projectNumber int64, opts *AddProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) AddProjectItemForUser(ctx context.Context, username string, projectNumber int, opts *AddProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items", username, projectNumber) req, err := s.client.NewRequest("POST", u, opts) if err != nil { From 27dd1c4bffc74dcc0e5f589190a61747b58931fc Mon Sep 17 00:00:00 2001 From: Jonathan Otalora Date: Wed, 29 Oct 2025 13:41:36 -0600 Subject: [PATCH 31/39] fixes ProjectV2FieldOption.ID type from int64 to string --- github/github-accessors.go | 28 ++++++++++++++++++++++++-- github/github-accessors_test.go | 35 ++++++++++++++++++++++++++++++++- github/projects.go | 8 ++++---- github/projects_test.go | 26 ++++++++++++------------ 4 files changed, 77 insertions(+), 20 deletions(-) diff --git a/github/github-accessors.go b/github/github-accessors.go index c0b360726c6..3b23c19bd50 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -19142,14 +19142,38 @@ func (p *ProjectV2Field) GetUpdatedAt() Timestamp { return *p.UpdatedAt } +// GetColor returns the Color field if it's non-nil, zero value otherwise. +func (p *ProjectV2FieldOption) GetColor() string { + if p == nil || p.Color == nil { + return "" + } + return *p.Color +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (p *ProjectV2FieldOption) GetDescription() string { + if p == nil || p.Description == nil { + return "" + } + return *p.Description +} + // GetID returns the ID field if it's non-nil, zero value otherwise. -func (p *ProjectV2FieldOption) GetID() int64 { +func (p *ProjectV2FieldOption) GetID() string { if p == nil || p.ID == nil { - return 0 + return "" } return *p.ID } +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (p *ProjectV2FieldOption) GetName() string { + if p == nil || p.Name == nil { + return "" + } + return *p.Name +} + // GetArchivedAt returns the ArchivedAt field if it's non-nil, zero value otherwise. func (p *ProjectV2Item) GetArchivedAt() Timestamp { if p == nil || p.ArchivedAt == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index bdbbdb019ff..c182f9f8113 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -24858,9 +24858,31 @@ func TestProjectV2Field_GetUpdatedAt(tt *testing.T) { p.GetUpdatedAt() } +func TestProjectV2FieldOption_GetColor(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &ProjectV2FieldOption{Color: &zeroValue} + p.GetColor() + p = &ProjectV2FieldOption{} + p.GetColor() + p = nil + p.GetColor() +} + +func TestProjectV2FieldOption_GetDescription(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &ProjectV2FieldOption{Description: &zeroValue} + p.GetDescription() + p = &ProjectV2FieldOption{} + p.GetDescription() + p = nil + p.GetDescription() +} + func TestProjectV2FieldOption_GetID(tt *testing.T) { tt.Parallel() - var zeroValue int64 + var zeroValue string p := &ProjectV2FieldOption{ID: &zeroValue} p.GetID() p = &ProjectV2FieldOption{} @@ -24869,6 +24891,17 @@ func TestProjectV2FieldOption_GetID(tt *testing.T) { p.GetID() } +func TestProjectV2FieldOption_GetName(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &ProjectV2FieldOption{Name: &zeroValue} + p.GetName() + p = &ProjectV2FieldOption{} + p.GetName() + p = nil + p.GetName() +} + func TestProjectV2Item_GetArchivedAt(tt *testing.T) { tt.Parallel() var zeroValue Timestamp diff --git a/github/projects.go b/github/projects.go index f9ee0cb8a25..c8697053bb6 100644 --- a/github/projects.go +++ b/github/projects.go @@ -79,10 +79,10 @@ type ListProjectsOptions struct { // // GitHub API docs: https://docs.github.com/rest/projects/fields type ProjectV2FieldOption struct { - ID *int64 `json:"id,omitempty"` // The unique identifier for this option. - Name string `json:"name,omitempty"` // The display name of the option. - Color string `json:"color,omitempty"` // The color associated with this option (e.g., "blue", "red"). - Description string `json:"description,omitempty"` // An optional description for this option. + ID *string `json:"id,omitempty"` // The unique identifier for this option. + Name *string `json:"name,omitempty"` // The display name of the option. + Color *string `json:"color,omitempty"` // The color associated with this option (e.g., "blue", "red"). + Description *string `json:"description,omitempty"` // An optional description for this option. } // ProjectV2Field represents a field in a GitHub Projects V2 project. diff --git a/github/projects_test.go b/github/projects_test.go index 525b1011f5f..9f86ff8f785 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -185,8 +185,8 @@ func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { "dataType": "single_select", "url": "https://api.github.com/projects/1/fields/field1", "options": [ - {"id": 1, "name": "Todo", "color": "blue", "description": "Tasks to be done"}, - {"id": 2, "name": "In Progress", "color": "yellow"} + {"id": "1", "name": "Todo", "color": "blue", "description": "Tasks to be done"}, + {"id": "2", "name": "In Progress", "color": "yellow"} ], "created_at": "2011-01-02T15:04:05Z", "updated_at": "2012-01-02T15:04:05Z" @@ -254,8 +254,8 @@ func TestProjectsService_ListProjectFieldsForUser(t *testing.T) { "dataType": "single_select", "url": "https://api.github.com/projects/1/fields/field1", "options": [ - {"id": 1, "name": "Todo", "color": "blue", "description": "Tasks to be done"}, - {"id": 2, "name": "In Progress", "color": "yellow"} + {"id": "1", "name": "Todo", "color": "blue", "description": "Tasks to be done"}, + {"id": "2", "name": "In Progress", "color": "yellow"} ], "created_at": "2011-01-02T15:04:05Z", "updated_at": "2012-01-02T15:04:05Z" @@ -317,8 +317,8 @@ func TestProjectsService_GetProjectFieldForOrg(t *testing.T) { "dataType": "single_select", "url": "https://api.github.com/projects/1/fields/field1", "options": [ - {"id": 1, "name": "Todo", "color": "blue", "description": "Tasks to be done"}, - {"id": 2, "name": "In Progress", "color": "yellow"} + {"id": "1", "name": "Todo", "color": "blue", "description": "Tasks to be done"}, + {"id": "2", "name": "In Progress", "color": "yellow"} ], "created_at": "2011-01-02T15:04:05Z", "updated_at": "2012-01-02T15:04:05Z" @@ -358,8 +358,8 @@ func TestProjectsService_GetProjectFieldForUser(t *testing.T) { "dataType": "single_select", "url": "https://api.github.com/projects/1/fields/field3", "options": [ - {"id": 1, "name": "Done", "color": "red", "description": "Done task"}, - {"id": 2, "name": "In Progress", "color": "yellow"} + {"id": "1", "name": "Done", "color": "red", "description": "Done task"}, + {"id": "2", "name": "In Progress", "color": "yellow"} ], "created_at": "2011-01-02T15:04:05Z", "updated_at": "2012-01-02T15:04:05Z" @@ -588,10 +588,10 @@ func TestProjectV2Field_Marshal(t *testing.T) { URL: "https://api.github.com/projects/1/fields/field1", Options: []*ProjectV2FieldOption{ { - ID: Ptr(int64(1)), - Name: "Todo", - Color: "blue", - Description: "Tasks to be done", + ID: Ptr("1"), + Name: Ptr("Todo"), + Color: Ptr("blue"), + Description: Ptr("Tasks to be done"), }, }, CreatedAt: &Timestamp{referenceTime}, @@ -606,7 +606,7 @@ func TestProjectV2Field_Marshal(t *testing.T) { "url": "https://api.github.com/projects/1/fields/field1", "options": [ { - "id": 1, + "id": "1", "name": "Todo", "color": "blue", "description": "Tasks to be done" From eac21dc91f7a282626d93d9fc0d8469e87b12347 Mon Sep 17 00:00:00 2001 From: Jonathan Otalora Date: Wed, 29 Oct 2025 16:30:38 -0600 Subject: [PATCH 32/39] ensures projectNumber is an int across the board --- github/projects.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/github/projects.go b/github/projects.go index c8697053bb6..3216507f883 100644 --- a/github/projects.go +++ b/github/projects.go @@ -244,7 +244,7 @@ func (s *ProjectsService) ListProjectFieldsForUser(ctx context.Context, user str // GitHub API docs: https://docs.github.com/rest/projects/fields#get-project-field-for-organization // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/fields/{field_id} -func (s *ProjectsService) GetProjectFieldForOrg(ctx context.Context, org string, projectNumber, fieldID int64) (*ProjectV2Field, *Response, error) { +func (s *ProjectsService) GetProjectFieldForOrg(ctx context.Context, org string, projectNumber int, fieldID int64) (*ProjectV2Field, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/fields/%v", org, projectNumber, fieldID) req, err := s.client.NewRequest("GET", u, nil) if err != nil { @@ -264,7 +264,7 @@ func (s *ProjectsService) GetProjectFieldForOrg(ctx context.Context, org string, // GitHub API docs: https://docs.github.com/rest/projects/fields#get-project-field-for-user // //meta:operation GET /users/{username}/projectsV2/{project_number}/fields/{field_id} -func (s *ProjectsService) GetProjectFieldForUser(ctx context.Context, user string, projectNumber, fieldID int64) (*ProjectV2Field, *Response, error) { +func (s *ProjectsService) GetProjectFieldForUser(ctx context.Context, user string, projectNumber int, fieldID int64) (*ProjectV2Field, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/fields/%v", user, projectNumber, fieldID) req, err := s.client.NewRequest("GET", u, nil) if err != nil { @@ -363,7 +363,7 @@ func (s *ProjectsService) AddProjectItemForOrg(ctx context.Context, org string, // GitHub API docs: https://docs.github.com/rest/projects/items#get-an-item-for-an-organization-owned-project // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) GetProjectItemForOrg(ctx context.Context, org string, projectNumber, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) GetProjectItemForOrg(ctx context.Context, org string, projectNumber int, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) req, err := s.client.NewRequest("GET", u, opts) if err != nil { @@ -382,7 +382,7 @@ func (s *ProjectsService) GetProjectItemForOrg(ctx context.Context, org string, // GitHub API docs: https://docs.github.com/rest/projects/items#update-project-item-for-organization // //meta:operation PATCH /orgs/{org}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) UpdateProjectItemForOrg(ctx context.Context, org string, projectNumber, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) UpdateProjectItemForOrg(ctx context.Context, org string, projectNumber int, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) req, err := s.client.NewRequest("PATCH", u, opts) if err != nil { @@ -401,7 +401,7 @@ func (s *ProjectsService) UpdateProjectItemForOrg(ctx context.Context, org strin // GitHub API docs: https://docs.github.com/rest/projects/items#delete-project-item-for-organization // //meta:operation DELETE /orgs/{org}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) DeleteProjectItemForOrg(ctx context.Context, org string, projectNumber, itemID int64) (*Response, error) { +func (s *ProjectsService) DeleteProjectItemForOrg(ctx context.Context, org string, projectNumber int, itemID int64) (*Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) req, err := s.client.NewRequest("DELETE", u, nil) if err != nil { @@ -457,7 +457,7 @@ func (s *ProjectsService) AddProjectItemForUser(ctx context.Context, username st // GitHub API docs: https://docs.github.com/rest/projects/items#get-an-item-for-a-user-owned-project // //meta:operation GET /users/{username}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) GetProjectItemForUser(ctx context.Context, username string, projectNumber, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) GetProjectItemForUser(ctx context.Context, username string, projectNumber int, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) req, err := s.client.NewRequest("GET", u, opts) if err != nil { @@ -476,7 +476,7 @@ func (s *ProjectsService) GetProjectItemForUser(ctx context.Context, username st // GitHub API docs: https://docs.github.com/rest/projects/items#update-project-item-for-user // //meta:operation PATCH /users/{username}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) UpdateProjectItemForUser(ctx context.Context, username string, projectNumber, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) UpdateProjectItemForUser(ctx context.Context, username string, projectNumber int, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) req, err := s.client.NewRequest("PATCH", u, opts) if err != nil { @@ -495,7 +495,7 @@ func (s *ProjectsService) UpdateProjectItemForUser(ctx context.Context, username // GitHub API docs: https://docs.github.com/rest/projects/items#delete-project-item-for-user // //meta:operation DELETE /users/{username}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) DeleteProjectItemForUser(ctx context.Context, username string, projectNumber, itemID int64) (*Response, error) { +func (s *ProjectsService) DeleteProjectItemForUser(ctx context.Context, username string, projectNumber int, itemID int64) (*Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) req, err := s.client.NewRequest("DELETE", u, nil) if err != nil { From 31e34f0fa7ce5f587db4d25bd6dbe44400b963f8 Mon Sep 17 00:00:00 2001 From: Jonathan Otalora Date: Wed, 29 Oct 2025 16:54:42 -0600 Subject: [PATCH 33/39] makes all optional fields in existing structs reference types --- github/projects.go | 16 ++++++++-------- github/projects_test.go | 36 ++++++++++++++++++------------------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/github/projects.go b/github/projects.go index 3216507f883..55c68edae8a 100644 --- a/github/projects.go +++ b/github/projects.go @@ -57,13 +57,13 @@ func (p ProjectV2) String() string { return Stringify(p) } // per page (max 100 per GitHub API docs). type ListProjectsPaginationOptions struct { // A cursor, as given in the Link header. If specified, the query only searches for events before this cursor. - Before string `url:"before,omitempty"` + Before *string `url:"before,omitempty"` // A cursor, as given in the Link header. If specified, the query only searches for events after this cursor. - After string `url:"after,omitempty"` + After *string `url:"after,omitempty"` // For paginated result sets, the number of results to include per page. - PerPage int `url:"per_page,omitempty"` + PerPage *int `url:"per_page,omitempty"` } // ListProjectsOptions specifies optional parameters to list projects for user / organization. @@ -71,7 +71,7 @@ type ListProjectsOptions struct { ListProjectsPaginationOptions // Q is an optional query string to limit results to projects of the specified type. - Query string `url:"q,omitempty"` + Query *string `url:"q,omitempty"` } // ProjectV2FieldOption represents an option for a project field of type single_select or multi_select. @@ -91,10 +91,10 @@ type ProjectV2FieldOption struct { // GitHub API docs: https://docs.github.com/rest/projects/fields type ProjectV2Field struct { ID *int64 `json:"id,omitempty"` - NodeID string `json:"node_id,omitempty"` - Name string `json:"name,omitempty"` - DataType string `json:"dataType,omitempty"` - URL string `json:"url,omitempty"` + NodeID *string `json:"node_id,omitempty"` + Name *string `json:"name,omitempty"` + DataType *string `json:"dataType,omitempty"` + URL *string `json:"url,omitempty"` Options []*ProjectV2FieldOption `json:"options,omitempty"` CreatedAt *Timestamp `json:"created_at,omitempty"` UpdatedAt *Timestamp `json:"updated_at,omitempty"` diff --git a/github/projects_test.go b/github/projects_test.go index 9f86ff8f785..60475d6354a 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -30,7 +30,7 @@ func TestProjectsService_ListProjectsForOrg(t *testing.T) { fmt.Fprint(w, `[{"id":1,"title":"T1","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) }) - opts := &ListProjectsOptions{Query: "alpha", ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: "2", Before: "1"}} + opts := &ListProjectsOptions{Query: Ptr("alpha"), ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: Ptr("2"), Before: Ptr("1")}} ctx := t.Context() projects, _, err := client.Projects.ListProjectsForOrg(ctx, "o", opts) if err != nil { @@ -56,7 +56,7 @@ func TestProjectsService_ListProjectsForOrg(t *testing.T) { // still allow both set (no validation enforced) – ensure it does not error ctxBypass := context.WithValue(t.Context(), BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectsForOrg(ctxBypass, "o", &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: "b", After: "a"}}); err != nil { + if _, _, err = client.Projects.ListProjectsForOrg(ctxBypass, "o", &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: String("b"), After: String("a")}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } @@ -105,7 +105,7 @@ func TestProjectsService_ListUserProjects(t *testing.T) { fmt.Fprint(w, `[{"id":2,"title":"UProj","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) }) - opts := &ListProjectsOptions{Query: "beta", ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: "1", After: "2", PerPage: 2}} + opts := &ListProjectsOptions{Query: Ptr("beta"), ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("1"), After: Ptr("2"), PerPage: Ptr(2)}} ctx := t.Context() var ctxBypass context.Context projects, _, err := client.Projects.ListProjectsForUser(ctx, "u", opts) @@ -132,7 +132,7 @@ func TestProjectsService_ListUserProjects(t *testing.T) { // still allow both set (no validation enforced) – ensure it does not error ctxBypass = context.WithValue(t.Context(), BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectsForUser(ctxBypass, "u", &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: "b", After: "a"}}); err != nil { + if _, _, err = client.Projects.ListProjectsForUser(ctxBypass, "u", &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("b"), After: Ptr("a")}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } @@ -203,7 +203,7 @@ func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { ]`) }) - opts := &ListProjectsOptions{Query: "text", ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: "2", Before: "1"}} + opts := &ListProjectsOptions{Query: Ptr("text"), ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: Ptr("2"), Before: Ptr("1")}} ctx := t.Context() fields, _, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) if err != nil { @@ -229,7 +229,7 @@ func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { return resp, err }) ctxBypass := context.WithValue(ctx, BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectFieldsForOrg(ctxBypass, "o", 1, &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: "b", After: "a"}}); err != nil { + if _, _, err = client.Projects.ListProjectFieldsForOrg(ctxBypass, "o", 1, &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("b"), After: Ptr("a")}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } @@ -272,7 +272,7 @@ func TestProjectsService_ListProjectFieldsForUser(t *testing.T) { ]`) }) - opts := &ListProjectsOptions{Query: "text", ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: "2", Before: "1"}} + opts := &ListProjectsOptions{Query: Ptr("text"), ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: Ptr("2"), Before: Ptr("1")}} ctx := t.Context() fields, _, err := client.Projects.ListProjectFieldsForUser(ctx, "u", 1, opts) if err != nil { @@ -298,7 +298,7 @@ func TestProjectsService_ListProjectFieldsForUser(t *testing.T) { return resp, err }) ctxBypass := context.WithValue(ctx, BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectFieldsForUser(ctxBypass, "u", 1, &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: "b", After: "a"}}); err != nil { + if _, _, err = client.Projects.ListProjectFieldsForUser(ctxBypass, "u", 1, &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("b"), After: Ptr("a")}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } @@ -416,7 +416,7 @@ func TestProjectsService_ListProjectsForUser_pagination(t *testing.T) { t.Fatalf("expected resp.After=ucursor2 got %q", resp.After) } - opts := &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: resp.After}} + opts := &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: Ptr(resp.After)}} second, resp2, err := client.Projects.ListProjectsForUser(ctx, "u", opts) if err != nil { t.Fatalf("second page error: %v", err) @@ -489,7 +489,7 @@ func TestProjectsService_ListProjectFieldsForOrg_pagination(t *testing.T) { t.Fatalf("expected resp.After=cursor2 got %q", resp.After) } - opts := &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: resp.After}} + opts := &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: Ptr(resp.After)}} second, resp2, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) if err != nil { t.Fatalf("second page error: %v", err) @@ -535,7 +535,7 @@ func TestProjectsService_ListProjectsForOrg_pagination(t *testing.T) { t.Fatalf("expected resp.After=ocursor2 got %q", resp.After) } - opts := &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: resp.After}} + opts := &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: Ptr(resp.After)}} second, resp2, err := client.Projects.ListProjectsForOrg(ctx, "o", opts) if err != nil { t.Fatalf("second page error: %v", err) @@ -582,10 +582,10 @@ func TestProjectV2Field_Marshal(t *testing.T) { field := &ProjectV2Field{ ID: Ptr(int64(2)), - NodeID: "node_1", - Name: "Status", - DataType: "single_select", - URL: "https://api.github.com/projects/1/fields/field1", + NodeID: Ptr("node_1"), + Name: Ptr("Status"), + DataType: Ptr("single_select"), + URL: Ptr("https://api.github.com/projects/1/fields/field1"), Options: []*ProjectV2FieldOption{ { ID: Ptr("1"), @@ -634,7 +634,7 @@ func TestProjectsService_ListProjectItemsForOrg(t *testing.T) { fmt.Fprint(w, `[{"id":17,"node_id":"PVTI_node"}]`) }) - opts := &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: "2", Before: "1", PerPage: 50}, Query: "status:open"}, Fields: []int64{10, 11}} + opts := &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: Ptr("2"), Before: Ptr("1"), PerPage: Ptr(50)}, Query: Ptr("status:open")}, Fields: []int64{10, 11}} ctx := t.Context() items, _, err := client.Projects.ListProjectItemsForOrg(ctx, "o", 1, opts) if err != nil { @@ -659,7 +659,7 @@ func TestProjectsService_ListProjectItemsForOrg(t *testing.T) { }) ctxBypass := context.WithValue(ctx, BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectItemsForOrg(ctxBypass, "o", 1, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: "b", After: "a"}}}); err != nil { + if _, _, err = client.Projects.ListProjectItemsForOrg(ctxBypass, "o", 1, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("b"), After: Ptr("a")}}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } @@ -821,7 +821,7 @@ func TestProjectsService_ListProjectItemsForUser(t *testing.T) { fmt.Fprint(w, `[{"id":7,"node_id":"PVTI_user"}]`) }) ctx := t.Context() - items, _, err := client.Projects.ListProjectItemsForUser(ctx, "u", 2, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{PerPage: 20}, Query: "type:issue"}}) + items, _, err := client.Projects.ListProjectItemsForUser(ctx, "u", 2, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{PerPage: Ptr(20)}, Query: Ptr("type:issue")}}) if err != nil { t.Fatalf("ListProjectItemsForUser error: %v", err) } From 032e591199b02960880ecda6df1f7bf6b9a944ad Mon Sep 17 00:00:00 2001 From: Jonathan Otalora Date: Wed, 29 Oct 2025 17:09:49 -0600 Subject: [PATCH 34/39] additional updates following struct changes to reference types for consistency --- github/github-accessors.go | 64 ++++++++++++++++++++++++ github/github-accessors_test.go | 88 +++++++++++++++++++++++++++++++++ github/projects_test.go | 2 +- 3 files changed, 153 insertions(+), 1 deletion(-) diff --git a/github/github-accessors.go b/github/github-accessors.go index 3b23c19bd50..986e6ed37e7 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -14078,6 +14078,38 @@ func (l *ListOrganizations) GetTotalCount() int { return *l.TotalCount } +// GetQuery returns the Query field if it's non-nil, zero value otherwise. +func (l *ListProjectsOptions) GetQuery() string { + if l == nil || l.Query == nil { + return "" + } + return *l.Query +} + +// GetAfter returns the After field if it's non-nil, zero value otherwise. +func (l *ListProjectsPaginationOptions) GetAfter() string { + if l == nil || l.After == nil { + return "" + } + return *l.After +} + +// GetBefore returns the Before field if it's non-nil, zero value otherwise. +func (l *ListProjectsPaginationOptions) GetBefore() string { + if l == nil || l.Before == nil { + return "" + } + return *l.Before +} + +// GetPerPage returns the PerPage field if it's non-nil, zero value otherwise. +func (l *ListProjectsPaginationOptions) GetPerPage() int { + if l == nil || l.PerPage == nil { + return 0 + } + return *l.PerPage +} + // GetTotalCount returns the TotalCount field if it's non-nil, zero value otherwise. func (l *ListRepositories) GetTotalCount() int { if l == nil || l.TotalCount == nil { @@ -19126,6 +19158,14 @@ func (p *ProjectV2Field) GetCreatedAt() Timestamp { return *p.CreatedAt } +// GetDataType returns the DataType field if it's non-nil, zero value otherwise. +func (p *ProjectV2Field) GetDataType() string { + if p == nil || p.DataType == nil { + return "" + } + return *p.DataType +} + // GetID returns the ID field if it's non-nil, zero value otherwise. func (p *ProjectV2Field) GetID() int64 { if p == nil || p.ID == nil { @@ -19134,6 +19174,22 @@ func (p *ProjectV2Field) GetID() int64 { return *p.ID } +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (p *ProjectV2Field) GetName() string { + if p == nil || p.Name == nil { + return "" + } + return *p.Name +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (p *ProjectV2Field) GetNodeID() string { + if p == nil || p.NodeID == nil { + return "" + } + return *p.NodeID +} + // GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. func (p *ProjectV2Field) GetUpdatedAt() Timestamp { if p == nil || p.UpdatedAt == nil { @@ -19142,6 +19198,14 @@ func (p *ProjectV2Field) GetUpdatedAt() Timestamp { return *p.UpdatedAt } +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (p *ProjectV2Field) GetURL() string { + if p == nil || p.URL == nil { + return "" + } + return *p.URL +} + // GetColor returns the Color field if it's non-nil, zero value otherwise. func (p *ProjectV2FieldOption) GetColor() string { if p == nil || p.Color == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index c182f9f8113..e6c8b60b64d 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -18288,6 +18288,50 @@ func TestListOrganizations_GetTotalCount(tt *testing.T) { l.GetTotalCount() } +func TestListProjectsOptions_GetQuery(tt *testing.T) { + tt.Parallel() + var zeroValue string + l := &ListProjectsOptions{Query: &zeroValue} + l.GetQuery() + l = &ListProjectsOptions{} + l.GetQuery() + l = nil + l.GetQuery() +} + +func TestListProjectsPaginationOptions_GetAfter(tt *testing.T) { + tt.Parallel() + var zeroValue string + l := &ListProjectsPaginationOptions{After: &zeroValue} + l.GetAfter() + l = &ListProjectsPaginationOptions{} + l.GetAfter() + l = nil + l.GetAfter() +} + +func TestListProjectsPaginationOptions_GetBefore(tt *testing.T) { + tt.Parallel() + var zeroValue string + l := &ListProjectsPaginationOptions{Before: &zeroValue} + l.GetBefore() + l = &ListProjectsPaginationOptions{} + l.GetBefore() + l = nil + l.GetBefore() +} + +func TestListProjectsPaginationOptions_GetPerPage(tt *testing.T) { + tt.Parallel() + var zeroValue int + l := &ListProjectsPaginationOptions{PerPage: &zeroValue} + l.GetPerPage() + l = &ListProjectsPaginationOptions{} + l.GetPerPage() + l = nil + l.GetPerPage() +} + func TestListRepositories_GetTotalCount(tt *testing.T) { tt.Parallel() var zeroValue int @@ -24836,6 +24880,17 @@ func TestProjectV2Field_GetCreatedAt(tt *testing.T) { p.GetCreatedAt() } +func TestProjectV2Field_GetDataType(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &ProjectV2Field{DataType: &zeroValue} + p.GetDataType() + p = &ProjectV2Field{} + p.GetDataType() + p = nil + p.GetDataType() +} + func TestProjectV2Field_GetID(tt *testing.T) { tt.Parallel() var zeroValue int64 @@ -24847,6 +24902,28 @@ func TestProjectV2Field_GetID(tt *testing.T) { p.GetID() } +func TestProjectV2Field_GetName(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &ProjectV2Field{Name: &zeroValue} + p.GetName() + p = &ProjectV2Field{} + p.GetName() + p = nil + p.GetName() +} + +func TestProjectV2Field_GetNodeID(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &ProjectV2Field{NodeID: &zeroValue} + p.GetNodeID() + p = &ProjectV2Field{} + p.GetNodeID() + p = nil + p.GetNodeID() +} + func TestProjectV2Field_GetUpdatedAt(tt *testing.T) { tt.Parallel() var zeroValue Timestamp @@ -24858,6 +24935,17 @@ func TestProjectV2Field_GetUpdatedAt(tt *testing.T) { p.GetUpdatedAt() } +func TestProjectV2Field_GetURL(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &ProjectV2Field{URL: &zeroValue} + p.GetURL() + p = &ProjectV2Field{} + p.GetURL() + p = nil + p.GetURL() +} + func TestProjectV2FieldOption_GetColor(tt *testing.T) { tt.Parallel() var zeroValue string diff --git a/github/projects_test.go b/github/projects_test.go index 60475d6354a..1c6149ba7bd 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -56,7 +56,7 @@ func TestProjectsService_ListProjectsForOrg(t *testing.T) { // still allow both set (no validation enforced) – ensure it does not error ctxBypass := context.WithValue(t.Context(), BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectsForOrg(ctxBypass, "o", &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: String("b"), After: String("a")}}); err != nil { + if _, _, err = client.Projects.ListProjectsForOrg(ctxBypass, "o", &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("b"), After: Ptr("a")}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } From c2f8bfadc2701c3504de4e4d05e623d478899dba Mon Sep 17 00:00:00 2001 From: Jonathan Otalora Date: Thu, 30 Oct 2025 12:43:13 -0600 Subject: [PATCH 35/39] Refactors project field specific method names to match the standardized naming convention introduced in #3761 for consistency across the client API. --- github/projects.go | 16 +++++++------- github/projects_test.go | 36 +++++++++++++++---------------- test/integration/projects_test.go | 4 ++-- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/github/projects.go b/github/projects.go index 55c68edae8a..b87c304b2bc 100644 --- a/github/projects.go +++ b/github/projects.go @@ -189,12 +189,12 @@ func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string return project, resp, nil } -// ListProjectFieldsForOrg lists Projects V2 for an organization. +// ListOrganizationProjectFields lists Projects V2 for an organization. // // GitHub API docs: https://docs.github.com/rest/projects/fields#list-project-fields-for-organization // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/fields -func (s *ProjectsService) ListProjectFieldsForOrg(ctx context.Context, org string, projectNumber int, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { +func (s *ProjectsService) ListOrganizationProjectFields(ctx context.Context, org string, projectNumber int, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/fields", org, projectNumber) u, err := addOptions(u, opts) if err != nil { @@ -214,12 +214,12 @@ func (s *ProjectsService) ListProjectFieldsForOrg(ctx context.Context, org strin return fields, resp, nil } -// ListProjectFieldsForUser lists Projects V2 for a user. +// ListUserProjectFields lists Projects V2 for a user. // // GitHub API docs: https://docs.github.com/rest/projects/fields#list-project-fields-for-user // //meta:operation GET /users/{username}/projectsV2/{project_number}/fields -func (s *ProjectsService) ListProjectFieldsForUser(ctx context.Context, user string, projectNumber int, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { +func (s *ProjectsService) ListUserProjectFields(ctx context.Context, user string, projectNumber int, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/fields", user, projectNumber) u, err := addOptions(u, opts) if err != nil { @@ -239,12 +239,12 @@ func (s *ProjectsService) ListProjectFieldsForUser(ctx context.Context, user str return fields, resp, nil } -// GetProjectFieldForOrg gets a single project field from an organization owned project. +// GetOrganizationProjectField gets a single project field from an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/fields#get-project-field-for-organization // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/fields/{field_id} -func (s *ProjectsService) GetProjectFieldForOrg(ctx context.Context, org string, projectNumber int, fieldID int64) (*ProjectV2Field, *Response, error) { +func (s *ProjectsService) GetOrganizationProjectField(ctx context.Context, org string, projectNumber int, fieldID int64) (*ProjectV2Field, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/fields/%v", org, projectNumber, fieldID) req, err := s.client.NewRequest("GET", u, nil) if err != nil { @@ -259,12 +259,12 @@ func (s *ProjectsService) GetProjectFieldForOrg(ctx context.Context, org string, return field, resp, nil } -// GetProjectFieldForUser gets a single project field from a user owned project. +// GetUserProjectField gets a single project field from a user owned project. // // GitHub API docs: https://docs.github.com/rest/projects/fields#get-project-field-for-user // //meta:operation GET /users/{username}/projectsV2/{project_number}/fields/{field_id} -func (s *ProjectsService) GetProjectFieldForUser(ctx context.Context, user string, projectNumber int, fieldID int64) (*ProjectV2Field, *Response, error) { +func (s *ProjectsService) GetUserProjectField(ctx context.Context, user string, projectNumber int, fieldID int64) (*ProjectV2Field, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/fields/%v", user, projectNumber, fieldID) req, err := s.client.NewRequest("GET", u, nil) if err != nil { diff --git a/github/projects_test.go b/github/projects_test.go index 1c6149ba7bd..c00e01b4073 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -205,7 +205,7 @@ func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { opts := &ListProjectsOptions{Query: Ptr("text"), ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: Ptr("2"), Before: Ptr("1")}} ctx := t.Context() - fields, _, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) + fields, _, err := client.Projects.ListOrganizationProjectFields(ctx, "o", 1, opts) if err != nil { t.Fatalf("Projects.ListProjectFieldsForOrg returned error: %v", err) } @@ -218,23 +218,23 @@ func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { const methodName = "ListProjectFieldsForOrg" testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Projects.ListProjectFieldsForOrg(ctx, "\n", 1, opts) + _, _, err = client.Projects.ListOrganizationProjectFields(ctx, "\n", 1, opts) return err }) testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) + got, resp, err := client.Projects.ListOrganizationProjectFields(ctx, "o", 1, opts) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } return resp, err }) ctxBypass := context.WithValue(ctx, BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectFieldsForOrg(ctxBypass, "o", 1, &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("b"), After: Ptr("a")}}); err != nil { + if _, _, err = client.Projects.ListOrganizationProjectFields(ctxBypass, "o", 1, &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("b"), After: Ptr("a")}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } -func TestProjectsService_ListProjectFieldsForUser(t *testing.T) { +func TestProjectsService_ListUserProjectFields(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -274,7 +274,7 @@ func TestProjectsService_ListProjectFieldsForUser(t *testing.T) { opts := &ListProjectsOptions{Query: Ptr("text"), ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: Ptr("2"), Before: Ptr("1")}} ctx := t.Context() - fields, _, err := client.Projects.ListProjectFieldsForUser(ctx, "u", 1, opts) + fields, _, err := client.Projects.ListUserProjectFields(ctx, "u", 1, opts) if err != nil { t.Fatalf("Projects.ListProjectFieldsForUser returned error: %v", err) } @@ -287,23 +287,23 @@ func TestProjectsService_ListProjectFieldsForUser(t *testing.T) { const methodName = "ListProjectFieldsForUser" testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Projects.ListProjectFieldsForUser(ctx, "\n", 1, opts) + _, _, err = client.Projects.ListUserProjectFields(ctx, "\n", 1, opts) return err }) testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.ListProjectFieldsForUser(ctx, "u", 1, opts) + got, resp, err := client.Projects.ListUserProjectFields(ctx, "u", 1, opts) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } return resp, err }) ctxBypass := context.WithValue(ctx, BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectFieldsForUser(ctxBypass, "u", 1, &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("b"), After: Ptr("a")}}); err != nil { + if _, _, err = client.Projects.ListUserProjectFields(ctxBypass, "u", 1, &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("b"), After: Ptr("a")}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } -func TestProjectsService_GetProjectFieldForOrg(t *testing.T) { +func TestProjectsService_GetOrganizationProjectField(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -326,7 +326,7 @@ func TestProjectsService_GetProjectFieldForOrg(t *testing.T) { }) ctx := t.Context() - field, _, err := client.Projects.GetProjectFieldForOrg(ctx, "o", 1, 1) + field, _, err := client.Projects.GetOrganizationProjectField(ctx, "o", 1, 1) if err != nil { t.Fatalf("Projects.GetProjectFieldForOrg returned error: %v", err) } @@ -336,7 +336,7 @@ func TestProjectsService_GetProjectFieldForOrg(t *testing.T) { const methodName = "GetProjectFieldForOrg" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.GetProjectFieldForOrg(ctx, "o", 1, 1) + got, resp, err := client.Projects.GetOrganizationProjectField(ctx, "o", 1, 1) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -344,7 +344,7 @@ func TestProjectsService_GetProjectFieldForOrg(t *testing.T) { }) } -func TestProjectsService_GetProjectFieldForUser(t *testing.T) { +func TestProjectsService_GetUserProjectField(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -367,7 +367,7 @@ func TestProjectsService_GetProjectFieldForUser(t *testing.T) { }) ctx := t.Context() - field, _, err := client.Projects.GetProjectFieldForUser(ctx, "u", 1, 3) + field, _, err := client.Projects.GetUserProjectField(ctx, "u", 1, 3) if err != nil { t.Fatalf("Projects.GetProjectFieldForUser returned error: %v", err) } @@ -377,7 +377,7 @@ func TestProjectsService_GetProjectFieldForUser(t *testing.T) { const methodName = "GetProjectFieldForUser" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.GetProjectFieldForUser(ctx, "u", 1, 3) + got, resp, err := client.Projects.GetUserProjectField(ctx, "u", 1, 3) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -452,7 +452,7 @@ func TestProjectsService_ListProjectsForUser_error(t *testing.T) { }) } -func TestProjectsService_ListProjectFieldsForOrg_pagination(t *testing.T) { +func TestProjectsService_ListOrganizationProjectFields_pagination(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -478,7 +478,7 @@ func TestProjectsService_ListProjectFieldsForOrg_pagination(t *testing.T) { }) ctx := t.Context() - first, resp, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, nil) + first, resp, err := client.Projects.ListOrganizationProjectFields(ctx, "o", 1, nil) if err != nil { t.Fatalf("first page error: %v", err) } @@ -490,7 +490,7 @@ func TestProjectsService_ListProjectFieldsForOrg_pagination(t *testing.T) { } opts := &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: Ptr(resp.After)}} - second, resp2, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) + second, resp2, err := client.Projects.ListOrganizationProjectFields(ctx, "o", 1, opts) if err != nil { t.Fatalf("second page error: %v", err) } diff --git a/test/integration/projects_test.go b/test/integration/projects_test.go index f51af52691f..d9f43151c3a 100644 --- a/test/integration/projects_test.go +++ b/test/integration/projects_test.go @@ -66,9 +66,9 @@ func TestProjectsV2_Org(t *testing.T) { t.Fatalf("GetProjectForOrg returned unexpected project number: got %+v want %d", proj.Number, projectNumber) } - _, _, err = client.Projects.ListProjectFieldsForOrg(ctx, org, projectNumber, nil) + _, _, err = client.Projects.ListOrganizationProjectFields(ctx, org, projectNumber, nil) if err != nil { - t.Fatalf("Projects.ListProjectFieldsForOrg returned error: %v. Fields listing might require extra permissions", err) + t.Fatalf("Projects.ListOrganizationProjectFields returned error: %v. Fields listing might require extra permissions", err) } } From 00873b1da2e58f5c8410a7c245e2e150faffa23d Mon Sep 17 00:00:00 2001 From: Jonathan Otalora Date: Thu, 30 Oct 2025 12:58:49 -0600 Subject: [PATCH 36/39] Refactors project specific method names to adhere to standardized naming convention defined in #3761 --- github/projects.go | 16 +++++------ github/projects_test.go | 44 +++++++++++++++---------------- test/integration/projects_test.go | 20 +++++++------- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/github/projects.go b/github/projects.go index b87c304b2bc..16f13053dfb 100644 --- a/github/projects.go +++ b/github/projects.go @@ -100,12 +100,12 @@ type ProjectV2Field struct { UpdatedAt *Timestamp `json:"updated_at,omitempty"` } -// ListProjectsForOrg lists Projects V2 for an organization. +// ListOrganizationProjects lists Projects V2 for an organization. // // GitHub API docs: https://docs.github.com/rest/projects/projects#list-projects-for-organization // //meta:operation GET /orgs/{org}/projectsV2 -func (s *ProjectsService) ListProjectsForOrg(ctx context.Context, org string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { +func (s *ProjectsService) ListOrganizationProjects(ctx context.Context, org string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2", org) u, err := addOptions(u, opts) if err != nil { @@ -125,12 +125,12 @@ func (s *ProjectsService) ListProjectsForOrg(ctx context.Context, org string, op return projects, resp, nil } -// GetProjectForOrg gets a Projects V2 project for an organization by ID. +// GetOrganizationProject gets a Projects V2 project for an organization by ID. // // GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-organization // //meta:operation GET /orgs/{org}/projectsV2/{project_number} -func (s *ProjectsService) GetProjectForOrg(ctx context.Context, org string, projectNumber int) (*ProjectV2, *Response, error) { +func (s *ProjectsService) GetOrganizationProject(ctx context.Context, org string, projectNumber int) (*ProjectV2, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v", org, projectNumber) req, err := s.client.NewRequest("GET", u, nil) if err != nil { @@ -145,12 +145,12 @@ func (s *ProjectsService) GetProjectForOrg(ctx context.Context, org string, proj return project, resp, nil } -// ListProjectsForUser lists Projects V2 for a user. +// ListUserProjects lists Projects V2 for a user. // // GitHub API docs: https://docs.github.com/rest/projects/projects#list-projects-for-user // //meta:operation GET /users/{username}/projectsV2 -func (s *ProjectsService) ListProjectsForUser(ctx context.Context, username string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { +func (s *ProjectsService) ListUserProjects(ctx context.Context, username string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2", username) u, err := addOptions(u, opts) if err != nil { @@ -169,12 +169,12 @@ func (s *ProjectsService) ListProjectsForUser(ctx context.Context, username stri return projects, resp, nil } -// GetProjectForUser gets a Projects V2 project for a user by ID. +// GetUserProject gets a Projects V2 project for a user by ID. // // GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-user // //meta:operation GET /users/{username}/projectsV2/{project_number} -func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string, projectNumber int) (*ProjectV2, *Response, error) { +func (s *ProjectsService) GetUserProject(ctx context.Context, username string, projectNumber int) (*ProjectV2, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v", username, projectNumber) req, err := s.client.NewRequest("GET", u, nil) if err != nil { diff --git a/github/projects_test.go b/github/projects_test.go index c00e01b4073..b5b8b56638d 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -32,7 +32,7 @@ func TestProjectsService_ListProjectsForOrg(t *testing.T) { opts := &ListProjectsOptions{Query: Ptr("alpha"), ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: Ptr("2"), Before: Ptr("1")}} ctx := t.Context() - projects, _, err := client.Projects.ListProjectsForOrg(ctx, "o", opts) + projects, _, err := client.Projects.ListOrganizationProjects(ctx, "o", opts) if err != nil { t.Fatalf("Projects.ListProjectsForOrg returned error: %v", err) } @@ -42,12 +42,12 @@ func TestProjectsService_ListProjectsForOrg(t *testing.T) { const methodName = "ListProjectsForOrg" testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Projects.ListProjectsForOrg(ctx, "\n", opts) + _, _, err = client.Projects.ListOrganizationProjects(ctx, "\n", opts) return err }) testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.ListProjectsForOrg(ctx, "o", opts) + got, resp, err := client.Projects.ListOrganizationProjects(ctx, "o", opts) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -56,12 +56,12 @@ func TestProjectsService_ListProjectsForOrg(t *testing.T) { // still allow both set (no validation enforced) – ensure it does not error ctxBypass := context.WithValue(t.Context(), BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectsForOrg(ctxBypass, "o", &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("b"), After: Ptr("a")}}); err != nil { + if _, _, err = client.Projects.ListOrganizationProjects(ctxBypass, "o", &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("b"), After: Ptr("a")}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } -func TestProjectsService_GetProjectForOrg(t *testing.T) { +func TestProjectsService_GetOrganizationProject(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -71,7 +71,7 @@ func TestProjectsService_GetProjectForOrg(t *testing.T) { }) ctx := t.Context() - project, _, err := client.Projects.GetProjectForOrg(ctx, "o", 1) + project, _, err := client.Projects.GetOrganizationProject(ctx, "o", 1) if err != nil { t.Fatalf("Projects.GetProjectForOrg returned error: %v", err) } @@ -81,7 +81,7 @@ func TestProjectsService_GetProjectForOrg(t *testing.T) { const methodName = "GetProjectForOrg" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.GetProjectForOrg(ctx, "o", 1) + got, resp, err := client.Projects.GetOrganizationProject(ctx, "o", 1) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -108,7 +108,7 @@ func TestProjectsService_ListUserProjects(t *testing.T) { opts := &ListProjectsOptions{Query: Ptr("beta"), ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("1"), After: Ptr("2"), PerPage: Ptr(2)}} ctx := t.Context() var ctxBypass context.Context - projects, _, err := client.Projects.ListProjectsForUser(ctx, "u", opts) + projects, _, err := client.Projects.ListUserProjects(ctx, "u", opts) if err != nil { t.Fatalf("Projects.ListProjectsForUser returned error: %v", err) } @@ -118,12 +118,12 @@ func TestProjectsService_ListUserProjects(t *testing.T) { const methodName = "ListProjectsForUser" testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Projects.ListProjectsForUser(ctx, "\n", opts) + _, _, err = client.Projects.ListUserProjects(ctx, "\n", opts) return err }) testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.ListProjectsForUser(ctx, "u", opts) + got, resp, err := client.Projects.ListUserProjects(ctx, "u", opts) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -132,12 +132,12 @@ func TestProjectsService_ListUserProjects(t *testing.T) { // still allow both set (no validation enforced) – ensure it does not error ctxBypass = context.WithValue(t.Context(), BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectsForUser(ctxBypass, "u", &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("b"), After: Ptr("a")}}); err != nil { + if _, _, err = client.Projects.ListUserProjects(ctxBypass, "u", &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("b"), After: Ptr("a")}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } -func TestProjectsService_GetProjectForUser(t *testing.T) { +func TestProjectsService_GetUserProject(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -147,7 +147,7 @@ func TestProjectsService_GetProjectForUser(t *testing.T) { }) ctx := t.Context() - project, _, err := client.Projects.GetProjectForUser(ctx, "u", 3) + project, _, err := client.Projects.GetUserProject(ctx, "u", 3) if err != nil { t.Fatalf("Projects.GetProjectForUser returned error: %v", err) } @@ -157,7 +157,7 @@ func TestProjectsService_GetProjectForUser(t *testing.T) { const methodName = "GetProjectForUser" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.GetProjectForUser(ctx, "u", 3) + got, resp, err := client.Projects.GetUserProject(ctx, "u", 3) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -385,7 +385,7 @@ func TestProjectsService_GetUserProjectField(t *testing.T) { }) } -func TestProjectsService_ListProjectsForUser_pagination(t *testing.T) { +func TestProjectsService_ListUserProjects_pagination(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/users/u/projectsV2", func(w http.ResponseWriter, r *http.Request) { @@ -405,7 +405,7 @@ func TestProjectsService_ListProjectsForUser_pagination(t *testing.T) { http.Error(w, "unexpected query", http.StatusBadRequest) }) ctx := t.Context() - first, resp, err := client.Projects.ListProjectsForUser(ctx, "u", nil) + first, resp, err := client.Projects.ListUserProjects(ctx, "u", nil) if err != nil { t.Fatalf("first page error: %v", err) } @@ -417,7 +417,7 @@ func TestProjectsService_ListProjectsForUser_pagination(t *testing.T) { } opts := &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: Ptr(resp.After)}} - second, resp2, err := client.Projects.ListProjectsForUser(ctx, "u", opts) + second, resp2, err := client.Projects.ListUserProjects(ctx, "u", opts) if err != nil { t.Fatalf("second page error: %v", err) } @@ -439,7 +439,7 @@ func TestProjectsService_ListProjectsForUser_error(t *testing.T) { ctx := t.Context() const methodName = "ListProjectsForUser" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.ListProjectsForUser(ctx, "u", nil) + got, resp, err := client.Projects.ListUserProjects(ctx, "u", nil) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -447,7 +447,7 @@ func TestProjectsService_ListProjectsForUser_error(t *testing.T) { }) // bad options (bad username) should error testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Projects.ListProjectsForUser(ctx, "\n", nil) + _, _, err = client.Projects.ListUserProjects(ctx, "\n", nil) return err }) } @@ -502,7 +502,7 @@ func TestProjectsService_ListOrganizationProjectFields_pagination(t *testing.T) } } -func TestProjectsService_ListProjectsForOrg_pagination(t *testing.T) { +func TestProjectsService_ListOrganizationProjects_pagination(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -524,7 +524,7 @@ func TestProjectsService_ListProjectsForOrg_pagination(t *testing.T) { }) ctx := t.Context() - first, resp, err := client.Projects.ListProjectsForOrg(ctx, "o", nil) + first, resp, err := client.Projects.ListOrganizationProjects(ctx, "o", nil) if err != nil { t.Fatalf("first page error: %v", err) } @@ -536,7 +536,7 @@ func TestProjectsService_ListProjectsForOrg_pagination(t *testing.T) { } opts := &ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: Ptr(resp.After)}} - second, resp2, err := client.Projects.ListProjectsForOrg(ctx, "o", opts) + second, resp2, err := client.Projects.ListOrganizationProjects(ctx, "o", opts) if err != nil { t.Fatalf("second page error: %v", err) } diff --git a/test/integration/projects_test.go b/test/integration/projects_test.go index d9f43151c3a..1357fcca816 100644 --- a/test/integration/projects_test.go +++ b/test/integration/projects_test.go @@ -41,10 +41,10 @@ func TestProjectsV2_Org(t *testing.T) { opts := &github.ListProjectsOptions{} // List projects for org; pick the first available project we can read. - projects, _, err := client.Projects.ListProjectsForOrg(ctx, org, opts) + projects, _, err := client.Projects.ListOrganizationProjects(ctx, org, opts) if err != nil { // If listing itself fails, abort this test. - t.Fatalf("Projects.ListProjectsForOrg returned error: %v", err) + t.Fatalf("Projects.ListOrganizationProjects returned error: %v", err) } if len(projects) == 0 { t.Skipf("no Projects V2 found for org %s", org) @@ -56,14 +56,14 @@ func TestProjectsV2_Org(t *testing.T) { projectNumber := *project.Number // Re-fetch via Get to exercise endpoint explicitly. - proj, _, err := client.Projects.GetProjectForOrg(ctx, org, projectNumber) + proj, _, err := client.Projects.GetOrganizationProject(ctx, org, projectNumber) if err != nil { // Permission mismatch? Skip CRUD while still reporting failure would make the test fail; // we want correctness so treat as fatal here. - t.Fatalf("Projects.GetProjectForOrg returned error: %v", err) + t.Fatalf("Projects.GetOrganizationProject returned error: %v", err) } if proj.Number == nil || *proj.Number != projectNumber { - t.Fatalf("GetProjectForOrg returned unexpected project number: got %+v want %d", proj.Number, projectNumber) + t.Fatalf("GetOrganizationProject returned unexpected project number: got %+v want %d", proj.Number, projectNumber) } _, _, err = client.Projects.ListOrganizationProjectFields(ctx, org, projectNumber, nil) @@ -81,9 +81,9 @@ func TestProjectsV2_User(t *testing.T) { ctx := t.Context() opts := &github.ListProjectsOptions{} - projects, _, err := client.Projects.ListProjectsForUser(ctx, user, opts) + projects, _, err := client.Projects.ListUserProjects(ctx, user, opts) if err != nil { - t.Fatalf("Projects.ListProjectsForUser returned error: %v. This indicates API or permission issue", err) + t.Fatalf("Projects.ListUserProjects returned error: %v. This indicates API or permission issue", err) } if len(projects) == 0 { t.Skipf("no Projects V2 found for user %s", user) @@ -93,12 +93,12 @@ func TestProjectsV2_User(t *testing.T) { t.Skip("selected user project has nil Number field") } - proj, _, err := client.Projects.GetProjectForUser(ctx, user, *project.Number) + proj, _, err := client.Projects.GetUserProject(ctx, user, *project.Number) if err != nil { // can't fetch specific project; treat as fatal - t.Fatalf("Projects.GetProjectForUser returned error: %v", err) + t.Fatalf("Projects.GetUserProject returned error: %v", err) } if proj.Number == nil || *proj.Number != *project.Number { - t.Fatalf("GetProjectForUser returned unexpected project number: got %+v want %d", proj.Number, *project.Number) + t.Fatalf("GetUserProject returned unexpected project number: got %+v want %d", proj.Number, *project.Number) } } From 7f8c2f9abdb2731b17e9c7602b2afa122e5d2fd6 Mon Sep 17 00:00:00 2001 From: Jonathan Otalora Date: Thu, 30 Oct 2025 13:28:14 -0600 Subject: [PATCH 37/39] Refctors project items specific method names to adhere to standardized naming convention defined in #3761 --- github/projects.go | 40 ++++++++++---------- github/projects_test.go | 82 ++++++++++++++++++++--------------------- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/github/projects.go b/github/projects.go index 16f13053dfb..44d3abd4636 100644 --- a/github/projects.go +++ b/github/projects.go @@ -313,12 +313,12 @@ type UpdateProjectItemOptions struct { Fields []*ProjectV2Field `json:"fields,omitempty"` } -// ListProjectItemsForOrg lists items for an organization owned project. +// ListOrganizationProjectItems lists items for an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items#list-items-for-an-organization-owned-project // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/items -func (s *ProjectsService) ListProjectItemsForOrg(ctx context.Context, org string, projectNumber int, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { +func (s *ProjectsService) ListOrganizationProjectItems(ctx context.Context, org string, projectNumber int, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items", org, projectNumber) u, err := addOptions(u, opts) if err != nil { @@ -338,12 +338,12 @@ func (s *ProjectsService) ListProjectItemsForOrg(ctx context.Context, org string return items, resp, nil } -// AddProjectItemForOrg adds an issue or pull request item to an organization owned project. +// AddOrganizationProjectItem adds an issue or pull request item to an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items#add-item-to-organization-owned-project // //meta:operation POST /orgs/{org}/projectsV2/{project_number}/items -func (s *ProjectsService) AddProjectItemForOrg(ctx context.Context, org string, projectNumber int, opts *AddProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) AddOrganizationProjectItem(ctx context.Context, org string, projectNumber int, opts *AddProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items", org, projectNumber) req, err := s.client.NewRequest("POST", u, opts) if err != nil { @@ -358,12 +358,12 @@ func (s *ProjectsService) AddProjectItemForOrg(ctx context.Context, org string, return item, resp, nil } -// GetProjectItemForOrg gets a single item from an organization owned project. +// GetOrganizationProjectItem gets a single item from an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items#get-an-item-for-an-organization-owned-project // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) GetProjectItemForOrg(ctx context.Context, org string, projectNumber int, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) GetOrganizationProjectItem(ctx context.Context, org string, projectNumber int, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) req, err := s.client.NewRequest("GET", u, opts) if err != nil { @@ -377,12 +377,12 @@ func (s *ProjectsService) GetProjectItemForOrg(ctx context.Context, org string, return item, resp, nil } -// UpdateProjectItemForOrg updates an item in an organization owned project. +// UpdateOrganizationProjectItem updates an item in an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items#update-project-item-for-organization // //meta:operation PATCH /orgs/{org}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) UpdateProjectItemForOrg(ctx context.Context, org string, projectNumber int, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) UpdateOrganizationProjectItem(ctx context.Context, org string, projectNumber int, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) req, err := s.client.NewRequest("PATCH", u, opts) if err != nil { @@ -396,12 +396,12 @@ func (s *ProjectsService) UpdateProjectItemForOrg(ctx context.Context, org strin return item, resp, nil } -// DeleteProjectItemForOrg deletes an item from an organization owned project. +// DeleteOrganizationProjectItem deletes an item from an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items#delete-project-item-for-organization // //meta:operation DELETE /orgs/{org}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) DeleteProjectItemForOrg(ctx context.Context, org string, projectNumber int, itemID int64) (*Response, error) { +func (s *ProjectsService) DeleteOrganizationProjectItem(ctx context.Context, org string, projectNumber int, itemID int64) (*Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) req, err := s.client.NewRequest("DELETE", u, nil) if err != nil { @@ -410,12 +410,12 @@ func (s *ProjectsService) DeleteProjectItemForOrg(ctx context.Context, org strin return s.client.Do(ctx, req, nil) } -// ListProjectItemsForUser lists items for a user owned project. +// ListUserProjectItems lists items for a user owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items#list-items-for-a-user-owned-project // //meta:operation GET /users/{username}/projectsV2/{project_number}/items -func (s *ProjectsService) ListProjectItemsForUser(ctx context.Context, username string, projectNumber int, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { +func (s *ProjectsService) ListUserProjectItems(ctx context.Context, username string, projectNumber int, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items", username, projectNumber) u, err := addOptions(u, opts) if err != nil { @@ -433,12 +433,12 @@ func (s *ProjectsService) ListProjectItemsForUser(ctx context.Context, username return items, resp, nil } -// AddProjectItemForUser adds an issue or pull request item to a user owned project. +// AddUserProjectItem adds an issue or pull request item to a user owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items#add-item-to-user-owned-project // //meta:operation POST /users/{username}/projectsV2/{project_number}/items -func (s *ProjectsService) AddProjectItemForUser(ctx context.Context, username string, projectNumber int, opts *AddProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) AddUserProjectItem(ctx context.Context, username string, projectNumber int, opts *AddProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items", username, projectNumber) req, err := s.client.NewRequest("POST", u, opts) if err != nil { @@ -452,12 +452,12 @@ func (s *ProjectsService) AddProjectItemForUser(ctx context.Context, username st return item, resp, nil } -// GetProjectItemForUser gets a single item from a user owned project. +// GetUserProjectItem gets a single item from a user owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items#get-an-item-for-a-user-owned-project // //meta:operation GET /users/{username}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) GetProjectItemForUser(ctx context.Context, username string, projectNumber int, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) GetUserProjectItem(ctx context.Context, username string, projectNumber int, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) req, err := s.client.NewRequest("GET", u, opts) if err != nil { @@ -471,12 +471,12 @@ func (s *ProjectsService) GetProjectItemForUser(ctx context.Context, username st return item, resp, nil } -// UpdateProjectItemForUser updates an item in a user owned project. +// UpdateUserProjectItem updates an item in a user owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items#update-project-item-for-user // //meta:operation PATCH /users/{username}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) UpdateProjectItemForUser(ctx context.Context, username string, projectNumber int, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { +func (s *ProjectsService) UpdateUserProjectItem(ctx context.Context, username string, projectNumber int, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) req, err := s.client.NewRequest("PATCH", u, opts) if err != nil { @@ -490,12 +490,12 @@ func (s *ProjectsService) UpdateProjectItemForUser(ctx context.Context, username return item, resp, nil } -// DeleteProjectItemForUser deletes an item from a user owned project. +// DeleteUserProjectItem deletes an item from a user owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items#delete-project-item-for-user // //meta:operation DELETE /users/{username}/projectsV2/{project_number}/items/{item_id} -func (s *ProjectsService) DeleteProjectItemForUser(ctx context.Context, username string, projectNumber int, itemID int64) (*Response, error) { +func (s *ProjectsService) DeleteUserProjectItem(ctx context.Context, username string, projectNumber int, itemID int64) (*Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) req, err := s.client.NewRequest("DELETE", u, nil) if err != nil { diff --git a/github/projects_test.go b/github/projects_test.go index b5b8b56638d..96751e6107d 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -619,7 +619,7 @@ func TestProjectV2Field_Marshal(t *testing.T) { testJSONMarshal(t, field, want) } -func TestProjectsService_ListProjectItemsForOrg(t *testing.T) { +func TestProjectsService_ListOrganizationProjectItems(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -636,7 +636,7 @@ func TestProjectsService_ListProjectItemsForOrg(t *testing.T) { opts := &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{After: Ptr("2"), Before: Ptr("1"), PerPage: Ptr(50)}, Query: Ptr("status:open")}, Fields: []int64{10, 11}} ctx := t.Context() - items, _, err := client.Projects.ListProjectItemsForOrg(ctx, "o", 1, opts) + items, _, err := client.Projects.ListOrganizationProjectItems(ctx, "o", 1, opts) if err != nil { t.Fatalf("Projects.ListProjectItemsForOrg returned error: %v", err) } @@ -646,12 +646,12 @@ func TestProjectsService_ListProjectItemsForOrg(t *testing.T) { const methodName = "ListProjectItemsForOrg" testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Projects.ListProjectItemsForOrg(ctx, "\n", 1, opts) + _, _, err = client.Projects.ListOrganizationProjectItems(ctx, "\n", 1, opts) return err }) testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.ListProjectItemsForOrg(ctx, "o", 1, opts) + got, resp, err := client.Projects.ListOrganizationProjectItems(ctx, "o", 1, opts) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -659,12 +659,12 @@ func TestProjectsService_ListProjectItemsForOrg(t *testing.T) { }) ctxBypass := context.WithValue(ctx, BypassRateLimitCheck, true) - if _, _, err = client.Projects.ListProjectItemsForOrg(ctxBypass, "o", 1, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("b"), After: Ptr("a")}}}); err != nil { + if _, _, err = client.Projects.ListOrganizationProjectItems(ctxBypass, "o", 1, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{Before: Ptr("b"), After: Ptr("a")}}}); err != nil { t.Fatalf("unexpected error when both before/after set: %v", err) } } -func TestProjectsService_AddProjectItemForOrg(t *testing.T) { +func TestProjectsService_AddOrganizationProjectItem(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -679,7 +679,7 @@ func TestProjectsService_AddProjectItemForOrg(t *testing.T) { }) ctx := t.Context() - item, _, err := client.Projects.AddProjectItemForOrg(ctx, "o", 1, &AddProjectItemOptions{Type: "Issue", ID: 99}) + item, _, err := client.Projects.AddOrganizationProjectItem(ctx, "o", 1, &AddProjectItemOptions{Type: "Issue", ID: 99}) if err != nil { t.Fatalf("Projects.AddProjectItemForOrg returned error: %v", err) } @@ -699,7 +699,7 @@ func TestProjectsService_AddProjectItemForOrg_error(t *testing.T) { ctx := t.Context() const methodName = "AddProjectItemForOrg" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.AddProjectItemForOrg(ctx, "o", 1, &AddProjectItemOptions{Type: "Issue", ID: 1}) + got, resp, err := client.Projects.AddOrganizationProjectItem(ctx, "o", 1, &AddProjectItemOptions{Type: "Issue", ID: 1}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -707,7 +707,7 @@ func TestProjectsService_AddProjectItemForOrg_error(t *testing.T) { }) } -func TestProjectsService_GetProjectItemForOrg(t *testing.T) { +func TestProjectsService_GetOrganizationProjectItem(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { @@ -716,7 +716,7 @@ func TestProjectsService_GetProjectItemForOrg(t *testing.T) { }) ctx := t.Context() opts := &GetProjectItemOptions{} - item, _, err := client.Projects.GetProjectItemForOrg(ctx, "o", 1, 17, opts) + item, _, err := client.Projects.GetOrganizationProjectItem(ctx, "o", 1, 17, opts) if err != nil { t.Fatalf("GetProjectItemForOrg error: %v", err) } @@ -725,7 +725,7 @@ func TestProjectsService_GetProjectItemForOrg(t *testing.T) { } } -func TestProjectsService_GetProjectItemForOrg_error(t *testing.T) { +func TestProjectsService_GetOrganizationProjectItem_error(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { @@ -735,7 +735,7 @@ func TestProjectsService_GetProjectItemForOrg_error(t *testing.T) { ctx := t.Context() const methodName = "GetProjectItemForOrg" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.GetProjectItemForOrg(ctx, "o", 1, 17, &GetProjectItemOptions{}) + got, resp, err := client.Projects.GetOrganizationProjectItem(ctx, "o", 1, 17, &GetProjectItemOptions{}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -743,7 +743,7 @@ func TestProjectsService_GetProjectItemForOrg_error(t *testing.T) { }) } -func TestProjectsService_UpdateProjectItemForOrg(t *testing.T) { +func TestProjectsService_UpdateOrganizationProjectItem(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { @@ -757,7 +757,7 @@ func TestProjectsService_UpdateProjectItemForOrg(t *testing.T) { }) archived := true ctx := t.Context() - item, _, err := client.Projects.UpdateProjectItemForOrg(ctx, "o", 1, 17, &UpdateProjectItemOptions{Archived: &archived}) + item, _, err := client.Projects.UpdateOrganizationProjectItem(ctx, "o", 1, 17, &UpdateProjectItemOptions{Archived: &archived}) if err != nil { t.Fatalf("UpdateProjectItemForOrg error: %v", err) } @@ -766,7 +766,7 @@ func TestProjectsService_UpdateProjectItemForOrg(t *testing.T) { } } -func TestProjectsService_UpdateProjectItemForOrg_error(t *testing.T) { +func TestProjectsService_UpdateOrganizationProjectItem_error(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { @@ -777,7 +777,7 @@ func TestProjectsService_UpdateProjectItemForOrg_error(t *testing.T) { ctx := t.Context() const methodName = "UpdateProjectItemForOrg" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.UpdateProjectItemForOrg(ctx, "o", 1, 17, &UpdateProjectItemOptions{Archived: &archived}) + got, resp, err := client.Projects.UpdateOrganizationProjectItem(ctx, "o", 1, 17, &UpdateProjectItemOptions{Archived: &archived}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -785,7 +785,7 @@ func TestProjectsService_UpdateProjectItemForOrg_error(t *testing.T) { }) } -func TestProjectsService_DeleteProjectItemForOrg(t *testing.T) { +func TestProjectsService_DeleteOrganizationProjectItem(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { @@ -793,12 +793,12 @@ func TestProjectsService_DeleteProjectItemForOrg(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) ctx := t.Context() - if _, err := client.Projects.DeleteProjectItemForOrg(ctx, "o", 1, 17); err != nil { + if _, err := client.Projects.DeleteOrganizationProjectItem(ctx, "o", 1, 17); err != nil { t.Fatalf("DeleteProjectItemForOrg error: %v", err) } } -func TestProjectsService_DeleteProjectItemForOrg_error(t *testing.T) { +func TestProjectsService_DeleteOrganizationProjectItem_error(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { @@ -808,11 +808,11 @@ func TestProjectsService_DeleteProjectItemForOrg_error(t *testing.T) { ctx := t.Context() const methodName = "DeleteProjectItemForOrg" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - return client.Projects.DeleteProjectItemForOrg(ctx, "o", 1, 17) + return client.Projects.DeleteOrganizationProjectItem(ctx, "o", 1, 17) }) } -func TestProjectsService_ListProjectItemsForUser(t *testing.T) { +func TestProjectsService_ListUserProjectItems(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/users/u/projectsV2/2/items", func(w http.ResponseWriter, r *http.Request) { @@ -821,7 +821,7 @@ func TestProjectsService_ListProjectItemsForUser(t *testing.T) { fmt.Fprint(w, `[{"id":7,"node_id":"PVTI_user"}]`) }) ctx := t.Context() - items, _, err := client.Projects.ListProjectItemsForUser(ctx, "u", 2, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{PerPage: Ptr(20)}, Query: Ptr("type:issue")}}) + items, _, err := client.Projects.ListUserProjectItems(ctx, "u", 2, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{PerPage: Ptr(20)}, Query: Ptr("type:issue")}}) if err != nil { t.Fatalf("ListProjectItemsForUser error: %v", err) } @@ -830,7 +830,7 @@ func TestProjectsService_ListProjectItemsForUser(t *testing.T) { } } -func TestProjectsService_ListProjectItemsForUser_error(t *testing.T) { +func TestProjectsService_ListUserProjectItems_error(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/users/u/projectsV2/2/items", func(w http.ResponseWriter, r *http.Request) { @@ -840,19 +840,19 @@ func TestProjectsService_ListProjectItemsForUser_error(t *testing.T) { ctx := t.Context() const methodName = "ListProjectItemsForUser" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.ListProjectItemsForUser(ctx, "u", 2, nil) + got, resp, err := client.Projects.ListUserProjectItems(ctx, "u", 2, nil) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } return resp, err }) testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Projects.ListProjectItemsForUser(ctx, "\n", 2, nil) + _, _, err = client.Projects.ListUserProjectItems(ctx, "\n", 2, nil) return err }) } -func TestProjectsService_AddProjectItemForUser(t *testing.T) { +func TestProjectsService_AddUserProjectItem(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/users/u/projectsV2/2/items", func(w http.ResponseWriter, r *http.Request) { @@ -865,7 +865,7 @@ func TestProjectsService_AddProjectItemForUser(t *testing.T) { fmt.Fprint(w, `{"id":123,"node_id":"PVTI_new_user"}`) }) ctx := t.Context() - item, _, err := client.Projects.AddProjectItemForUser(ctx, "u", 2, &AddProjectItemOptions{Type: "PullRequest", ID: 123}) + item, _, err := client.Projects.AddUserProjectItem(ctx, "u", 2, &AddProjectItemOptions{Type: "PullRequest", ID: 123}) if err != nil { t.Fatalf("AddProjectItemForUser error: %v", err) } @@ -874,7 +874,7 @@ func TestProjectsService_AddProjectItemForUser(t *testing.T) { } } -func TestProjectsService_AddProjectItemForUser_error(t *testing.T) { +func TestProjectsService_AddUserProjectItem_error(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/users/u/projectsV2/2/items", func(w http.ResponseWriter, r *http.Request) { @@ -884,7 +884,7 @@ func TestProjectsService_AddProjectItemForUser_error(t *testing.T) { ctx := t.Context() const methodName = "AddProjectItemForUser" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.AddProjectItemForUser(ctx, "u", 2, &AddProjectItemOptions{Type: "Issue", ID: 5}) + got, resp, err := client.Projects.AddUserProjectItem(ctx, "u", 2, &AddProjectItemOptions{Type: "Issue", ID: 5}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -892,7 +892,7 @@ func TestProjectsService_AddProjectItemForUser_error(t *testing.T) { }) } -func TestProjectsService_GetProjectItemForUser(t *testing.T) { +func TestProjectsService_GetUserProjectItem(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { @@ -901,7 +901,7 @@ func TestProjectsService_GetProjectItemForUser(t *testing.T) { }) ctx := t.Context() opts := &GetProjectItemOptions{} - item, _, err := client.Projects.GetProjectItemForUser(ctx, "u", 2, 55, opts) + item, _, err := client.Projects.GetUserProjectItem(ctx, "u", 2, 55, opts) if err != nil { t.Fatalf("GetProjectItemForUser error: %v", err) } @@ -910,7 +910,7 @@ func TestProjectsService_GetProjectItemForUser(t *testing.T) { } } -func TestProjectsService_GetProjectItemForUser_error(t *testing.T) { +func TestProjectsService_GetUserProjectItem_error(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { @@ -920,7 +920,7 @@ func TestProjectsService_GetProjectItemForUser_error(t *testing.T) { ctx := t.Context() const methodName = "GetProjectItemForUser" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.GetProjectItemForUser(ctx, "u", 2, 55, &GetProjectItemOptions{}) + got, resp, err := client.Projects.GetUserProjectItem(ctx, "u", 2, 55, &GetProjectItemOptions{}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -928,7 +928,7 @@ func TestProjectsService_GetProjectItemForUser_error(t *testing.T) { }) } -func TestProjectsService_UpdateProjectItemForUser(t *testing.T) { +func TestProjectsService_UpdateUserProjectItem(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { @@ -942,7 +942,7 @@ func TestProjectsService_UpdateProjectItemForUser(t *testing.T) { }) archived := false ctx := t.Context() - item, _, err := client.Projects.UpdateProjectItemForUser(ctx, "u", 2, 55, &UpdateProjectItemOptions{Archived: &archived}) + item, _, err := client.Projects.UpdateUserProjectItem(ctx, "u", 2, 55, &UpdateProjectItemOptions{Archived: &archived}) if err != nil { t.Fatalf("UpdateProjectItemForUser error: %v", err) } @@ -951,7 +951,7 @@ func TestProjectsService_UpdateProjectItemForUser(t *testing.T) { } } -func TestProjectsService_UpdateProjectItemForUser_error(t *testing.T) { +func TestProjectsService_UpdateUserProjectItem_error(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { @@ -962,7 +962,7 @@ func TestProjectsService_UpdateProjectItemForUser_error(t *testing.T) { ctx := t.Context() const methodName = "UpdateProjectItemForUser" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Projects.UpdateProjectItemForUser(ctx, "u", 2, 55, &UpdateProjectItemOptions{Archived: &archived}) + got, resp, err := client.Projects.UpdateUserProjectItem(ctx, "u", 2, 55, &UpdateProjectItemOptions{Archived: &archived}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -970,7 +970,7 @@ func TestProjectsService_UpdateProjectItemForUser_error(t *testing.T) { }) } -func TestProjectsService_DeleteProjectItemForUser(t *testing.T) { +func TestProjectsService_DeleteUserProjectItem(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { @@ -978,12 +978,12 @@ func TestProjectsService_DeleteProjectItemForUser(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) ctx := t.Context() - if _, err := client.Projects.DeleteProjectItemForUser(ctx, "u", 2, 55); err != nil { + if _, err := client.Projects.DeleteUserProjectItem(ctx, "u", 2, 55); err != nil { t.Fatalf("DeleteProjectItemForUser error: %v", err) } } -func TestProjectsService_DeleteProjectItemForUser_error(t *testing.T) { +func TestProjectsService_DeleteUserProjectItem_error(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { @@ -993,6 +993,6 @@ func TestProjectsService_DeleteProjectItemForUser_error(t *testing.T) { ctx := t.Context() const methodName = "DeleteProjectItemForUser" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - return client.Projects.DeleteProjectItemForUser(ctx, "u", 2, 55) + return client.Projects.DeleteUserProjectItem(ctx, "u", 2, 55) }) } From 84376261953f20e893b7632ea01d598bc4875311 Mon Sep 17 00:00:00 2001 From: Jonathan Otalora Date: Thu, 30 Oct 2025 15:27:08 -0600 Subject: [PATCH 38/39] additional cleanup due to refactor --- github/projects_test.go | 92 ++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/github/projects_test.go b/github/projects_test.go index 96751e6107d..b5379dd65f8 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -13,7 +13,7 @@ import ( "testing" ) -func TestProjectsService_ListProjectsForOrg(t *testing.T) { +func TestProjectsService_ListOrganizationProjects(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -34,13 +34,13 @@ func TestProjectsService_ListProjectsForOrg(t *testing.T) { ctx := t.Context() projects, _, err := client.Projects.ListOrganizationProjects(ctx, "o", opts) if err != nil { - t.Fatalf("Projects.ListProjectsForOrg returned error: %v", err) + t.Fatalf("Projects.ListOrganizationProjects returned error: %v", err) } if len(projects) != 1 || projects[0].GetID() != 1 || projects[0].GetTitle() != "T1" { - t.Fatalf("Projects.ListProjectsForOrg returned %+v", projects) + t.Fatalf("Projects.ListOrganizationProjects returned %+v", projects) } - const methodName = "ListProjectsForOrg" + const methodName = "ListOrganizationProjects" testBadOptions(t, methodName, func() (err error) { _, _, err = client.Projects.ListOrganizationProjects(ctx, "\n", opts) return err @@ -73,13 +73,13 @@ func TestProjectsService_GetOrganizationProject(t *testing.T) { ctx := t.Context() project, _, err := client.Projects.GetOrganizationProject(ctx, "o", 1) if err != nil { - t.Fatalf("Projects.GetProjectForOrg returned error: %v", err) + t.Fatalf("Projects.GetOrganizationProject returned error: %v", err) } if project.GetID() != 1 || project.GetTitle() != "OrgProj" { - t.Fatalf("Projects.GetProjectForOrg returned %+v", project) + t.Fatalf("Projects.GetOrganizationProject returned %+v", project) } - const methodName = "GetProjectForOrg" + const methodName = "GetOrganizationProject" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.GetOrganizationProject(ctx, "o", 1) if got != nil { @@ -110,13 +110,13 @@ func TestProjectsService_ListUserProjects(t *testing.T) { var ctxBypass context.Context projects, _, err := client.Projects.ListUserProjects(ctx, "u", opts) if err != nil { - t.Fatalf("Projects.ListProjectsForUser returned error: %v", err) + t.Fatalf("Projects.ListUserProjects returned error: %v", err) } if len(projects) != 1 || projects[0].GetID() != 2 || projects[0].GetTitle() != "UProj" { - t.Fatalf("Projects.ListProjectsForUser returned %+v", projects) + t.Fatalf("Projects.ListUserProjects returned %+v", projects) } - const methodName = "ListProjectsForUser" + const methodName = "ListUserProjects" testBadOptions(t, methodName, func() (err error) { _, _, err = client.Projects.ListUserProjects(ctx, "\n", opts) return err @@ -149,13 +149,13 @@ func TestProjectsService_GetUserProject(t *testing.T) { ctx := t.Context() project, _, err := client.Projects.GetUserProject(ctx, "u", 3) if err != nil { - t.Fatalf("Projects.GetProjectForUser returned error: %v", err) + t.Fatalf("Projects.GetUserProject returned error: %v", err) } if project.GetID() != 3 || project.GetTitle() != "UserProj" { - t.Fatalf("Projects.GetProjectForUser returned %+v", project) + t.Fatalf("Projects.GetUserProject returned %+v", project) } - const methodName = "GetProjectForUser" + const methodName = "GetUserProject" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.GetUserProject(ctx, "u", 3) if got != nil { @@ -165,7 +165,7 @@ func TestProjectsService_GetUserProject(t *testing.T) { }) } -func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { +func TestProjectsService_ListOrganizationProjectFields(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -207,16 +207,16 @@ func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { ctx := t.Context() fields, _, err := client.Projects.ListOrganizationProjectFields(ctx, "o", 1, opts) if err != nil { - t.Fatalf("Projects.ListProjectFieldsForOrg returned error: %v", err) + t.Fatalf("Projects.ListOrganizationProjectFields returned error: %v", err) } if len(fields) != 2 { - t.Fatalf("Projects.ListProjectFieldsForOrg returned %d fields, want 2", len(fields)) + t.Fatalf("Projects.ListOrganizationProjectFields returned %d fields, want 2", len(fields)) } if fields[0].ID == nil || *fields[0].ID != 1 || fields[1].ID == nil || *fields[1].ID != 2 { t.Fatalf("unexpected field IDs: %+v", fields) } - const methodName = "ListProjectFieldsForOrg" + const methodName = "ListOrganizationProjectFields" testBadOptions(t, methodName, func() (err error) { _, _, err = client.Projects.ListOrganizationProjectFields(ctx, "\n", 1, opts) return err @@ -276,16 +276,16 @@ func TestProjectsService_ListUserProjectFields(t *testing.T) { ctx := t.Context() fields, _, err := client.Projects.ListUserProjectFields(ctx, "u", 1, opts) if err != nil { - t.Fatalf("Projects.ListProjectFieldsForUser returned error: %v", err) + t.Fatalf("Projects.ListUserProjectFields returned error: %v", err) } if len(fields) != 2 { - t.Fatalf("Projects.ListProjectFieldsForUser returned %d fields, want 2", len(fields)) + t.Fatalf("Projects.ListUserProjectFields returned %d fields, want 2", len(fields)) } if fields[0].ID == nil || *fields[0].ID != 1 || fields[1].ID == nil || *fields[1].ID != 2 { t.Fatalf("unexpected field IDs: %+v", fields) } - const methodName = "ListProjectFieldsForUser" + const methodName = "ListUserProjectFields" testBadOptions(t, methodName, func() (err error) { _, _, err = client.Projects.ListUserProjectFields(ctx, "\n", 1, opts) return err @@ -328,13 +328,13 @@ func TestProjectsService_GetOrganizationProjectField(t *testing.T) { ctx := t.Context() field, _, err := client.Projects.GetOrganizationProjectField(ctx, "o", 1, 1) if err != nil { - t.Fatalf("Projects.GetProjectFieldForOrg returned error: %v", err) + t.Fatalf("Projects.GetOrganizationProjectField returned error: %v", err) } if field == nil || field.ID == nil || *field.ID != 1 { t.Fatalf("unexpected field: %+v", field) } - const methodName = "GetProjectFieldForOrg" + const methodName = "GetOrganizationProjectField" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.GetOrganizationProjectField(ctx, "o", 1, 1) if got != nil { @@ -369,13 +369,13 @@ func TestProjectsService_GetUserProjectField(t *testing.T) { ctx := t.Context() field, _, err := client.Projects.GetUserProjectField(ctx, "u", 1, 3) if err != nil { - t.Fatalf("Projects.GetProjectFieldForUser returned error: %v", err) + t.Fatalf("Projects.GetUserProjectField returned error: %v", err) } if field == nil || field.ID == nil || *field.ID != 3 { t.Fatalf("unexpected field: %+v", field) } - const methodName = "GetProjectFieldForUser" + const methodName = "GetUserProjectField" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.GetUserProjectField(ctx, "u", 1, 3) if got != nil { @@ -429,7 +429,7 @@ func TestProjectsService_ListUserProjects_pagination(t *testing.T) { } } -func TestProjectsService_ListProjectsForUser_error(t *testing.T) { +func TestProjectsService_ListUserProjects_error(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/users/u/projectsV2", func(w http.ResponseWriter, r *http.Request) { @@ -437,7 +437,7 @@ func TestProjectsService_ListProjectsForUser_error(t *testing.T) { fmt.Fprint(w, `[]`) }) ctx := t.Context() - const methodName = "ListProjectsForUser" + const methodName = "ListUserProjects" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.ListUserProjects(ctx, "u", nil) if got != nil { @@ -638,13 +638,13 @@ func TestProjectsService_ListOrganizationProjectItems(t *testing.T) { ctx := t.Context() items, _, err := client.Projects.ListOrganizationProjectItems(ctx, "o", 1, opts) if err != nil { - t.Fatalf("Projects.ListProjectItemsForOrg returned error: %v", err) + t.Fatalf("Projects.ListOrganizationProjectItems returned error: %v", err) } if len(items) != 1 || items[0].GetID() != 17 { - t.Fatalf("Projects.ListProjectItemsForOrg returned %+v", items) + t.Fatalf("Projects.ListOrganizationProjectItems returned %+v", items) } - const methodName = "ListProjectItemsForOrg" + const methodName = "ListOrganizationProjectItems" testBadOptions(t, methodName, func() (err error) { _, _, err = client.Projects.ListOrganizationProjectItems(ctx, "\n", 1, opts) return err @@ -681,7 +681,7 @@ func TestProjectsService_AddOrganizationProjectItem(t *testing.T) { ctx := t.Context() item, _, err := client.Projects.AddOrganizationProjectItem(ctx, "o", 1, &AddProjectItemOptions{Type: "Issue", ID: 99}) if err != nil { - t.Fatalf("Projects.AddProjectItemForOrg returned error: %v", err) + t.Fatalf("Projects.AddOrganizationProjectItem returned error: %v", err) } if item.GetID() != 99 { t.Fatalf("unexpected item: %+v", item) @@ -697,7 +697,7 @@ func TestProjectsService_AddProjectItemForOrg_error(t *testing.T) { fmt.Fprint(w, `{"id":1}`) }) ctx := t.Context() - const methodName = "AddProjectItemForOrg" + const methodName = "AddOrganizationProjectItem" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.AddOrganizationProjectItem(ctx, "o", 1, &AddProjectItemOptions{Type: "Issue", ID: 1}) if got != nil { @@ -718,7 +718,7 @@ func TestProjectsService_GetOrganizationProjectItem(t *testing.T) { opts := &GetProjectItemOptions{} item, _, err := client.Projects.GetOrganizationProjectItem(ctx, "o", 1, 17, opts) if err != nil { - t.Fatalf("GetProjectItemForOrg error: %v", err) + t.Fatalf("GetOrganizationProjectItem error: %v", err) } if item.GetID() != 17 { t.Fatalf("unexpected item: %+v", item) @@ -733,7 +733,7 @@ func TestProjectsService_GetOrganizationProjectItem_error(t *testing.T) { fmt.Fprint(w, `{"id":17}`) }) ctx := t.Context() - const methodName = "GetProjectItemForOrg" + const methodName = "GetOrganizationProjectItem" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.GetOrganizationProjectItem(ctx, "o", 1, 17, &GetProjectItemOptions{}) if got != nil { @@ -759,7 +759,7 @@ func TestProjectsService_UpdateOrganizationProjectItem(t *testing.T) { ctx := t.Context() item, _, err := client.Projects.UpdateOrganizationProjectItem(ctx, "o", 1, 17, &UpdateProjectItemOptions{Archived: &archived}) if err != nil { - t.Fatalf("UpdateProjectItemForOrg error: %v", err) + t.Fatalf("UpdateOrganizationProjectItem error: %v", err) } if item.GetID() != 17 { t.Fatalf("unexpected item: %+v", item) @@ -794,7 +794,7 @@ func TestProjectsService_DeleteOrganizationProjectItem(t *testing.T) { }) ctx := t.Context() if _, err := client.Projects.DeleteOrganizationProjectItem(ctx, "o", 1, 17); err != nil { - t.Fatalf("DeleteProjectItemForOrg error: %v", err) + t.Fatalf("DeleteOrganizationProjectItem error: %v", err) } } @@ -806,7 +806,7 @@ func TestProjectsService_DeleteOrganizationProjectItem_error(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) ctx := t.Context() - const methodName = "DeleteProjectItemForOrg" + const methodName = "DeleteOrganizationProjectItem" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { return client.Projects.DeleteOrganizationProjectItem(ctx, "o", 1, 17) }) @@ -823,7 +823,7 @@ func TestProjectsService_ListUserProjectItems(t *testing.T) { ctx := t.Context() items, _, err := client.Projects.ListUserProjectItems(ctx, "u", 2, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{ListProjectsPaginationOptions: ListProjectsPaginationOptions{PerPage: Ptr(20)}, Query: Ptr("type:issue")}}) if err != nil { - t.Fatalf("ListProjectItemsForUser error: %v", err) + t.Fatalf("ListUserProjectItems error: %v", err) } if len(items) != 1 || items[0].GetID() != 7 { t.Fatalf("unexpected items: %+v", items) @@ -838,7 +838,7 @@ func TestProjectsService_ListUserProjectItems_error(t *testing.T) { fmt.Fprint(w, `[]`) }) ctx := t.Context() - const methodName = "ListProjectItemsForUser" + const methodName = "ListUserProjectItems" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.ListUserProjectItems(ctx, "u", 2, nil) if got != nil { @@ -867,7 +867,7 @@ func TestProjectsService_AddUserProjectItem(t *testing.T) { ctx := t.Context() item, _, err := client.Projects.AddUserProjectItem(ctx, "u", 2, &AddProjectItemOptions{Type: "PullRequest", ID: 123}) if err != nil { - t.Fatalf("AddProjectItemForUser error: %v", err) + t.Fatalf("AddUserProjectItem error: %v", err) } if item.GetID() != 123 { t.Fatalf("unexpected item: %+v", item) @@ -882,7 +882,7 @@ func TestProjectsService_AddUserProjectItem_error(t *testing.T) { fmt.Fprint(w, `{"id":5}`) }) ctx := t.Context() - const methodName = "AddProjectItemForUser" + const methodName = "AddUserProjectItem" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.AddUserProjectItem(ctx, "u", 2, &AddProjectItemOptions{Type: "Issue", ID: 5}) if got != nil { @@ -903,7 +903,7 @@ func TestProjectsService_GetUserProjectItem(t *testing.T) { opts := &GetProjectItemOptions{} item, _, err := client.Projects.GetUserProjectItem(ctx, "u", 2, 55, opts) if err != nil { - t.Fatalf("GetProjectItemForUser error: %v", err) + t.Fatalf("GetUserProjectItem error: %v", err) } if item.GetID() != 55 { t.Fatalf("unexpected item: %+v", item) @@ -918,7 +918,7 @@ func TestProjectsService_GetUserProjectItem_error(t *testing.T) { fmt.Fprint(w, `{"id":55}`) }) ctx := t.Context() - const methodName = "GetProjectItemForUser" + const methodName = "GetUserProjectItem" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.GetUserProjectItem(ctx, "u", 2, 55, &GetProjectItemOptions{}) if got != nil { @@ -944,7 +944,7 @@ func TestProjectsService_UpdateUserProjectItem(t *testing.T) { ctx := t.Context() item, _, err := client.Projects.UpdateUserProjectItem(ctx, "u", 2, 55, &UpdateProjectItemOptions{Archived: &archived}) if err != nil { - t.Fatalf("UpdateProjectItemForUser error: %v", err) + t.Fatalf("UpdateUserProjectItem error: %v", err) } if item.GetID() != 55 { t.Fatalf("unexpected item: %+v", item) @@ -960,7 +960,7 @@ func TestProjectsService_UpdateUserProjectItem_error(t *testing.T) { }) archived := false ctx := t.Context() - const methodName = "UpdateProjectItemForUser" + const methodName = "UpdateUserProjectItem" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { got, resp, err := client.Projects.UpdateUserProjectItem(ctx, "u", 2, 55, &UpdateProjectItemOptions{Archived: &archived}) if got != nil { @@ -979,7 +979,7 @@ func TestProjectsService_DeleteUserProjectItem(t *testing.T) { }) ctx := t.Context() if _, err := client.Projects.DeleteUserProjectItem(ctx, "u", 2, 55); err != nil { - t.Fatalf("DeleteProjectItemForUser error: %v", err) + t.Fatalf("DeleteUserProjectItem error: %v", err) } } @@ -991,7 +991,7 @@ func TestProjectsService_DeleteUserProjectItem_error(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) ctx := t.Context() - const methodName = "DeleteProjectItemForUser" + const methodName = "DeleteUserProjectItem" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { return client.Projects.DeleteUserProjectItem(ctx, "u", 2, 55) }) From 2d4bc8bd56a487e5a4d82d6eb95b65e68349bf65 Mon Sep 17 00:00:00 2001 From: Jonathan Otalora Date: Sun, 2 Nov 2025 17:52:12 -0700 Subject: [PATCH 39/39] fixes configuration and incorrect types for project fields --- github/github-accessors.go | 64 +++++++++++++- github/github-accessors_test.go | 86 +++++++++++++++++-- github/projects.go | 38 +++++++-- github/projects_test.go | 142 ++++++++++++++++++++++++++++---- 4 files changed, 297 insertions(+), 33 deletions(-) diff --git a/github/github-accessors.go b/github/github-accessors.go index 986e6ed37e7..7844b5b36d0 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -19150,6 +19150,14 @@ func (p *ProjectV2Event) GetSender() *User { return p.Sender } +// GetConfiguration returns the Configuration field. +func (p *ProjectV2Field) GetConfiguration() *ProjectV2FieldConfiguration { + if p == nil { + return nil + } + return p.Configuration +} + // GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. func (p *ProjectV2Field) GetCreatedAt() Timestamp { if p == nil || p.CreatedAt == nil { @@ -19190,6 +19198,14 @@ func (p *ProjectV2Field) GetNodeID() string { return *p.NodeID } +// GetProjectURL returns the ProjectURL field if it's non-nil, zero value otherwise. +func (p *ProjectV2Field) GetProjectURL() string { + if p == nil || p.ProjectURL == nil { + return "" + } + return *p.ProjectURL +} + // GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. func (p *ProjectV2Field) GetUpdatedAt() Timestamp { if p == nil || p.UpdatedAt == nil { @@ -19198,12 +19214,52 @@ func (p *ProjectV2Field) GetUpdatedAt() Timestamp { return *p.UpdatedAt } -// GetURL returns the URL field if it's non-nil, zero value otherwise. -func (p *ProjectV2Field) GetURL() string { - if p == nil || p.URL == nil { +// GetDuration returns the Duration field if it's non-nil, zero value otherwise. +func (p *ProjectV2FieldConfiguration) GetDuration() int { + if p == nil || p.Duration == nil { + return 0 + } + return *p.Duration +} + +// GetStartDay returns the StartDay field if it's non-nil, zero value otherwise. +func (p *ProjectV2FieldConfiguration) GetStartDay() int { + if p == nil || p.StartDay == nil { + return 0 + } + return *p.StartDay +} + +// GetDuration returns the Duration field if it's non-nil, zero value otherwise. +func (p *ProjectV2FieldIteration) GetDuration() int { + if p == nil || p.Duration == nil { + return 0 + } + return *p.Duration +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *ProjectV2FieldIteration) GetID() string { + if p == nil || p.ID == nil { return "" } - return *p.URL + return *p.ID +} + +// GetStartDate returns the StartDate field if it's non-nil, zero value otherwise. +func (p *ProjectV2FieldIteration) GetStartDate() string { + if p == nil || p.StartDate == nil { + return "" + } + return *p.StartDate +} + +// GetTitle returns the Title field if it's non-nil, zero value otherwise. +func (p *ProjectV2FieldIteration) GetTitle() string { + if p == nil || p.Title == nil { + return "" + } + return *p.Title } // GetColor returns the Color field if it's non-nil, zero value otherwise. diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index e6c8b60b64d..c6ece7ee50a 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -24869,6 +24869,14 @@ func TestProjectV2Event_GetSender(tt *testing.T) { p.GetSender() } +func TestProjectV2Field_GetConfiguration(tt *testing.T) { + tt.Parallel() + p := &ProjectV2Field{} + p.GetConfiguration() + p = nil + p.GetConfiguration() +} + func TestProjectV2Field_GetCreatedAt(tt *testing.T) { tt.Parallel() var zeroValue Timestamp @@ -24924,6 +24932,17 @@ func TestProjectV2Field_GetNodeID(tt *testing.T) { p.GetNodeID() } +func TestProjectV2Field_GetProjectURL(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &ProjectV2Field{ProjectURL: &zeroValue} + p.GetProjectURL() + p = &ProjectV2Field{} + p.GetProjectURL() + p = nil + p.GetProjectURL() +} + func TestProjectV2Field_GetUpdatedAt(tt *testing.T) { tt.Parallel() var zeroValue Timestamp @@ -24935,15 +24954,70 @@ func TestProjectV2Field_GetUpdatedAt(tt *testing.T) { p.GetUpdatedAt() } -func TestProjectV2Field_GetURL(tt *testing.T) { +func TestProjectV2FieldConfiguration_GetDuration(tt *testing.T) { + tt.Parallel() + var zeroValue int + p := &ProjectV2FieldConfiguration{Duration: &zeroValue} + p.GetDuration() + p = &ProjectV2FieldConfiguration{} + p.GetDuration() + p = nil + p.GetDuration() +} + +func TestProjectV2FieldConfiguration_GetStartDay(tt *testing.T) { + tt.Parallel() + var zeroValue int + p := &ProjectV2FieldConfiguration{StartDay: &zeroValue} + p.GetStartDay() + p = &ProjectV2FieldConfiguration{} + p.GetStartDay() + p = nil + p.GetStartDay() +} + +func TestProjectV2FieldIteration_GetDuration(tt *testing.T) { + tt.Parallel() + var zeroValue int + p := &ProjectV2FieldIteration{Duration: &zeroValue} + p.GetDuration() + p = &ProjectV2FieldIteration{} + p.GetDuration() + p = nil + p.GetDuration() +} + +func TestProjectV2FieldIteration_GetID(tt *testing.T) { tt.Parallel() var zeroValue string - p := &ProjectV2Field{URL: &zeroValue} - p.GetURL() - p = &ProjectV2Field{} - p.GetURL() + p := &ProjectV2FieldIteration{ID: &zeroValue} + p.GetID() + p = &ProjectV2FieldIteration{} + p.GetID() p = nil - p.GetURL() + p.GetID() +} + +func TestProjectV2FieldIteration_GetStartDate(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &ProjectV2FieldIteration{StartDate: &zeroValue} + p.GetStartDate() + p = &ProjectV2FieldIteration{} + p.GetStartDate() + p = nil + p.GetStartDate() +} + +func TestProjectV2FieldIteration_GetTitle(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &ProjectV2FieldIteration{Title: &zeroValue} + p.GetTitle() + p = &ProjectV2FieldIteration{} + p.GetTitle() + p = nil + p.GetTitle() } func TestProjectV2FieldOption_GetColor(tt *testing.T) { diff --git a/github/projects.go b/github/projects.go index 44d3abd4636..1ccadaeaae5 100644 --- a/github/projects.go +++ b/github/projects.go @@ -85,19 +85,41 @@ type ProjectV2FieldOption struct { Description *string `json:"description,omitempty"` // An optional description for this option. } +// ProjectV2FieldIteration represents an iteration within a project field of type iteration. +// It defines a specific time-bound period that can be associated with project items. +// +// GitHub API docs: https://docs.github.com/rest/projects/fields +type ProjectV2FieldIteration struct { + ID *string `json:"id,omitempty"` // The unique identifier for the iteration. + Title *string `json:"title,omitempty"` // The title of the iteration. + StartDate *string `json:"start_date,omitempty"` // The start date of the iteration in ISO 8601 format. + Duration *int `json:"duration,omitempty"` // The duration of the iteration in seconds. +} + +// ProjectV2FieldConfiguration represents the configuration for a project field of type iteration. +// It defines settings such as duration and start day for iterations within the project. +// +// GitHub API docs: https://docs.github.com/rest/projects/fields +type ProjectV2FieldConfiguration struct { + Duration *int `json:"duration,omitempty"` // The duration of the iteration field in seconds. + StartDay *int `json:"start_day,omitempty"` // The start day for the iteration. + Iterations []*ProjectV2FieldIteration `json:"iterations,omitempty"` // The list of iterations associated with the configuration. +} + // ProjectV2Field represents a field in a GitHub Projects V2 project. // Fields define the structure and data types for project items. // // GitHub API docs: https://docs.github.com/rest/projects/fields type ProjectV2Field struct { - ID *int64 `json:"id,omitempty"` - NodeID *string `json:"node_id,omitempty"` - Name *string `json:"name,omitempty"` - DataType *string `json:"dataType,omitempty"` - URL *string `json:"url,omitempty"` - Options []*ProjectV2FieldOption `json:"options,omitempty"` - CreatedAt *Timestamp `json:"created_at,omitempty"` - UpdatedAt *Timestamp `json:"updated_at,omitempty"` + ID *int64 `json:"id,omitempty"` + NodeID *string `json:"node_id,omitempty"` + Name *string `json:"name,omitempty"` + DataType *string `json:"data_type,omitempty"` + ProjectURL *string `json:"project_url,omitempty"` + Options []*ProjectV2FieldOption `json:"options,omitempty"` + Configuration *ProjectV2FieldConfiguration `json:"configuration,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` } // ListOrganizationProjects lists Projects V2 for an organization. diff --git a/github/projects_test.go b/github/projects_test.go index b5379dd65f8..4bb656b9e4b 100644 --- a/github/projects_test.go +++ b/github/projects_test.go @@ -182,7 +182,7 @@ func TestProjectsService_ListOrganizationProjectFields(t *testing.T) { "id": 1, "node_id": "node_1", "name": "Status", - "dataType": "single_select", + "data_type": "single_select", "url": "https://api.github.com/projects/1/fields/field1", "options": [ {"id": "1", "name": "Todo", "color": "blue", "description": "Tasks to be done"}, @@ -195,7 +195,7 @@ func TestProjectsService_ListOrganizationProjectFields(t *testing.T) { "id": 2, "node_id": "node_2", "name": "Priority", - "dataType": "text", + "data_type": "text", "url": "https://api.github.com/projects/1/fields/field2", "created_at": "2011-01-02T15:04:05Z", "updated_at": "2012-01-02T15:04:05Z" @@ -251,7 +251,7 @@ func TestProjectsService_ListUserProjectFields(t *testing.T) { "id": 1, "node_id": "node_1", "name": "Status", - "dataType": "single_select", + "data_type": "single_select", "url": "https://api.github.com/projects/1/fields/field1", "options": [ {"id": "1", "name": "Todo", "color": "blue", "description": "Tasks to be done"}, @@ -264,7 +264,7 @@ func TestProjectsService_ListUserProjectFields(t *testing.T) { "id": 2, "node_id": "node_2", "name": "Priority", - "dataType": "text", + "data_type": "text", "url": "https://api.github.com/projects/1/fields/field2", "created_at": "2011-01-02T15:04:05Z", "updated_at": "2012-01-02T15:04:05Z" @@ -314,7 +314,7 @@ func TestProjectsService_GetOrganizationProjectField(t *testing.T) { "id": 1, "node_id": "node_1", "name": "Status", - "dataType": "single_select", + "data_type": "single_select", "url": "https://api.github.com/projects/1/fields/field1", "options": [ {"id": "1", "name": "Todo", "color": "blue", "description": "Tasks to be done"}, @@ -355,7 +355,7 @@ func TestProjectsService_GetUserProjectField(t *testing.T) { "id": 3, "node_id": "node_3", "name": "Status", - "dataType": "single_select", + "data_type": "single_select", "url": "https://api.github.com/projects/1/fields/field3", "options": [ {"id": "1", "name": "Done", "color": "red", "description": "Done task"}, @@ -464,13 +464,13 @@ func TestProjectsService_ListOrganizationProjectFields_pagination(t *testing.T) if after == "" && before == "" { // first request w.Header().Set("Link", "; rel=\"next\"") - fmt.Fprint(w, `[{"id":1,"name":"Status","dataType":"single_select","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + fmt.Fprint(w, `[{"id":1,"name":"Status","data_type":"single_select","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) return } if after == "cursor2" { // second request simulates a previous link w.Header().Set("Link", "; rel=\"prev\"") - fmt.Fprint(w, `[{"id":2,"name":"Priority","dataType":"text","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + fmt.Fprint(w, `[{"id":2,"name":"Priority","data_type":"text","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) return } // unexpected state @@ -581,11 +581,11 @@ func TestProjectV2Field_Marshal(t *testing.T) { testJSONMarshal(t, &ProjectV2FieldOption{}, "{}") field := &ProjectV2Field{ - ID: Ptr(int64(2)), - NodeID: Ptr("node_1"), - Name: Ptr("Status"), - DataType: Ptr("single_select"), - URL: Ptr("https://api.github.com/projects/1/fields/field1"), + ID: Ptr(int64(2)), + NodeID: Ptr("node_1"), + Name: Ptr("Status"), + DataType: Ptr("single_select"), + ProjectURL: Ptr("https://api.github.com/projects/67890"), Options: []*ProjectV2FieldOption{ { ID: Ptr("1"), @@ -602,8 +602,8 @@ func TestProjectV2Field_Marshal(t *testing.T) { "id": 2, "node_id": "node_1", "name": "Status", - "dataType": "single_select", - "url": "https://api.github.com/projects/1/fields/field1", + "data_type": "single_select", + "project_url": "https://api.github.com/projects/67890", "options": [ { "id": "1", @@ -619,6 +619,118 @@ func TestProjectV2Field_Marshal(t *testing.T) { testJSONMarshal(t, field, want) } +// Marshal test ensures ProjectV2FieldConfiguration marshals correctly. +func TestProjectV2FieldConfiguration_Marshal(t *testing.T) { + t.Parallel() + testJSONMarshal(t, &ProjectV2FieldConfiguration{}, "{}") + testJSONMarshal(t, &ProjectV2FieldIteration{}, "{}") + + // Test a field with configuration (iteration field) + fieldWithConfiguration := &ProjectV2Field{ + ID: Ptr(int64(3)), + NodeID: Ptr("node_3"), + Name: Ptr("Sprint"), + DataType: Ptr("iteration"), + ProjectURL: Ptr("https://api.github.com/projects/67890"), + Configuration: &ProjectV2FieldConfiguration{ + Duration: Ptr(1209600), // 2 weeks in seconds + StartDay: Ptr(1), // Monday + Iterations: []*ProjectV2FieldIteration{ + { + ID: Ptr("iter_1"), + Title: Ptr("Sprint 1"), + StartDate: Ptr("2025-01-06"), + Duration: Ptr(1209600), + }, + { + ID: Ptr("iter_2"), + Title: Ptr("Sprint 2"), + StartDate: Ptr("2025-01-20"), + Duration: Ptr(1209600), + }, + }, + }, + CreatedAt: &Timestamp{referenceTime}, + UpdatedAt: &Timestamp{referenceTime}, + } + + want := `{ + "id": 3, + "node_id": "node_3", + "name": "Sprint", + "data_type": "iteration", + "project_url": "https://api.github.com/projects/67890", + "configuration": { + "duration": 1209600, + "start_day": 1, + "iterations": [ + { + "id": "iter_1", + "title": "Sprint 1", + "start_date": "2025-01-06", + "duration": 1209600 + }, + { + "id": "iter_2", + "title": "Sprint 2", + "start_date": "2025-01-20", + "duration": 1209600 + } + ] + }, + "created_at": ` + referenceTimeStr + `, + "updated_at": ` + referenceTimeStr + ` + }` + + testJSONMarshal(t, fieldWithConfiguration, want) + + // Test just the configuration struct by itself + config := &ProjectV2FieldConfiguration{ + Duration: Ptr(604800), // 1 week in seconds + StartDay: Ptr(0), // Sunday + Iterations: []*ProjectV2FieldIteration{ + { + ID: Ptr("config_iter_1"), + Title: Ptr("Week 1"), + StartDate: Ptr("2025-01-01"), + Duration: Ptr(604800), + }, + }, + } + + configWant := `{ + "duration": 604800, + "start_day": 0, + "iterations": [ + { + "id": "config_iter_1", + "title": "Week 1", + "start_date": "2025-01-01", + "duration": 604800 + } + ] + }` + + testJSONMarshal(t, config, configWant) + + // Test iteration struct by itself + iteration := &ProjectV2FieldIteration{ + ID: Ptr("single_iter"), + Title: Ptr("Test Iteration"), + StartDate: Ptr("2025-02-01"), + Duration: Ptr(1209600), + } + + iterationWant := `{ + "id": "single_iter", + "title": "Test Iteration", + "start_date": "2025-02-01", + "duration": 1209600 + }` + + testJSONMarshal(t, iteration, iterationWant) +} + func TestProjectsService_ListOrganizationProjectItems(t *testing.T) { t.Parallel() client, mux, _ := setup(t)