Skip to content

Commit

Permalink
Support for IsTemplate and Create Template from Repository (#2331)
Browse files Browse the repository at this point in the history
  • Loading branch information
JonruAlveus committed Jun 30, 2022
1 parent 659ce5f commit f317f9d
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 22 deletions.
9 changes: 9 additions & 0 deletions Octokit.Reactive/Clients/IObservableRepositoriesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ public interface IObservableRepositoriesClient
/// <returns>An <see cref="IObservable{Repository}"/> instance for the created repository</returns>
IObservable<Repository> Create(string organizationLogin, NewRepository newRepository);

/// <summary>
/// Creates a new repository using a repository template.
/// </summary>
/// <param name="templateOwner">The owner of the template</param>
/// <param name="templateRepo">The name of the template</param>
/// <param name="newRepository">A <see cref="NewRepositoryFromTemplate"/> instance describing the new repository to create from a template</param>
/// <returns>An <see cref="IObservable{Repository}"/> instance for the created repository</returns>
IObservable<Repository> Generate(string templateOwner, string templateRepo, NewRepositoryFromTemplate newRepository);

/// <summary>
/// Deletes a repository for the specified owner and name.
/// </summary>
Expand Down
18 changes: 18 additions & 0 deletions Octokit.Reactive/Clients/ObservableRepositoriesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,24 @@ public IObservable<Repository> Create(string organizationLogin, NewRepository ne
return _client.Create(organizationLogin, newRepository).ToObservable();
}

/// <summary>
/// Creates a new repository from a template
/// </summary>
/// <param name="templateOwner">The organization or person who will owns the template</param>
/// <param name="templateRepo">The name of template repository to work from</param>
/// <param name="newRepository">A <see cref="NewRepositoryFromTemplate"/> instance describing the new repository to create from a template</param>
/// <returns></returns>
public IObservable<Repository> Generate(string templateOwner, string templateRepo, NewRepositoryFromTemplate newRepository)
{
Ensure.ArgumentNotNull(templateOwner, nameof(templateOwner));
Ensure.ArgumentNotNull(templateRepo, nameof(templateRepo));
Ensure.ArgumentNotNull(newRepository, nameof(newRepository));
if (string.IsNullOrEmpty(newRepository.Name))
throw new ArgumentException("The new repository's name must not be null.");

return _client.Generate(templateOwner, templateRepo, newRepository).ToObservable();
}

/// <summary>
/// Deletes a repository for the specified owner and name.
/// </summary>
Expand Down
45 changes: 45 additions & 0 deletions Octokit.Tests.Integration/Clients/RepositoriesClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,51 @@ public async Task CreatesARepositoryWithALicenseTemplate()
}
}

[IntegrationTest]
public async Task CreatesARepositoryAsTemplate()
{
var github = Helper.GetAuthenticatedClient();
var repoName = Helper.MakeNameWithTimestamp("repo-as-template");

var newRepository = new NewRepository(repoName)
{
IsTemplate = true
};

using (var context = await github.CreateRepositoryContext(newRepository))
{
var createdRepository = context.Repository;

var repository = await github.Repository.Get(Helper.UserName, repoName);

Assert.True(repository.IsTemplate);
}
}

[IntegrationTest]
public async Task CreatesARepositoryFromTemplate()
{
var github = Helper.GetAuthenticatedClient();
var repoTemplateName = Helper.MakeNameWithTimestamp("repo-template");
var repoFromTemplateName = Helper.MakeNameWithTimestamp("repo-from-template");
var owner = github.User.Current().Result.Login;

var newTemplate = new NewRepository(repoTemplateName)
{
IsTemplate = true
};

var newRepo = new NewRepositoryFromTemplate(repoFromTemplateName);

using (var templateContext = await github.CreateRepositoryContext(newTemplate))
using (var context = await github.Generate(owner, repoFromTemplateName, newRepo))
{
var repository = await github.Repository.Get(Helper.UserName, repoFromTemplateName);

Assert.Equal(repoFromTemplateName, repository.Name);
}
}

