Skip to content

Commit

Permalink
[WIP] Fixes #1718 Implement Repository Transfer functionality (#1813)
Browse files Browse the repository at this point in the history
* Add "transfer repository" accept header

* Create RepositoryTransfer class

This will be used to send the POST request to initiate the transfer.

* Create Ensure method to check for empty or null arrays

* Change arg name in Ensure for nonempty arrays

array -> value

* Add xmldoc for ArgumentNotNullOrEmptyArray

* Create Transfer method in IRepositoriesClient

* Implement Transfer method in RepositoriesClient

* Fix typo in xmldoc for Transfer

* Add <returns> to Transfer xmldoc

* Create Transfer method in IObservableRepositoriesClient

* Implement Transfer in ObservableRepositoriesClient

* Add DebuggerDIsplayAttribute do RepositoryTransfer

* Add unit tests for RepositoryTransfer constructors

* Change TeamId property type to IReadOnlyList<int>

* Rewrite DebuggerDisplay property into something more succint

* Make new Ensure method into an IEnumerable<T> checker

* Add XmlDoc to RepositoryTransfer

* Tweaks to first ctor XmlDoc

* Create basic unit tests for Transfer

* Create ApiUrls.RepositoryTransfer

* Use ApiUrls.RepositoryTransfer to get URI in Transfer

Previous implementation used wrong URI

* Start implementing RepositoriesClientTests.TheTransferMethod

* Implement org -> user transfer integration test

* Implement user -> org transfer integration test

* [WIP] Implement user -> org transfer with teams

Implementation doesn't work, API usage seems correct.

* Mark transfer user -> org w/ teams integration test with FIXME

* Add second end point URI to ApiUrls

* Add other Transfer overload to RepositoriesClient for other end point

* Create unit tests for other Transfer endpoint

* Add overload to IRepositoriesClient

* Add integration tests for overload

* Reorganize unit tests for TheTransferMethod

* Rename id to repositoryId

* Reorganize unit tests for RepositoriesClientTests.Transfer

* Add second endpoint to IObservableRepositoriesClient

* Add XmlDoc to second Transfer endpoint

* Add XmlDoc to second Transfer endpoint in RepositoriesClient

* Reimplement "with teams" integration tests using TeamContext

* Rename integration test for consistency

* Add asserts to actual ownership transfer

* Rename RepositoryTransfer.TeamId property to TeamIds

* Add awaiit to ThrowsAsync in RepositoriesClientTests

* Put await in right places for unit tests

* Add Ensures for Transfer method in RepositoriesClient

* Add XmlDoc to ApiUrls.RepositoryTransfer with repo id

* Update XmlDoc for RepositoryTransfer constructor and teamIds property

* Rename currentOwner to owner

* Add Ensure guards to ObservableRepositoriesClient.Transfer methods

* Add unit tests for ObservableRepositoriesClient
  • Loading branch information
wqferr authored and ryangribble committed Jun 24, 2018
1 parent fb928f6 commit 74dc51a
Show file tree
Hide file tree
Showing 12 changed files with 637 additions and 0 deletions.
23 changes: 23 additions & 0 deletions Octokit.Reactive/Clients/IObservableRepositoriesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,29 @@ public interface IObservableRepositoriesClient
/// <returns>An <see cref="IObservable{Unit}"/> for the operation</returns>
IObservable<Unit> Delete(long repositoryId);

/// <summary>
/// Transfers the ownership of the specified repository.
/// </summary>
/// <remarks>
/// See the <a href="https://developer.github.com/v3/repos/#transfer-a-repository">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The current owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="repositoryTransfer">Repository transfer information</param>
/// <returns>A <see cref="Repository"/></returns>
IObservable<Repository> Transfer(string owner, string name, RepositoryTransfer repositoryTransfer);

/// <summary>
/// Transfers the ownership of the specified repository.
/// </summary>
/// <remarks>
/// See the <a href="https://developer.github.com/v3/repos/#transfer-a-repository">API documentation</a> for more information.
/// </remarks>
/// <param name="repositoryId">The id of the repository</param>
/// <param name="repositoryTransfer">Repository transfer information</param>
/// <returns>A <see cref="Repository"/></returns>
IObservable<Repository> Transfer(long repositoryId, RepositoryTransfer repositoryTransfer);

