From ae1e7ca9f2e90dcfbe112b73d4f47e79170133bb Mon Sep 17 00:00:00 2001 From: Owen Mehegan Date: Tue, 7 Apr 2026 16:24:19 +1000 Subject: [PATCH 1/3] Add missing fields to Team struct The Team struct was missing several fields that the API returns: GraphQLID, DefaultMemberRole, MembersCanCreatePipelines, MembersCanCreateSuites, MembersCanCreateRegistries, MembersCanDestroyRegistries, and MembersCanDestroyPackages. Without these fields, API consumers performing read-modify-write updates (eg GET team, modify a field, PATCH team) would silently reset these values to their zero values. In particular, MembersCanCreatePipelines would always be sent as false on updates because the current team state could never be read into the struct. --- teams.go | 23 +++++++++++++------- teams_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 12 deletions(-) diff --git a/teams.go b/teams.go index 7488bcea..3fecd365 100644 --- a/teams.go +++ b/teams.go @@ -18,14 +18,21 @@ type TeamsService struct { // Team represents a buildkite team. type Team struct { - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Slug string `json:"slug,omitempty"` - Description string `json:"description,omitempty"` - Privacy string `json:"privacy,omitempty"` - Default bool `json:"default"` - CreatedAt *Timestamp `json:"created_at,omitempty"` - CreatedBy *User `json:"created_by,omitempty"` + ID string `json:"id,omitempty"` + GraphQLID string `json:"graphql_id,omitempty"` + Name string `json:"name,omitempty"` + Slug string `json:"slug,omitempty"` + Description string `json:"description,omitempty"` + Privacy string `json:"privacy,omitempty"` + Default bool `json:"default"` + DefaultMemberRole string `json:"default_member_role,omitempty"` + MembersCanCreatePipelines bool `json:"members_can_create_pipelines"` + MembersCanCreateSuites bool `json:"members_can_create_suites"` + MembersCanCreateRegistries bool `json:"members_can_create_registries"` + MembersCanDestroyRegistries bool `json:"members_can_destroy_registries"` + MembersCanDestroyPackages bool `json:"members_can_destroy_packages"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + CreatedBy *User `json:"created_by,omitempty"` } // TeamsListOptions specifies the optional parameters to the diff --git a/teams_test.go b/teams_test.go index e0440575..810da922 100644 --- a/teams_test.go +++ b/teams_test.go @@ -17,7 +17,19 @@ func TestTeamsService_List(t *testing.T) { server.HandleFunc("/v2/organizations/my-great-org/teams", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - _, _ = fmt.Fprint(w, `[{"id":"123"},{"id":"1234"}]`) + _, _ = fmt.Fprint(w, `[ + { + "id": "123", + "graphql_id": "VGVhbS0tLTEyMw==", + "default_member_role": "member", + "members_can_create_pipelines": true, + "members_can_create_suites": false, + "members_can_create_registries": true, + "members_can_destroy_registries": false, + "members_can_destroy_packages": false + }, + {"id": "1234"} + ]`) }) teams, _, err := client.Teams.List(context.Background(), "my-great-org", nil) @@ -25,7 +37,19 @@ func TestTeamsService_List(t *testing.T) { t.Errorf("Teams.List returned error: %v", err) } - want := []Team{{ID: "123"}, {ID: "1234"}} + want := []Team{ + { + ID: "123", + GraphQLID: "VGVhbS0tLTEyMw==", + DefaultMemberRole: "member", + MembersCanCreatePipelines: true, + MembersCanCreateSuites: false, + MembersCanCreateRegistries: true, + MembersCanDestroyRegistries: false, + MembersCanDestroyPackages: false, + }, + {ID: "1234"}, + } if diff := cmp.Diff(teams, want); diff != "" { t.Errorf("Teams.List diff: (-got +want)\n%s", diff) } @@ -65,7 +89,21 @@ func TestTeamsService_GetTeam(t *testing.T) { server.HandleFunc("/v2/organizations/my-great-org/teams/123", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - _, _ = fmt.Fprint(w, `{"id":"123"}`) + _, _ = fmt.Fprint(w, `{ + "id": "123", + "graphql_id": "VGVhbS0tLTEyMw==", + "name": "my-team", + "slug": "my-team", + "description": "A test team", + "privacy": "visible", + "default": true, + "default_member_role": "maintainer", + "members_can_create_pipelines": true, + "members_can_create_suites": true, + "members_can_create_registries": false, + "members_can_destroy_registries": false, + "members_can_destroy_packages": true + }`) }) team, err := client.Teams.GetTeam(context.Background(), "my-great-org", "123") @@ -73,7 +111,21 @@ func TestTeamsService_GetTeam(t *testing.T) { t.Errorf("Teams.GetTeam returned error: %v", err) } - want := Team{ID: "123"} + want := Team{ + ID: "123", + GraphQLID: "VGVhbS0tLTEyMw==", + Name: "my-team", + Slug: "my-team", + Description: "A test team", + Privacy: "visible", + Default: true, + DefaultMemberRole: "maintainer", + MembersCanCreatePipelines: true, + MembersCanCreateSuites: true, + MembersCanCreateRegistries: false, + MembersCanDestroyRegistries: false, + MembersCanDestroyPackages: true, + } if diff := cmp.Diff(team, want); diff != "" { t.Errorf("Teams.GetTeam diff: (-got +want)\n%s", diff) } From 2abea1fb35d4357e47dca937f76f7239136a6da5 Mon Sep 17 00:00:00 2001 From: Owen Mehegan Date: Wed, 8 Apr 2026 11:32:00 +1000 Subject: [PATCH 2/3] Update tests to cover new Team fields in all endpoints Address review feedback: ensure ListForUser, CreateTeam, and UpdateTeam tests also verify the new fields (GraphQLID, DefaultMemberRole, MembersCanCreatePipelines, etc.) are correctly deserialized in API responses. --- teams_test.go | 89 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 8 deletions(-) diff --git a/teams_test.go b/teams_test.go index 810da922..c60eca2e 100644 --- a/teams_test.go +++ b/teams_test.go @@ -66,7 +66,16 @@ func TestTeamsService_ListForUser(t *testing.T) { testFormValues(t, r, values{ "user_id": "abc", }) - _, _ = fmt.Fprint(w, `[{"id":"123"}]`) + _, _ = fmt.Fprint(w, `[{ + "id": "123", + "graphql_id": "VGVhbS0tLTEyMw==", + "default_member_role": "member", + "members_can_create_pipelines": true, + "members_can_create_suites": false, + "members_can_create_registries": false, + "members_can_destroy_registries": false, + "members_can_destroy_packages": false + }]`) }) opt := &TeamsListOptions{UserID: "abc"} @@ -75,7 +84,16 @@ func TestTeamsService_ListForUser(t *testing.T) { t.Errorf("Teams.List returned error: %v", err) } - want := []Team{{ID: "123"}} + want := []Team{{ + ID: "123", + GraphQLID: "VGVhbS0tLTEyMw==", + DefaultMemberRole: "member", + MembersCanCreatePipelines: true, + MembersCanCreateSuites: false, + MembersCanCreateRegistries: false, + MembersCanDestroyRegistries: false, + MembersCanDestroyPackages: false, + }} if diff := cmp.Diff(teams, want); diff != "" { t.Errorf("Teams.List diff: (-got +want)\n%s", diff) } @@ -139,15 +157,40 @@ func TestTeamsService_CreateTeam(t *testing.T) { server.HandleFunc("/v2/organizations/my-great-org/teams", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") - _, _ = fmt.Fprint(w, `{"id":"123"}`) + _, _ = fmt.Fprint(w, `{ + "id": "123", + "graphql_id": "VGVhbS0tLTEyMw==", + "name": "new-team", + "slug": "new-team", + "privacy": "visible", + "default": false, + "default_member_role": "member", + "members_can_create_pipelines": true, + "members_can_create_suites": false, + "members_can_create_registries": false, + "members_can_destroy_registries": false, + "members_can_destroy_packages": false + }`) }) - team, _, err := client.Teams.CreateTeam(context.Background(), "my-great-org", CreateTeam{}) + team, _, err := client.Teams.CreateTeam(context.Background(), "my-great-org", CreateTeam{ + Name: "new-team", + Privacy: "visible", + MembersCanCreatePipelines: true, + }) if err != nil { t.Errorf("Teams.CreateTeam returned error: %v", err) } - want := Team{ID: "123"} + want := Team{ + ID: "123", + GraphQLID: "VGVhbS0tLTEyMw==", + Name: "new-team", + Slug: "new-team", + Privacy: "visible", + DefaultMemberRole: "member", + MembersCanCreatePipelines: true, + } if diff := cmp.Diff(team, want); diff != "" { t.Errorf("Teams.CreateTeam diff: (-got +want)\n%s", diff) } @@ -161,15 +204,45 @@ func TestTeamsService_UpdateTeam(t *testing.T) { server.HandleFunc("/v2/organizations/my-great-org/teams/123", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "PATCH") - _, _ = fmt.Fprint(w, `{"id":"123"}`) + _, _ = fmt.Fprint(w, `{ + "id": "123", + "graphql_id": "VGVhbS0tLTEyMw==", + "name": "updated-team", + "slug": "updated-team", + "description": "Updated description", + "privacy": "secret", + "default": false, + "default_member_role": "maintainer", + "members_can_create_pipelines": true, + "members_can_create_suites": true, + "members_can_create_registries": false, + "members_can_destroy_registries": false, + "members_can_destroy_packages": false + }`) }) - team, _, err := client.Teams.UpdateTeam(context.Background(), "my-great-org", "123", CreateTeam{}) + team, _, err := client.Teams.UpdateTeam(context.Background(), "my-great-org", "123", CreateTeam{ + Name: "updated-team", + Description: "Updated description", + Privacy: "secret", + DefaultMemberRole: "maintainer", + MembersCanCreatePipelines: true, + }) if err != nil { t.Errorf("Teams.UpdateTeam returned error: %v", err) } - want := Team{ID: "123"} + want := Team{ + ID: "123", + GraphQLID: "VGVhbS0tLTEyMw==", + Name: "updated-team", + Slug: "updated-team", + Description: "Updated description", + Privacy: "secret", + DefaultMemberRole: "maintainer", + MembersCanCreatePipelines: true, + MembersCanCreateSuites: true, + } if diff := cmp.Diff(team, want); diff != "" { t.Errorf("Teams.UpdateTeam diff: (-got +want)\n%s", diff) } From d3342eb309c83d2e485ec2adf019d37705a7d4a2 Mon Sep 17 00:00:00 2001 From: Owen Mehegan Date: Wed, 8 Apr 2026 12:25:58 +1000 Subject: [PATCH 3/3] Add missing fields to CreateTeam struct The API accepts members_can_create_suites, members_can_create_registries, members_can_destroy_registries, and members_can_destroy_packages on create and update, but they were missing from CreateTeam. Without them, update operations would silently reset these permissions to false. --- teams.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/teams.go b/teams.go index 3fecd365..b968ef8b 100644 --- a/teams.go +++ b/teams.go @@ -42,14 +42,18 @@ type TeamsListOptions struct { UserID string `url:"user_id,omitempty"` } -// CreateTeam represents a request to create a team. +// CreateTeam represents a request to create or update a team. type CreateTeam struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Privacy string `json:"privacy,omitempty"` - IsDefaultTeam bool `json:"is_default_team"` - DefaultMemberRole string `json:"default_member_role,omitempty"` - MembersCanCreatePipelines bool `json:"members_can_create_pipelines"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Privacy string `json:"privacy,omitempty"` + IsDefaultTeam bool `json:"is_default_team"` + DefaultMemberRole string `json:"default_member_role,omitempty"` + MembersCanCreatePipelines bool `json:"members_can_create_pipelines"` + MembersCanCreateSuites bool `json:"members_can_create_suites"` + MembersCanCreateRegistries bool `json:"members_can_create_registries"` + MembersCanDestroyRegistries bool `json:"members_can_destroy_registries"` + MembersCanDestroyPackages bool `json:"members_can_destroy_packages"` } // Get the teams for an org.