[IntegrationTest]
public async Task CreatesARepositoryWithDeleteBranchOnMergeEnabled()
{
Expand Down
7 changes: 7 additions & 0 deletions Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ internal static async Task<RepositoryContext> CreateRepositoryContext(this IGitH
return new RepositoryContext(client.Connection, repo);
}

internal static async Task<RepositoryContext> Generate(this IGitHubClient client, string owner, string repoName, NewRepositoryFromTemplate newRepository)
{
var repo = await client.Repository.Generate(owner, repoName, newRepository);

return new RepositoryContext(client.Connection, repo);
}

internal static async Task<TeamContext> CreateTeamContext(this IGitHubClient client, string organization, NewTeam newTeam)
{
newTeam.Privacy = TeamPrivacy.Closed;
Expand Down
70 changes: 54 additions & 16 deletions Octokit.Tests/Clients/RepositoriesClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void UsesTheUserReposUrl()

connection.Received().Post<Repository>(Arg.Is<Uri>(u => u.ToString() == "user/repos"),
Arg.Any<NewRepository>(),
"application/vnd.github.nebula-preview+json");
"application/vnd.github.nebula-preview+json,application/vnd.github.baptiste-preview+json");
}

[Fact]
Expand All @@ -54,7 +54,7 @@ public void TheNewRepositoryDescription()

client.Create(newRepository);

connection.Received().Post<Repository>(Args.Uri, newRepository, "application/vnd.github.nebula-preview+json");
connection.Received().Post<Repository>(Args.Uri, newRepository, "application/vnd.github.nebula-preview+json,application/vnd.github.baptiste-preview+json");
}

[Fact]
Expand All @@ -70,7 +70,7 @@ public async Task ThrowsRepositoryExistsExceptionWhenRepositoryExistsForCurrentU
var connection = Substitute.For<IApiConnection>();
connection.Connection.BaseAddress.Returns(GitHubClient.GitHubApiUrl);
connection.Connection.Credentials.Returns(credentials);
connection.Post<Repository>(Args.Uri, newRepository, "application/vnd.github.nebula-preview+json")
connection.Post<Repository>(Args.Uri, newRepository, "application/vnd.github.nebula-preview+json,application/vnd.github.baptiste-preview+json")
.Returns<Task<Repository>>(_ => { throw new ApiValidationException(response); });
var client = new RepositoriesClient(connection);

Expand All @@ -97,7 +97,7 @@ public async Task ThrowsExceptionWhenPrivateRepositoryQuotaExceeded()
var connection = Substitute.For<IApiConnection>();
connection.Connection.BaseAddress.Returns(GitHubClient.GitHubApiUrl);
connection.Connection.Credentials.Returns(credentials);
connection.Post<Repository>(Args.Uri, newRepository, "application/vnd.github.nebula-preview+json")
connection.Post<Repository>(Args.Uri, newRepository, "application/vnd.github.nebula-preview+json,application/vnd.github.baptiste-preview+json")
.Returns<Task<Repository>>(_ => { throw new ApiValidationException(response); });
var client = new RepositoriesClient(connection);

Expand Down Expand Up @@ -130,7 +130,7 @@ public async Task UsesTheOrganizationsReposUrl()
connection.Received().Post<Repository>(
Arg.Is<Uri>(u => u.ToString() == "orgs/theLogin/repos"),
Args.NewRepository,
"application/vnd.github.nebula-preview+json");
"application/vnd.github.nebula-preview+json,application/vnd.github.baptiste-preview+json");
}

[Fact]
Expand All @@ -142,7 +142,7 @@ public async Task TheNewRepositoryDescription()

await client.Create("aLogin", newRepository);

connection.Received().Post<Repository>(Args.Uri, newRepository, "application/vnd.github.nebula-preview+json");
connection.Received().Post<Repository>(Args.Uri, newRepository, "application/vnd.github.nebula-preview+json,application/vnd.github.baptiste-preview+json");
}

[Fact]
Expand All @@ -156,7 +156,7 @@ public async Task ThrowsRepositoryExistsExceptionWhenRepositoryExistsForSpecifie
+ @"""code"":""custom"",""field"":""name"",""message"":""name already exists on this account""}]}");
var connection = Substitute.For<IApiConnection>();
connection.Connection.BaseAddress.Returns(GitHubClient.GitHubApiUrl);
connection.Post<Repository>(Args.Uri, newRepository, "application/vnd.github.nebula-preview+json")
connection.Post<Repository>(Args.Uri, newRepository, "application/vnd.github.nebula-preview+json,application/vnd.github.baptiste-preview+json")
.Returns<Task<Repository>>(_ => { throw new ApiValidationException(response); });
var client = new RepositoriesClient(connection);

Expand All @@ -181,7 +181,7 @@ public async Task ThrowsValidationException()
+ @"""http://developer.github.com/v3/repos/#create"",""errors"":[]}");
var connection = Substitute.For<IApiConnection>();
connection.Connection.BaseAddress.Returns(GitHubClient.GitHubApiUrl);
connection.Post<Repository>(Args.Uri, newRepository, "application/vnd.github.nebula-preview+json")
connection.Post<Repository>(Args.Uri, newRepository, "application/vnd.github.nebula-preview+json,application/vnd.github.baptiste-preview+json")
.Returns<Task<Repository>>(_ => { throw new ApiValidationException(response); });
var client = new RepositoriesClient(connection);