/// <summary>
/// Retrieves the <see cref="Repository"/> for the specified owner and name.
/// </summary>
Expand Down
35 changes: 35 additions & 0 deletions Octokit.Reactive/Clients/ObservableRepositoriesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,41 @@ public IObservable<Unit> Delete(long repositoryId)
{
return _client.Delete(repositoryId).ToObservable();
}

/// <summary>
/// Transfers the ownership of the specified repository.
/// </summary>
/// <remarks>
/// See the <a href="https://developer.github.com/v3/repos/#transfer-a-repository">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The current owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="repositoryTransfer">Repository transfer information</param>
/// <returns>A <see cref="Repository"/></returns>
public IObservable<Repository> Transfer(string owner, string name, RepositoryTransfer repositoryTransfer)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Ensure.ArgumentNotNullOrEmptyString(name, nameof(name));
Ensure.ArgumentNotNull(repositoryTransfer, nameof(repositoryTransfer));

return _client.Transfer(owner, name, repositoryTransfer).ToObservable();
}

/// <summary>
/// Transfers the ownership of the specified repository.
/// </summary>
/// <remarks>
/// See the <a href="https://developer.github.com/v3/repos/#transfer-a-repository">API documentation</a> for more information.
/// </remarks>
/// <param name="repositoryId">The id of the repository</param>
/// <param name="repositoryTransfer">Repository transfer information</param>
/// <returns>A <see cref="Repository"/></returns>
public IObservable<Repository> Transfer(long repositoryId, RepositoryTransfer repositoryTransfer)
{
Ensure.ArgumentNotNull(repositoryTransfer, nameof(repositoryTransfer));

return _client.Transfer(repositoryId, repositoryTransfer).ToObservable();
}

/// <summary>
/// Retrieves the <see cref="Repository"/> for the specified owner and name.
Expand Down
122 changes: 122 additions & 0 deletions Octokit.Tests.Integration/Clients/RepositoriesClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1681,4 +1681,126 @@ public async Task ReturnsLicenseContentWithRepositoryId()
Assert.Equal("MIT License", license.License.Name);
}
}

public class TheTransferMethod
{
[IntegrationTest]
public async Task TransfersFromOrgToUser()
{
var github = Helper.GetAuthenticatedClient();
var newRepo = new NewRepository(Helper.MakeNameWithTimestamp("transferred-repo"));
var newOwner = Helper.UserName;
using (var context = await github.CreateRepositoryContext(Helper.Organization, newRepo))
{
var transfer = new RepositoryTransfer(newOwner);
await github.Repository.Transfer(context.RepositoryOwner, context.RepositoryName, transfer);
var transferred = await github.Repository.Get(newOwner, context.RepositoryName);

Assert.Equal(newOwner, transferred.Owner.Login);
}
}

[IntegrationTest]
public async Task TransfersFromOrgToUserById()
{
var github = Helper.GetAuthenticatedClient();
var newRepo = new NewRepository(Helper.MakeNameWithTimestamp("transferred-repo"));
var newOwner = Helper.UserName;
using (var context = await github.CreateRepositoryContext(Helper.Organization, newRepo))
{
var transfer = new RepositoryTransfer(newOwner);
await github.Repository.Transfer(context.RepositoryId, transfer);
var transferred = await github.Repository.Get(context.RepositoryId);

Assert.Equal(newOwner, transferred.Owner.Login);
}
}

[IntegrationTest]
public async Task TransfersFromUserToOrg()
{
var github = Helper.GetAuthenticatedClient();
var newRepo = new NewRepository(Helper.MakeNameWithTimestamp("transferred-repo"));
var newOwner = Helper.Organization;
using (var context = await github.CreateRepositoryContext(newRepo))
{
var transfer = new RepositoryTransfer(newOwner);
await github.Repository.Transfer(context.RepositoryOwner, context.RepositoryName, transfer);
var transferred = await github.Repository.Get(newOwner, context.RepositoryName);

Assert.Equal(newOwner, transferred.Owner.Login);
}
}

[IntegrationTest]
public async Task TransfersFromUserToOrgById()
{
var github = Helper.GetAuthenticatedClient();
var newRepo = new NewRepository(Helper.MakeNameWithTimestamp("transferred-repo"));
var newOwner = Helper.Organization;
using (var context = await github.CreateRepositoryContext(newRepo))
{
var transfer = new RepositoryTransfer(newOwner);
await github.Repository.Transfer(context.RepositoryId, transfer);
var transferred = await github.Repository.Get(context.RepositoryId);

Assert.Equal(newOwner, transferred.Owner.Login);
}
}

[IntegrationTest]
public async Task TransfersFromUserToOrgWithTeams()
{
// FIXME API doesn't add teams when transferring to an organization
var github = Helper.GetAuthenticatedClient();
var newRepo = new NewRepository(Helper.MakeNameWithTimestamp("transferred-repo"));
var newOwner = Helper.Organization;

using (var repositoryContext = await github.CreateRepositoryContext(newRepo))
{
NewTeam team = new NewTeam(Helper.MakeNameWithTimestamp("transfer-team"));
using (var teamContext = await github.CreateTeamContext(Helper.Organization, team)) {
var transferTeamIds = new int[] { teamContext.TeamId };
var transfer = new RepositoryTransfer(newOwner, transferTeamIds);
await github.Repository.Transfer(
repositoryContext.RepositoryOwner, repositoryContext.RepositoryName, transfer);
var transferred = await github.Repository.Get(repositoryContext.RepositoryId);
var repoTeams = await github.Repository.GetAllTeams(repositoryContext.RepositoryId);

Assert.Equal(newOwner, transferred.Owner.Login);
// transferTeamIds is a subset of repoTeams
Assert.Empty(
transferTeamIds.Except(
repoTeams.Select(t => t.Id)));
}
}
}

[IntegrationTest]
public async Task TransfersFromUserToOrgWithTeamsById()
{
// FIXME API doesn't add teams when transferring to an organization
var github = Helper.GetAuthenticatedClient();
var newRepo = new NewRepository(Helper.MakeNameWithTimestamp("transferred-repo"));
var newOwner = Helper.Organization;

using (var repositoryContext = await github.CreateRepositoryContext(newRepo))
{
NewTeam team = new NewTeam(Helper.MakeNameWithTimestamp("transfer-team"));
using (var teamContext = await github.CreateTeamContext(Helper.Organization, team)) {
var transferTeamIds = new int[] { teamContext.TeamId };
var transfer = new RepositoryTransfer(newOwner, transferTeamIds);
await github.Repository.Transfer(repositoryContext.RepositoryId, transfer);
var transferred = await github.Repository.Get(repositoryContext.RepositoryId);
var repoTeams = await github.Repository.GetAllTeams(repositoryContext.RepositoryId);

Assert.Equal(newOwner, transferred.Owner.Login);
// transferTeamIds is a subset of repoTeams
Assert.Empty(
transferTeamIds.Except(
repoTeams.Select(t => t.Id)));
}
}
}
}
}
152 changes: 152 additions & 0 deletions Octokit.Tests/Clients/RepositoriesClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,158 @@ public async Task ThrowsRepositoryExistsExceptionForEnterpriseInstance()
}
}

