Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading