Merge pull request #58 from octokit/half-ogre/45-create-new-repository

Add method for POST /user/repos
This commit is contained in:
Drew Miller
2013-10-08 09:53:40 -07:00
11 changed files with 421 additions and 2 deletions
@@ -15,6 +15,20 @@ namespace Octokit.Reactive.Clients
_client = client;
}
/// <summary>
/// Creates a new repository for the current user.
/// </summary>
/// <param name="newRepository">A <see cref="NewRepository"/> instance describing the new repository to create</param>
/// <returns>An <see cref="IObservable{Repository}"/> instance for the created repository</returns>
public IObservable<Repository> Create(NewRepository newRepository)
{
Ensure.ArgumentNotNull(newRepository, "newRepository");
if (string.IsNullOrEmpty(newRepository.Name))
throw new ArgumentException("The new repository's name must not be null.");
return _client.Create(newRepository).ToObservable();
}
public IObservable<Repository> Get(string owner, string name)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
@@ -6,6 +6,13 @@ namespace Octokit.Reactive
{
public interface IObservableRepositoriesClient
{
/// <summary>
/// Creates a new repository for the current user.
/// </summary>
/// <param name="newRepository">A <see cref="NewRepository"/> instance describing the new repository to create</param>
/// <returns>An <see cref="IObservable{Repository}"/> instance for the created repository</returns>
IObservable<Repository> Create(NewRepository newRepository);
/// <summary>
/// Retrieves the <see cref="Repository"/> for the specified owner and name.
/// </summary>
@@ -57,5 +57,15 @@ namespace Octokit.Tests.Integration
/// Username of a GitHub test account (DO NOT USE A "REAL" ACCOUNT).
/// </summary>
public string GitHubUsername { get; private set; }
/// <summary>
/// Makes a name with an appended timestamp so that it's safe for testing (i.e., won't collide with existing names).
/// </summary>
/// <param name="name">The name to use as a base, to which a timestamp will be appended</param>
/// <returns>The name with a timestamp appended</returns>
public static string MakeNameWithTimestamp(string name)
{
return string.Concat(name, "-", DateTime.UtcNow.ToString("yyyyMMddhhmmssfff"));
}
}
}
@@ -1,4 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xunit;
@@ -6,6 +7,199 @@ namespace Octokit.Tests.Integration
{
public class RepositoriesClientTests
{
public class TheCreateMethod
{
[IntegrationTest]
public async Task CreatesANewPublicRepository()
{
var github = new GitHubClient("Test Runner User Agent")
{
Credentials = AutomationSettings.Current.GitHubCredentials
};
var repoName = AutomationSettings.MakeNameWithTimestamp("public-repo");
var createdRepository = await github.Repository.Create(new NewRepository { Name = repoName });
var cloneUrl = string.Format("https://github.com/{0}/{1}.git", github.Credentials.Login, repoName);
Assert.Equal(repoName, createdRepository.Name);
Assert.False(createdRepository.Private);
Assert.Equal(cloneUrl, createdRepository.CloneUrl);
var repository = await github.Repository.Get(github.Credentials.Login, repoName);
Assert.Equal(repoName, repository.Name);
Assert.Null(repository.Description);
Assert.False(repository.Private);
Assert.True(repository.HasDownloads);
Assert.True(repository.HasIssues);
Assert.True(repository.HasWiki);
Assert.Null(repository.Homepage);
}
[IntegrationTest]
public async Task CreatesANewPrivateRepository()
{
var github = new GitHubClient("Test Runner User Agent")
{
Credentials = AutomationSettings.Current.GitHubCredentials
};
var repoName = AutomationSettings.MakeNameWithTimestamp("private-repo");
var createdRepository = await github.Repository.Create(new NewRepository
{
Name = repoName,
Private = true
});
Assert.True(createdRepository.Private);
var repository = await github.Repository.Get(github.Credentials.Login, repoName);
Assert.True(repository.Private);
}
[IntegrationTest]
public async Task CreatesARepositoryWithoutDownloads()
{
var github = new GitHubClient("Test Runner User Agent")
{
Credentials = AutomationSettings.Current.GitHubCredentials
};
var repoName = AutomationSettings.MakeNameWithTimestamp("repo-without-downloads");
var createdRepository = await github.Repository.Create(new NewRepository
{
Name = repoName,
HasDownloads = false
});
Assert.False(createdRepository.HasDownloads);
var repository = await github.Repository.Get(github.Credentials.Login, repoName);
Assert.False(repository.HasDownloads);
}
[IntegrationTest]
public async Task CreatesARepositoryWithoutIssues()
{
var github = new GitHubClient("Test Runner User Agent")
{
Credentials = AutomationSettings.Current.GitHubCredentials
};
var repoName = AutomationSettings.MakeNameWithTimestamp("repo-without-issues");
var createdRepository = await github.Repository.Create(new NewRepository
{
Name = repoName,
HasIssues = false
});
Assert.False(createdRepository.HasIssues);
var repository = await github.Repository.Get(github.Credentials.Login, repoName);
Assert.False(repository.HasIssues);
}
[IntegrationTest]
public async Task CreatesARepositoryWithoutAWiki()
{
var github = new GitHubClient("Test Runner User Agent")
{
Credentials = AutomationSettings.Current.GitHubCredentials
};
var repoName = AutomationSettings.MakeNameWithTimestamp("repo-without-wiki");
var createdRepository = await github.Repository.Create(new NewRepository
{
Name = repoName,
HasWiki = false
});
Assert.False(createdRepository.HasWiki);
var repository = await github.Repository.Get(github.Credentials.Login, repoName);
Assert.False(repository.HasWiki);
}
[IntegrationTest]
public async Task CreatesARepositoryWithADescription()
{
var github = new GitHubClient("Test Runner User Agent")
{
Credentials = AutomationSettings.Current.GitHubCredentials
};
var repoName = AutomationSettings.MakeNameWithTimestamp("repo-with-description");
var createdRepository = await github.Repository.Create(new NewRepository
{
Name = repoName,
Description = "theDescription"
});
Assert.Equal("theDescription", createdRepository.Description);
var repository = await github.Repository.Get(github.Credentials.Login, repoName);
Assert.Equal("theDescription", repository.Description);
}
[IntegrationTest]
public async Task CreatesARepositoryWithAHomepage()
{
var github = new GitHubClient("Test Runner User Agent")
{
Credentials = AutomationSettings.Current.GitHubCredentials
};
var repoName = AutomationSettings.MakeNameWithTimestamp("repo-with-homepage");
var createdRepository = await github.Repository.Create(new NewRepository
{
Name = repoName,
Homepage = "http://aUrl.to/nowhere"
});
Assert.Equal("http://aUrl.to/nowhere", createdRepository.Homepage);
var repository = await github.Repository.Get(github.Credentials.Login, repoName);
Assert.Equal("http://aUrl.to/nowhere", repository.Homepage);
}
[IntegrationTest]
public async Task CreatesARepositoryWithAutoInit()
{
var github = new GitHubClient("Test Runner User Agent")
{
Credentials = AutomationSettings.Current.GitHubCredentials
};
var repoName = AutomationSettings.MakeNameWithTimestamp("repo-with-autoinit");
var createdRepository = await github.Repository.Create(new NewRepository
{
Name = repoName,
AutoInit = true
});
// TODO: Once the contents API has been added, check the actual files in the created repo
Assert.Equal(repoName, createdRepository.Name);
var repository = await github.Repository.Get(github.Credentials.Login, repoName);
Assert.Equal(repoName, repository.Name);
}
[IntegrationTest]
public async Task CreatesARepositoryWithAGitignoreTemplate()
{
var github = new GitHubClient("Test Runner User Agent")
{
Credentials = AutomationSettings.Current.GitHubCredentials
};
var repoName = AutomationSettings.MakeNameWithTimestamp("repo-with-gitignore");
var createdRepository = await github.Repository.Create(new NewRepository
{
Name = repoName,
AutoInit = true,
GitignoreTemplate = "visualstudio"
});
// TODO: Once the contents API has been added, check the actual files in the created repo
Assert.Equal(repoName, createdRepository.Name);
var repository = await github.Repository.Get(github.Credentials.Login, repoName);
Assert.Equal(repoName, repository.Name);
}
// TODO: Add a test for the team_id param once an overload that takes an oranization is added
}
public class TheGetAsyncMethod
{
[IntegrationTest]
@@ -24,6 +24,41 @@ namespace Octokit.Tests.Clients
}
}
public class TheCreateMethod
{
[Fact]
public async Task EnsuresNonNullArguments()
{
var repositoriesClient = new RepositoriesClient(Substitute.For<IApiConnection<Repository>>());
await AssertEx.Throws<ArgumentNullException>(async () => await repositoriesClient.Create(null));
await AssertEx.Throws<ArgumentException>(async () => await repositoriesClient.Create(new NewRepository { Name = null }));
}
[Fact]
public void UsesTheUserReposUrl()
{
var client = Substitute.For<IApiConnection<Repository>>();
var repositoriesClient = new RepositoriesClient(client);
repositoriesClient.Create(new NewRepository { Name = "aName" });
client.Received().Create(Arg.Is<Uri>(u => u.ToString() == "user/repos"), Arg.Any<NewRepository>());
}
[Fact]
public void TheNewRepositoryDescription()
{
var client = Substitute.For<IApiConnection<Repository>>();
var repositoriesClient = new RepositoriesClient(client);
var newRepository = new NewRepository { Name = "aName" };
repositoriesClient.Create(newRepository);
client.Received().Create(Arg.Any<Uri>(), newRepository);
}
}
public class TheGetMethod
{
[Fact]
+48 -1
View File
@@ -1,4 +1,5 @@
using Octokit.Http;
using System;
using Octokit.Http;
using Xunit;
namespace Octokit.Tests
@@ -16,6 +17,52 @@ namespace Octokit.Tests
Assert.Equal("{\"id\":42,\"first_name\":\"Phil\",\"is_something\":true,\"private\":true}", json);
}
[Fact]
public void OmitsPropertiesWithNullValue()
{
var item = new
{
Object = (object)null,
NullableInt = (int?)null,
NullableBool = (bool?)null
};
var json = new SimpleJsonSerializer().Serialize(item);
Assert.Equal("{}", json);
}
[Fact]
public void DoesNotOmitsNullablePropertiesWithAValue()
{
var item = new
{
Object = new { Id = 42 },
NullableInt = (int?)1066,
NullableBool = (bool?)true
};
var json = new SimpleJsonSerializer().Serialize(item);
Assert.Equal("{\"object\":{\"id\":42},\"nullable_int\":1066,\"nullable_bool\":true}", json);
}
[Fact]
public void HandlesMixingNullAndNotNullData()
{
var item = new
{
Int = 42,
Bool = true,
NullableInt = (int?)null,
NullableBool = (bool?)null
};
var json = new SimpleJsonSerializer().Serialize(item);
Assert.Equal("{\"int\":42,\"bool\":true}", json);
}
}
public class TheDeserializeMethod
+15
View File
@@ -11,6 +11,21 @@ namespace Octokit.Clients
{
}
/// <summary>
/// Creates a new repository for the current user.
/// </summary>
/// <param name="newRepository">A <see cref="NewRepository"/> instance describing the new repository to create</param>
/// <returns>A <see cref="Repository"/> instance for the created repository</returns>
public async Task<Repository> Create(NewRepository newRepository)
{
Ensure.ArgumentNotNull(newRepository, "newRepository");
if (string.IsNullOrEmpty(newRepository.Name))
throw new ArgumentException("The new repository's name must not be null.");
var endpoint = new Uri("user/repos", UriKind.Relative);
return await Client.Create(endpoint, newRepository);
}
public async Task<Repository> Get(string owner, string name)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
+57
View File
@@ -530,4 +530,61 @@ namespace Octokit
public string Resource { get; set; }
}
/// <summary>
/// Describes a new repository to create via the <see cref="IRepositoriesClient.Create"/> method.
/// </summary>
public class NewRepository
{
/// <summary>
/// Optional. Gets or sets whether to create an initial commit with empty README. The default is false.
/// </summary>
public bool? AutoInit { get; set; }
/// <summary>
/// Required. Gets or sets the new repository's description
/// </summary>
public string Description { get; set; }
/// <summary>s
/// Optional. Gets or sets whether to the enable downloads for the new repository. The default is true.
/// </summary>
public bool? HasDownloads { get; set; }
/// <summary>s
/// Optional. Gets or sets whether to the enable issues for the new repository. The default is true.
/// </summary>
public bool? HasIssues { get; set; }
/// <summary>s
/// Optional. Gets or sets whether to the enable the wiki for the new repository. The default is true.
/// </summary>
public bool? HasWiki { get; set; }
/// <summary>
/// Optional. Gets or sets the new repository's optional website.
/// </summary>
public string Homepage { get; set; }
/// <summary>
/// Optional. Gets or sets the desired language's or platform's .gitignore template to apply. Use the name of the template without the extension; "Haskell", for example. Ignored if <see cref="AutoInit"/> is null or false.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Gitignore", Justification = "It needs to be this way for proper serialization.")]
public string GitignoreTemplate { get; set; }
/// <summary>
/// Required. Gets or sets the new repository's name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Optional. Gets or sets whether the new repository is private; the default is false.
/// </summary>
public bool? Private { get; set; }
/// <summary>
/// Optional. Gets or sets the ID of the team to grant access to this repository. This is only valid when creating a repository for an organization.
/// </summary>
public int? TeamId { get; set; }
}
}
+33 -1
View File
@@ -1,4 +1,10 @@
namespace Octokit.Http
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Octokit.Reflection;
namespace Octokit.Http
{
public class SimpleJsonSerializer : IJsonSerializer
{
@@ -20,6 +26,32 @@
{
return clrPropertyName.ToRubyCase();
}
// This is overridden so that null values are omitted from serialized objects.
[SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")]
protected override bool TrySerializeUnknownTypes(object input, out object output)
{
if (input == null) throw new ArgumentNullException("input");
output = null;
Type type = input.GetType();
if (type.FullName == null)
return false;
IDictionary<string, object> obj = new JsonObject();
IDictionary<string, ReflectionUtils.GetDelegate> getters = GetCache[type];
foreach (KeyValuePair<string, ReflectionUtils.GetDelegate> getter in getters)
{
if (getter.Value != null)
{
var value = getter.Value(input);
if (value == null)
continue;
obj.Add(MapClrMemberNameToJsonFieldName(getter.Key), value);
}
}
output = obj;
return true;
}
}
}
}
+7
View File
@@ -7,6 +7,13 @@ namespace Octokit
{
public interface IRepositoriesClient
{
/// <summary>
/// Creates a new repository for the current user.
/// </summary>
/// <param name="newRepository">A <see cref="NewRepository"/> instance describing the new repository to create</param>
/// <returns>A <see cref="Repository"/> instance for the created repository</returns>
Task<Repository> Create(NewRepository newRepository);
/// <summary>
/// Retrieves the <see cref="Repository"/> for the specified owner and name.
/// </summary>
+1
View File
@@ -72,6 +72,7 @@
<Reference Include="System.Runtime">
<HintPath>..\packages\Microsoft.Bcl.1.1.3\lib\net40\System.Runtime.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Threading.Tasks">
<HintPath>..\packages\Microsoft.Bcl.1.1.3\lib\net40\System.Threading.Tasks.dll</HintPath>
</Reference>