From da0c7a57854c85f28871a260ccf9eba4c3cd8bad Mon Sep 17 00:00:00 2001 From: Ryan Gribble Date: Tue, 26 Sep 2017 21:37:43 +1000 Subject: [PATCH] Implement Team Membership changes (#1670) * Fixup TeamContext helper name * Implement overload for GetAllMembers to take request parameter * Update tests * Implement Obersvable client changes * Observable tests * Implement AddOrEditMembership function returning a new response model, and obsolete the old AddMembership function returning an enum * Implement GetMembershipDetails function returning new TeamMembershipDetails response model, and obsolete the old GetMembership function returning an enum * Clarify that an exception is thrown when not a member * Add integration tests for AddOrEditMembership and GetMembershipDetails * fixup exception test for observable client * Update wording of obsolete message --- .../Clients/IObservableTeamsClient.cs | 55 ++++++-- .../Clients/ObservableTeamsClient.cs | 73 +++++++++- .../Clients/TeamsClientTests.cs | 123 ++++++++++++++-- .../ObservableGithubClientExtensions.cs | 2 +- .../ObservableEnterpriseLdapClientTests.cs | 2 +- .../Reactive/ObservableTeamsClientTests.cs | 131 ++++++++++++++++-- Octokit.Tests/Clients/TeamsClientTests.cs | 84 ++++++++++- .../Reactive/ObservableTeamsClientTests.cs | 102 +++++++++++++- Octokit/Clients/ITeamsClient.cs | 59 ++++++-- Octokit/Clients/TeamsClient.cs | 83 ++++++++++- Octokit/Models/Request/TeamMembersRequest.cs | 55 ++++++++ .../Models/Request/UpdateTeamMembership.cs | 31 +++++ Octokit/Models/Response/MembershipState.cs | 23 +++ Octokit/Models/Response/TeamMembership.cs | 5 +- .../Models/Response/TeamMembershipDetails.cs | 40 ++++++ 15 files changed, 813 insertions(+), 55 deletions(-) create mode 100644 Octokit/Models/Request/TeamMembersRequest.cs create mode 100644 Octokit/Models/Request/UpdateTeamMembership.cs create mode 100644 Octokit/Models/Response/MembershipState.cs create mode 100644 Octokit/Models/Response/TeamMembershipDetails.cs diff --git a/Octokit.Reactive/Clients/IObservableTeamsClient.cs b/Octokit.Reactive/Clients/IObservableTeamsClient.cs index a5555c60..d6f4f6b1 100644 --- a/Octokit.Reactive/Clients/IObservableTeamsClient.cs +++ b/Octokit.Reactive/Clients/IObservableTeamsClient.cs @@ -65,8 +65,6 @@ namespace Octokit.Reactive /// https://developer.github.com/v3/orgs/teams/#list-team-members /// /// The team identifier - /// Thrown when a general API error occurs. - /// A list of the team's member s. IObservable GetAllMembers(int id); /// @@ -77,10 +75,29 @@ namespace Octokit.Reactive /// /// The team identifier /// Options to change API behaviour. - /// Thrown when a general API error occurs. - /// A list of the team's member s. IObservable GetAllMembers(int id, ApiOptions options); + /// + /// Returns all members with the specified role in the given team of the given role. + /// + /// + /// https://developer.github.com/v3/orgs/teams/#list-team-members + /// + /// The team identifier + /// The request filter + IObservable GetAllMembers(int id, TeamMembersRequest request); + + /// + /// Returns all members with the specified role in the given team of the given role. + /// + /// + /// https://developer.github.com/v3/orgs/teams/#list-team-members + /// + /// The team identifier + /// The request filter + /// Options to change API behaviour. + IObservable GetAllMembers(int id, TeamMembersRequest request, ApiOptions options); + /// /// Returns newly created for the current org. /// @@ -106,14 +123,24 @@ namespace Octokit.Reactive /// Adds a to a . /// /// - /// See the API documentation for more information. + /// See the API documentation for more information. /// /// The team identifier. /// The user to add to the team. - /// Thrown if you attempt to add an organization to a team. - /// A result indicating the membership status + [Obsolete("Please use AddOrEditMembership instead")] IObservable AddMembership(int id, string login); + /// + /// Adds a to a . + /// + /// + /// See the API documentation for more information. + /// + /// The team identifier. + /// The user to add to the team. + /// Additional parameters for the request + IObservable AddOrEditMembership(int id, string login, UpdateTeamMembership request); + /// /// Removes a from a . /// @@ -131,9 +158,21 @@ namespace Octokit.Reactive /// /// The team to check. /// The user to check. - /// A result indicating the membership status + [Obsolete("Please use GetMembershipDetails instead")] IObservable GetMembership(int id, string login); + /// + /// Gets whether the user with the given + /// is a member of the team with the given . + /// A is thrown if the user is not a member. + /// + /// + /// See the API documentation for more information. + /// + /// The team to check. + /// The user to check. + IObservable GetMembershipDetails(int id, string login); + /// /// Returns all team's repositories. /// diff --git a/Octokit.Reactive/Clients/ObservableTeamsClient.cs b/Octokit.Reactive/Clients/ObservableTeamsClient.cs index e6297e28..e023549c 100644 --- a/Octokit.Reactive/Clients/ObservableTeamsClient.cs +++ b/Octokit.Reactive/Clients/ObservableTeamsClient.cs @@ -121,6 +121,38 @@ namespace Octokit.Reactive return _connection.GetAndFlattenAllPages(ApiUrls.TeamMembers(id), options); } + /// + /// Returns all members with the specified role in the given team of the given role. + /// + /// + /// https://developer.github.com/v3/orgs/teams/#list-team-members + /// + /// The team identifier + /// The request filter + public IObservable GetAllMembers(int id, TeamMembersRequest request) + { + Ensure.ArgumentNotNull(request, nameof(request)); + + return GetAllMembers(id, request, ApiOptions.None); + } + + /// + /// Returns all members with the specified role in the given team of the given role. + /// + /// + /// https://developer.github.com/v3/orgs/teams/#list-team-members + /// + /// The team identifier + /// The request filter + /// Options to change API behaviour. + public IObservable GetAllMembers(int id, TeamMembersRequest request, ApiOptions options) + { + Ensure.ArgumentNotNull(request, nameof(request)); + Ensure.ArgumentNotNull(options, nameof(options)); + + return _connection.GetAndFlattenAllPages(ApiUrls.TeamMembers(id), request.ToParametersDictionary(), options); + } + /// /// Returns newly created for the current org. /// @@ -160,12 +192,11 @@ namespace Octokit.Reactive /// Adds a to a . /// /// - /// See the API documentation for more information. + /// See the API documentation for more information. /// /// The team identifier. /// The user to add to the team. - /// Thrown if you attempt to add an organization to a team. - /// A result indicating the membership status + [Obsolete("Please use AddOrEditMembership instead")] public IObservable AddMembership(int id, string login) { Ensure.ArgumentNotNullOrEmptyString(login, "login"); @@ -173,6 +204,23 @@ namespace Octokit.Reactive return _client.AddMembership(id, login).ToObservable(); } + /// + /// Adds a to a . + /// + /// + /// See the API documentation for more information. + /// + /// The team identifier. + /// The user to add to the team. + /// Additional parameters for the request + public IObservable AddOrEditMembership(int id, string login, UpdateTeamMembership request) + { + Ensure.ArgumentNotNullOrEmptyString(login, nameof(login)); + Ensure.ArgumentNotNull(request, nameof(request)); + + return _client.AddOrEditMembership(id, login, request).ToObservable(); + } + /// /// Removes a from a . /// @@ -195,7 +243,7 @@ namespace Octokit.Reactive /// /// The team to check. /// The user to check. - /// A result indicating the membership status + [Obsolete("Please use GetMembershipDetails instead")] public IObservable GetMembership(int id, string login) { Ensure.ArgumentNotNullOrEmptyString(login, "login"); @@ -203,6 +251,23 @@ namespace Octokit.Reactive return _client.GetMembership(id, login).ToObservable(); } + /// + /// Gets whether the user with the given + /// is a member of the team with the given . + /// A is thrown if the user is not a member. + /// + /// + /// See the API documentation for more information. + /// + /// The team to check. + /// The user to check. + public IObservable GetMembershipDetails(int id, string login) + { + Ensure.ArgumentNotNullOrEmptyString(login, "login"); + + return _client.GetMembershipDetails(id, login).ToObservable(); + } + /// /// Returns all team's repositories. /// diff --git a/Octokit.Tests.Integration/Clients/TeamsClientTests.cs b/Octokit.Tests.Integration/Clients/TeamsClientTests.cs index 1af5833f..db8f9c37 100644 --- a/Octokit.Tests.Integration/Clients/TeamsClientTests.cs +++ b/Octokit.Tests.Integration/Clients/TeamsClientTests.cs @@ -52,6 +52,56 @@ public class TeamsClientTests } } + public class TheAddOrEditMembershipMethod : IDisposable + { + private readonly IGitHubClient _github; + private readonly TeamContext _teamContext; + + public TheAddOrEditMembershipMethod() + { + _github = Helper.GetAuthenticatedClient(); + + var newTeam = new NewTeam(Helper.MakeNameWithTimestamp("team-fixture")); + newTeam.Maintainers.Add(Helper.UserName); + + _teamContext = _github.CreateTeamContext(Helper.Organization, newTeam).Result; + } + + [OrganizationTest] + public async Task AddsMembership() + { + var login = "octokitnet-test1"; + + var membership = await _github.Organization.Team.AddOrEditMembership(_teamContext.TeamId, login, new UpdateTeamMembership(TeamRole.Member)); + + Assert.Equal(TeamRole.Member, membership.Role); + Assert.Equal(MembershipState.Pending, membership.State); + } + + [OrganizationTest] + public async Task EditsMembership() + { + var login = "octokitnet-test1"; + + // Add as member + await _github.Organization.Team.AddOrEditMembership(_teamContext.TeamId, login, new UpdateTeamMembership(TeamRole.Member)); + + // Update to maintainer + var membership = await _github.Organization.Team.AddOrEditMembership(_teamContext.TeamId, login, new UpdateTeamMembership(TeamRole.Maintainer)); + + Assert.Equal(TeamRole.Maintainer, membership.Role); + Assert.Equal(MembershipState.Pending, membership.State); + } + + public void Dispose() + { + if (_teamContext != null) + { + _teamContext.Dispose(); + } + } + } + public class TheGetMembershipMethod { readonly Team team; @@ -94,26 +144,83 @@ public class TeamsClientTests } } - public class TheGetAllMembersMethod + public class TheGetMembershipDetailsMethod : IDisposable { - readonly Team team; + private readonly IGitHubClient _github; + private readonly TeamContext _teamContext; - public TheGetAllMembersMethod() + public TheGetMembershipDetailsMethod() { - var github = Helper.GetAuthenticatedClient(); + _github = Helper.GetAuthenticatedClient(); - team = github.Organization.Team.GetAll(Helper.Organization).Result.First(); + var newTeam = new NewTeam(Helper.MakeNameWithTimestamp("team-fixture")); + newTeam.Maintainers.Add(Helper.UserName); + + _teamContext = _github.CreateTeamContext(Helper.Organization, newTeam).Result; } [OrganizationTest] - public async Task GetsAllMembersWhenAuthenticated() + public async Task GetsMembershipDetails() { - var github = Helper.GetAuthenticatedClient(); + var membership = await _github.Organization.Team.GetMembershipDetails(_teamContext.TeamId, Helper.UserName); - var members = await github.Organization.Team.GetAllMembers(team.Id); + Assert.Equal(TeamRole.Maintainer, membership.Role); + Assert.Equal(MembershipState.Active, membership.State); + } + + [OrganizationTest] + public async Task ThrowsWhenNotAMember() + { + await Assert.ThrowsAsync(() => _github.Organization.Team.GetMembershipDetails(_teamContext.TeamId, "foo")); + } + + public void Dispose() + { + if (_teamContext != null) + { + _teamContext.Dispose(); + } + } + } + + public class TheGetAllMembersMethod : IDisposable + { + private readonly IGitHubClient _github; + private readonly TeamContext _teamContext; + + public TheGetAllMembersMethod() + { + _github = Helper.GetAuthenticatedClient(); + + var newTeam = new NewTeam(Helper.MakeNameWithTimestamp("team-fixture")); + newTeam.Maintainers.Add(Helper.UserName); + + _teamContext = _github.CreateTeamContext(Helper.Organization, newTeam).Result; + } + + [OrganizationTest] + public async Task GetsAllMembers() + { + var members = await _github.Organization.Team.GetAllMembers(_teamContext.TeamId); Assert.Contains(Helper.UserName, members.Select(u => u.Login)); } + + [OrganizationTest] + public async Task GetsAllMembersWithRoleFilter() + { + var members = await _github.Organization.Team.GetAllMembers(_teamContext.TeamId, new TeamMembersRequest(TeamRoleFilter.Member)); + + Assert.Empty(members); + } + + public void Dispose() + { + if (_teamContext != null) + { + _teamContext.Dispose(); + } + } } public class TheGetAllRepositoriesMethod diff --git a/Octokit.Tests.Integration/Helpers/ObservableGithubClientExtensions.cs b/Octokit.Tests.Integration/Helpers/ObservableGithubClientExtensions.cs index 90d8e435..bd8900ba 100644 --- a/Octokit.Tests.Integration/Helpers/ObservableGithubClientExtensions.cs +++ b/Octokit.Tests.Integration/Helpers/ObservableGithubClientExtensions.cs @@ -28,7 +28,7 @@ namespace Octokit.Tests.Integration.Helpers return new RepositoryContext(client.Connection, repo); } - internal static async Task CreateEnterpriseTeamContext(this IObservableGitHubClient client, string organization, NewTeam newTeam) + internal static async Task CreateTeamContext(this IObservableGitHubClient client, string organization, NewTeam newTeam) { var team = await client.Organization.Team.Create(organization, newTeam); diff --git a/Octokit.Tests.Integration/Reactive/Enterprise/ObservableEnterpriseLdapClientTests.cs b/Octokit.Tests.Integration/Reactive/Enterprise/ObservableEnterpriseLdapClientTests.cs index 01441e96..f09b45ee 100644 --- a/Octokit.Tests.Integration/Reactive/Enterprise/ObservableEnterpriseLdapClientTests.cs +++ b/Octokit.Tests.Integration/Reactive/Enterprise/ObservableEnterpriseLdapClientTests.cs @@ -23,7 +23,7 @@ namespace Octokit.Tests.Integration _github = new ObservableGitHubClient(EnterpriseHelper.GetAuthenticatedClient()); NewTeam newTeam = new NewTeam(Helper.MakeNameWithTimestamp("test-team")) { Description = "Test Team" }; - _context = _github.CreateEnterpriseTeamContext(EnterpriseHelper.Organization, newTeam).Result; + _context = _github.CreateTeamContext(EnterpriseHelper.Organization, newTeam).Result; } [GitHubEnterpriseTest] diff --git a/Octokit.Tests.Integration/Reactive/ObservableTeamsClientTests.cs b/Octokit.Tests.Integration/Reactive/ObservableTeamsClientTests.cs index 6c02f885..77773dad 100644 --- a/Octokit.Tests.Integration/Reactive/ObservableTeamsClientTests.cs +++ b/Octokit.Tests.Integration/Reactive/ObservableTeamsClientTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Reactive.Linq; using System.Threading.Tasks; using Octokit; @@ -9,28 +10,132 @@ using Xunit; public class ObservableTeamsClientTests { - public class TheGetAllMembersMethod + public class TheAddOrEditMembershipMethod : IDisposable { - readonly Team _team; + private readonly IObservableGitHubClient _github; + private readonly TeamContext _teamContext; - public TheGetAllMembersMethod() + public TheAddOrEditMembershipMethod() { - var github = Helper.GetAuthenticatedClient(); + _github = new ObservableGitHubClient(Helper.GetAuthenticatedClient()); - _team = github.Organization.Team.GetAll(Helper.Organization).Result.First(); + var newTeam = new NewTeam(Helper.MakeNameWithTimestamp("team-fixture")); + newTeam.Maintainers.Add(Helper.UserName); + + _teamContext = _github.CreateTeamContext(Helper.Organization, newTeam).Result; } [OrganizationTest] - public async Task GetsAllMembersWhenAuthenticated() + public async Task AddsMembership() { - var github = Helper.GetAuthenticatedClient(); - var client = new ObservableTeamsClient(github); + var login = "octokitnet-test1"; - var observable = client.GetAllMembers(_team.Id, ApiOptions.None); - var members = await observable.ToList(); + var membership = await _github.Organization.Team.AddOrEditMembership(_teamContext.TeamId, login, new UpdateTeamMembership(TeamRole.Member)); - Assert.True(members.Count > 0); - Assert.True(members.Any(x => x.Login == Helper.UserName)); + Assert.Equal(TeamRole.Member, membership.Role); + Assert.Equal(MembershipState.Pending, membership.State); + } + + [OrganizationTest] + public async Task EditsMembership() + { + var login = "octokitnet-test1"; + + // Add as member + await _github.Organization.Team.AddOrEditMembership(_teamContext.TeamId, login, new UpdateTeamMembership(TeamRole.Member)); + + // Update to maintainer + var membership = await _github.Organization.Team.AddOrEditMembership(_teamContext.TeamId, login, new UpdateTeamMembership(TeamRole.Maintainer)); + + Assert.Equal(TeamRole.Maintainer, membership.Role); + Assert.Equal(MembershipState.Pending, membership.State); + } + + public void Dispose() + { + if (_teamContext != null) + { + _teamContext.Dispose(); + } + } + } + + public class TheGetMembershipDetailsMethod : IDisposable + { + private readonly IObservableGitHubClient _github; + private readonly TeamContext _teamContext; + + public TheGetMembershipDetailsMethod() + { + _github = new ObservableGitHubClient(Helper.GetAuthenticatedClient()); + + var newTeam = new NewTeam(Helper.MakeNameWithTimestamp("team-fixture")); + newTeam.Maintainers.Add(Helper.UserName); + + _teamContext = _github.CreateTeamContext(Helper.Organization, newTeam).Result; + } + + [OrganizationTest] + public async Task GetsMembershipDetails() + { + var membership = await _github.Organization.Team.GetMembershipDetails(_teamContext.TeamId, Helper.UserName); + + Assert.Equal(TeamRole.Maintainer, membership.Role); + Assert.Equal(MembershipState.Active, membership.State); + } + + [OrganizationTest] + public async Task ThrowsWhenNotAMember() + { + Assert.ThrowsAsync(async () => await _github.Organization.Team.GetMembershipDetails(_teamContext.TeamId, "foo")); + } + + public void Dispose() + { + if (_teamContext != null) + { + _teamContext.Dispose(); + } + } + } + + public class TheGetAllMembersMethod : IDisposable + { + private readonly IObservableGitHubClient _github; + private readonly TeamContext _teamContext; + + public TheGetAllMembersMethod() + { + _github = new ObservableGitHubClient(Helper.GetAuthenticatedClient()); + + var newTeam = new NewTeam(Helper.MakeNameWithTimestamp("team-fixture")); + newTeam.Maintainers.Add(Helper.UserName); + + _teamContext = _github.CreateTeamContext(Helper.Organization, newTeam).Result; + } + + [OrganizationTest] + public async Task GetsAllMembers() + { + var members = await _github.Organization.Team.GetAllMembers(_teamContext.TeamId).ToList(); + + Assert.Contains(Helper.UserName, members.Select(u => u.Login)); + } + + [OrganizationTest] + public async Task GetsAllMembersWithRequest() + { + var members = await _github.Organization.Team.GetAllMembers(_teamContext.TeamId, new TeamMembersRequest(TeamRoleFilter.Member)).ToList(); + + Assert.Empty(members); + } + + public void Dispose() + { + if (_teamContext != null) + { + _teamContext.Dispose(); + } } } diff --git a/Octokit.Tests/Clients/TeamsClientTests.cs b/Octokit.Tests/Clients/TeamsClientTests.cs index f769987f..0cafb140 100644 --- a/Octokit.Tests/Clients/TeamsClientTests.cs +++ b/Octokit.Tests/Clients/TeamsClientTests.cs @@ -61,7 +61,7 @@ namespace Octokit.Tests.Clients } } - public class TheGetMembersMethod + public class TheGetAllMembersMethod { [Fact] public void RequestsTheCorrectUrl() @@ -75,6 +75,34 @@ namespace Octokit.Tests.Clients Arg.Is(u => u.ToString() == "teams/1/members"), Args.ApiOptions); } + + [Fact] + public void RequestsTheCorrectUrlWithRequest() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + + client.GetAllMembers(1, new TeamMembersRequest(TeamRoleFilter.Maintainer)); + + connection.Received().GetAll( + Arg.Is(u => u.ToString() == "teams/1/members"), + Arg.Is>(d => d["role"] == "maintainer"), + Args.ApiOptions); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + + await Assert.ThrowsAsync(() => client.GetAllMembers(1, (TeamMembersRequest)null)); + + await Assert.ThrowsAsync(() => client.GetAllMembers(1, (ApiOptions)null)); + + await Assert.ThrowsAsync(() => client.GetAllMembers(1, null, ApiOptions.None)); + await Assert.ThrowsAsync(() => client.GetAllMembers(1, new TeamMembersRequest(TeamRoleFilter.All), null)); + } } public class TheCreateMethod @@ -184,6 +212,36 @@ namespace Octokit.Tests.Clients } } + public class TheAddOrEditMembershipMethod + { + [Fact] + public async Task RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + var request = new UpdateTeamMembership(TeamRole.Maintainer); + + await client.AddOrEditMembership(1, "user", request); + + connection.Received().Put( + Arg.Is(u => u.ToString() == "teams/1/memberships/user"), + Arg.Is(x => x.Role == TeamRole.Maintainer)); + } + + [Fact] + public async Task EnsuresNonNullOrEmptyArguments() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + var request = new UpdateTeamMembership(TeamRole.Maintainer); + + await Assert.ThrowsAsync(() => client.AddOrEditMembership(1, null, request)); + await Assert.ThrowsAsync(() => client.AddOrEditMembership(1, "user", null)); + + await Assert.ThrowsAsync(() => client.AddOrEditMembership(1, "", request)); + } + } + public class TheGetAllForCurrentMethod { [Fact] @@ -221,6 +279,30 @@ namespace Octokit.Tests.Clients } } + public class TheGetMembershipDetailsMethod + { + [Fact] + public async Task RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + await client.GetMembershipDetails(1, "user"); + + connection.Received().Get(Arg.Is(u => u.ToString() == "teams/1/memberships/user")); + } + + [Fact] + public async Task EnsuresNonNullOrEmptyArguments() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + + await Assert.ThrowsAsync(() => client.GetMembershipDetails(1, null)); + + await Assert.ThrowsAsync(() => client.GetMembershipDetails(1, "")); + } + } + public class TheRemoveMembershipMethod { [Fact] diff --git a/Octokit.Tests/Reactive/ObservableTeamsClientTests.cs b/Octokit.Tests/Reactive/ObservableTeamsClientTests.cs index eee914fc..fa5a4a95 100644 --- a/Octokit.Tests/Reactive/ObservableTeamsClientTests.cs +++ b/Octokit.Tests/Reactive/ObservableTeamsClientTests.cs @@ -11,6 +11,15 @@ namespace Octokit.Tests.Reactive { public class ObservableTeamsClientTests { + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws(() => new ObservableTeamsClient(null)); + } + } + public class TheCreateMethod { [Fact] @@ -37,12 +46,101 @@ namespace Octokit.Tests.Reactive } } - public class TheCtor + public class TheGetMembershipDetailsMethod { + [Fact] + public void RequestsTheCorrectUrl() + { + var github = Substitute.For(); + var client = new ObservableTeamsClient(github); + + client.GetMembershipDetails(1, "user"); + + github.Organization.Team.Received().GetMembershipDetails(1, "user"); + } + [Fact] public void EnsuresNonNullArguments() { - Assert.Throws(() => new ObservableTeamsClient(null)); + var github = Substitute.For(); + var client = new ObservableTeamsClient(github); + + Assert.Throws(() => client.GetMembershipDetails(1, null)); + + Assert.Throws(() => client.GetMembershipDetails(1, "")); + } + } + + public class TheGetAllMembersMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var github = Substitute.For(); + var client = new ObservableTeamsClient(github); + + client.GetAllMembers(1); + + github.Connection.Received().Get>( + Arg.Is(u => u.ToString() == "teams/1/members"), + Args.EmptyDictionary, + null); + } + + [Fact] + public void RequestsTheCorrectUrlWithRequest() + { + var github = Substitute.For(); + var client = new ObservableTeamsClient(github); + + client.GetAllMembers(1, new TeamMembersRequest(TeamRoleFilter.Maintainer)); + + github.Connection.Received().Get>( + Arg.Is(u => u.ToString() == "teams/1/members"), + Arg.Is>(d => d["role"] == "maintainer"), + null); + } + + [Fact] + public void EnsuresNonNullArguments() + { + var github = Substitute.For(); + var client = new ObservableTeamsClient(github); + + Assert.Throws(() => client.GetAllMembers(1, (TeamMembersRequest)null)); + + Assert.Throws(() => client.GetAllMembers(1, (ApiOptions)null)); + + Assert.Throws(() => client.GetAllMembers(1, null, ApiOptions.None)); + Assert.Throws(() => client.GetAllMembers(1, new TeamMembersRequest(TeamRoleFilter.All), null)); + } + } + + public class TheAddOrEditMembershipMethod + { + [Fact] + public async Task RequestsTheCorrectUrl() + { + var github = Substitute.For(); + var client = new ObservableTeamsClient(github); + var request = new UpdateTeamMembership(TeamRole.Maintainer); + + client.AddOrEditMembership(1, "user", request); + + github.Organization.Team.Received().AddOrEditMembership(1, "user", request); + } + + [Fact] + public async Task EnsuresNonNullOrEmptyArguments() + { + var github = Substitute.For(); + var client = new ObservableTeamsClient(github); + var request = new UpdateTeamMembership(TeamRole.Maintainer); + + Assert.Throws(() => client.AddOrEditMembership(1, null, request)); + Assert.Throws(() => client.AddOrEditMembership(1, "user", null)); + + Assert.Throws(() => client.AddOrEditMembership(1, "", request)); } } diff --git a/Octokit/Clients/ITeamsClient.cs b/Octokit/Clients/ITeamsClient.cs index 7a42619c..f67fb1a2 100644 --- a/Octokit/Clients/ITeamsClient.cs +++ b/Octokit/Clients/ITeamsClient.cs @@ -62,24 +62,43 @@ namespace Octokit /// /// Returns all members of the given team. /// - /// The team identifier /// /// https://developer.github.com/v3/orgs/teams/#list-team-members /// - /// A list of the team's member s. + /// The team identifier Task> GetAllMembers(int id); /// /// Returns all members of the given team. /// - /// The team identifier - /// Options to change API behaviour. /// /// https://developer.github.com/v3/orgs/teams/#list-team-members /// - /// A list of the team's member s. + /// The team identifier + /// Options to change API behaviour. Task> GetAllMembers(int id, ApiOptions options); + /// + /// Returns all members with the specified role in the given team of the given role. + /// + /// + /// https://developer.github.com/v3/orgs/teams/#list-team-members + /// + /// The team identifier + /// The request filter + Task> GetAllMembers(int id, TeamMembersRequest request); + + /// + /// Returns all members with the specified role in the given team of the given role. + /// + /// + /// https://developer.github.com/v3/orgs/teams/#list-team-members + /// + /// The team identifier + /// The request filter + /// Options to change API behaviour. + Task> GetAllMembers(int id, TeamMembersRequest request, ApiOptions options); + /// /// Returns newly created for the current org. /// @@ -105,14 +124,24 @@ namespace Octokit /// Adds a to a . /// /// - /// See the API documentation for more information. + /// See the API documentation for more information. /// /// The team identifier. /// The user to add to the team. - /// Thrown if you attempt to add an organization to a team. - /// A result indicating the membership status + [Obsolete("Please use AddOrEditMembership instead")] Task AddMembership(int id, string login); + /// + /// Adds a to a . + /// + /// + /// See the API documentation for more information. + /// + /// The team identifier. + /// The user to add to the team. + /// Additional parameters for the request + Task AddOrEditMembership(int id, string login, UpdateTeamMembership request); + /// /// Removes a from a . /// @@ -130,9 +159,21 @@ namespace Octokit /// /// The team to check. /// The user to check. - /// A result indicating the membership status + [Obsolete("Please use GetMembershipDetails instead")] Task GetMembership(int id, string login); + /// + /// Gets whether the user with the given + /// is a member of the team with the given . + /// A is thrown if the user is not a member. + /// + /// + /// See the API documentation for more information. + /// + /// The team to check. + /// The user to check. + Task GetMembershipDetails(int id, string login); + /// /// Returns all team's repositories. /// diff --git a/Octokit/Clients/TeamsClient.cs b/Octokit/Clients/TeamsClient.cs index fd2ac393..8da6b769 100644 --- a/Octokit/Clients/TeamsClient.cs +++ b/Octokit/Clients/TeamsClient.cs @@ -92,11 +92,10 @@ namespace Octokit /// /// Returns all members of the given team. /// - /// The team identifier /// /// https://developer.github.com/v3/orgs/teams/#list-team-members /// - /// A list of the team's member s. + /// The team identifier public Task> GetAllMembers(int id) { return GetAllMembers(id, ApiOptions.None); @@ -110,7 +109,6 @@ namespace Octokit /// /// The team identifier /// Options to change API behaviour. - /// A list of the team's member s. public Task> GetAllMembers(int id, ApiOptions options) { Ensure.ArgumentNotNull(options, "options"); @@ -120,13 +118,47 @@ namespace Octokit return ApiConnection.GetAll(endpoint, options); } + /// + /// Returns all members with the specified role in the given team of the given role. + /// + /// + /// https://developer.github.com/v3/orgs/teams/#list-team-members + /// + /// The team identifier + /// The request filter + public Task> GetAllMembers(int id, TeamMembersRequest request) + { + Ensure.ArgumentNotNull(request, nameof(request)); + + return GetAllMembers(id, request, ApiOptions.None); + } + + /// + /// Returns all members with the specified role in the given team of the given role. + /// + /// + /// https://developer.github.com/v3/orgs/teams/#list-team-members + /// + /// The team identifier + /// The request filter + /// Options to change API behaviour. + public Task> GetAllMembers(int id, TeamMembersRequest request, ApiOptions options) + { + Ensure.ArgumentNotNull(request, nameof(request)); + Ensure.ArgumentNotNull(options, nameof(options)); + + var endpoint = ApiUrls.TeamMembers(id); + + return ApiConnection.GetAll(endpoint, request.ToParametersDictionary(), options); + } + /// /// Gets whether the user with the given /// is a member of the team with the given . /// /// The team to check. /// The user to check. - /// A result indicating the membership status + [Obsolete("Please use GetMembershipDetails instead")] public async Task GetMembership(int id, string login) { Ensure.ArgumentNotNullOrEmptyString(login, "login"); @@ -149,6 +181,25 @@ namespace Octokit : TeamMembership.Pending; } + /// + /// Gets whether the user with the given + /// is a member of the team with the given . + /// A is thrown if the user is not a member. + /// + /// + /// See the API documentation for more information. + /// + /// The team to check. + /// The user to check. + public Task GetMembershipDetails(int id, string login) + { + Ensure.ArgumentNotNullOrEmptyString(login, nameof(login)); + + var endpoint = ApiUrls.TeamMember(id, login); + + return ApiConnection.Get(endpoint); + } + /// /// Returns newly created for the current org. /// @@ -192,12 +243,11 @@ namespace Octokit /// Adds a to a . /// /// - /// See the API documentation for more information. + /// See the API documentation for more information. /// /// The team identifier. /// The user to add to the team. - /// Thrown if you attempt to add an organization to a team. - /// A result indicating the membership status + [Obsolete("Please use AddOrEditMembership instead")] public async Task AddMembership(int id, string login) { Ensure.ArgumentNotNullOrEmptyString(login, "login"); @@ -225,6 +275,25 @@ namespace Octokit : TeamMembership.Pending; } + /// + /// Adds a to a . + /// + /// + /// See the API documentation for more information. + /// + /// The team identifier. + /// The user to add to the team. + /// Additional parameters for the request + public Task AddOrEditMembership(int id, string login, UpdateTeamMembership request) + { + Ensure.ArgumentNotNullOrEmptyString(login, nameof(login)); + Ensure.ArgumentNotNull(request, nameof(request)); + + var endpoint = ApiUrls.TeamMember(id, login); + + return ApiConnection.Put(endpoint, request); + } + /// /// Removes a from a . /// diff --git a/Octokit/Models/Request/TeamMembersRequest.cs b/Octokit/Models/Request/TeamMembersRequest.cs new file mode 100644 index 00000000..babc18d4 --- /dev/null +++ b/Octokit/Models/Request/TeamMembersRequest.cs @@ -0,0 +1,55 @@ +using System.Diagnostics; +using System.Globalization; +using Octokit.Internal; + +namespace Octokit +{ + /// + /// Used to filter requests for team members + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class TeamMembersRequest : RequestParameters + { + public TeamMembersRequest(TeamRoleFilter role) + { + Role = role; + } + + /// + /// Which membership roles to get + /// + public TeamRoleFilter Role { get; private set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "Role {0} ", Role); + } + } + } + + /// + /// Filtering by Roles within a Team + /// + public enum TeamRoleFilter + { + /// + /// Regular Team Member + /// + [Parameter(Value = "member")] + Member, + + /// + /// Team Maintainer + /// + [Parameter(Value = "maintainer")] + Maintainer, + + /// + /// All Roles + /// + [Parameter(Value = "all")] + All + } +} diff --git a/Octokit/Models/Request/UpdateTeamMembership.cs b/Octokit/Models/Request/UpdateTeamMembership.cs new file mode 100644 index 00000000..369537ba --- /dev/null +++ b/Octokit/Models/Request/UpdateTeamMembership.cs @@ -0,0 +1,31 @@ +using System.Diagnostics; +using System.Globalization; +using Octokit.Internal; + +namespace Octokit +{ + /// + /// Used to filter requests for team members + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class UpdateTeamMembership + { + public UpdateTeamMembership(TeamRole role) + { + Role = role; + } + + /// + /// Which membership roles to get + /// + public TeamRole Role { get; private set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "Role {0}", Role); + } + } + } +} diff --git a/Octokit/Models/Response/MembershipState.cs b/Octokit/Models/Response/MembershipState.cs new file mode 100644 index 00000000..697d8799 --- /dev/null +++ b/Octokit/Models/Response/MembershipState.cs @@ -0,0 +1,23 @@ +using System; +using Octokit.Internal; + +namespace Octokit +{ + /// + /// States of a Team/Organization Membership + /// + public enum MembershipState + { + /// + /// The membership is pending + /// + [Parameter(Value = "pending")] + Pending, + + /// + /// The membership is active + /// + [Parameter(Value = "active")] + Active + } +} diff --git a/Octokit/Models/Response/TeamMembership.cs b/Octokit/Models/Response/TeamMembership.cs index 2dba0d8b..fc865c97 100644 --- a/Octokit/Models/Response/TeamMembership.cs +++ b/Octokit/Models/Response/TeamMembership.cs @@ -1,5 +1,8 @@ -namespace Octokit +using System; + +namespace Octokit { + [Obsolete("Please use TeamMembershipDetails response class instead")] public enum TeamMembership { NotFound = 0, diff --git a/Octokit/Models/Response/TeamMembershipDetails.cs b/Octokit/Models/Response/TeamMembershipDetails.cs new file mode 100644 index 00000000..53abf76b --- /dev/null +++ b/Octokit/Models/Response/TeamMembershipDetails.cs @@ -0,0 +1,40 @@ +using System.Diagnostics; +using System.Globalization; +using Octokit.Internal; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class TeamMembershipDetails + { + public StringEnum Role { get; protected set; } + + public StringEnum State { get; protected set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "Role: {0} State: {1}", Role, State); + } + } + } + + /// + /// Roles within a Team + /// + public enum TeamRole + { + /// + /// Regular Team Member + /// + [Parameter(Value = "member")] + Member, + + /// + /// Team Maintainer + /// + [Parameter(Value = "maintainer")] + Maintainer + } +}