diff --git a/github/examples_test.go b/github/examples_test.go index 98d6ed3a676..261d710c584 100644 --- a/github/examples_test.go +++ b/github/examples_test.go @@ -131,7 +131,7 @@ func ExamplePullRequestsService_Create() { fmt.Printf("PR created: %s\n", pr.GetHTMLURL()) } -func ExampleOrganizationsService_ListTeams() { +func ExampleTeamsService_ListTeams() { // This example shows how to get a team ID corresponding to a given team name. // Note that authentication is needed here as you are performing a lookup on @@ -147,7 +147,7 @@ func ExampleOrganizationsService_ListTeams() { opts := &github.ListOptions{} for { - teams, resp, err := client.Organizations.ListTeams(ctx, "myOrganization", opts) + teams, resp, err := client.Teams.ListTeams(ctx, "myOrganization", opts) if err != nil { fmt.Println(err) return diff --git a/github/github-accessors.go b/github/github-accessors.go index d9939c2043f..e9ebfbda39d 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -1837,7 +1837,7 @@ func (d *DiscussionComment) GetNodeID() string { } // GetNumber returns the Number field if it's non-nil, zero value otherwise. -func (d *DiscussionComment) GetNumber() int64 { +func (d *DiscussionComment) GetNumber() int { if d == nil || d.Number == nil { return 0 } @@ -9701,7 +9701,7 @@ func (t *TeamDiscussion) GetBodyVersion() string { } // GetCommentsCount returns the CommentsCount field if it's non-nil, zero value otherwise. -func (t *TeamDiscussion) GetCommentsCount() int64 { +func (t *TeamDiscussion) GetCommentsCount() int { if t == nil || t.CommentsCount == nil { return 0 } @@ -9749,7 +9749,7 @@ func (t *TeamDiscussion) GetNodeID() string { } // GetNumber returns the Number field if it's non-nil, zero value otherwise. -func (t *TeamDiscussion) GetNumber() int64 { +func (t *TeamDiscussion) GetNumber() int { if t == nil || t.Number == nil { return 0 } diff --git a/github/orgs_teams.go b/github/orgs_teams.go deleted file mode 100644 index b3cc9f07da3..00000000000 --- a/github/orgs_teams.go +++ /dev/null @@ -1,514 +0,0 @@ -// 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" - "strings" - "time" -) - -// Team represents a team within a GitHub organization. Teams are used to -// manage access to an organization's repositories. -type Team struct { - ID *int64 `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - URL *string `json:"url,omitempty"` - Slug *string `json:"slug,omitempty"` - - // Permission specifies the default permission for repositories owned by the team. - Permission *string `json:"permission,omitempty"` - - // Privacy identifies the level of privacy this team should have. - // Possible values are: - // secret - only visible to organization owners and members of this team - // closed - visible to all members of this organization - // Default is "secret". - Privacy *string `json:"privacy,omitempty"` - - MembersCount *int `json:"members_count,omitempty"` - ReposCount *int `json:"repos_count,omitempty"` - Organization *Organization `json:"organization,omitempty"` - MembersURL *string `json:"members_url,omitempty"` - RepositoriesURL *string `json:"repositories_url,omitempty"` - Parent *Team `json:"parent,omitempty"` - - // LDAPDN is only available in GitHub Enterprise and when the team - // membership is synchronized with LDAP. - LDAPDN *string `json:"ldap_dn,omitempty"` -} - -func (t Team) String() string { - return Stringify(t) -} - -// Invitation represents a team member's invitation status. -type Invitation struct { - ID *int64 `json:"id,omitempty"` - Login *string `json:"login,omitempty"` - Email *string `json:"email,omitempty"` - // Role can be one of the values - 'direct_member', 'admin', 'billing_manager', 'hiring_manager', or 'reinstate'. - Role *string `json:"role,omitempty"` - CreatedAt *time.Time `json:"created_at,omitempty"` - Inviter *User `json:"inviter,omitempty"` - TeamCount *int `json:"team_count,omitempty"` - InvitationTeamURL *string `json:"invitation_team_url,omitempty"` -} - -func (i Invitation) String() string { - return Stringify(i) -} - -// ListTeams lists all of the teams for an organization. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#list-teams -func (s *OrganizationsService) ListTeams(ctx context.Context, org string, opt *ListOptions) ([]*Team, *Response, error) { - u := fmt.Sprintf("orgs/%v/teams", org) - u, err := addOptions(u, opt) - if err != nil { - return nil, nil, err - } - - req, err := s.client.NewRequest("GET", u, nil) - if err != nil { - return nil, nil, err - } - - // TODO: remove custom Accept header when this API fully launches. - req.Header.Set("Accept", mediaTypeNestedTeamsPreview) - - var teams []*Team - resp, err := s.client.Do(ctx, req, &teams) - if err != nil { - return nil, resp, err - } - - return teams, resp, nil -} - -// GetTeam fetches a team by ID. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#get-team -func (s *OrganizationsService) GetTeam(ctx context.Context, team int64) (*Team, *Response, error) { - u := fmt.Sprintf("teams/%v", team) - req, err := s.client.NewRequest("GET", u, nil) - if err != nil { - return nil, nil, err - } - - // TODO: remove custom Accept header when this API fully launches. - req.Header.Set("Accept", mediaTypeNestedTeamsPreview) - - t := new(Team) - resp, err := s.client.Do(ctx, req, t) - if err != nil { - return nil, resp, err - } - - return t, resp, nil -} - -// NewTeam represents a team to be created or modified. -type NewTeam struct { - Name string `json:"name"` // Name of the team. (Required.) - Description *string `json:"description,omitempty"` - Maintainers []string `json:"maintainers,omitempty"` - RepoNames []string `json:"repo_names,omitempty"` - ParentTeamID *int64 `json:"parent_team_id,omitempty"` - - // Deprecated: Permission is deprecated when creating or editing a team in an org - // using the new GitHub permission model. It no longer identifies the - // permission a team has on its repos, but only specifies the default - // permission a repo is initially added with. Avoid confusion by - // specifying a permission value when calling AddTeamRepo. - Permission *string `json:"permission,omitempty"` - - // Privacy identifies the level of privacy this team should have. - // Possible values are: - // secret - only visible to organization owners and members of this team - // closed - visible to all members of this organization - // Default is "secret". - Privacy *string `json:"privacy,omitempty"` - - // LDAPDN may be used in GitHub Enterprise when the team membership - // is synchronized with LDAP. - LDAPDN *string `json:"ldap_dn,omitempty"` -} - -func (s NewTeam) String() string { - return Stringify(s) -} - -// CreateTeam creates a new team within an organization. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#create-team -func (s *OrganizationsService) CreateTeam(ctx context.Context, org string, team *NewTeam) (*Team, *Response, error) { - u := fmt.Sprintf("orgs/%v/teams", org) - req, err := s.client.NewRequest("POST", u, team) - if err != nil { - return nil, nil, err - } - - // TODO: remove custom Accept header when this API fully launches. - req.Header.Set("Accept", mediaTypeNestedTeamsPreview) - - t := new(Team) - resp, err := s.client.Do(ctx, req, t) - if err != nil { - return nil, resp, err - } - - return t, resp, nil -} - -// EditTeam edits a team. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#edit-team -func (s *OrganizationsService) EditTeam(ctx context.Context, id int64, team *NewTeam) (*Team, *Response, error) { - u := fmt.Sprintf("teams/%v", id) - req, err := s.client.NewRequest("PATCH", u, team) - if err != nil { - return nil, nil, err - } - - // TODO: remove custom Accept header when this API fully launches. - req.Header.Set("Accept", mediaTypeNestedTeamsPreview) - - t := new(Team) - resp, err := s.client.Do(ctx, req, t) - if err != nil { - return nil, resp, err - } - - return t, resp, nil -} - -// DeleteTeam deletes a team. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#delete-team -func (s *OrganizationsService) DeleteTeam(ctx context.Context, team int64) (*Response, error) { - u := fmt.Sprintf("teams/%v", team) - req, err := s.client.NewRequest("DELETE", u, nil) - if err != nil { - return nil, err - } - - req.Header.Set("Accept", mediaTypeNestedTeamsPreview) - - return s.client.Do(ctx, req, nil) -} - -// OrganizationListTeamMembersOptions specifies the optional parameters to the -// OrganizationsService.ListTeamMembers method. -type OrganizationListTeamMembersOptions struct { - // Role filters members returned by their role in the team. Possible - // values are "all", "member", "maintainer". Default is "all". - Role string `url:"role,omitempty"` - - ListOptions -} - -// ListChildTeams lists child teams for a team. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#list-child-teams -func (s *OrganizationsService) ListChildTeams(ctx context.Context, teamID int64, opt *ListOptions) ([]*Team, *Response, error) { - u := fmt.Sprintf("teams/%v/teams", teamID) - u, err := addOptions(u, opt) - 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", mediaTypeNestedTeamsPreview) - - var teams []*Team - resp, err := s.client.Do(ctx, req, &teams) - if err != nil { - return nil, resp, err - } - - return teams, resp, nil -} - -// ListTeamMembers lists all of the users who are members of the specified -// team. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#list-team-members -func (s *OrganizationsService) ListTeamMembers(ctx context.Context, team int64, opt *OrganizationListTeamMembersOptions) ([]*User, *Response, error) { - u := fmt.Sprintf("teams/%v/members", team) - u, err := addOptions(u, opt) - 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", mediaTypeNestedTeamsPreview) - - var members []*User - resp, err := s.client.Do(ctx, req, &members) - if err != nil { - return nil, resp, err - } - - return members, resp, nil -} - -// IsTeamMember checks if a user is a member of the specified team. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#get-team-member -// -// Deprecated: This API has been marked as deprecated in the Github API docs, -// OrganizationsService.GetTeamMembership method should be used instead. -func (s *OrganizationsService) IsTeamMember(ctx context.Context, team int64, user string) (bool, *Response, error) { - u := fmt.Sprintf("teams/%v/members/%v", team, user) - req, err := s.client.NewRequest("GET", u, nil) - if err != nil { - return false, nil, err - } - - resp, err := s.client.Do(ctx, req, nil) - member, err := parseBoolResponse(err) - return member, resp, err -} - -// ListTeamRepos lists the repositories that the specified team has access to. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#list-team-repos -func (s *OrganizationsService) ListTeamRepos(ctx context.Context, team int64, opt *ListOptions) ([]*Repository, *Response, error) { - u := fmt.Sprintf("teams/%v/repos", team) - u, err := addOptions(u, opt) - if err != nil { - return nil, nil, err - } - - req, err := s.client.NewRequest("GET", u, nil) - if err != nil { - return nil, nil, err - } - - // TODO: remove custom Accept header when topics API fully launches. - headers := []string{mediaTypeTopicsPreview, mediaTypeNestedTeamsPreview} - req.Header.Set("Accept", strings.Join(headers, ", ")) - - var repos []*Repository - resp, err := s.client.Do(ctx, req, &repos) - if err != nil { - return nil, resp, err - } - - return repos, resp, nil -} - -// IsTeamRepo checks if a team manages the specified repository. If the -// repository is managed by team, a Repository is returned which includes the -// permissions team has for that repo. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#check-if-a-team-manages-a-repository -func (s *OrganizationsService) IsTeamRepo(ctx context.Context, team int64, owner string, repo string) (*Repository, *Response, error) { - u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) - req, err := s.client.NewRequest("GET", u, nil) - if err != nil { - return nil, nil, err - } - - headers := []string{mediaTypeOrgPermissionRepo, mediaTypeNestedTeamsPreview} - req.Header.Set("Accept", strings.Join(headers, ", ")) - - repository := new(Repository) - resp, err := s.client.Do(ctx, req, repository) - if err != nil { - return nil, resp, err - } - - return repository, resp, nil -} - -// OrganizationAddTeamRepoOptions specifies the optional parameters to the -// OrganizationsService.AddTeamRepo method. -type OrganizationAddTeamRepoOptions struct { - // Permission specifies the permission to grant the team on this repository. - // Possible values are: - // pull - team members can pull, but not push to or administer this repository - // push - team members can pull and push, but not administer this repository - // admin - team members can pull, push and administer this repository - // - // If not specified, the team's permission attribute will be used. - Permission string `json:"permission,omitempty"` -} - -// AddTeamRepo adds a repository to be managed by the specified team. The -// specified repository must be owned by the organization to which the team -// belongs, or a direct fork of a repository owned by the organization. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#add-team-repo -func (s *OrganizationsService) AddTeamRepo(ctx context.Context, team int64, owner string, repo string, opt *OrganizationAddTeamRepoOptions) (*Response, error) { - u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) - req, err := s.client.NewRequest("PUT", u, opt) - if err != nil { - return nil, err - } - - return s.client.Do(ctx, req, nil) -} - -// RemoveTeamRepo removes a repository from being managed by the specified -// team. Note that this does not delete the repository, it just removes it -// from the team. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#remove-team-repo -func (s *OrganizationsService) RemoveTeamRepo(ctx context.Context, team int64, owner string, repo string) (*Response, error) { - u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) - req, err := s.client.NewRequest("DELETE", u, nil) - if err != nil { - return nil, err - } - - return s.client.Do(ctx, req, nil) -} - -// ListUserTeams lists a user's teams -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#list-user-teams -func (s *OrganizationsService) ListUserTeams(ctx context.Context, opt *ListOptions) ([]*Team, *Response, error) { - u := "user/teams" - u, err := addOptions(u, opt) - if err != nil { - return nil, nil, err - } - - req, err := s.client.NewRequest("GET", u, nil) - if err != nil { - return nil, nil, err - } - - // TODO: remove custom Accept header when this API fully launches. - req.Header.Set("Accept", mediaTypeNestedTeamsPreview) - - var teams []*Team - resp, err := s.client.Do(ctx, req, &teams) - if err != nil { - return nil, resp, err - } - - return teams, resp, nil -} - -// GetTeamMembership returns the membership status for a user in a team. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#get-team-membership -func (s *OrganizationsService) GetTeamMembership(ctx context.Context, team int64, user string) (*Membership, *Response, error) { - u := fmt.Sprintf("teams/%v/memberships/%v", team, user) - req, err := s.client.NewRequest("GET", u, nil) - if err != nil { - return nil, nil, err - } - - req.Header.Set("Accept", mediaTypeNestedTeamsPreview) - - t := new(Membership) - resp, err := s.client.Do(ctx, req, t) - if err != nil { - return nil, resp, err - } - - return t, resp, nil -} - -// OrganizationAddTeamMembershipOptions does stuff specifies the optional -// parameters to the OrganizationsService.AddTeamMembership method. -type OrganizationAddTeamMembershipOptions struct { - // Role specifies the role the user should have in the team. Possible - // values are: - // member - a normal member of the team - // maintainer - a team maintainer. Able to add/remove other team - // members, promote other team members to team - // maintainer, and edit the team’s name and description - // - // Default value is "member". - Role string `json:"role,omitempty"` -} - -// AddTeamMembership adds or invites a user to a team. -// -// In order to add a membership between a user and a team, the authenticated -// user must have 'admin' permissions to the team or be an owner of the -// organization that the team is associated with. -// -// If the user is already a part of the team's organization (meaning they're on -// at least one other team in the organization), this endpoint will add the -// user to the team. -// -// If the user is completely unaffiliated with the team's organization (meaning -// they're on none of the organization's teams), this endpoint will send an -// invitation to the user via email. This newly-created membership will be in -// the "pending" state until the user accepts the invitation, at which point -// the membership will transition to the "active" state and the user will be -// added as a member of the team. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#add-team-membership -func (s *OrganizationsService) AddTeamMembership(ctx context.Context, team int64, user string, opt *OrganizationAddTeamMembershipOptions) (*Membership, *Response, error) { - u := fmt.Sprintf("teams/%v/memberships/%v", team, user) - req, err := s.client.NewRequest("PUT", u, opt) - if err != nil { - return nil, nil, err - } - - t := new(Membership) - resp, err := s.client.Do(ctx, req, t) - if err != nil { - return nil, resp, err - } - - return t, resp, nil -} - -// RemoveTeamMembership removes a user from a team. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#remove-team-membership -func (s *OrganizationsService) RemoveTeamMembership(ctx context.Context, team int64, user string) (*Response, error) { - u := fmt.Sprintf("teams/%v/memberships/%v", team, user) - req, err := s.client.NewRequest("DELETE", u, nil) - if err != nil { - return nil, err - } - - return s.client.Do(ctx, req, nil) -} - -// ListPendingTeamInvitations get pending invitaion list in team. -// Warning: The API may change without advance notice during the preview period. -// Preview features are not supported for production use. -// -// GitHub API docs: https://developer.github.com/v3/orgs/teams/#list-pending-team-invitations -func (s *OrganizationsService) ListPendingTeamInvitations(ctx context.Context, team int64, opt *ListOptions) ([]*Invitation, *Response, error) { - u := fmt.Sprintf("teams/%v/invitations", team) - u, err := addOptions(u, opt) - if err != nil { - return nil, nil, err - } - - req, err := s.client.NewRequest("GET", u, nil) - if err != nil { - return nil, nil, err - } - - var pendingInvitations []*Invitation - resp, err := s.client.Do(ctx, req, &pendingInvitations) - if err != nil { - return nil, resp, err - } - - return pendingInvitations, resp, nil -} diff --git a/github/orgs_teams_test.go b/github/orgs_teams_test.go deleted file mode 100644 index ead8024e77a..00000000000 --- a/github/orgs_teams_test.go +++ /dev/null @@ -1,663 +0,0 @@ -// 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" - "encoding/json" - "fmt" - "net/http" - "reflect" - "strings" - "testing" - "time" -) - -func TestOrganizationsService_ListTeams(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/orgs/o/teams", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) - testFormValues(t, r, values{"page": "2"}) - fmt.Fprint(w, `[{"id":1}]`) - }) - - opt := &ListOptions{Page: 2} - teams, _, err := client.Organizations.ListTeams(context.Background(), "o", opt) - if err != nil { - t.Errorf("Organizations.ListTeams returned error: %v", err) - } - - want := []*Team{{ID: Int64(1)}} - if !reflect.DeepEqual(teams, want) { - t.Errorf("Organizations.ListTeams returned %+v, want %+v", teams, want) - } -} - -func TestOrganizationsService_ListTeams_invalidOrg(t *testing.T) { - client, _, _, teardown := setup() - defer teardown() - - _, _, err := client.Organizations.ListTeams(context.Background(), "%", nil) - testURLParseError(t, err) -} - -func TestOrganizationsService_GetTeam(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) - fmt.Fprint(w, `{"id":1, "name":"n", "description": "d", "url":"u", "slug": "s", "permission":"p", "ldap_dn":"cn=n,ou=groups,dc=example,dc=com", "parent":null}`) - }) - - team, _, err := client.Organizations.GetTeam(context.Background(), 1) - if err != nil { - t.Errorf("Organizations.GetTeam returned error: %v", err) - } - - want := &Team{ID: Int64(1), Name: String("n"), Description: String("d"), URL: String("u"), Slug: String("s"), Permission: String("p"), LDAPDN: String("cn=n,ou=groups,dc=example,dc=com")} - if !reflect.DeepEqual(team, want) { - t.Errorf("Organizations.GetTeam returned %+v, want %+v", team, want) - } -} - -func TestOrganizationService_GetTeam_nestedTeams(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) - fmt.Fprint(w, `{"id":1, "name":"n", "description": "d", "url":"u", "slug": "s", "permission":"p", - "parent": {"id":2, "name":"n", "description": "d", "parent": null}}`) - }) - - team, _, err := client.Organizations.GetTeam(context.Background(), 1) - if err != nil { - t.Errorf("Organizations.GetTeam returned error: %v", err) - } - - want := &Team{ID: Int64(1), Name: String("n"), Description: String("d"), URL: String("u"), Slug: String("s"), Permission: String("p"), - Parent: &Team{ID: Int64(2), Name: String("n"), Description: String("d")}, - } - if !reflect.DeepEqual(team, want) { - t.Errorf("Organizations.GetTeam returned %+v, want %+v", team, want) - } -} - -func TestOrganizationsService_CreateTeam(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - input := &NewTeam{Name: "n", Privacy: String("closed"), RepoNames: []string{"r"}} - - mux.HandleFunc("/orgs/o/teams", func(w http.ResponseWriter, r *http.Request) { - v := new(NewTeam) - json.NewDecoder(r.Body).Decode(v) - - testMethod(t, r, "POST") - testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) - if !reflect.DeepEqual(v, input) { - t.Errorf("Request body = %+v, want %+v", v, input) - } - - fmt.Fprint(w, `{"id":1}`) - }) - - team, _, err := client.Organizations.CreateTeam(context.Background(), "o", input) - if err != nil { - t.Errorf("Organizations.CreateTeam returned error: %v", err) - } - - want := &Team{ID: Int64(1)} - if !reflect.DeepEqual(team, want) { - t.Errorf("Organizations.CreateTeam returned %+v, want %+v", team, want) - } -} - -func TestOrganizationsService_CreateTeam_invalidOrg(t *testing.T) { - client, _, _, teardown := setup() - defer teardown() - - _, _, err := client.Organizations.CreateTeam(context.Background(), "%", nil) - testURLParseError(t, err) -} - -func TestOrganizationsService_EditTeam(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - input := &NewTeam{Name: "n", Privacy: String("closed")} - - mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { - v := new(NewTeam) - json.NewDecoder(r.Body).Decode(v) - - testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) - testMethod(t, r, "PATCH") - if !reflect.DeepEqual(v, input) { - t.Errorf("Request body = %+v, want %+v", v, input) - } - - fmt.Fprint(w, `{"id":1}`) - }) - - team, _, err := client.Organizations.EditTeam(context.Background(), 1, input) - if err != nil { - t.Errorf("Organizations.EditTeam returned error: %v", err) - } - - want := &Team{ID: Int64(1)} - if !reflect.DeepEqual(team, want) { - t.Errorf("Organizations.EditTeam returned %+v, want %+v", team, want) - } -} - -func TestOrganizationsService_DeleteTeam(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") - testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) - }) - - _, err := client.Organizations.DeleteTeam(context.Background(), 1) - if err != nil { - t.Errorf("Organizations.DeleteTeam returned error: %v", err) - } -} - -func TestOrganizationsService_ListChildTeams(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1/teams", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) - testFormValues(t, r, values{"page": "2"}) - fmt.Fprint(w, `[{"id":2}]`) - }) - - opt := &ListOptions{Page: 2} - teams, _, err := client.Organizations.ListChildTeams(context.Background(), 1, opt) - if err != nil { - t.Errorf("Organizations.ListTeams returned error: %v", err) - } - - want := []*Team{{ID: Int64(2)}} - if !reflect.DeepEqual(teams, want) { - t.Errorf("Organizations.ListTeams returned %+v, want %+v", teams, want) - } -} - -func TestOrganizationsService_ListTeamMembers(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1/members", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) - testFormValues(t, r, values{"role": "member", "page": "2"}) - fmt.Fprint(w, `[{"id":1}]`) - }) - - opt := &OrganizationListTeamMembersOptions{Role: "member", ListOptions: ListOptions{Page: 2}} - members, _, err := client.Organizations.ListTeamMembers(context.Background(), 1, opt) - if err != nil { - t.Errorf("Organizations.ListTeamMembers returned error: %v", err) - } - - want := []*User{{ID: Int64(1)}} - if !reflect.DeepEqual(members, want) { - t.Errorf("Organizations.ListTeamMembers returned %+v, want %+v", members, want) - } -} - -func TestOrganizationsService_IsTeamMember_true(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - }) - - member, _, err := client.Organizations.IsTeamMember(context.Background(), 1, "u") - if err != nil { - t.Errorf("Organizations.IsTeamMember returned error: %v", err) - } - if want := true; member != want { - t.Errorf("Organizations.IsTeamMember returned %+v, want %+v", member, want) - } -} - -// ensure that a 404 response is interpreted as "false" and not an error -func TestOrganizationsService_IsTeamMember_false(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - w.WriteHeader(http.StatusNotFound) - }) - - member, _, err := client.Organizations.IsTeamMember(context.Background(), 1, "u") - if err != nil { - t.Errorf("Organizations.IsTeamMember returned error: %+v", err) - } - if want := false; member != want { - t.Errorf("Organizations.IsTeamMember returned %+v, want %+v", member, want) - } -} - -// ensure that a 400 response is interpreted as an actual error, and not simply -// as "false" like the above case of a 404 -func TestOrganizationsService_IsTeamMember_error(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - http.Error(w, "BadRequest", http.StatusBadRequest) - }) - - member, _, err := client.Organizations.IsTeamMember(context.Background(), 1, "u") - if err == nil { - t.Errorf("Expected HTTP 400 response") - } - if want := false; member != want { - t.Errorf("Organizations.IsTeamMember returned %+v, want %+v", member, want) - } -} - -func TestOrganizationsService_IsTeamMember_invalidUser(t *testing.T) { - client, _, _, teardown := setup() - defer teardown() - - _, _, err := client.Organizations.IsTeamMember(context.Background(), 1, "%") - testURLParseError(t, err) -} - -func TestOrganizationsService_PublicizeMembership(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") - w.WriteHeader(http.StatusNoContent) - }) - - _, err := client.Organizations.PublicizeMembership(context.Background(), "o", "u") - if err != nil { - t.Errorf("Organizations.PublicizeMembership returned error: %v", err) - } -} - -func TestOrganizationsService_PublicizeMembership_invalidOrg(t *testing.T) { - client, _, _, teardown := setup() - defer teardown() - - _, err := client.Organizations.PublicizeMembership(context.Background(), "%", "u") - testURLParseError(t, err) -} - -func TestOrganizationsService_ConcealMembership(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") - w.WriteHeader(http.StatusNoContent) - }) - - _, err := client.Organizations.ConcealMembership(context.Background(), "o", "u") - if err != nil { - t.Errorf("Organizations.ConcealMembership returned error: %v", err) - } -} - -func TestOrganizationsService_ConcealMembership_invalidOrg(t *testing.T) { - client, _, _, teardown := setup() - defer teardown() - - _, err := client.Organizations.ConcealMembership(context.Background(), "%", "u") - testURLParseError(t, err) -} - -func TestOrganizationsService_ListTeamRepos(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1/repos", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - acceptHeaders := []string{mediaTypeTopicsPreview, mediaTypeNestedTeamsPreview} - testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) - testFormValues(t, r, values{"page": "2"}) - fmt.Fprint(w, `[{"id":1}]`) - }) - - opt := &ListOptions{Page: 2} - members, _, err := client.Organizations.ListTeamRepos(context.Background(), 1, opt) - if err != nil { - t.Errorf("Organizations.ListTeamRepos returned error: %v", err) - } - - want := []*Repository{{ID: Int64(1)}} - if !reflect.DeepEqual(members, want) { - t.Errorf("Organizations.ListTeamRepos returned %+v, want %+v", members, want) - } -} - -func TestOrganizationsService_IsTeamRepo_true(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - acceptHeaders := []string{mediaTypeOrgPermissionRepo, mediaTypeNestedTeamsPreview} - testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) - fmt.Fprint(w, `{"id":1}`) - }) - - repo, _, err := client.Organizations.IsTeamRepo(context.Background(), 1, "o", "r") - if err != nil { - t.Errorf("Organizations.IsTeamRepo returned error: %v", err) - } - - want := &Repository{ID: Int64(1)} - if !reflect.DeepEqual(repo, want) { - t.Errorf("Organizations.IsTeamRepo returned %+v, want %+v", repo, want) - } -} - -func TestOrganizationsService_IsTeamRepo_false(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - w.WriteHeader(http.StatusNotFound) - }) - - repo, resp, err := client.Organizations.IsTeamRepo(context.Background(), 1, "o", "r") - if err == nil { - t.Errorf("Expected HTTP 404 response") - } - if got, want := resp.Response.StatusCode, http.StatusNotFound; got != want { - t.Errorf("Organizations.IsTeamRepo returned status %d, want %d", got, want) - } - if repo != nil { - t.Errorf("Organizations.IsTeamRepo returned %+v, want nil", repo) - } -} - -func TestOrganizationsService_IsTeamRepo_error(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - http.Error(w, "BadRequest", http.StatusBadRequest) - }) - - repo, resp, err := client.Organizations.IsTeamRepo(context.Background(), 1, "o", "r") - if err == nil { - t.Errorf("Expected HTTP 400 response") - } - if got, want := resp.Response.StatusCode, http.StatusBadRequest; got != want { - t.Errorf("Organizations.IsTeamRepo returned status %d, want %d", got, want) - } - if repo != nil { - t.Errorf("Organizations.IsTeamRepo returned %+v, want nil", repo) - } -} - -func TestOrganizationsService_IsTeamRepo_invalidOwner(t *testing.T) { - client, _, _, teardown := setup() - defer teardown() - - _, _, err := client.Organizations.IsTeamRepo(context.Background(), 1, "%", "r") - testURLParseError(t, err) -} - -func TestOrganizationsService_AddTeamRepo(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - opt := &OrganizationAddTeamRepoOptions{Permission: "admin"} - - mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { - v := new(OrganizationAddTeamRepoOptions) - json.NewDecoder(r.Body).Decode(v) - - testMethod(t, r, "PUT") - if !reflect.DeepEqual(v, opt) { - t.Errorf("Request body = %+v, want %+v", v, opt) - } - - w.WriteHeader(http.StatusNoContent) - }) - - _, err := client.Organizations.AddTeamRepo(context.Background(), 1, "o", "r", opt) - if err != nil { - t.Errorf("Organizations.AddTeamRepo returned error: %v", err) - } -} - -func TestOrganizationsService_AddTeamRepo_noAccess(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "PUT") - w.WriteHeader(http.StatusUnprocessableEntity) - }) - - _, err := client.Organizations.AddTeamRepo(context.Background(), 1, "o", "r", nil) - if err == nil { - t.Errorf("Expcted error to be returned") - } -} - -func TestOrganizationsService_AddTeamRepo_invalidOwner(t *testing.T) { - client, _, _, teardown := setup() - defer teardown() - - _, err := client.Organizations.AddTeamRepo(context.Background(), 1, "%", "r", nil) - testURLParseError(t, err) -} - -func TestOrganizationsService_RemoveTeamRepo(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") - w.WriteHeader(http.StatusNoContent) - }) - - _, err := client.Organizations.RemoveTeamRepo(context.Background(), 1, "o", "r") - if err != nil { - t.Errorf("Organizations.RemoveTeamRepo returned error: %v", err) - } -} - -func TestOrganizationsService_RemoveTeamRepo_invalidOwner(t *testing.T) { - client, _, _, teardown := setup() - defer teardown() - - _, err := client.Organizations.RemoveTeamRepo(context.Background(), 1, "%", "r") - testURLParseError(t, err) -} - -func TestOrganizationsService_GetTeamMembership(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) - fmt.Fprint(w, `{"url":"u", "state":"active"}`) - }) - - membership, _, err := client.Organizations.GetTeamMembership(context.Background(), 1, "u") - if err != nil { - t.Errorf("Organizations.GetTeamMembership returned error: %v", err) - } - - want := &Membership{URL: String("u"), State: String("active")} - if !reflect.DeepEqual(membership, want) { - t.Errorf("Organizations.GetTeamMembership returned %+v, want %+v", membership, want) - } -} - -func TestOrganizationsService_AddTeamMembership(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - opt := &OrganizationAddTeamMembershipOptions{Role: "maintainer"} - - mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { - v := new(OrganizationAddTeamMembershipOptions) - json.NewDecoder(r.Body).Decode(v) - - testMethod(t, r, "PUT") - if !reflect.DeepEqual(v, opt) { - t.Errorf("Request body = %+v, want %+v", v, opt) - } - - fmt.Fprint(w, `{"url":"u", "state":"pending"}`) - }) - - membership, _, err := client.Organizations.AddTeamMembership(context.Background(), 1, "u", opt) - if err != nil { - t.Errorf("Organizations.AddTeamMembership returned error: %v", err) - } - - want := &Membership{URL: String("u"), State: String("pending")} - if !reflect.DeepEqual(membership, want) { - t.Errorf("Organizations.AddTeamMembership returned %+v, want %+v", membership, want) - } -} - -func TestOrganizationsService_RemoveTeamMembership(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "DELETE") - w.WriteHeader(http.StatusNoContent) - }) - - _, err := client.Organizations.RemoveTeamMembership(context.Background(), 1, "u") - if err != nil { - t.Errorf("Organizations.RemoveTeamMembership returned error: %v", err) - } -} - -func TestOrganizationsService_ListUserTeams(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/user/teams", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) - testFormValues(t, r, values{"page": "1"}) - fmt.Fprint(w, `[{"id":1}]`) - }) - - opt := &ListOptions{Page: 1} - teams, _, err := client.Organizations.ListUserTeams(context.Background(), opt) - if err != nil { - t.Errorf("Organizations.ListUserTeams returned error: %v", err) - } - - want := []*Team{{ID: Int64(1)}} - if !reflect.DeepEqual(teams, want) { - t.Errorf("Organizations.ListUserTeams returned %+v, want %+v", teams, want) - } -} - -func TestOrganizationsService_ListPendingTeamInvitations(t *testing.T) { - client, mux, _, teardown := setup() - defer teardown() - - mux.HandleFunc("/teams/1/invitations", func(w http.ResponseWriter, r *http.Request) { - testMethod(t, r, "GET") - testFormValues(t, r, values{"page": "1"}) - fmt.Fprint(w, `[ - { - "id": 1, - "login": "monalisa", - "email": "octocat@github.com", - "role": "direct_member", - "created_at": "2017-01-21T00:00:00Z", - "inviter": { - "login": "other_user", - "id": 1, - "avatar_url": "https://github.com/images/error/other_user_happy.gif", - "gravatar_id": "", - "url": "https://api.github.com/users/other_user", - "html_url": "https://github.com/other_user", - "followers_url": "https://api.github.com/users/other_user/followers", - "following_url": "https://api.github.com/users/other_user/following/other_user", - "gists_url": "https://api.github.com/users/other_user/gists/gist_id", - "starred_url": "https://api.github.com/users/other_user/starred/owner/repo", - "subscriptions_url": "https://api.github.com/users/other_user/subscriptions", - "organizations_url": "https://api.github.com/users/other_user/orgs", - "repos_url": "https://api.github.com/users/other_user/repos", - "events_url": "https://api.github.com/users/other_user/events/privacy", - "received_events_url": "https://api.github.com/users/other_user/received_events/privacy", - "type": "User", - "site_admin": false - } - } - ]`) - }) - - opt := &ListOptions{Page: 1} - invitations, _, err := client.Organizations.ListPendingTeamInvitations(context.Background(), 1, opt) - if err != nil { - t.Errorf("Organizations.ListPendingTeamInvitations returned error: %v", err) - } - - createdAt := time.Date(2017, 01, 21, 0, 0, 0, 0, time.UTC) - want := []*Invitation{ - { - ID: Int64(1), - Login: String("monalisa"), - Email: String("octocat@github.com"), - Role: String("direct_member"), - CreatedAt: &createdAt, - Inviter: &User{ - Login: String("other_user"), - ID: Int64(1), - AvatarURL: String("https://github.com/images/error/other_user_happy.gif"), - GravatarID: String(""), - URL: String("https://api.github.com/users/other_user"), - HTMLURL: String("https://github.com/other_user"), - FollowersURL: String("https://api.github.com/users/other_user/followers"), - FollowingURL: String("https://api.github.com/users/other_user/following/other_user"), - GistsURL: String("https://api.github.com/users/other_user/gists/gist_id"), - StarredURL: String("https://api.github.com/users/other_user/starred/owner/repo"), - SubscriptionsURL: String("https://api.github.com/users/other_user/subscriptions"), - OrganizationsURL: String("https://api.github.com/users/other_user/orgs"), - ReposURL: String("https://api.github.com/users/other_user/repos"), - EventsURL: String("https://api.github.com/users/other_user/events/privacy"), - ReceivedEventsURL: String("https://api.github.com/users/other_user/received_events/privacy"), - Type: String("User"), - SiteAdmin: Bool(false), - }, - }} - - if !reflect.DeepEqual(invitations, want) { - t.Errorf("Organizations.ListPendingTeamInvitations returned %+v, want %+v", invitations, want) - } -} diff --git a/github/teams.go b/github/teams.go index 1021d538fd4..840043bc793 100644 --- a/github/teams.go +++ b/github/teams.go @@ -1,7 +1,352 @@ package github +import ( + "context" + "fmt" + "strings" + "time" +) + // TeamsService provides access to the team-related functions // in the GitHub API. // // GitHub API docs: https://developer.github.com/v3/teams/ type TeamsService service + +// Team represents a team within a GitHub organization. Teams are used to +// manage access to an organization's repositories. +type Team struct { + ID *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + URL *string `json:"url,omitempty"` + Slug *string `json:"slug,omitempty"` + + // Permission specifies the default permission for repositories owned by the team. + Permission *string `json:"permission,omitempty"` + + // Privacy identifies the level of privacy this team should have. + // Possible values are: + // secret - only visible to organization owners and members of this team + // closed - visible to all members of this organization + // Default is "secret". + Privacy *string `json:"privacy,omitempty"` + + MembersCount *int `json:"members_count,omitempty"` + ReposCount *int `json:"repos_count,omitempty"` + Organization *Organization `json:"organization,omitempty"` + MembersURL *string `json:"members_url,omitempty"` + RepositoriesURL *string `json:"repositories_url,omitempty"` + Parent *Team `json:"parent,omitempty"` + + // LDAPDN is only available in GitHub Enterprise and when the team + // membership is synchronized with LDAP. + LDAPDN *string `json:"ldap_dn,omitempty"` +} + +func (t Team) String() string { + return Stringify(t) +} + +// Invitation represents a team member's invitation status. +type Invitation struct { + ID *int64 `json:"id,omitempty"` + Login *string `json:"login,omitempty"` + Email *string `json:"email,omitempty"` + // Role can be one of the values - 'direct_member', 'admin', 'billing_manager', 'hiring_manager', or 'reinstate'. + Role *string `json:"role,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + Inviter *User `json:"inviter,omitempty"` + TeamCount *int `json:"team_count,omitempty"` + InvitationTeamURL *string `json:"invitation_team_url,omitempty"` +} + +func (i Invitation) String() string { + return Stringify(i) +} + +// ListTeams lists all of the teams for an organization. +// +// GitHub API docs: https://developer.github.com/v3/teams/#list-teams +func (s *TeamsService) ListTeams(ctx context.Context, org string, opt *ListOptions) ([]*Team, *Response, error) { + u := fmt.Sprintf("orgs/%v/teams", org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + var teams []*Team + resp, err := s.client.Do(ctx, req, &teams) + if err != nil { + return nil, resp, err + } + + return teams, resp, nil +} + +// GetTeam fetches a team by ID. +// +// GitHub API docs: https://developer.github.com/v3/teams/#get-team +func (s *TeamsService) GetTeam(ctx context.Context, team int64) (*Team, *Response, error) { + u := fmt.Sprintf("teams/%v", team) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + t := new(Team) + resp, err := s.client.Do(ctx, req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, nil +} + +// NewTeam represents a team to be created or modified. +type NewTeam struct { + Name string `json:"name"` // Name of the team. (Required.) + Description *string `json:"description,omitempty"` + Maintainers []string `json:"maintainers,omitempty"` + RepoNames []string `json:"repo_names,omitempty"` + ParentTeamID *int64 `json:"parent_team_id,omitempty"` + + // Deprecated: Permission is deprecated when creating or editing a team in an org + // using the new GitHub permission model. It no longer identifies the + // permission a team has on its repos, but only specifies the default + // permission a repo is initially added with. Avoid confusion by + // specifying a permission value when calling AddTeamRepo. + Permission *string `json:"permission,omitempty"` + + // Privacy identifies the level of privacy this team should have. + // Possible values are: + // secret - only visible to organization owners and members of this team + // closed - visible to all members of this organization + // Default is "secret". + Privacy *string `json:"privacy,omitempty"` + + // LDAPDN may be used in GitHub Enterprise when the team membership + // is synchronized with LDAP. + LDAPDN *string `json:"ldap_dn,omitempty"` +} + +func (s NewTeam) String() string { + return Stringify(s) +} + +// CreateTeam creates a new team within an organization. +// +// GitHub API docs: https://developer.github.com/v3/teams/#create-team +func (s *TeamsService) CreateTeam(ctx context.Context, org string, team NewTeam) (*Team, *Response, error) { + u := fmt.Sprintf("orgs/%v/teams", org) + req, err := s.client.NewRequest("POST", u, team) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + t := new(Team) + resp, err := s.client.Do(ctx, req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, nil +} + +// EditTeam edits a team. +// +// GitHub API docs: https://developer.github.com/v3/teams/#edit-team +func (s *TeamsService) EditTeam(ctx context.Context, id int64, team NewTeam) (*Team, *Response, error) { + u := fmt.Sprintf("teams/%v", id) + req, err := s.client.NewRequest("PATCH", u, team) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + t := new(Team) + resp, err := s.client.Do(ctx, req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, nil +} + +// DeleteTeam deletes a team. +// +// GitHub API docs: https://developer.github.com/v3/teams/#delete-team +func (s *TeamsService) DeleteTeam(ctx context.Context, team int64) (*Response, error) { + u := fmt.Sprintf("teams/%v", team) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + return s.client.Do(ctx, req, nil) +} + +// ListChildTeams lists child teams for a team. +// +// GitHub API docs: https://developer.github.com/v3/teams/#list-child-teams +func (s *TeamsService) ListChildTeams(ctx context.Context, teamID int64, opt *ListOptions) ([]*Team, *Response, error) { + u := fmt.Sprintf("teams/%v/teams", teamID) + u, err := addOptions(u, opt) + 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", mediaTypeNestedTeamsPreview) + + var teams []*Team + resp, err := s.client.Do(ctx, req, &teams) + if err != nil { + return nil, resp, err + } + + return teams, resp, nil +} + +// ListTeamRepos lists the repositories that the specified team has access to. +// +// GitHub API docs: https://developer.github.com/v3/teams/#list-team-repos +func (s *TeamsService) ListTeamRepos(ctx context.Context, team int64, opt *ListOptions) ([]*Repository, *Response, error) { + u := fmt.Sprintf("teams/%v/repos", team) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when topics API fully launches. + headers := []string{mediaTypeTopicsPreview, mediaTypeNestedTeamsPreview} + req.Header.Set("Accept", strings.Join(headers, ", ")) + + var repos []*Repository + resp, err := s.client.Do(ctx, req, &repos) + if err != nil { + return nil, resp, err + } + + return repos, resp, nil +} + +// IsTeamRepo checks if a team manages the specified repository. If the +// repository is managed by team, a Repository is returned which includes the +// permissions team has for that repo. +// +// GitHub API docs: https://developer.github.com/v3/teams/#check-if-a-team-manages-a-repository +func (s *TeamsService) IsTeamRepo(ctx context.Context, team int64, owner string, repo string) (*Repository, *Response, error) { + u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + headers := []string{mediaTypeOrgPermissionRepo, mediaTypeNestedTeamsPreview} + req.Header.Set("Accept", strings.Join(headers, ", ")) + + repository := new(Repository) + resp, err := s.client.Do(ctx, req, repository) + if err != nil { + return nil, resp, err + } + + return repository, resp, nil +} + +// TeamAddTeamRepoOptions specifies the optional parameters to the +// TeamsService.AddTeamRepo method. +type TeamAddTeamRepoOptions struct { + // Permission specifies the permission to grant the team on this repository. + // Possible values are: + // pull - team members can pull, but not push to or administer this repository + // push - team members can pull and push, but not administer this repository + // admin - team members can pull, push and administer this repository + // + // If not specified, the team's permission attribute will be used. + Permission string `json:"permission,omitempty"` +} + +// AddTeamRepo adds a repository to be managed by the specified team. The +// specified repository must be owned by the organization to which the team +// belongs, or a direct fork of a repository owned by the organization. +// +// GitHub API docs: https://developer.github.com/v3/teams/#add-team-repo +func (s *TeamsService) AddTeamRepo(ctx context.Context, team int64, owner string, repo string, opt *TeamAddTeamRepoOptions) (*Response, error) { + u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// RemoveTeamRepo removes a repository from being managed by the specified +// team. Note that this does not delete the repository, it just removes it +// from the team. +// +// GitHub API docs: https://developer.github.com/v3/teams/#remove-team-repo +func (s *TeamsService) RemoveTeamRepo(ctx context.Context, team int64, owner string, repo string) (*Response, error) { + u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// ListUserTeams lists a user's teams +// GitHub API docs: https://developer.github.com/v3/teams/#list-user-teams +func (s *TeamsService) ListUserTeams(ctx context.Context, opt *ListOptions) ([]*Team, *Response, error) { + u := "user/teams" + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + var teams []*Team + resp, err := s.client.Do(ctx, req, &teams) + if err != nil { + return nil, resp, err + } + + return teams, resp, nil +} diff --git a/github/teams_discussion_comments.go b/github/teams_discussion_comments.go index 26d0e8c506f..383d559eefb 100644 --- a/github/teams_discussion_comments.go +++ b/github/teams_discussion_comments.go @@ -21,7 +21,7 @@ type DiscussionComment struct { DiscussionURL *string `json:"discussion_url,omitempty"` HTMLURL *string `json:"html_url,omitempty"` NodeID *string `json:"node_id,omitempty"` - Number *int64 `json:"number,omitempty"` + Number *int `json:"number,omitempty"` UpdatedAt *Timestamp `json:"updated_at,omitempty"` URL *string `json:"url,omitempty"` } diff --git a/github/teams_discussion_comments_test.go b/github/teams_discussion_comments_test.go index da5acf261d6..4131c38568d 100644 --- a/github/teams_discussion_comments_test.go +++ b/github/teams_discussion_comments_test.go @@ -96,7 +96,7 @@ func TestTeamsService_ListComments(t *testing.T) { DiscussionURL: String("https://api.github.com/teams/2/discussions/3"), HTMLURL: String("https://github.com/orgs/1/teams/2/discussions/3/comments/4"), NodeID: String("node"), - Number: Int64(4), + Number: Int(4), UpdatedAt: &Timestamp{time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC)}, URL: String("https://api.github.com/teams/2/discussions/3/comments/4"), }, @@ -121,7 +121,7 @@ func TestTeamsService_GetComment(t *testing.T) { t.Errorf("Teams.GetComment returned error: %v", err) } - want := &DiscussionComment{Number: Int64(4)} + want := &DiscussionComment{Number: Int(4)} if !reflect.DeepEqual(comment, want) { t.Errorf("Teams.GetComment returned %+v, want %+v", comment, want) } @@ -151,7 +151,7 @@ func TestTeamsService_CreateComment(t *testing.T) { t.Errorf("Teams.CreateComment returned error: %v", err) } - want := &DiscussionComment{Number: Int64(4)} + want := &DiscussionComment{Number: Int(4)} if !reflect.DeepEqual(comment, want) { t.Errorf("Teams.CreateComment returned %+v, want %+v", comment, want) } @@ -181,7 +181,7 @@ func TestTeamsService_EditComment(t *testing.T) { t.Errorf("Teams.EditComment returned error: %v", err) } - want := &DiscussionComment{Number: Int64(4)} + want := &DiscussionComment{Number: Int(4)} if !reflect.DeepEqual(comment, want) { t.Errorf("Teams.EditComment returned %+v, want %+v", comment, want) } diff --git a/github/teams_discussions.go b/github/teams_discussions.go index fc9b25a58d9..5db06d109c3 100644 --- a/github/teams_discussions.go +++ b/github/teams_discussions.go @@ -16,13 +16,13 @@ type TeamDiscussion struct { Body *string `json:"body,omitempty"` BodyHTML *string `json:"body_html,omitempty"` BodyVersion *string `json:"body_version,omitempty"` - CommentsCount *int64 `json:"comments_count,omitempty"` + CommentsCount *int `json:"comments_count,omitempty"` CommentsURL *string `json:"comments_url,omitempty"` CreatedAt *Timestamp `json:"created_at,omitempty"` LastEditedAt *Timestamp `json:"last_edited_at,omitempty"` HTMLURL *string `json:"html_url,omitempty"` NodeID *string `json:"node_id,omitempty"` - Number *int64 `json:"number,omitempty"` + Number *int `json:"number,omitempty"` Pinned *bool `json:"pinned,omitempty"` Private *bool `json:"private,omitempty"` TeamURL *string `json:"team_url,omitempty"` diff --git a/github/teams_discussions_test.go b/github/teams_discussions_test.go index 9ebfc50b2f7..edd3ac83c8e 100644 --- a/github/teams_discussions_test.go +++ b/github/teams_discussions_test.go @@ -95,13 +95,13 @@ func TestTeamsService_ListDiscussions(t *testing.T) { Body: String("test"), BodyHTML: String("

test

"), BodyVersion: String("version"), - CommentsCount: Int64(1), + CommentsCount: Int(1), CommentsURL: String("https://api.github.com/teams/2/discussions/3/comments"), CreatedAt: &Timestamp{time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC)}, LastEditedAt: nil, HTMLURL: String("https://github.com/orgs/1/teams/2/discussions/3"), NodeID: String("node"), - Number: Int64(3), + Number: Int(3), Pinned: Bool(false), Private: Bool(false), TeamURL: String("https://api.github.com/teams/2"), @@ -130,7 +130,7 @@ func TestTeamsService_GetDiscussion(t *testing.T) { t.Errorf("Teams.GetDiscussion returned error: %v", err) } - want := &TeamDiscussion{Number: Int64(3)} + want := &TeamDiscussion{Number: Int(3)} if !reflect.DeepEqual(discussion, want) { t.Errorf("Teams.GetDiscussion returned %+v, want %+v", discussion, want) } @@ -160,7 +160,7 @@ func TestTeamsService_CreateDiscussion(t *testing.T) { t.Errorf("Teams.CreateDiscussion returned error: %v", err) } - want := &TeamDiscussion{Number: Int64(3)} + want := &TeamDiscussion{Number: Int(3)} if !reflect.DeepEqual(comment, want) { t.Errorf("Teams.CreateDiscussion returned %+v, want %+v", comment, want) } @@ -190,7 +190,7 @@ func TestTeamsService_EditDiscussion(t *testing.T) { t.Errorf("Teams.EditDiscussion returned error: %v", err) } - want := &TeamDiscussion{Number: Int64(3)} + want := &TeamDiscussion{Number: Int(3)} if !reflect.DeepEqual(comment, want) { t.Errorf("Teams.EditDiscussion returned %+v, want %+v", comment, want) } diff --git a/github/teams_members.go b/github/teams_members.go new file mode 100644 index 00000000000..d5cfa0dc7dd --- /dev/null +++ b/github/teams_members.go @@ -0,0 +1,174 @@ +// Copyright 2018 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" +) + +// TeamListTeamMembersOptions specifies the optional parameters to the +// TeamsService.ListTeamMembers method. +type TeamListTeamMembersOptions struct { + // Role filters members returned by their role in the team. Possible + // values are "all", "member", "maintainer". Default is "all". + Role string `url:"role,omitempty"` + + ListOptions +} + +// ListTeamMembers lists all of the users who are members of the specified +// team. +// +// GitHub API docs: https://developer.github.com/v3/teams/members/#list-team-members +func (s *TeamsService) ListTeamMembers(ctx context.Context, team int64, opt *TeamListTeamMembersOptions) ([]*User, *Response, error) { + u := fmt.Sprintf("teams/%v/members", team) + u, err := addOptions(u, opt) + 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", mediaTypeNestedTeamsPreview) + + var members []*User + resp, err := s.client.Do(ctx, req, &members) + if err != nil { + return nil, resp, err + } + + return members, resp, nil +} + +// IsTeamMember checks if a user is a member of the specified team. +// +// GitHub API docs: https://developer.github.com/v3/teams/members/#get-team-member +// +// Deprecated: This API has been marked as deprecated in the Github API docs, +// TeamsService.GetTeamMembership method should be used instead. +func (s *TeamsService) IsTeamMember(ctx context.Context, team int64, user string) (bool, *Response, error) { + u := fmt.Sprintf("teams/%v/members/%v", team, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + member, err := parseBoolResponse(err) + return member, resp, err +} + +// GetTeamMembership returns the membership status for a user in a team. +// +// GitHub API docs: https://developer.github.com/v3/teams/members/#get-team-membership +func (s *TeamsService) GetTeamMembership(ctx context.Context, team int64, user string) (*Membership, *Response, error) { + u := fmt.Sprintf("teams/%v/memberships/%v", team, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + t := new(Membership) + resp, err := s.client.Do(ctx, req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, nil +} + +// TeamAddTeamMembershipOptions specifies the optional +// parameters to the TeamsService.AddTeamMembership method. +type TeamAddTeamMembershipOptions struct { + // Role specifies the role the user should have in the team. Possible + // values are: + // member - a normal member of the team + // maintainer - a team maintainer. Able to add/remove other team + // members, promote other team members to team + // maintainer, and edit the team’s name and description + // + // Default value is "member". + Role string `json:"role,omitempty"` +} + +// AddTeamMembership adds or invites a user to a team. +// +// In order to add a membership between a user and a team, the authenticated +// user must have 'admin' permissions to the team or be an owner of the +// organization that the team is associated with. +// +// If the user is already a part of the team's organization (meaning they're on +// at least one other team in the organization), this endpoint will add the +// user to the team. +// +// If the user is completely unaffiliated with the team's organization (meaning +// they're on none of the organization's teams), this endpoint will send an +// invitation to the user via email. This newly-created membership will be in +// the "pending" state until the user accepts the invitation, at which point +// the membership will transition to the "active" state and the user will be +// added as a member of the team. +// +// GitHub API docs: https://developer.github.com/v3/teams/members/#add-or-update-team-membership +func (s *TeamsService) AddTeamMembership(ctx context.Context, team int64, user string, opt *TeamAddTeamMembershipOptions) (*Membership, *Response, error) { + u := fmt.Sprintf("teams/%v/memberships/%v", team, user) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, nil, err + } + + t := new(Membership) + resp, err := s.client.Do(ctx, req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, nil +} + +// RemoveTeamMembership removes a user from a team. +// +// GitHub API docs: https://developer.github.com/v3/teams/members/#remove-team-membership +func (s *TeamsService) RemoveTeamMembership(ctx context.Context, team int64, user string) (*Response, error) { + u := fmt.Sprintf("teams/%v/memberships/%v", team, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// ListPendingTeamInvitations get pending invitaion list in team. +// Warning: The API may change without advance notice during the preview period. +// Preview features are not supported for production use. +// +// GitHub API docs: https://developer.github.com/v3/teams/members/#list-pending-team-invitations +func (s *TeamsService) ListPendingTeamInvitations(ctx context.Context, team int64, opt *ListOptions) ([]*Invitation, *Response, error) { + u := fmt.Sprintf("teams/%v/invitations", team) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var pendingInvitations []*Invitation + resp, err := s.client.Do(ctx, req, &pendingInvitations) + if err != nil { + return nil, resp, err + } + + return pendingInvitations, resp, nil +} diff --git a/github/teams_members_test.go b/github/teams_members_test.go new file mode 100644 index 00000000000..2489da0e2b7 --- /dev/null +++ b/github/teams_members_test.go @@ -0,0 +1,167 @@ +// Copyright 2018 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" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestTeamsService__ListTeamMembers(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/members", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + testFormValues(t, r, values{"role": "member", "page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &TeamListTeamMembersOptions{Role: "member", ListOptions: ListOptions{Page: 2}} + members, _, err := client.Teams.ListTeamMembers(context.Background(), 1, opt) + if err != nil { + t.Errorf("Teams.ListTeamMembers returned error: %v", err) + } + + want := []*User{{ID: Int64(1)}} + if !reflect.DeepEqual(members, want) { + t.Errorf("Teams.ListTeamMembers returned %+v, want %+v", members, want) + } +} + +func TestTeamsService__IsTeamMember_true(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + }) + + member, _, err := client.Teams.IsTeamMember(context.Background(), 1, "u") + if err != nil { + t.Errorf("Teams.IsTeamMember returned error: %v", err) + } + if want := true; member != want { + t.Errorf("Teams.IsTeamMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 404 response is interpreted as "false" and not an error +func TestTeamsService__IsTeamMember_false(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + member, _, err := client.Teams.IsTeamMember(context.Background(), 1, "u") + if err != nil { + t.Errorf("Teams.IsTeamMember returned error: %+v", err) + } + if want := false; member != want { + t.Errorf("Teams.IsTeamMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 400 response is interpreted as an actual error, and not simply +// as "false" like the above case of a 404 +func TestTeamsService__IsTeamMember_error(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + member, _, err := client.Teams.IsTeamMember(context.Background(), 1, "u") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; member != want { + t.Errorf("Teams.IsTeamMember returned %+v, want %+v", member, want) + } +} + +func TestTeamsService__IsTeamMember_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Teams.IsTeamMember(context.Background(), 1, "%") + testURLParseError(t, err) +} + +func TestTeamsService__GetTeamMembership(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + fmt.Fprint(w, `{"url":"u", "state":"active"}`) + }) + + membership, _, err := client.Teams.GetTeamMembership(context.Background(), 1, "u") + if err != nil { + t.Errorf("Teams.GetTeamMembership returned error: %v", err) + } + + want := &Membership{URL: String("u"), State: String("active")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Teams.GetTeamMembership returned %+v, want %+v", membership, want) + } +} + +func TestTeamsService__AddTeamMembership(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + opt := &TeamAddTeamMembershipOptions{Role: "maintainer"} + + mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { + v := new(TeamAddTeamMembershipOptions) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, opt) { + t.Errorf("Request body = %+v, want %+v", v, opt) + } + + fmt.Fprint(w, `{"url":"u", "state":"pending"}`) + }) + + membership, _, err := client.Teams.AddTeamMembership(context.Background(), 1, "u", opt) + if err != nil { + t.Errorf("Teams.AddTeamMembership returned error: %v", err) + } + + want := &Membership{URL: String("u"), State: String("pending")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Teams.AddTeamMembership returned %+v, want %+v", membership, want) + } +} + +func TestTeamsService__RemoveTeamMembership(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Teams.RemoveTeamMembership(context.Background(), 1, "u") + if err != nil { + t.Errorf("Teams.RemoveTeamMembership returned error: %v", err) + } +} diff --git a/github/teams_test.go b/github/teams_test.go new file mode 100644 index 00000000000..b2a3e9b6795 --- /dev/null +++ b/github/teams_test.go @@ -0,0 +1,465 @@ +// Copyright 2018 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" + "encoding/json" + "fmt" + "net/http" + "reflect" + "strings" + "testing" + "time" +) + +func TestTeamsService_ListTeams(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/teams", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + teams, _, err := client.Teams.ListTeams(context.Background(), "o", opt) + if err != nil { + t.Errorf("Teams.ListTeams returned error: %v", err) + } + + want := []*Team{{ID: Int64(1)}} + if !reflect.DeepEqual(teams, want) { + t.Errorf("Teams.ListTeams returned %+v, want %+v", teams, want) + } +} + +func TestTeamsService_ListTeams_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Teams.ListTeams(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestTeamsService_GetTeam(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + fmt.Fprint(w, `{"id":1, "name":"n", "description": "d", "url":"u", "slug": "s", "permission":"p", "ldap_dn":"cn=n,ou=groups,dc=example,dc=com", "parent":null}`) + }) + + team, _, err := client.Teams.GetTeam(context.Background(), 1) + if err != nil { + t.Errorf("Teams.GetTeam returned error: %v", err) + } + + want := &Team{ID: Int64(1), Name: String("n"), Description: String("d"), URL: String("u"), Slug: String("s"), Permission: String("p"), LDAPDN: String("cn=n,ou=groups,dc=example,dc=com")} + if !reflect.DeepEqual(team, want) { + t.Errorf("Teams.GetTeam returned %+v, want %+v", team, want) + } +} + +func TestTeamsService_GetTeam_nestedTeams(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + fmt.Fprint(w, `{"id":1, "name":"n", "description": "d", "url":"u", "slug": "s", "permission":"p", + "parent": {"id":2, "name":"n", "description": "d", "parent": null}}`) + }) + + team, _, err := client.Teams.GetTeam(context.Background(), 1) + if err != nil { + t.Errorf("Teams.GetTeam returned error: %v", err) + } + + want := &Team{ID: Int64(1), Name: String("n"), Description: String("d"), URL: String("u"), Slug: String("s"), Permission: String("p"), + Parent: &Team{ID: Int64(2), Name: String("n"), Description: String("d")}, + } + if !reflect.DeepEqual(team, want) { + t.Errorf("Teams.GetTeam returned %+v, want %+v", team, want) + } +} + +func TestTeamsService_CreateTeam(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := NewTeam{Name: "n", Privacy: String("closed"), RepoNames: []string{"r"}} + + mux.HandleFunc("/orgs/o/teams", func(w http.ResponseWriter, r *http.Request) { + v := new(NewTeam) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + if !reflect.DeepEqual(v, &input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + team, _, err := client.Teams.CreateTeam(context.Background(), "o", input) + if err != nil { + t.Errorf("Teams.CreateTeam returned error: %v", err) + } + + want := &Team{ID: Int64(1)} + if !reflect.DeepEqual(team, want) { + t.Errorf("Teams.CreateTeam returned %+v, want %+v", team, want) + } +} + +func TestTeamsService_CreateTeam_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Teams.CreateTeam(context.Background(), "%", NewTeam{}) + testURLParseError(t, err) +} + +func TestTeamsService_EditTeam(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := NewTeam{Name: "n", Privacy: String("closed")} + + mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { + v := new(NewTeam) + json.NewDecoder(r.Body).Decode(v) + + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, &input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + team, _, err := client.Teams.EditTeam(context.Background(), 1, input) + if err != nil { + t.Errorf("Teams.EditTeam returned error: %v", err) + } + + want := &Team{ID: Int64(1)} + if !reflect.DeepEqual(team, want) { + t.Errorf("Teams.EditTeam returned %+v, want %+v", team, want) + } +} + +func TestTeamsService_DeleteTeam(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + }) + + _, err := client.Teams.DeleteTeam(context.Background(), 1) + if err != nil { + t.Errorf("Teams.DeleteTeam returned error: %v", err) + } +} + +func TestTeamsService_ListChildTeams(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/teams", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + teams, _, err := client.Teams.ListChildTeams(context.Background(), 1, opt) + if err != nil { + t.Errorf("Teams.ListTeams returned error: %v", err) + } + + want := []*Team{{ID: Int64(2)}} + if !reflect.DeepEqual(teams, want) { + t.Errorf("Teams.ListTeams returned %+v, want %+v", teams, want) + } +} + +func TestTeamsService_ListTeamRepos(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + acceptHeaders := []string{mediaTypeTopicsPreview, mediaTypeNestedTeamsPreview} + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + members, _, err := client.Teams.ListTeamRepos(context.Background(), 1, opt) + if err != nil { + t.Errorf("Teams.ListTeamRepos returned error: %v", err) + } + + want := []*Repository{{ID: Int64(1)}} + if !reflect.DeepEqual(members, want) { + t.Errorf("Teams.ListTeamRepos returned %+v, want %+v", members, want) + } +} + +func TestTeamsService_IsTeamRepo_true(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + acceptHeaders := []string{mediaTypeOrgPermissionRepo, mediaTypeNestedTeamsPreview} + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + fmt.Fprint(w, `{"id":1}`) + }) + + repo, _, err := client.Teams.IsTeamRepo(context.Background(), 1, "o", "r") + if err != nil { + t.Errorf("Teams.IsTeamRepo returned error: %v", err) + } + + want := &Repository{ID: Int64(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Teams.IsTeamRepo returned %+v, want %+v", repo, want) + } +} + +func TestTeamsService_IsTeamRepo_false(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + repo, resp, err := client.Teams.IsTeamRepo(context.Background(), 1, "o", "r") + if err == nil { + t.Errorf("Expected HTTP 404 response") + } + if got, want := resp.Response.StatusCode, http.StatusNotFound; got != want { + t.Errorf("Teams.IsTeamRepo returned status %d, want %d", got, want) + } + if repo != nil { + t.Errorf("Teams.IsTeamRepo returned %+v, want nil", repo) + } +} + +func TestTeamsService_IsTeamRepo_error(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + repo, resp, err := client.Teams.IsTeamRepo(context.Background(), 1, "o", "r") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if got, want := resp.Response.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("Teams.IsTeamRepo returned status %d, want %d", got, want) + } + if repo != nil { + t.Errorf("Teams.IsTeamRepo returned %+v, want nil", repo) + } +} + +func TestTeamsService_IsTeamRepo_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Teams.IsTeamRepo(context.Background(), 1, "%", "r") + testURLParseError(t, err) +} + +func TestTeamsService_AddTeamRepo(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + opt := &TeamAddTeamRepoOptions{Permission: "admin"} + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + v := new(TeamAddTeamRepoOptions) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, opt) { + t.Errorf("Request body = %+v, want %+v", v, opt) + } + + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Teams.AddTeamRepo(context.Background(), 1, "o", "r", opt) + if err != nil { + t.Errorf("Teams.AddTeamRepo returned error: %v", err) + } +} + +func TestTeamsService_AddTeamRepo_noAccess(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusUnprocessableEntity) + }) + + _, err := client.Teams.AddTeamRepo(context.Background(), 1, "o", "r", nil) + if err == nil { + t.Errorf("Expcted error to be returned") + } +} + +func TestTeamsService_AddTeamRepo_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Teams.AddTeamRepo(context.Background(), 1, "%", "r", nil) + testURLParseError(t, err) +} + +func TestTeamsService_RemoveTeamRepo(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Teams.RemoveTeamRepo(context.Background(), 1, "o", "r") + if err != nil { + t.Errorf("Teams.RemoveTeamRepo returned error: %v", err) + } +} + +func TestTeamsService_RemoveTeamRepo_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Teams.RemoveTeamRepo(context.Background(), 1, "%", "r") + testURLParseError(t, err) +} + +func TestTeamsService_ListUserTeams(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/teams", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + testFormValues(t, r, values{"page": "1"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1} + teams, _, err := client.Teams.ListUserTeams(context.Background(), opt) + if err != nil { + t.Errorf("Teams.ListUserTeams returned error: %v", err) + } + + want := []*Team{{ID: Int64(1)}} + if !reflect.DeepEqual(teams, want) { + t.Errorf("Teams.ListUserTeams returned %+v, want %+v", teams, want) + } +} + +func TestTeamsService_ListPendingTeamInvitations(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/invitations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "1"}) + fmt.Fprint(w, `[ + { + "id": 1, + "login": "monalisa", + "email": "octocat@github.com", + "role": "direct_member", + "created_at": "2017-01-21T00:00:00Z", + "inviter": { + "login": "other_user", + "id": 1, + "avatar_url": "https://github.com/images/error/other_user_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/other_user", + "html_url": "https://github.com/other_user", + "followers_url": "https://api.github.com/users/other_user/followers", + "following_url": "https://api.github.com/users/other_user/following/other_user", + "gists_url": "https://api.github.com/users/other_user/gists/gist_id", + "starred_url": "https://api.github.com/users/other_user/starred/owner/repo", + "subscriptions_url": "https://api.github.com/users/other_user/subscriptions", + "organizations_url": "https://api.github.com/users/other_user/orgs", + "repos_url": "https://api.github.com/users/other_user/repos", + "events_url": "https://api.github.com/users/other_user/events/privacy", + "received_events_url": "https://api.github.com/users/other_user/received_events/privacy", + "type": "User", + "site_admin": false + } + } + ]`) + }) + + opt := &ListOptions{Page: 1} + invitations, _, err := client.Teams.ListPendingTeamInvitations(context.Background(), 1, opt) + if err != nil { + t.Errorf("Teams.ListPendingTeamInvitations returned error: %v", err) + } + + createdAt := time.Date(2017, 01, 21, 0, 0, 0, 0, time.UTC) + want := []*Invitation{ + { + ID: Int64(1), + Login: String("monalisa"), + Email: String("octocat@github.com"), + Role: String("direct_member"), + CreatedAt: &createdAt, + Inviter: &User{ + Login: String("other_user"), + ID: Int64(1), + AvatarURL: String("https://github.com/images/error/other_user_happy.gif"), + GravatarID: String(""), + URL: String("https://api.github.com/users/other_user"), + HTMLURL: String("https://github.com/other_user"), + FollowersURL: String("https://api.github.com/users/other_user/followers"), + FollowingURL: String("https://api.github.com/users/other_user/following/other_user"), + GistsURL: String("https://api.github.com/users/other_user/gists/gist_id"), + StarredURL: String("https://api.github.com/users/other_user/starred/owner/repo"), + SubscriptionsURL: String("https://api.github.com/users/other_user/subscriptions"), + OrganizationsURL: String("https://api.github.com/users/other_user/orgs"), + ReposURL: String("https://api.github.com/users/other_user/repos"), + EventsURL: String("https://api.github.com/users/other_user/events/privacy"), + ReceivedEventsURL: String("https://api.github.com/users/other_user/received_events/privacy"), + Type: String("User"), + SiteAdmin: Bool(false), + }, + }} + + if !reflect.DeepEqual(invitations, want) { + t.Errorf("Teams.ListPendingTeamInvitations returned %+v, want %+v", invitations, want) + } +}