Expand All @@ -202,7 +202,7 @@ public async Task ThrowsRepositoryExistsExceptionForEnterpriseInstance()
+ @"""code"":""custom"",""field"":""name"",""message"":""name already exists on this account""}]}");
var connection = Substitute.For<IApiConnection>();
connection.Connection.BaseAddress.Returns(new Uri("https://example.com"));
connection.Post<Repository>(Args.Uri, newRepository, "application/vnd.github.nebula-preview+json")
connection.Post<Repository>(Args.Uri, newRepository, "application/vnd.github.nebula-preview+json,application/vnd.github.baptiste-preview+json")
.Returns<Task<Repository>>(_ => { throw new ApiValidationException(response); });
var client = new RepositoriesClient(connection);

Expand All @@ -214,6 +214,44 @@ public async Task ThrowsRepositoryExistsExceptionForEnterpriseInstance()
}
}

public class TheGenerateMethod
{
[Fact]
public async Task EnsuresNonNullArguments()
{
var client = new RepositoriesClient(Substitute.For<IApiConnection>());

await Assert.ThrowsAsync<ArgumentNullException>(() => client.Generate(null, null, null));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.Generate("asd", null, null));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.Generate("asd", "asd", null));
}

[Fact]
public void UsesTheUserReposUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new RepositoriesClient(connection);

client.Generate("asd", "asd", new NewRepositoryFromTemplate("aName"));

connection.Received().Post<Repository>(Arg.Is<Uri>(u => u.ToString() == "repos/asd/asd/generate"),
Arg.Any<NewRepositoryFromTemplate>(),
"application/vnd.github.baptiste-preview+json");
}

[Fact]
public void TheNewRepositoryDescription()
{
var connection = Substitute.For<IApiConnection>();
var client = new RepositoriesClient(connection);
var newRepository = new NewRepositoryFromTemplate("aName");

client.Generate("anOwner", "aRepo", newRepository);

connection.Received().Post<Repository>(Args.Uri, newRepository, "application/vnd.github.baptiste-preview+json");
}
}

public class TheTransferMethod
{
[Fact]
Expand Down Expand Up @@ -490,7 +528,7 @@ public async Task RequestsTheCorrectUrlAndReturnsRepositories()
connection.Received()
.GetAll<Repository>(Arg.Is<Uri>(u => u.ToString() == "user/repos"),
null,
"application/vnd.github.nebula-preview+json",
"application/vnd.github.nebula-preview+json,application/vnd.github.baptiste-preview+json",
Args.ApiOptions);
}

Expand Down Expand Up @@ -644,7 +682,7 @@ public async Task RequestsTheCorrectUrl()
await client.GetAllForOrg("orgname");

connection.Received()
.GetAll<Repository>(Arg.Is<Uri>(u => u.ToString() == "orgs/orgname/repos"), null, "application/vnd.github.nebula-preview+json", Args.ApiOptions);
.GetAll<Repository>(Arg.Is<Uri>(u => u.ToString() == "orgs/orgname/repos"), null, "application/vnd.github.nebula-preview+json,application/vnd.github.baptiste-preview+json", Args.ApiOptions);
}

[Fact]
Expand Down Expand Up @@ -1078,7 +1116,7 @@ public void PatchesCorrectUrl()
connection.Received()
.Patch<Repository>(Arg.Is<Uri>(u => u.ToString() == "repos/owner/repo"),
Arg.Any<RepositoryUpdate>(),
"application/vnd.github.nebula-preview+json");
"application/vnd.github.nebula-preview+json,application/vnd.github.baptiste-preview+json");
}

[Fact]
Expand Down Expand Up @@ -1332,7 +1370,7 @@ public async Task RequestsTheCorrectUrlForOwnerAndRepoWithEmptyTopics()
await _client.ReplaceAllTopics("owner", "name", _emptyTopics);

_connection.Received()
.Put<RepositoryTopics>(Arg.Is<Uri>(u => u.ToString() == "repos/owner/name/topics"), _emptyTopics, null,"application/vnd.github.mercy-preview+json");
.Put<RepositoryTopics>(Arg.Is<Uri>(u => u.ToString() == "repos/owner/name/topics"), _emptyTopics, null, "application/vnd.github.mercy-preview+json");
}

[Fact]
Expand All @@ -1341,7 +1379,7 @@ public async Task RequestsTheCorrectUrlForOwnerAndRepoWithListOfTopics()
await _client.ReplaceAllTopics("owner", "name", _listOfTopics);

_connection.Received()
.Put<RepositoryTopics>(Arg.Is<Uri>(u => u.ToString() == "repos/owner/name/topics"), _listOfTopics,null, "application/vnd.github.mercy-preview+json");
.Put<RepositoryTopics>(Arg.Is<Uri>(u => u.ToString() == "repos/owner/name/topics"), _listOfTopics, null, "application/vnd.github.mercy-preview+json");
}

[Fact]
Expand All @@ -1356,10 +1394,10 @@ public async Task RequestsTheCorrectUrlForRepoIdWithEmptyTopics()
[Fact]
public async Task RequestsTheCorrectUrlForRepoIdWithListOfTopics()
{
await _client.ReplaceAllTopics(1234,_listOfTopics);
await _client.ReplaceAllTopics(1234, _listOfTopics);

_connection.Received()
.Put<RepositoryTopics>(Arg.Is<Uri>(u => u.ToString() == "repositories/1234/topics"), _listOfTopics,null, "application/vnd.github.mercy-preview+json");
.Put<RepositoryTopics>(Arg.Is<Uri>(u => u.ToString() == "repositories/1234/topics"), _listOfTopics, null, "application/vnd.github.mercy-preview+json");
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions Octokit/Clients/IRepositoriesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ public interface IRepositoriesClient
/// <returns>A <see cref="Repository"/> instance for the created repository</returns>
Task<Repository> Create(string organizationLogin, NewRepository newRepository);

/// <summary>
/// Creates a new repository from a template
/// </summary>
/// <param name="templateOwner">The organization or person who will owns the template</param>
/// <param name="templateRepo">The name of template repository to work from</param>
/// <param name="newRepository"></param>
/// <returns></returns>
Task<Repository> Generate(string templateOwner, string templateRepo, NewRepositoryFromTemplate newRepository);


/// <summary>
/// Deletes the specified repository.
/// </summary>
Expand Down

0 comments on commit f317f9d

Please sign in to comment.