public class TheTransferMethod
{
[Fact]
public async Task EnsuresNonNullArguments()
{
var connection = Substitute.For<IApiConnection>();
var client = new RepositoriesClient(connection);
var transfer = new RepositoryTransfer("newOwner");

await Assert.ThrowsAsync<ArgumentNullException>(
() => client.Transfer(null, "name", transfer));
await Assert.ThrowsAsync<ArgumentNullException>(
() => client.Transfer("owner", null, transfer));
await Assert.ThrowsAsync<ArgumentNullException>(
() => client.Transfer("owner", "name", null));
}

[Fact]
public async Task EnsuresNonNullArgumentsById()
{
var connection = Substitute.For<IApiConnection>();
var client = new RepositoriesClient(connection);
var transfer = new RepositoryTransfer("newOwner");
var repositoryId = 1;

await Assert.ThrowsAsync<ArgumentNullException>(
() => client.Transfer(repositoryId, null));
}

[Fact]
public async Task EnsuresNonEmptyArguments()
{
var connection = Substitute.For<IApiConnection>();
var client = new RepositoriesClient(connection);
var transfer = new RepositoryTransfer("newOwner");

await Assert.ThrowsAsync<ArgumentException>(
() => client.Transfer("", "name", transfer));
await Assert.ThrowsAsync<ArgumentException>(
() => client.Transfer("owner", "", transfer));
}

[Fact]
public async Task RequestsCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new RepositoriesClient(connection);
var teamId = new int[2] {35, 42};
var transfer = new RepositoryTransfer("newOwner", teamId);

await client.Transfer("owner", "name", transfer);

connection.Received()
.Post<Repository>(
Arg.Is<Uri>(u => u.ToString() == "repos/owner/name/transfer"),
Arg.Any<RepositoryTransfer>(),
Arg.Any<string>());
}

[Fact]
public async Task RequestsCorrectUrlById()
{
var connection = Substitute.For<IApiConnection>();
var client = new RepositoriesClient(connection);
var teamId = new int[2] {35, 42};
var transfer = new RepositoryTransfer("newOwner", teamId);
var repositoryId = 1;

await client.Transfer(repositoryId, transfer);

connection.Received()
.Post<Repository>(
Arg.Is<Uri>(u => u.ToString() == "repositories/1/transfer"),
Arg.Any<RepositoryTransfer>(),
Arg.Any<string>());
}

[Fact]
public async Task SendsCorrectRequest()
{
var connection = Substitute.For<IApiConnection>();
var client = new RepositoriesClient(connection);
var teamId = new int[2] {35, 42};
var transfer = new RepositoryTransfer("newOwner", teamId);

await client.Transfer("owner", "name", transfer);

connection.Received()
.Post<Repository>(
Arg.Any<Uri>(),
Arg.Is<RepositoryTransfer>(
t => t.NewOwner == "newOwner" && object.Equals(teamId, t.TeamIds)),
Arg.Any<string>());
}

[Fact]
public async Task SendsCorrectRequestById()
{
var connection = Substitute.For<IApiConnection>();
var client = new RepositoriesClient(connection);
var teamId = new int[2] {35, 42};
var transfer = new RepositoryTransfer("newOwner", teamId);
var repositoryId = 1;

await client.Transfer(repositoryId, transfer);

connection.Received()
.Post<Repository>(
Arg.Any<Uri>(),
Arg.Is<RepositoryTransfer>(
t => t.NewOwner == "newOwner" && object.Equals(teamId, t.TeamIds)),
Arg.Any<string>());
}

[Fact]
public async Task SendsPreviewHeader()
{
var connection = Substitute.For<IApiConnection>();
var client = new RepositoriesClient(connection);
var teamId = new int[2] {35, 42};
var transfer = new RepositoryTransfer("newOwner", teamId);

await client.Transfer("owner", "name", transfer);

connection.Received()
.Post<Repository>(
Arg.Any<Uri>(),
Arg.Any<RepositoryTransfer>(),
Arg.Is<string>(
s => s.Contains(AcceptHeaders.RepositoryTransferPreview)));
}

[Fact]
public async Task SendsPreviewHeaderById()
{
var connection = Substitute.For<IApiConnection>();
var client = new RepositoriesClient(connection);
var teamId = new int[2] {35, 42};
var transfer = new RepositoryTransfer("newOwner", teamId);
var repositoryId = 1;

await client.Transfer(repositoryId, transfer);

connection.Received()
.Post<Repository>(
Arg.Any<Uri>(),
Arg.Any<RepositoryTransfer>(),
Arg.Is<string>(
s => s.Contains(AcceptHeaders.RepositoryTransferPreview)));
}
}

public class TheDeleteMethod
{
[Fact]
Expand Down
Loading

0 comments on commit 74dc51a

Please sign in to comment.