diff --git a/Octokit.Tests.Integration/Clients/UserKeysClientTests.cs b/Octokit.Tests.Integration/Clients/UserKeysClientTests.cs index fc2e83ab..29d3d90d 100644 --- a/Octokit.Tests.Integration/Clients/UserKeysClientTests.cs +++ b/Octokit.Tests.Integration/Clients/UserKeysClientTests.cs @@ -1,27 +1,32 @@ -using System.Threading.Tasks; +using System.Linq; +using System.Threading.Tasks; using Xunit; +using Octokit.Tests.Integration.Helpers; namespace Octokit.Tests.Integration.Clients { public class UserKeysClientTests { - [IntegrationTest(Skip = "see https://github.com/octokit/octokit.net/issues/533 for the resolution to this failing test")] - public async Task GetAll() + [IntegrationTest] + public async Task CanGetAllForCurrentUser() { var github = Helper.GetAuthenticatedClient(); - var keys = await github.User.Keys.GetAll(); - Assert.NotEmpty(keys); + using (var context = await github.CreatePublicKeyContext()) + { + var keys = await github.User.Keys.GetAll(); + Assert.NotEmpty(keys); - var first = keys[0]; - Assert.NotNull(first.Id); - Assert.NotNull(first.Key); - Assert.NotNull(first.Title); - Assert.NotNull(first.Url); + var first = keys[0]; + Assert.NotNull(first.Id); + Assert.NotNull(first.Key); + Assert.NotNull(first.Title); + Assert.NotNull(first.Url); + } } [IntegrationTest] - public async Task GetAllForGivenUser() + public async Task CanGetAllForGivenUser() { var github = Helper.GetAuthenticatedClient(); @@ -34,5 +39,41 @@ namespace Octokit.Tests.Integration.Clients Assert.Null(first.Title); Assert.Null(first.Url); } + + [IntegrationTest] + public async Task CanGetKeyById() + { + var github = Helper.GetAuthenticatedClient(); + + using (var context = await github.CreatePublicKeyContext()) + { + var key = await github.User.Keys.Get(context.KeyId); + + Assert.Equal(key.Title, context.KeyTitle); + Assert.Equal(key.Key, context.KeyData); + } + } + + [IntegrationTest] + public async Task CanCreateAndDeleteKey() + { + // Create a key + string keyTitle = "title"; + string keyData = "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAjo4DqFKg8dOxiz/yjypmN1A4itU5QOStyYrfOFuTinesU/2zm9hqxJ5BctIhgtSHJ5foxkhsiBji0qrUg73Q25BThgNg8YFE8njr4EwjmqSqW13akx/zLV0GFFU0SdJ2F6rBldhi93lMnl0ex9swBqa3eLTY8C+HQGBI6MQUMw+BKp0oFkz87Kv+Pfp6lt/Uo32ejSxML1PT5hTH5n+fyl0ied+sRmPGZWmWoHB5Bc9mox7lB6I6A/ZgjtBqbEEn4HQ2/6vp4ojKfSgA4Mm7XMu0bZzX0itKjH1QWD9Lr5apV1cmZsj49Xf8SHucTtH+bq98hb8OOXEGFzplwsX2MQ=="; + var github = Helper.GetAuthenticatedClient(); + + var key = await github.User.Keys.Create(new NewPublicKey(keyTitle, keyData)); + + Assert.NotNull(key); + Assert.Equal(key.Title, "title"); + Assert.Equal(key.Key, keyData); + + // Delete key + await github.User.Keys.Delete(key.Id); + + // Verify key no longer exists + var keys = await github.User.Keys.GetAll(); + Assert.False(keys.Any(k => k.Title == keyTitle && k.Key == keyData)); + } } } diff --git a/Octokit.Tests.Integration/Helper.cs b/Octokit.Tests.Integration/Helper.cs index efef287f..52eaeb8c 100644 --- a/Octokit.Tests.Integration/Helper.cs +++ b/Octokit.Tests.Integration/Helper.cs @@ -126,6 +126,22 @@ namespace Octokit.Tests.Integration catch { } } + public static void DeleteKey(PublicKey key) + { + if (key != null) + DeleteKey(key.Id); + } + + public static void DeleteKey(int keyId) + { + var api = GetAuthenticatedClient(); + try + { + api.User.Keys.Delete(keyId).Wait(TimeSpan.FromSeconds(15)); + } + catch { } + } + public static string MakeNameWithTimestamp(string name) { return string.Concat(name, "-", DateTime.UtcNow.ToString("yyyyMMddhhmmssfff")); diff --git a/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs b/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs index e262aba0..95de0794 100644 --- a/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs +++ b/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs @@ -43,5 +43,16 @@ namespace Octokit.Tests.Integration.Helpers return new EnterpriseUserContext(user); } + + internal async static Task CreatePublicKeyContext(this IGitHubClient client) + { + // Create a key + string keyTitle = "title"; + string keyData = "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAjo4DqFKg8dOxiz/yjypmN1A4itU5QOStyYrfOFuTinesU/2zm9hqxJ5BctIhgtSHJ5foxkhsiBji0qrUg73Q25BThgNg8YFE8njr4EwjmqSqW13akx/zLV0GFFU0SdJ2F6rBldhi93lMnl0ex9swBqa3eLTY8C+HQGBI6MQUMw+BKp0oFkz87Kv+Pfp6lt/Uo32ejSxML1PT5hTH5n+fyl0ied+sRmPGZWmWoHB5Bc9mox7lB6I6A/ZgjtBqbEEn4HQ2/6vp4ojKfSgA4Mm7XMu0bZzX0itKjH1QWD9Lr5apV1cmZsj49Xf8SHucTtH+bq98hb8OOXEGFzplwsX2MQ=="; + + var key = await client.User.Keys.Create(new NewPublicKey(keyTitle, keyData)); + + return new PublicKeyContext(key); + } } } \ No newline at end of file diff --git a/Octokit.Tests.Integration/Helpers/PublicKeyContext.cs b/Octokit.Tests.Integration/Helpers/PublicKeyContext.cs new file mode 100644 index 00000000..30fbaac7 --- /dev/null +++ b/Octokit.Tests.Integration/Helpers/PublicKeyContext.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Octokit.Tests.Integration.Helpers +{ + internal sealed class PublicKeyContext : IDisposable + { + internal PublicKeyContext(PublicKey key) + { + Key = key; + KeyId = key.Id; + KeyTitle = key.Title; + KeyData = key.Key; + } + + internal int KeyId { get; private set; } + internal string KeyTitle { get; private set; } + internal string KeyData { get; private set; } + + internal PublicKey Key { get; private set; } + + public void Dispose() + { + Helper.DeleteKey(Key); + } + } +} diff --git a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj index 3900738a..84358320 100644 --- a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj +++ b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj @@ -113,6 +113,7 @@ + diff --git a/Octokit.Tests/Clients/UserKeysClientTests.cs b/Octokit.Tests/Clients/UserKeysClientTests.cs new file mode 100644 index 00000000..90d60f1d --- /dev/null +++ b/Octokit.Tests/Clients/UserKeysClientTests.cs @@ -0,0 +1,139 @@ +using NSubstitute; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Octokit.Tests.Clients +{ + public class UserKeysClientTests + { + public class TheGetAllMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new UserKeysClient(connection); + + var expectedUri = "user/keys"; + client.GetAll(); + + connection.Received().GetAll( + Arg.Is(u => u.ToString() == expectedUri)); + } + } + + public class TheGetAllForUserMethod + { + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new UserKeysClient(Substitute.For()); + await Assert.ThrowsAsync(() => client.GetAll(null)); + } + + [Fact] + public async Task EnsuresNonEmptyString() + { + var client = new UserKeysClient(Substitute.For()); + var exception = await Assert.ThrowsAsync(() => client.GetAll("")); + Assert.Equal("userName", exception.ParamName); + } + + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new UserKeysClient(connection); + + var expectedUri = "users/auser/keys"; + client.GetAll("auser"); + + connection.Received().GetAll( + Arg.Is(u => u.ToString() == expectedUri)); + } + } + + public class TheGetMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new UserKeysClient(connection); + + var expectedUri = "user/keys/1"; + client.Get(1); + + connection.Received().Get( + Arg.Is(u => u.ToString() == expectedUri)); + } + } + + public class TheCreateMethod + { + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new UserKeysClient(Substitute.For()); + await Assert.ThrowsAsync(() => client.Create(null)); + } + + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new UserKeysClient(connection); + + var expectedUri = "user/keys"; + client.Create(new NewPublicKey("title", "ABCDEFG")); + + connection.Received().Post( + Arg.Is(u => u.ToString() == expectedUri), + Arg.Any()); + } + + [Fact] + public void PassesRequestObject() + { + var connection = Substitute.For(); + var client = new UserKeysClient(connection); + + client.Create(new NewPublicKey("title", "ABCDEFG")); + + connection.Received().Post( + Arg.Any(), + Arg.Is(a => + a.Title == "title" && + a.Key == "ABCDEFG")); + } + } + + public class TheDeleteMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new UserKeysClient(connection); + + var expectedUri = "user/keys/1"; + client.Delete(1); + + connection.Received().Delete( + Arg.Is(u => u.ToString() == expectedUri)); + } + } + + public class TheCtor + { + [Fact] + public void EnsuresArguments() + { + Assert.Throws( + () => new UserEmailsClient(null)); + } + } + } +} diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index cca0f7ae..6cf9e013 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -129,6 +129,7 @@ + diff --git a/Octokit/Clients/IUserKeysClient.cs b/Octokit/Clients/IUserKeysClient.cs index d04c9546..226778b6 100644 --- a/Octokit/Clients/IUserKeysClient.cs +++ b/Octokit/Clients/IUserKeysClient.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; namespace Octokit @@ -17,7 +18,7 @@ namespace Octokit /// /// https://developer.github.com/v3/users/keys/#list-your-public-keys /// - /// The s for the authenticated user. + /// Task> GetAll(); /// @@ -26,7 +27,38 @@ namespace Octokit /// /// https://developer.github.com/v3/users/keys/#list-public-keys-for-a-user /// - /// The s for the user. + /// Task> GetAll(string userName); + + /// + /// Retrieves the for the specified id. + /// + /// + /// https://developer.github.com/v3/users/keys/#get-a-single-public-key + /// + /// The ID of the SSH key + /// + [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get")] + Task Get(int id); + + /// + /// Create a public key . + /// + /// + /// https://developer.github.com/v3/users/keys/#create-a-public-key + /// + /// The SSH Key contents + /// + Task Create(NewPublicKey newKey); + + /// + /// Delete a public key. + /// + /// + /// https://developer.github.com/v3/users/keys/#delete-a-public-key + /// + /// The id of the key to delete + /// + Task Delete(int id); } } diff --git a/Octokit/Clients/UserKeysClient.cs b/Octokit/Clients/UserKeysClient.cs index 29cc3fb7..012b32ae 100644 --- a/Octokit/Clients/UserKeysClient.cs +++ b/Octokit/Clients/UserKeysClient.cs @@ -22,7 +22,7 @@ namespace Octokit /// /// https://developer.github.com/v3/users/keys/#list-your-public-keys /// - /// The s for the authenticated user. + /// public Task> GetAll() { return ApiConnection.GetAll(ApiUrls.Keys()); @@ -34,10 +34,53 @@ namespace Octokit /// /// https://developer.github.com/v3/users/keys/#list-public-keys-for-a-user /// - /// The s for the user. + /// public Task> GetAll(string userName) { + Ensure.ArgumentNotNullOrEmptyString(userName, "userName"); + return ApiConnection.GetAll(ApiUrls.Keys(userName)); } + + /// + /// Retrieves the for the specified id. + /// + /// + /// https://developer.github.com/v3/users/keys/#get-a-single-public-key + /// + /// The ID of the SSH key + /// + public Task Get(int id) + { + return ApiConnection.Get(ApiUrls.Keys(id)); + } + + /// + /// Create a public key . + /// + /// + /// https://developer.github.com/v3/users/keys/#create-a-public-key + /// + /// The SSH Key contents + /// + public Task Create(NewPublicKey newKey) + { + Ensure.ArgumentNotNull(newKey, "newKey"); + + return ApiConnection.Post(ApiUrls.Keys(), newKey); + } + + /// + /// Delete a public key. + /// + /// + /// https://developer.github.com/v3/users/keys/#delete-a-public-key + /// + /// The id of the key to delete + /// + public Task Delete(int id) + { + return ApiConnection.Delete(ApiUrls.Keys(id)); + } } } \ No newline at end of file diff --git a/Octokit/Helpers/ApiUrls.Keys.cs b/Octokit/Helpers/ApiUrls.Keys.cs deleted file mode 100644 index 327f11ef..00000000 --- a/Octokit/Helpers/ApiUrls.Keys.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace Octokit -{ - public static partial class ApiUrls - { - static readonly Uri _currentUserKeysUrl = new Uri("user/keys", UriKind.Relative); - - /// - /// Returns the to retrieve keys for the current user. - /// - public static Uri Keys() - { - return _currentUserKeysUrl; - } - - /// - /// Returns the to retrieve keys for a given user. - /// - /// The user to search on - public static Uri Keys(string userName) - { - return "users/{0}/keys".FormatUri(userName); - } - } -} diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index 1f067602..bb34ce46 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -107,6 +107,32 @@ namespace Octokit return "users/{0}/keys".FormatUri(login); } + /// + /// Returns the to retrieve keys for the current user. + /// + public static Uri Keys() + { + return "user/keys".FormatUri(); + } + + /// + /// Returns the to retrieve keys for a given user. + /// + /// The user to search on + public static Uri Keys(string userName) + { + return "users/{0}/keys".FormatUri(userName); + } + + /// + /// Returns the to retrieve a given key. + /// + /// The Key Id to retrieve + public static Uri Keys(int id) + { + return "user/keys/{0}".FormatUri(id); + } + /// /// Returns the that returns all of the email addresses for the currently logged in user. /// diff --git a/Octokit/Models/Request/NewPublicKey.cs b/Octokit/Models/Request/NewPublicKey.cs new file mode 100644 index 00000000..eac92c4a --- /dev/null +++ b/Octokit/Models/Request/NewPublicKey.cs @@ -0,0 +1,43 @@ +using System; +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + /// + /// Used to create a public SSH key + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class NewPublicKey + { + public NewPublicKey() + { } + + public NewPublicKey(string title, string key) + { + Ensure.ArgumentNotNullOrEmptyString(title, "title"); + Ensure.ArgumentNotNullOrEmptyString(key, "key"); + + Title = title; + Key = key; + } + + /// + /// The title of the key + /// + public string Title { get; set; } + + /// + /// The Key data + /// + public string Key { get; set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "Title: {0} Key: {1}", Title, Key); + } + } + } +} \ No newline at end of file diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj index 1adf178b..eca26aea 100644 --- a/Octokit/Octokit-Mono.csproj +++ b/Octokit/Octokit-Mono.csproj @@ -371,7 +371,6 @@ - @@ -456,6 +455,7 @@ + \ No newline at end of file diff --git a/Octokit/Octokit-MonoAndroid.csproj b/Octokit/Octokit-MonoAndroid.csproj index 96b6ba41..5b72f2a8 100644 --- a/Octokit/Octokit-MonoAndroid.csproj +++ b/Octokit/Octokit-MonoAndroid.csproj @@ -464,6 +464,7 @@ + \ No newline at end of file diff --git a/Octokit/Octokit-Monotouch.csproj b/Octokit/Octokit-Monotouch.csproj index 491e5cbb..f02ec307 100644 --- a/Octokit/Octokit-Monotouch.csproj +++ b/Octokit/Octokit-Monotouch.csproj @@ -460,6 +460,7 @@ + diff --git a/Octokit/Octokit-Portable.csproj b/Octokit/Octokit-Portable.csproj index f378486f..e4d48b60 100644 --- a/Octokit/Octokit-Portable.csproj +++ b/Octokit/Octokit-Portable.csproj @@ -368,7 +368,6 @@ - @@ -453,6 +452,7 @@ + diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj index 67015b13..da554e94 100644 --- a/Octokit/Octokit-netcore45.csproj +++ b/Octokit/Octokit-netcore45.csproj @@ -373,7 +373,6 @@ - @@ -460,6 +459,7 @@ + diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index 0109e67a..b30d6435 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -101,7 +101,6 @@ - @@ -119,6 +118,7 @@ +