From e93646c9f134555d4f4c8160251b4812113598d9 Mon Sep 17 00:00:00 2001 From: Ryan Gribble Date: Wed, 4 Oct 2017 16:21:42 +1000 Subject: [PATCH] Implement Nested Teams preview API changes (#1682) * Add new AcceptsHeader add Parent field to Team add ParentId field to NewTeam and UpdateTeam update Create Edit and Delete Team methods to use preview header * Implement new API call GetAllChildTeams() * Implement GetAllChildTeams for ObservableClient * add integration test for observable client * Add pagination tests for GetAllChildTeams * Add NestedTeams preview header to all the API calls that use it * Update tests for accepts header * Add accepts header to observable client calls * Fix DELETE implementation to use correct overload * Fix tests - parent and child teams must be visibility Closed whereas the default if not specified is Private * make sure all tests are flagged as [OrganizationTest] * Make sure Update tests change the parent of the team * Update new methods with NesterTeams preview API header and adjust tests --- .../Clients/IObservableTeamsClient.cs | 18 +++ .../Clients/ObservableTeamsClient.cs | 36 ++++- .../Clients/TeamsClientTests.cs | 105 +++++++++++++- .../Helpers/GithubClientExtensions.cs | 1 + .../ObservableGithubClientExtensions.cs | 1 + .../Reactive/ObservableTeamsClientTests.cs | 132 ++++++++++++++++++ Octokit.Tests/Clients/TeamsClientTests.cs | 76 ++++++++-- .../Reactive/ObservableTeamsClientTests.cs | 48 ++++++- Octokit/Clients/ITeamsClient.cs | 18 +++ Octokit/Clients/TeamsClient.cs | 58 ++++++-- Octokit/Helpers/AcceptHeaders.cs | 2 + Octokit/Helpers/ApiUrls.cs | 10 ++ Octokit/Models/Request/NewTeam.cs | 5 + Octokit/Models/Request/UpdateTeam.cs | 5 + Octokit/Models/Response/Team.cs | 5 + 15 files changed, 487 insertions(+), 33 deletions(-) diff --git a/Octokit.Reactive/Clients/IObservableTeamsClient.cs b/Octokit.Reactive/Clients/IObservableTeamsClient.cs index d6f4f6b1..11aba6f4 100644 --- a/Octokit.Reactive/Clients/IObservableTeamsClient.cs +++ b/Octokit.Reactive/Clients/IObservableTeamsClient.cs @@ -58,6 +58,24 @@ namespace Octokit.Reactive [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] IObservable GetAllForCurrent(ApiOptions options); + /// + /// Returns all child teams of the given team. + /// + /// The team identifier + /// + /// https://developer.github.com/v3/orgs/teams/#list-child-teams + /// + IObservable GetAllChildTeams(int id); + + /// + /// Returns all child teams of the given team. + /// + /// The team identifier + /// + /// https://developer.github.com/v3/orgs/teams/#list-child-teams + /// + IObservable GetAllChildTeams(int id, ApiOptions options); + /// /// Returns all members of the given team. /// diff --git a/Octokit.Reactive/Clients/ObservableTeamsClient.cs b/Octokit.Reactive/Clients/ObservableTeamsClient.cs index e023549c..dec15fbf 100644 --- a/Octokit.Reactive/Clients/ObservableTeamsClient.cs +++ b/Octokit.Reactive/Clients/ObservableTeamsClient.cs @@ -64,7 +64,7 @@ namespace Octokit.Reactive Ensure.ArgumentNotNullOrEmptyString(org, "org"); Ensure.ArgumentNotNull(options, "options"); - return _connection.GetAndFlattenAllPages(ApiUrls.OrganizationTeams(org), options); + return _connection.GetAndFlattenAllPages(ApiUrls.OrganizationTeams(org), null, AcceptHeaders.NestedTeamsPreview, options); } /// @@ -87,7 +87,33 @@ namespace Octokit.Reactive { Ensure.ArgumentNotNull(options, "options"); - return _connection.GetAndFlattenAllPages(ApiUrls.UserTeams(), options); + return _connection.GetAndFlattenAllPages(ApiUrls.UserTeams(), null, AcceptHeaders.NestedTeamsPreview, options); + } + + /// + /// Returns all child teams of the given team. + /// + /// The team identifier + /// + /// https://developer.github.com/v3/orgs/teams/#list-child-teams + /// + public IObservable GetAllChildTeams(int id) + { + return GetAllChildTeams(id, ApiOptions.None); + } + + /// + /// Returns all child teams of the given team. + /// + /// The team identifier + /// + /// https://developer.github.com/v3/orgs/teams/#list-child-teams + /// + public IObservable GetAllChildTeams(int id, ApiOptions options) + { + Ensure.ArgumentNotNull(options, "options"); + + return _connection.GetAndFlattenAllPages(ApiUrls.TeamChildTeams(id), null, AcceptHeaders.NestedTeamsPreview, options); } /// @@ -118,7 +144,7 @@ namespace Octokit.Reactive { Ensure.ArgumentNotNull(options, "options"); - return _connection.GetAndFlattenAllPages(ApiUrls.TeamMembers(id), options); + return _connection.GetAndFlattenAllPages(ApiUrls.TeamMembers(id), null, AcceptHeaders.NestedTeamsPreview, options); } /// @@ -150,7 +176,7 @@ namespace Octokit.Reactive Ensure.ArgumentNotNull(request, nameof(request)); Ensure.ArgumentNotNull(options, nameof(options)); - return _connection.GetAndFlattenAllPages(ApiUrls.TeamMembers(id), request.ToParametersDictionary(), options); + return _connection.GetAndFlattenAllPages(ApiUrls.TeamMembers(id), request.ToParametersDictionary(), AcceptHeaders.NestedTeamsPreview, options); } /// @@ -289,7 +315,7 @@ namespace Octokit.Reactive { Ensure.ArgumentNotNull(options, "options"); - return _connection.GetAndFlattenAllPages(ApiUrls.TeamRepositories(id), null, AcceptHeaders.OrganizationPermissionsPreview, options); + return _connection.GetAndFlattenAllPages(ApiUrls.TeamRepositories(id), null, AcceptHeaders.NestedTeamsPreview, options); } /// diff --git a/Octokit.Tests.Integration/Clients/TeamsClientTests.cs b/Octokit.Tests.Integration/Clients/TeamsClientTests.cs index db8f9c37..0df992a6 100644 --- a/Octokit.Tests.Integration/Clients/TeamsClientTests.cs +++ b/Octokit.Tests.Integration/Clients/TeamsClientTests.cs @@ -43,7 +43,7 @@ public class TeamsClientTests public class TheGetAllForCurrentMethod { - [IntegrationTest] + [OrganizationTest] public async Task GetsAllForCurrentWhenAuthenticated() { var github = Helper.GetAuthenticatedClient(); @@ -52,6 +52,104 @@ public class TeamsClientTests } } + public class TheGetAllChildTeamsMethod + { + private readonly IGitHubClient _github; + + public TheGetAllChildTeamsMethod() + { + _github = Helper.GetAuthenticatedClient(); + } + + [OrganizationTest] + public async Task GetsAllChildTeams() + { + using (var parentTeamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("parent-team")))) + { + var team1 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + var team2 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + + var teams = await _github.Organization.Team.GetAllChildTeams(parentTeamContext.TeamId); + + Assert.Equal(2, teams.Count); + Assert.True(teams.Any(x => x.Id == team1.Id)); + Assert.True(teams.Any(x => x.Id == team2.Id)); + } + } + + [OrganizationTest] + public async Task ReturnsCorrectCountOfChildTeamsWithoutStart() + { + using (var parentTeamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("parent-team")))) + { + var team1 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + var team2 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + + var options = new ApiOptions + { + PageSize = 1, + PageCount = 1 + }; + + var teams = await _github.Organization.Team.GetAllChildTeams(parentTeamContext.TeamId, options); + + Assert.Equal(1, teams.Count); + Assert.Equal(team1.Id, teams[0].Id); + } + } + + [OrganizationTest] + public async Task ReturnsCorrectCountOfChildTeamsWithStart() + { + using (var parentTeamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("parent-team")))) + { + var team1 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + var team2 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + + var options = new ApiOptions + { + PageSize = 1, + PageCount = 1, + StartPage = 2 + }; + + var teams = await _github.Organization.Team.GetAllChildTeams(parentTeamContext.TeamId, options); + + Assert.Equal(1, teams.Count); + Assert.Equal(team2.Id, teams[0].Id); + } + } + + [OrganizationTest] + public async Task ReturnsDistinctChildTeamsBasedOnStartPage() + { + using (var parentTeamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("parent-team")))) + { + var team1 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + var team2 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + + var startOptions = new ApiOptions + { + PageSize = 1, + PageCount = 1 + }; + + var firstPage = await _github.Organization.Team.GetAllChildTeams(parentTeamContext.TeamId, startOptions); + + var skipStartOptions = new ApiOptions + { + PageSize = 1, + PageCount = 1, + StartPage = 2 + }; + + var secondPage = await _github.Organization.Team.GetAllChildTeams(parentTeamContext.TeamId, skipStartOptions); + + Assert.NotEqual(firstPage[0].Id, secondPage[0].Id); + } + } + } + public class TheAddOrEditMembershipMethod : IDisposable { private readonly IGitHubClient _github; @@ -384,6 +482,7 @@ public class TeamsClientTests [OrganizationTest] public async Task UpdatesTeam() { + using (var parentTeamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("parent-team")))) using (var teamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team-fixture")))) { var teamName = Helper.MakeNameWithTimestamp("updated-team"); @@ -391,7 +490,8 @@ public class TeamsClientTests var update = new UpdateTeam(teamName) { Description = teamDescription, - Privacy = TeamPrivacy.Closed + Privacy = TeamPrivacy.Closed, + ParentTeamId = parentTeamContext.TeamId }; var team = await _github.Organization.Team.Update(teamContext.TeamId, update); @@ -399,6 +499,7 @@ public class TeamsClientTests Assert.Equal(teamName, team.Name); Assert.Equal(teamDescription, team.Description); Assert.Equal(TeamPrivacy.Closed, team.Privacy); + Assert.Equal(parentTeamContext.TeamId, team.Parent.Id); } } } diff --git a/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs b/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs index ff869c62..1dbd2d68 100644 --- a/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs +++ b/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs @@ -28,6 +28,7 @@ namespace Octokit.Tests.Integration.Helpers internal static async Task CreateTeamContext(this IGitHubClient client, string organization, NewTeam newTeam) { + newTeam.Privacy = TeamPrivacy.Closed; var team = await client.Organization.Team.Create(organization, newTeam); return new TeamContext(client.Connection, team); diff --git a/Octokit.Tests.Integration/Helpers/ObservableGithubClientExtensions.cs b/Octokit.Tests.Integration/Helpers/ObservableGithubClientExtensions.cs index bd8900ba..c3656a0b 100644 --- a/Octokit.Tests.Integration/Helpers/ObservableGithubClientExtensions.cs +++ b/Octokit.Tests.Integration/Helpers/ObservableGithubClientExtensions.cs @@ -30,6 +30,7 @@ namespace Octokit.Tests.Integration.Helpers internal static async Task CreateTeamContext(this IObservableGitHubClient client, string organization, NewTeam newTeam) { + newTeam.Privacy = TeamPrivacy.Closed; var team = await client.Organization.Team.Create(organization, newTeam); return new TeamContext(client.Connection, team); diff --git a/Octokit.Tests.Integration/Reactive/ObservableTeamsClientTests.cs b/Octokit.Tests.Integration/Reactive/ObservableTeamsClientTests.cs index 77773dad..d21e354c 100644 --- a/Octokit.Tests.Integration/Reactive/ObservableTeamsClientTests.cs +++ b/Octokit.Tests.Integration/Reactive/ObservableTeamsClientTests.cs @@ -10,6 +10,104 @@ using Xunit; public class ObservableTeamsClientTests { + public class TheGetAllChildTeamsMethod + { + private readonly IObservableGitHubClient _github; + + public TheGetAllChildTeamsMethod() + { + _github = new ObservableGitHubClient(Helper.GetAuthenticatedClient()); + } + + [OrganizationTest] + public async Task GetsChildTeams() + { + using (var parentTeamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("parent-team")))) + { + var team1 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + var team2 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + + var teams = await _github.Organization.Team.GetAllChildTeams(parentTeamContext.TeamId).ToList(); + + Assert.Equal(2, teams.Count); + Assert.True(teams.Any(x => x.Id == team1.Id)); + Assert.True(teams.Any(x => x.Id == team2.Id)); + } + } + + [OrganizationTest] + public async Task ReturnsCorrectCountOfChildTeamsWithoutStart() + { + using (var parentTeamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("parent-team")))) + { + var team1 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + var team2 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + + var options = new ApiOptions + { + PageSize = 1, + PageCount = 1 + }; + + var teams = await _github.Organization.Team.GetAllChildTeams(parentTeamContext.TeamId, options).ToList(); + + Assert.Equal(1, teams.Count); + Assert.Equal(team1.Id, teams[0].Id); + } + } + + [OrganizationTest] + public async Task ReturnsCorrectCountOfChildTeamsWithStart() + { + using (var parentTeamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("parent-team")))) + { + var team1 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + var team2 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + + var options = new ApiOptions + { + PageSize = 1, + PageCount = 1, + StartPage = 2 + }; + + var teams = await _github.Organization.Team.GetAllChildTeams(parentTeamContext.TeamId, options).ToList(); + + Assert.Equal(1, teams.Count); + Assert.Equal(team2.Id, teams[0].Id); + } + } + + [OrganizationTest] + public async Task ReturnsDistinctChildTeamsBasedOnStartPage() + { + using (var parentTeamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("parent-team")))) + { + var team1 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + var team2 = await _github.Organization.Team.Create(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("child-team")) { Privacy = TeamPrivacy.Closed, ParentTeamId = parentTeamContext.TeamId }); + + var startOptions = new ApiOptions + { + PageSize = 1, + PageCount = 1 + }; + + var firstPage = await _github.Organization.Team.GetAllChildTeams(parentTeamContext.TeamId, startOptions).ToList(); + + var skipStartOptions = new ApiOptions + { + PageSize = 1, + PageCount = 1, + StartPage = 2 + }; + + var secondPage = await _github.Organization.Team.GetAllChildTeams(parentTeamContext.TeamId, skipStartOptions).ToList(); + + Assert.NotEqual(firstPage[0].Id, secondPage[0].Id); + } + } + } + public class TheAddOrEditMembershipMethod : IDisposable { private readonly IObservableGitHubClient _github; @@ -263,4 +361,38 @@ public class ObservableTeamsClientTests } } } + + public class TheUpdateMethod + { + private readonly IObservableGitHubClient _github; + + public TheUpdateMethod() + { + _github = new ObservableGitHubClient(Helper.GetAuthenticatedClient()); + } + + [OrganizationTest] + public async Task UpdatesTeam() + { + using (var parentTeamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("parent-team")))) + using (var teamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team-fixture")))) + { + var teamName = Helper.MakeNameWithTimestamp("updated-team"); + var teamDescription = Helper.MakeNameWithTimestamp("updated description"); + var update = new UpdateTeam(teamName) + { + Description = teamDescription, + Privacy = TeamPrivacy.Closed, + ParentTeamId = parentTeamContext.TeamId + }; + + var team = await _github.Organization.Team.Update(teamContext.TeamId, update); + + Assert.Equal(teamName, team.Name); + Assert.Equal(teamDescription, team.Description); + Assert.Equal(TeamPrivacy.Closed, team.Privacy); + Assert.Equal(parentTeamContext.TeamId, team.Parent.Id); + } + } + } } diff --git a/Octokit.Tests/Clients/TeamsClientTests.cs b/Octokit.Tests/Clients/TeamsClientTests.cs index 0cafb140..285887cb 100644 --- a/Octokit.Tests/Clients/TeamsClientTests.cs +++ b/Octokit.Tests/Clients/TeamsClientTests.cs @@ -32,7 +32,10 @@ namespace Octokit.Tests.Clients client.Get(1); - connection.Received().Get(Arg.Is(u => u.ToString() == "teams/1")); + connection.Received().Get( + Arg.Is(u => u.ToString() == "teams/1"), + null, + "application/vnd.github.hellcat-preview+json"); } } @@ -48,6 +51,8 @@ namespace Octokit.Tests.Clients connection.Received().GetAll( Arg.Is(u => u.ToString() == "orgs/orgName/teams"), + null, + "application/vnd.github.hellcat-preview+json", Args.ApiOptions); } @@ -61,6 +66,33 @@ namespace Octokit.Tests.Clients } } + public class TheGetAllChildTeamsMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + + client.GetAllChildTeams(1); + + connection.Received().GetAll( + Arg.Is(u => u.ToString() == "teams/1/teams"), + null, + "application/vnd.github.hellcat-preview+json", + Args.ApiOptions); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + + await Assert.ThrowsAsync(() => client.GetAllChildTeams(1, null)); + } + } + public class TheGetAllMembersMethod { [Fact] @@ -73,6 +105,8 @@ namespace Octokit.Tests.Clients connection.Received().GetAll( Arg.Is(u => u.ToString() == "teams/1/members"), + null, + "application/vnd.github.hellcat-preview+json", Args.ApiOptions); } @@ -87,6 +121,7 @@ namespace Octokit.Tests.Clients connection.Received().GetAll( Arg.Is(u => u.ToString() == "teams/1/members"), Arg.Is>(d => d["role"] == "maintainer"), + "application/vnd.github.hellcat-preview+json", Args.ApiOptions); } @@ -116,7 +151,10 @@ namespace Octokit.Tests.Clients client.Create("orgName", team); - connection.Received().Post(Arg.Is(u => u.ToString() == "orgs/orgName/teams"), team); + connection.Received().Post( + Arg.Is(u => u.ToString() == "orgs/orgName/teams"), + team, + "application/vnd.github.hellcat-preview+json"); } [Fact] @@ -143,7 +181,10 @@ namespace Octokit.Tests.Clients client.Update(1, team); - connection.Received().Patch(Arg.Is(u => u.ToString() == "teams/1"), team); + connection.Received().Patch( + Arg.Is(u => u.ToString() == "teams/1"), + team, + "application/vnd.github.hellcat-preview+json"); } [Fact] @@ -165,7 +206,10 @@ namespace Octokit.Tests.Clients var client = new TeamsClient(connection); client.Delete(1); - connection.Received().Delete(Arg.Is(u => u.ToString() == "teams/1")); + connection.Received().Delete( + Arg.Is(u => u.ToString() == "teams/1"), + Args.Object, + "application/vnd.github.hellcat-preview+json"); } } @@ -254,6 +298,8 @@ namespace Octokit.Tests.Clients connection.Received().GetAll( Arg.Is(u => u.ToString() == "user/teams"), + null, + "application/vnd.github.hellcat-preview+json", Args.ApiOptions); } } @@ -288,7 +334,10 @@ namespace Octokit.Tests.Clients var client = new TeamsClient(connection); await client.GetMembershipDetails(1, "user"); - connection.Received().Get(Arg.Is(u => u.ToString() == "teams/1/memberships/user")); + connection.Received().Get( + Arg.Is(u => u.ToString() == "teams/1/memberships/user"), + null, + "application/vnd.github.hellcat-preview+json"); } [Fact] @@ -312,7 +361,10 @@ namespace Octokit.Tests.Clients var client = new TeamsClient(connection); await client.RemoveMembership(1, "user"); - connection.Connection.Received().Delete(Arg.Is(u => u.ToString() == "teams/1/memberships/user")); + connection.Connection.Received().Delete( + Arg.Is(u => u.ToString() == "teams/1/memberships/user"), + Arg.Any(), + "application/vnd.github.hellcat-preview+json"); } [Fact] @@ -339,7 +391,7 @@ namespace Octokit.Tests.Clients connection.Received().GetAll( Arg.Is(u => u.ToString() == "teams/1/repos"), null, - "application/vnd.github.ironman-preview+json", + "application/vnd.github.hellcat-preview+json", Args.ApiOptions); } } @@ -382,7 +434,9 @@ namespace Octokit.Tests.Clients var client = new TeamsClient(connection); await client.AddRepository(1, "org", "repo"); - connection.Connection.Received().Put(Arg.Is(u => u.ToString() == "teams/1/repos/org/repo")); + connection.Connection.Received().Put( + Arg.Is(u => u.ToString() == "teams/1/repos/org/repo"), + "application/vnd.github.hellcat-preview+json"); } [Fact] @@ -394,7 +448,11 @@ namespace Octokit.Tests.Clients await client.AddRepository(1, "org", "repo", newPermission); - connection.Connection.Received().Put(Arg.Is(u => u.ToString() == "teams/1/repos/org/repo"), Arg.Any(), "", "application/vnd.github.ironman-preview+json"); + connection.Connection.Received().Put( + Arg.Is(u => u.ToString() == "teams/1/repos/org/repo"), + Arg.Any(), + "", + "application/vnd.github.hellcat-preview+json"); } [Fact] diff --git a/Octokit.Tests/Reactive/ObservableTeamsClientTests.cs b/Octokit.Tests/Reactive/ObservableTeamsClientTests.cs index fa5a4a95..66d29643 100644 --- a/Octokit.Tests/Reactive/ObservableTeamsClientTests.cs +++ b/Octokit.Tests/Reactive/ObservableTeamsClientTests.cs @@ -84,7 +84,7 @@ namespace Octokit.Tests.Reactive github.Connection.Received().Get>( Arg.Is(u => u.ToString() == "teams/1/members"), Args.EmptyDictionary, - null); + "application/vnd.github.hellcat-preview+json"); } [Fact] @@ -98,7 +98,7 @@ namespace Octokit.Tests.Reactive github.Connection.Received().Get>( Arg.Is(u => u.ToString() == "teams/1/members"), Arg.Is>(d => d["role"] == "maintainer"), - null); + "application/vnd.github.hellcat-preview+json"); } [Fact] @@ -144,6 +144,50 @@ namespace Octokit.Tests.Reactive } } + public class TheGetAllChildTeamsMethod + { + [Fact] + public void EnsuresNonNullArguments() + { + var client = new ObservableTeamsClient(Substitute.For()); + + Assert.Throws(() => client.GetAllChildTeams(1, null)); + } + + [Fact] + public void RequestsTheCorrectUrl() + { + var gitHub = Substitute.For(); + var client = new ObservableTeamsClient(gitHub); + + client.GetAllChildTeams(1); + + gitHub.Connection.Received().Get>( + Arg.Is(u => u.ToString() == "teams/1/teams"), + Args.EmptyDictionary, + "application/vnd.github.hellcat-preview+json"); + } + + [Fact] + public void RequestsTheCorrectUrlWithApiOptions() + { + var gitHub = Substitute.For(); + var client = new ObservableTeamsClient(gitHub); + var options = new ApiOptions + { + PageCount = 1, + PageSize = 1, + StartPage = 1 + }; + client.GetAllChildTeams(1, options); + + gitHub.Connection.Received().Get>( + Arg.Is(u => u.ToString() == "teams/1/teams"), + Arg.Is>(dictionary => dictionary.Count == 2), + "application/vnd.github.hellcat-preview+json"); + } + } + public class TheGetAllPendingInvitationsMethod { [Fact] diff --git a/Octokit/Clients/ITeamsClient.cs b/Octokit/Clients/ITeamsClient.cs index f67fb1a2..ebe939db 100644 --- a/Octokit/Clients/ITeamsClient.cs +++ b/Octokit/Clients/ITeamsClient.cs @@ -59,6 +59,24 @@ namespace Octokit [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] Task> GetAllForCurrent(ApiOptions options); + /// + /// Returns all child teams of the given team. + /// + /// The team identifier + /// + /// https://developer.github.com/v3/orgs/teams/#list-child-teams + /// + Task> GetAllChildTeams(int id); + + /// + /// Returns all child teams of the given team. + /// + /// The team identifier + /// + /// https://developer.github.com/v3/orgs/teams/#list-child-teams + /// + Task> GetAllChildTeams(int id, ApiOptions options); + /// /// Returns all members of the given team. /// diff --git a/Octokit/Clients/TeamsClient.cs b/Octokit/Clients/TeamsClient.cs index 8da6b769..db3fe9f4 100644 --- a/Octokit/Clients/TeamsClient.cs +++ b/Octokit/Clients/TeamsClient.cs @@ -34,7 +34,7 @@ namespace Octokit { var endpoint = ApiUrls.Teams(id); - return ApiConnection.Get(endpoint); + return ApiConnection.Get(endpoint, null, AcceptHeaders.NestedTeamsPreview); } /// @@ -61,7 +61,7 @@ namespace Octokit Ensure.ArgumentNotNull(options, "options"); var endpoint = ApiUrls.OrganizationTeams(org); - return ApiConnection.GetAll(endpoint, options); + return ApiConnection.GetAll(endpoint, null, AcceptHeaders.NestedTeamsPreview, options); } /// @@ -86,7 +86,35 @@ namespace Octokit var endpoint = ApiUrls.UserTeams(); - return ApiConnection.GetAll(endpoint, options); + return ApiConnection.GetAll(endpoint, null, AcceptHeaders.NestedTeamsPreview, options); + } + + /// + /// Returns all child teams of the given team. + /// + /// The team identifier + /// + /// https://developer.github.com/v3/orgs/teams/#list-child-teams + /// + public Task> GetAllChildTeams(int id) + { + return GetAllChildTeams(id, ApiOptions.None); + } + + /// + /// Returns all child teams of the given team. + /// + /// The team identifier + /// + /// https://developer.github.com/v3/orgs/teams/#list-child-teams + /// + public Task> GetAllChildTeams(int id, ApiOptions options) + { + Ensure.ArgumentNotNull(options, nameof(options)); + + var endpoint = ApiUrls.TeamChildTeams(id); + + return ApiConnection.GetAll(endpoint, null, AcceptHeaders.NestedTeamsPreview, options); } /// @@ -115,7 +143,7 @@ namespace Octokit var endpoint = ApiUrls.TeamMembers(id); - return ApiConnection.GetAll(endpoint, options); + return ApiConnection.GetAll(endpoint, null, AcceptHeaders.NestedTeamsPreview, options); } /// @@ -149,7 +177,7 @@ namespace Octokit var endpoint = ApiUrls.TeamMembers(id); - return ApiConnection.GetAll(endpoint, request.ToParametersDictionary(), options); + return ApiConnection.GetAll(endpoint, request.ToParametersDictionary(), AcceptHeaders.NestedTeamsPreview, options); } /// @@ -169,7 +197,7 @@ namespace Octokit try { - response = await ApiConnection.Get>(endpoint).ConfigureAwait(false); + response = await ApiConnection.Get>(endpoint, null, AcceptHeaders.NestedTeamsPreview).ConfigureAwait(false); } catch (NotFoundException) { @@ -197,7 +225,7 @@ namespace Octokit var endpoint = ApiUrls.TeamMember(id, login); - return ApiConnection.Get(endpoint); + return ApiConnection.Get(endpoint, null, AcceptHeaders.NestedTeamsPreview); } /// @@ -211,7 +239,7 @@ namespace Octokit Ensure.ArgumentNotNull(team, "team"); var endpoint = ApiUrls.OrganizationTeams(org); - return ApiConnection.Post(endpoint, team); + return ApiConnection.Post(endpoint, team, AcceptHeaders.NestedTeamsPreview); } /// @@ -224,7 +252,7 @@ namespace Octokit Ensure.ArgumentNotNull(team, "team"); var endpoint = ApiUrls.Teams(id); - return ApiConnection.Patch(endpoint, team); + return ApiConnection.Patch(endpoint, team, AcceptHeaders.NestedTeamsPreview); } /// @@ -236,7 +264,7 @@ namespace Octokit { var endpoint = ApiUrls.Teams(id); - return ApiConnection.Delete(endpoint); + return ApiConnection.Delete(endpoint, new object(), AcceptHeaders.NestedTeamsPreview); } /// @@ -311,7 +339,7 @@ namespace Octokit try { - var httpStatusCode = await ApiConnection.Connection.Delete(endpoint).ConfigureAwait(false); + var httpStatusCode = await ApiConnection.Connection.Delete(endpoint, null, AcceptHeaders.NestedTeamsPreview).ConfigureAwait(false); return httpStatusCode == HttpStatusCode.NoContent; } @@ -345,7 +373,7 @@ namespace Octokit var endpoint = ApiUrls.TeamRepositories(id); - return ApiConnection.GetAll(endpoint, null, AcceptHeaders.OrganizationPermissionsPreview, options); + return ApiConnection.GetAll(endpoint, null, AcceptHeaders.NestedTeamsPreview, options); } /// @@ -362,7 +390,7 @@ namespace Octokit try { - var httpStatusCode = await ApiConnection.Connection.Put(endpoint).ConfigureAwait(false); + var httpStatusCode = await ApiConnection.Connection.Put(endpoint, AcceptHeaders.NestedTeamsPreview).ConfigureAwait(false); return httpStatusCode == HttpStatusCode.NoContent; } catch (NotFoundException) @@ -389,7 +417,7 @@ namespace Octokit try { - var httpStatusCode = await ApiConnection.Connection.Put(endpoint, permission, "", AcceptHeaders.OrganizationPermissionsPreview).ConfigureAwait(false); + var httpStatusCode = await ApiConnection.Connection.Put(endpoint, permission, "", AcceptHeaders.NestedTeamsPreview).ConfigureAwait(false); return httpStatusCode.HttpResponse.StatusCode == HttpStatusCode.NoContent; } catch (NotFoundException) @@ -441,7 +469,7 @@ namespace Octokit try { - var response = await ApiConnection.Connection.GetResponse(endpoint).ConfigureAwait(false); + var response = await ApiConnection.Connection.Get(endpoint, null, AcceptHeaders.NestedTeamsPreview).ConfigureAwait(false); return response.HttpResponse.StatusCode == HttpStatusCode.NoContent; } catch (NotFoundException) diff --git a/Octokit/Helpers/AcceptHeaders.cs b/Octokit/Helpers/AcceptHeaders.cs index f283982c..afd058d7 100644 --- a/Octokit/Helpers/AcceptHeaders.cs +++ b/Octokit/Helpers/AcceptHeaders.cs @@ -48,5 +48,7 @@ namespace Octokit public const string ProjectsApiPreview = "application/vnd.github.inertia-preview+json"; public const string OrganizationMembershipPreview = "application/vnd.github.korra-preview+json"; + + public const string NestedTeamsPreview = "application/vnd.github.hellcat-preview+json"; } } diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index ef471b4a..24120b31 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -1581,6 +1581,16 @@ namespace Octokit return "user/teams".FormatUri(); } + /// + /// Returns the for child teams + /// + /// + /// + public static Uri TeamChildTeams(int id) + { + return "teams/{0}/teams".FormatUri(id); + } + /// /// Returns the for teams /// use for getting, updating, or deleting a . diff --git a/Octokit/Models/Request/NewTeam.cs b/Octokit/Models/Request/NewTeam.cs index 9316c176..fc9716c9 100644 --- a/Octokit/Models/Request/NewTeam.cs +++ b/Octokit/Models/Request/NewTeam.cs @@ -57,6 +57,11 @@ namespace Octokit /// public Permission? Permission { get; set; } + /// + /// Id of a team to set as the parent team + /// + public int? ParentTeamId { get; set; } + internal string DebuggerDisplay { get diff --git a/Octokit/Models/Request/UpdateTeam.cs b/Octokit/Models/Request/UpdateTeam.cs index 8590b8b4..8724a02d 100644 --- a/Octokit/Models/Request/UpdateTeam.cs +++ b/Octokit/Models/Request/UpdateTeam.cs @@ -51,6 +51,11 @@ namespace Octokit /// public Permission? Permission { get; set; } + /// + /// Id of a team to set as the parent team + /// + public long? ParentTeamId { get; set; } + internal string DebuggerDisplay { get diff --git a/Octokit/Models/Response/Team.cs b/Octokit/Models/Response/Team.cs index d2966eac..33698b9a 100644 --- a/Octokit/Models/Response/Team.cs +++ b/Octokit/Models/Response/Team.cs @@ -71,6 +71,11 @@ namespace Octokit /// public Organization Organization { get; protected set; } + /// + /// The parent team + /// + public Team Parent { get; protected set; } + /// /// LDAP Binding (GitHub Enterprise only) ///