From 56406646e35203f8ec1c38729375841651bf036d Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Wed, 2 Oct 2013 10:01:14 -0400 Subject: [PATCH 01/14] Get all releases for a repository. --- Octokit.Tests/Clients/ReleasesClientTests.cs | 40 ++++++++++++++++++++ Octokit.Tests/Octokit.Tests.csproj | 1 + Octokit/Clients/ReleasesClient.cs | 21 ++++++++++ Octokit/GitHubModels.cs | 18 +++++++++ Octokit/IReleasesClient.cs | 9 +++++ Octokit/Octokit.csproj | 2 + 6 files changed, 91 insertions(+) create mode 100644 Octokit.Tests/Clients/ReleasesClientTests.cs create mode 100644 Octokit/Clients/ReleasesClient.cs create mode 100644 Octokit/IReleasesClient.cs diff --git a/Octokit.Tests/Clients/ReleasesClientTests.cs b/Octokit.Tests/Clients/ReleasesClientTests.cs new file mode 100644 index 00000000..fed75bd9 --- /dev/null +++ b/Octokit.Tests/Clients/ReleasesClientTests.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; +using NSubstitute; +using Octokit.Clients; +using Octokit.Http; +using Octokit.Tests.Helpers; +using Xunit; + +namespace Octokit.Tests.Clients +{ + /// + /// Client tests mostly just need to make sure they call the IApiConnection with the correct + /// relative Uri. No need to fake up the response. All *those* tests are in ApiConnectionTests.cs. + /// + public class ReleasesClientTests + { + public class TheGetAllMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var client = Substitute.For>(); + var repositoriesClient = new ReleasesClient(client); + + repositoriesClient.GetAll("fake", "repo"); + + client.Received().GetAll(Arg.Is(u => u.ToString() == "/repos/fake/repo/releases"), null); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var repositoriesClient = new ReleasesClient(Substitute.For>()); + + await AssertEx.Throws(async () => await repositoriesClient.GetAll(null, "name")); + await AssertEx.Throws(async () => await repositoriesClient.GetAll("owner", null)); + } + } + } +} diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index 1a2d582e..e76e24c1 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -51,6 +51,7 @@ + diff --git a/Octokit/Clients/ReleasesClient.cs b/Octokit/Clients/ReleasesClient.cs new file mode 100644 index 00000000..1ee99993 --- /dev/null +++ b/Octokit/Clients/ReleasesClient.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using Octokit.Http; + +namespace Octokit.Clients +{ + public class ReleasesClient : ApiClient, IReleasesClient + { + public ReleasesClient(IApiConnection client) : base(client) + { + } + + public async Task> GetAll(string owner, string repository) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(repository, "repository"); + + var endpoint = "/repos/{0}/{1}/releases".FormatUri(owner, repository); + return await Client.GetAll(endpoint); + } + } +} diff --git a/Octokit/GitHubModels.cs b/Octokit/GitHubModels.cs index 8d0895de..4ac44070 100644 --- a/Octokit/GitHubModels.cs +++ b/Octokit/GitHubModels.cs @@ -454,6 +454,24 @@ namespace Octokit public bool Primary { get; set; } } + public class Release + { + public string Url { get; set; } + public string HtmlUrl { get; set; } + public string AssetsUrl { get; set; } + public string UploadUrl { get; set; } + public int Id { get; set; } + public string TagName { get; set; } + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Commitish", Justification = "Spelling is correct")] + public string TargetCommitish { get; set; } + public string Name { get; set; } + public string Body { get; set; } + public bool Draft { get; set; } + public bool Prerelease { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset PublishedAt { get; set; } + } + public class ApiError { public string Message { get; set; } diff --git a/Octokit/IReleasesClient.cs b/Octokit/IReleasesClient.cs new file mode 100644 index 00000000..88510a2f --- /dev/null +++ b/Octokit/IReleasesClient.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Octokit +{ + public interface IReleasesClient + { + Task> GetAll(string owner, string repository); + } +} diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index dd4104b5..43984922 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -83,6 +83,7 @@ + @@ -106,6 +107,7 @@ + From 903fb84d97658dd625f38d014e4c9c10a337f1f0 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Wed, 2 Oct 2013 10:36:40 -0400 Subject: [PATCH 02/14] Move Releases API to RepositoriesClient. Releases are grouped under Repositories on developer.github.com. --- .../RepositoriesClientTests.cs | 18 +++++++++ Octokit.Tests/Clients/ReleasesClientTests.cs | 40 ------------------- .../Clients/RepositoriesClientTests.cs | 23 +++++++++++ Octokit.Tests/Octokit.Tests.csproj | 1 - Octokit/ApiExtensions.cs | 8 ++++ Octokit/Clients/ApiPagination.cs | 13 ++++++ Octokit/Clients/ReleasesClient.cs | 21 ---------- Octokit/Clients/RepositoriesClient.cs | 9 +++++ Octokit/Http/ApiConnection.cs | 15 +++++++ Octokit/Http/IApiConnection.cs | 1 + Octokit/IApiPagination.cs | 1 + Octokit/IReleasesClient.cs | 9 ----- Octokit/IRepositoriesClient.cs | 8 ++++ Octokit/Octokit.csproj | 2 - 14 files changed, 96 insertions(+), 73 deletions(-) delete mode 100644 Octokit.Tests/Clients/ReleasesClientTests.cs delete mode 100644 Octokit/Clients/ReleasesClient.cs delete mode 100644 Octokit/IReleasesClient.cs diff --git a/Octokit.Tests.Integration/RepositoriesClientTests.cs b/Octokit.Tests.Integration/RepositoriesClientTests.cs index 3b55a09c..b599e9d0 100644 --- a/Octokit.Tests.Integration/RepositoriesClientTests.cs +++ b/Octokit.Tests.Integration/RepositoriesClientTests.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Xunit; @@ -89,5 +90,22 @@ namespace Octokit.Tests.Integration Assert.Contains("

WARNING: This is some haacky code.", readMeHtml); } } + + public class TheGetReleasesMethod + { + [IntegrationTest] + public async Task ReturnsReleases() + { + var github = new GitHubClient + { + Credentials = AutomationSettings.Current.GitHubCredentials + }; + + var releases = await github.Repository.GetReleases("git-tfs", "git-tfs"); + + Assert.True(releases.Count > 5); + Assert.True(releases.Any(release => release.TagName == "v0.18.0")); + } + } } } diff --git a/Octokit.Tests/Clients/ReleasesClientTests.cs b/Octokit.Tests/Clients/ReleasesClientTests.cs deleted file mode 100644 index fed75bd9..00000000 --- a/Octokit.Tests/Clients/ReleasesClientTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Threading.Tasks; -using NSubstitute; -using Octokit.Clients; -using Octokit.Http; -using Octokit.Tests.Helpers; -using Xunit; - -namespace Octokit.Tests.Clients -{ - ///

- /// Client tests mostly just need to make sure they call the IApiConnection with the correct - /// relative Uri. No need to fake up the response. All *those* tests are in ApiConnectionTests.cs. - /// - public class ReleasesClientTests - { - public class TheGetAllMethod - { - [Fact] - public void RequestsCorrectUrl() - { - var client = Substitute.For>(); - var repositoriesClient = new ReleasesClient(client); - - repositoriesClient.GetAll("fake", "repo"); - - client.Received().GetAll(Arg.Is(u => u.ToString() == "/repos/fake/repo/releases"), null); - } - - [Fact] - public async Task EnsuresNonNullArguments() - { - var repositoriesClient = new ReleasesClient(Substitute.For>()); - - await AssertEx.Throws(async () => await repositoriesClient.GetAll(null, "name")); - await AssertEx.Throws(async () => await repositoriesClient.GetAll("owner", null)); - } - } - } -} diff --git a/Octokit.Tests/Clients/RepositoriesClientTests.cs b/Octokit.Tests/Clients/RepositoriesClientTests.cs index 08a0ec5b..37a1da54 100644 --- a/Octokit.Tests/Clients/RepositoriesClientTests.cs +++ b/Octokit.Tests/Clients/RepositoriesClientTests.cs @@ -139,5 +139,28 @@ namespace Octokit.Tests.Clients client.Received().GetHtml(Arg.Is(u => u.ToString() == "https://github.example.com/readme"), null); } } + + public class TheGetReleasesMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var client = Substitute.For>(); + var repositoriesClient = new RepositoriesClient(client); + + repositoriesClient.GetReleases("fake", "repo"); + + client.Received().GetAll(Arg.Is(u => u.ToString() == "/repos/fake/repo/releases"), null); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var repositoriesClient = new RepositoriesClient(Substitute.For>()); + + await AssertEx.Throws(async () => await repositoriesClient.GetReleases(null, "name")); + await AssertEx.Throws(async () => await repositoriesClient.GetReleases("owner", null)); + } + } } } diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index e76e24c1..1a2d582e 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -51,7 +51,6 @@
- diff --git a/Octokit/ApiExtensions.cs b/Octokit/ApiExtensions.cs index d813a203..58a882ef 100644 --- a/Octokit/ApiExtensions.cs +++ b/Octokit/ApiExtensions.cs @@ -26,6 +26,14 @@ namespace Octokit return connection.GetAll(endpoint, null); } + public static Task> GetAll(this IApiConnection connection, Uri endpoint) + { + Ensure.ArgumentNotNull(connection, "connection"); + Ensure.ArgumentNotNull(endpoint, "endpoint"); + + return connection.GetAll(endpoint, null); + } + public static Task GetHtml(this IApiConnection connection, Uri endpoint) { Ensure.ArgumentNotNull(connection, "connection"); diff --git a/Octokit/Clients/ApiPagination.cs b/Octokit/Clients/ApiPagination.cs index 997d9a13..ff5e0a8d 100644 --- a/Octokit/Clients/ApiPagination.cs +++ b/Octokit/Clients/ApiPagination.cs @@ -26,5 +26,18 @@ namespace Octokit.Clients } return new ReadOnlyCollection(allItems); } + + public async Task> GetAllPages(Func>> getFirstPage) + { + Ensure.ArgumentNotNull(getFirstPage, "getFirstPage"); + + var page = await getFirstPage(); + var allItems = new List(page); + while ((page = await page.GetNextPage()) != null) + { + allItems.AddRange(page); + } + return new ReadOnlyCollection(allItems); + } } } diff --git a/Octokit/Clients/ReleasesClient.cs b/Octokit/Clients/ReleasesClient.cs deleted file mode 100644 index 1ee99993..00000000 --- a/Octokit/Clients/ReleasesClient.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading.Tasks; -using Octokit.Http; - -namespace Octokit.Clients -{ - public class ReleasesClient : ApiClient, IReleasesClient - { - public ReleasesClient(IApiConnection client) : base(client) - { - } - - public async Task> GetAll(string owner, string repository) - { - Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); - Ensure.ArgumentNotNullOrEmptyString(repository, "repository"); - - var endpoint = "/repos/{0}/{1}/releases".FormatUri(owner, repository); - return await Client.GetAll(endpoint); - } - } -} diff --git a/Octokit/Clients/RepositoriesClient.cs b/Octokit/Clients/RepositoriesClient.cs index e09e0dbb..d3d3f768 100644 --- a/Octokit/Clients/RepositoriesClient.cs +++ b/Octokit/Clients/RepositoriesClient.cs @@ -53,5 +53,14 @@ namespace Octokit.Clients var readmeInfo = await Client.GetItem(endpoint, null); return new Readme(readmeInfo, Client); } + + public async Task> GetReleases(string owner, string name) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "repository"); + + var endpoint = "/repos/{0}/{1}/releases".FormatUri(owner, name); + return await Client.GetAll(endpoint); + } } } diff --git a/Octokit/Http/ApiConnection.cs b/Octokit/Http/ApiConnection.cs index 3869f24e..637cc894 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -54,6 +54,13 @@ namespace Octokit.Http return await pagination.GetAllPages(async () => await GetPage(endpoint, parameters)); } + public async Task> GetAll(Uri endpoint, IDictionary parameters) + { + Ensure.ArgumentNotNull(endpoint, "endpoint"); + + return await pagination.GetAllPages(async () => await GetPage(endpoint, parameters)); + } + public async Task Create(Uri endpoint, object data) { Ensure.ArgumentNotNull(endpoint, "endpoint"); @@ -99,5 +106,13 @@ namespace Octokit.Http return new ReadOnlyPagedCollection(response, Connection); } + async Task> GetPage(Uri endpoint, IDictionary parameters) + { + Ensure.ArgumentNotNull(endpoint, "endpoint"); + + var response = await Connection.GetAsync>(endpoint, parameters); + return new ReadOnlyPagedCollection(response, Connection); + } + } } diff --git a/Octokit/Http/IApiConnection.cs b/Octokit/Http/IApiConnection.cs index 73e06011..ad3decb4 100644 --- a/Octokit/Http/IApiConnection.cs +++ b/Octokit/Http/IApiConnection.cs @@ -17,6 +17,7 @@ namespace Octokit.Http Task GetItem(Uri endpoint, IDictionary parameters); Task GetHtml(Uri endpoint, IDictionary parameters); Task> GetAll(Uri endpoint, IDictionary parameters); + Task> GetAll(Uri endpoint, IDictionary parameters); Task Create(Uri endpoint, object data); Task Update(Uri endpoint, object data); Task Delete(Uri endpoint); diff --git a/Octokit/IApiPagination.cs b/Octokit/IApiPagination.cs index a6aae0e7..b7986b71 100644 --- a/Octokit/IApiPagination.cs +++ b/Octokit/IApiPagination.cs @@ -7,5 +7,6 @@ namespace Octokit public interface IApiPagination { Task> GetAllPages(Func>> getFirstPage); + Task> GetAllPages(Func>> getFirstPage); } } \ No newline at end of file diff --git a/Octokit/IReleasesClient.cs b/Octokit/IReleasesClient.cs deleted file mode 100644 index 88510a2f..00000000 --- a/Octokit/IReleasesClient.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace Octokit -{ - public interface IReleasesClient - { - Task> GetAll(string owner, string repository); - } -} diff --git a/Octokit/IRepositoriesClient.cs b/Octokit/IRepositoriesClient.cs index 347e1ef8..efa70a67 100644 --- a/Octokit/IRepositoriesClient.cs +++ b/Octokit/IRepositoriesClient.cs @@ -56,5 +56,13 @@ namespace Octokit /// The name of the repository. /// Task GetReadme(string owner, string name); + + /// + /// Retrieves every for the specified repository. + /// + /// The owner of the repository. + /// The name of the reposiitory + /// A of . + Task> GetReleases(string owner, string name); } } diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index 43984922..dd4104b5 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -83,7 +83,6 @@ - @@ -107,7 +106,6 @@ - From 4cc62af306545ff6ec21c9813c125a39584ec0d1 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Wed, 2 Oct 2013 12:01:10 -0400 Subject: [PATCH 03/14] Get integration tests to work for releases. --- Octokit/Http/JsonHttpPipeline.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Octokit/Http/JsonHttpPipeline.cs b/Octokit/Http/JsonHttpPipeline.cs index ccc9ee0f..e42bd1b1 100644 --- a/Octokit/Http/JsonHttpPipeline.cs +++ b/Octokit/Http/JsonHttpPipeline.cs @@ -26,6 +26,8 @@ namespace Octokit.Http Ensure.ArgumentNotNull(request, "request"); request.Headers["Accept"] = "application/vnd.github.v3+json; charset=utf-8"; + if (request.Endpoint != null && request.Endpoint.ToString().Contains("releases")) + request.Headers["Accept"] = "application/vnd.github.manifold-preview; charset=utf-8"; if (request.Method == HttpMethod.Get || request.Body == null) return; if (request.Body is string) return; From 3ee008cfca92c64339f34876b4547b61e1f0577d Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Wed, 2 Oct 2013 12:32:17 -0400 Subject: [PATCH 04/14] Create a new release. --- .../Clients/RepositoriesClientTests.cs | 27 +++++++++++++++++++ Octokit/Clients/RepositoriesClient.cs | 11 ++++++++ Octokit/GitHubModels.cs | 19 ++++++++++++- Octokit/Http/ApiConnection.cs | 10 +++++++ Octokit/Http/IApiConnection.cs | 1 + Octokit/IRepositoriesClient.cs | 11 +++++++- 6 files changed, 77 insertions(+), 2 deletions(-) diff --git a/Octokit.Tests/Clients/RepositoriesClientTests.cs b/Octokit.Tests/Clients/RepositoriesClientTests.cs index 37a1da54..87379473 100644 --- a/Octokit.Tests/Clients/RepositoriesClientTests.cs +++ b/Octokit.Tests/Clients/RepositoriesClientTests.cs @@ -162,5 +162,32 @@ namespace Octokit.Tests.Clients await AssertEx.Throws(async () => await repositoriesClient.GetReleases("owner", null)); } } + + public class TheCreateReleaseMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var client = Substitute.For>(); + var repositoriesClient = new RepositoriesClient(client); + var data = new ReleaseUpdate("fake-tag"); + + repositoriesClient.CreateRelease("fake", "repo", data); + + client.Received().Create(Arg.Is(u => u.ToString() == "/repos/fake/repo/releases"), data); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var repositoriesClient = new RepositoriesClient(Substitute.For>()); + var data = new ReleaseUpdate("fake-tag"); + + Assert.Throws(() => new ReleaseUpdate(null)); + await AssertEx.Throws(async () => await repositoriesClient.CreateRelease(null, "name", data)); + await AssertEx.Throws(async () => await repositoriesClient.CreateRelease("owner", null, data)); + await AssertEx.Throws(async () => await repositoriesClient.CreateRelease("owner", "name", null)); + } + } } } diff --git a/Octokit/Clients/RepositoriesClient.cs b/Octokit/Clients/RepositoriesClient.cs index d3d3f768..a8353832 100644 --- a/Octokit/Clients/RepositoriesClient.cs +++ b/Octokit/Clients/RepositoriesClient.cs @@ -62,5 +62,16 @@ namespace Octokit.Clients var endpoint = "/repos/{0}/{1}/releases".FormatUri(owner, name); return await Client.GetAll(endpoint); } + + + public async Task CreateRelease(string owner, string name, ReleaseUpdate data) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "repository"); + Ensure.ArgumentNotNull(data, "data"); + + var endpoint = "/repos/{0}/{1}/releases".FormatUri(owner, name); + return await Client.Create(endpoint, data); + } } } diff --git a/Octokit/GitHubModels.cs b/Octokit/GitHubModels.cs index 4ac44070..29645349 100644 --- a/Octokit/GitHubModels.cs +++ b/Octokit/GitHubModels.cs @@ -462,7 +462,7 @@ namespace Octokit public string UploadUrl { get; set; } public int Id { get; set; } public string TagName { get; set; } - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Commitish", Justification = "Spelling is correct")] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Commitish")] public string TargetCommitish { get; set; } public string Name { get; set; } public string Body { get; set; } @@ -472,6 +472,23 @@ namespace Octokit public DateTimeOffset PublishedAt { get; set; } } + public class ReleaseUpdate + { + public ReleaseUpdate(string tagName) + { + Ensure.ArgumentNotNullOrEmptyString(tagName, "tagName"); + TagName = tagName; + } + + public string TagName { get; private set; } + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Commitish")] + public string TargetCommitish { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public bool Draft { get; set; } + public bool Prerelease { get; set; } + } + public class ApiError { public string Message { get; set; } diff --git a/Octokit/Http/ApiConnection.cs b/Octokit/Http/ApiConnection.cs index 637cc894..ff1f22dc 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -71,6 +71,16 @@ namespace Octokit.Http return response.BodyAsObject; } + public async Task Create(Uri endpoint, object data) + { + Ensure.ArgumentNotNull(endpoint, "endpoint"); + Ensure.ArgumentNotNull(data, "data"); + + var response = await Connection.PostAsync(endpoint, data); + + return response.BodyAsObject; + } + public async Task Update(Uri endpoint, object data) { Ensure.ArgumentNotNull(endpoint, "endpoint"); diff --git a/Octokit/Http/IApiConnection.cs b/Octokit/Http/IApiConnection.cs index ad3decb4..b4389ac9 100644 --- a/Octokit/Http/IApiConnection.cs +++ b/Octokit/Http/IApiConnection.cs @@ -19,6 +19,7 @@ namespace Octokit.Http Task> GetAll(Uri endpoint, IDictionary parameters); Task> GetAll(Uri endpoint, IDictionary parameters); Task Create(Uri endpoint, object data); + Task Create(Uri endpoint, object data); Task Update(Uri endpoint, object data); Task Delete(Uri endpoint); } diff --git a/Octokit/IRepositoriesClient.cs b/Octokit/IRepositoriesClient.cs index efa70a67..fd26ae56 100644 --- a/Octokit/IRepositoriesClient.cs +++ b/Octokit/IRepositoriesClient.cs @@ -61,8 +61,17 @@ namespace Octokit /// Retrieves every for the specified repository. /// /// The owner of the repository. - /// The name of the reposiitory + /// The name of the repository. /// A of . Task> GetReleases(string owner, string name); + + /// + /// Create a for the specified repository. + /// + /// The owner of the repository. + /// The name of the repository. + /// The data for the release. + /// A new . + Task CreateRelease(string owner, string name, ReleaseUpdate data); } } From 9a6e46d608ee4cab94ebcbe6889a27ecf9a8f39d Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Wed, 2 Oct 2013 13:14:18 -0400 Subject: [PATCH 05/14] Add fixtures for releases json. --- Octokit.Tests/Fixtures/Fixtures.cs | 9 + Octokit.Tests/Fixtures/release.json | 13 ++ Octokit.Tests/Fixtures/release_asset.json | 12 ++ Octokit.Tests/Fixtures/releases.json | 230 ++++++++++++++++++++++ Octokit.Tests/Octokit.Tests.csproj | 3 + 5 files changed, 267 insertions(+) create mode 100644 Octokit.Tests/Fixtures/release.json create mode 100644 Octokit.Tests/Fixtures/release_asset.json create mode 100644 Octokit.Tests/Fixtures/releases.json diff --git a/Octokit.Tests/Fixtures/Fixtures.cs b/Octokit.Tests/Fixtures/Fixtures.cs index b9bdac72..faf5497a 100644 --- a/Octokit.Tests/Fixtures/Fixtures.cs +++ b/Octokit.Tests/Fixtures/Fixtures.cs @@ -19,5 +19,14 @@ public static EmbeddedResource RepositoriesJson = new EmbeddedResource(typeof(Fixtures).Assembly, "Octokit.Tests.Fixtures.repositories.json"); + + public static EmbeddedResource ReleasesJson = + new EmbeddedResource(typeof(Fixtures).Assembly, "Octokit.Tests.Fixtures.releases.json"); + + public static EmbeddedResource ReleaseJson = + new EmbeddedResource(typeof(Fixtures).Assembly, "Octokit.Tests.Fixtures.release.json"); + + public static EmbeddedResource ReleaseAssetJson = + new EmbeddedResource(typeof(Fixtures).Assembly, "Octokit.Tests.Fixtures.release_asset.json"); } } diff --git a/Octokit.Tests/Fixtures/release.json b/Octokit.Tests/Fixtures/release.json new file mode 100644 index 00000000..78847fed --- /dev/null +++ b/Octokit.Tests/Fixtures/release.json @@ -0,0 +1,13 @@ +{"url": "https://api.github.com/repos/octocat/Hello-World/releases/1", + "html_url": "https://github.com/octocat/Hello-World/releases/v1.0.0", + "assets_url": "https://api.github.com/repos/octocat/Hello-World/releases/1/assets", + "upload_url": "https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name}", + "id": 1, + "tag_name": "v1.0.0", + "target_commitish": "master", + "name": "v1.0.0", + "body": "Description of the release", + "draft": false, + "prerelease": false, + "created_at": "2013-02-27T19:35:32Z", + "published_at": "2013-02-27T19:35:32Z"} \ No newline at end of file diff --git a/Octokit.Tests/Fixtures/release_asset.json b/Octokit.Tests/Fixtures/release_asset.json new file mode 100644 index 00000000..aba80fa7 --- /dev/null +++ b/Octokit.Tests/Fixtures/release_asset.json @@ -0,0 +1,12 @@ +{ + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/assets/7057", + "id": 7057, + "name": "GitTfs-0.18.0.zip", + "label": "GitTfs-0.18.0.zip", + "content_type": "application/zip", + "state": "uploaded", + "size": 1098250, + "download_count": 1337, + "created_at": "2013-07-26T15:11:05Z", + "updated_at": "2013-07-26T15:11:19Z" +} \ No newline at end of file diff --git a/Octokit.Tests/Fixtures/releases.json b/Octokit.Tests/Fixtures/releases.json new file mode 100644 index 00000000..36022092 --- /dev/null +++ b/Octokit.Tests/Fixtures/releases.json @@ -0,0 +1,230 @@ +[ + { + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/16259", + "assets_url": "https://api.github.com/repos/git-tfs/git-tfs/releases/16259/assets", + "upload_url": "https://uploads.github.com/repos/git-tfs/git-tfs/releases/16259/assets{?name}", + "html_url": "https://github.com/git-tfs/git-tfs/releases/v0.18.0", + "id": 16259, + "tag_name": "v0.18.0", + "target_commitish": "master", + "name": "0.18.0", + "body": "* Improve unshelve (#351)\r\n* Better support for running git-tfs in bare repositories (#353)\r\n* Use the saved author file for `rcheckin` (#366)\r\n* Add \"except\" to include an otherwise \"ignored\" file (#377)\r\n* Update libgit2sharp (#387)\r\n* Add a `--nofetch` option to the `branch` (#392) and `init-branch` (#379) commands\r\n* Reduce memory consumption during a fetch (#394)\r\n* Use only one workspace per fetch (rather than one workspace per changeset fetched) (#414)\r\n* Other fixes (#367, #376, #385, #389, #390, #397, #398, #400, #416)\r\n\r\n[Full diff](https://github.com/git-tfs/git-tfs/compare/v0.17.2...v0.18.0)", + "draft": false, + "prerelease": false, + "created_at": "2013-07-26T14:26:15Z", + "published_at": "2013-07-26T15:10:28Z", + "assets": [ + { + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/assets/7057", + "id": 7057, + "name": "GitTfs-0.18.0.zip", + "label": "GitTfs-0.18.0.zip", + "content_type": "application/zip", + "state": "uploaded", + "size": 1098250, + "download_count": 1337, + "created_at": "2013-07-26T15:11:05Z", + "updated_at": "2013-07-26T15:11:19Z" + } + ] + }, + { + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/10541", + "assets_url": "https://api.github.com/repos/git-tfs/git-tfs/releases/10541/assets", + "upload_url": "https://uploads.github.com/repos/git-tfs/git-tfs/releases/10541/assets{?name}", + "html_url": "https://github.com/git-tfs/git-tfs/releases/v0.12.1", + "id": 10541, + "tag_name": "v0.12.1", + "target_commitish": "master", + "name": "", + "body": "- Fixed: 'TF14045: The identity MYDOMAIN\\John Doe is not a recognized identity' (#76, #81)\r\n- Fixed: exception on unshelve if some items was renamed (#77)\r\n- Fixed: rare problem when TFS' mixed mode assemblies cannot be loaded correctly (#93)\r\n- Some fixes for Unicode filenames and TFS usernames (#80)\r\n- git-tfs exit codes are now positive\r\n- git-tfs cleans up files if clone command resulted in exception (#94)\r\n- Restored VS2008 functionality (#99)", + "draft": false, + "prerelease": false, + "created_at": "2011-11-02T12:28:53Z", + "published_at": "2013-07-16T10:14:20Z", + "assets": [ + + ] + }, + { + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/10540", + "assets_url": "https://api.github.com/repos/git-tfs/git-tfs/releases/10540/assets", + "upload_url": "https://uploads.github.com/repos/git-tfs/git-tfs/releases/10540/assets{?name}", + "html_url": "https://github.com/git-tfs/git-tfs/releases/v0.14.0", + "id": 10540, + "tag_name": "v0.14.0", + "target_commitish": "master", + "name": "", + "body": "- Fixed a bug in shelve (#133).\r\n- Fixed rename problem in checkintool (#148).\r\n- Fixed shelve -f (#157).\r\n- Fixed (or unfixed) case sensitivity (#159).\r\n- When a git subprocess exits with error, show the return/error code (#151).\r\n- Add support for VS11.", + "draft": false, + "prerelease": false, + "created_at": "2012-05-21T15:34:23Z", + "published_at": "2013-07-16T10:13:38Z", + "assets": [ + + ] + }, + { + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/10539", + "assets_url": "https://api.github.com/repos/git-tfs/git-tfs/releases/10539/assets", + "upload_url": "https://uploads.github.com/repos/git-tfs/git-tfs/releases/10539/assets{?name}", + "html_url": "https://github.com/git-tfs/git-tfs/releases/v0.15.0", + "id": 10539, + "tag_name": "v0.15.0", + "target_commitish": "master", + "name": "", + "body": "- Use [libgit2sharp](https://github.com/libgit2/libgit2sharp).\r\n- Add default comment for shelves (#187)\r\n- Add support for files with international characters (#200)\r\n- Fix the mixed case problem (once and for all?) (#213)\r\n- Add support for authors file\r\n- Set up CI with [travis](http://travis-ci.org/git-tfs/git-tfs) and [teamcity](http://teamcity.codebetter.com/)", + "draft": false, + "prerelease": false, + "created_at": "2012-09-10T13:19:18Z", + "published_at": "2013-07-16T10:13:00Z", + "assets": [ + + ] + }, + { + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/277", + "assets_url": "https://api.github.com/repos/git-tfs/git-tfs/releases/277/assets", + "upload_url": "https://uploads.github.com/repos/git-tfs/git-tfs/releases/277/assets{?name}", + "html_url": "https://github.com/git-tfs/git-tfs/releases/v0.17.2", + "id": 277, + "tag_name": "v0.17.2", + "target_commitish": "master", + "name": "0.17.2", + "body": "* Use the git author as the TFS committer during `git tfs rcheckin` (#336) and `git tfs rcheckin --quick` (#357)\r\n* Improve temporary workspace handling (#328, #372)\r\n* Use libgit2sharp more and git-core less (#361)\r\n* Bug fix for bare repositories (#352)\r\n* Bug fix for crash during `git tfs clone` (#349)\r\n* Bug fix for VS2008 (#362)\r\n* Update libgit2sharp\r\n* Improved release process (#333, #340)\r\n\r\n[Full diff](https://github.com/git-tfs/git-tfs/compare/v0.17.1...v0.17.2)", + "draft": false, + "prerelease": false, + "created_at": "2013-05-23T10:16:55Z", + "published_at": "2013-05-23T11:31:35Z", + "assets": [ + { + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/assets/931", + "id": 931, + "name": "gittfs-0.17.2.zip", + "label": "gittfs-0.17.2.zip", + "content_type": "application/zip", + "state": "uploaded", + "size": 1048704, + "download_count": 276, + "created_at": "2013-07-03T11:22:11Z", + "updated_at": "2013-07-03T11:22:25Z" + } + ] + }, + { + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/203", + "assets_url": "https://api.github.com/repos/git-tfs/git-tfs/releases/203/assets", + "upload_url": "https://uploads.github.com/repos/git-tfs/git-tfs/releases/203/assets{?name}", + "html_url": "https://github.com/git-tfs/git-tfs/releases/v0.17.1", + "id": 203, + "tag_name": "v0.17.1", + "target_commitish": "master", + "name": "v0.17.1", + "body": "- Fixed `git tfs clone` broken in some cases in 0.17 (#330)", + "draft": false, + "prerelease": false, + "created_at": "2013-03-25T15:36:32Z", + "published_at": "2013-03-25T15:43:42Z", + "assets": [ + { + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/assets/40", + "id": 40, + "name": "GitTfs-0.17.1.zip", + "label": "GitTfs-0.17.1.zip", + "content_type": "application/x-zip-compressed", + "state": "uploaded", + "size": 879773, + "download_count": 1750, + "created_at": "2013-03-25T15:40:00Z", + "updated_at": "2013-07-16T10:10:26Z" + } + ] + }, + { + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/198", + "assets_url": "https://api.github.com/repos/git-tfs/git-tfs/releases/198/assets", + "upload_url": "https://uploads.github.com/repos/git-tfs/git-tfs/releases/198/assets{?name}", + "html_url": "https://github.com/git-tfs/git-tfs/releases/v0.17.0", + "id": 198, + "tag_name": "v0.17.0", + "target_commitish": "master", + "name": "0.17.0", + "body": "- [branch](commands/branch.md)\r\n- [labels](commands/labels.md) (#256)\r\n- git tfs pull --rebase (#254)\r\n- git tfs clone --with-branches (#255)\r\n- unicode support (#204)\r\n- Use a custom workspace (#266)\r\n- Clean workspaces directory (#269)\r\n- Add a note on the commit to keep trace of the workitems (#276)\r\n- Remove orphan folders (except in specific cases) (#323)", + "draft": false, + "prerelease": false, + "created_at": "2013-03-22T01:50:42Z", + "published_at": "2013-03-22T02:14:10Z", + "assets": [ + { + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/assets/39", + "id": 39, + "name": "GitTfs-0.17.0.zip", + "label": "GitTfs-0.17.0.zip", + "content_type": "application/zip", + "state": "uploaded", + "size": 879769, + "download_count": 27, + "created_at": "2013-03-22T02:13:02Z", + "updated_at": "2013-07-16T10:10:54Z" + } + ] + }, + { + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/171", + "assets_url": "https://api.github.com/repos/git-tfs/git-tfs/releases/171/assets", + "upload_url": "https://uploads.github.com/repos/git-tfs/git-tfs/releases/171/assets{?name}", + "html_url": "https://github.com/git-tfs/git-tfs/releases/v0.16.0", + "id": 171, + "tag_name": "v0.16.0", + "target_commitish": "master", + "name": "0.16.0", + "body": "- [init-branch](commands/init-branch.md)!! (#232)\r\n- Faster clone (#226) and quick-clone.\r\n- Add `git tfs info` (#219)\r\n- Better metadata processing during rcheckin: remove the flags (#237), ignore whitespace (#238), add `git-tfs-force:` reason (#219).\r\n- Always use CRLF in TFS checkin comments (#239)\r\n- Checkin notes (#245)\r\n- Use authors file more, and save it so you don't have to tell git-tfs about it every time you need it. (#252)", + "draft": false, + "prerelease": false, + "created_at": "2012-12-05T23:02:27Z", + "published_at": "2013-03-08T12:59:16Z", + "assets": [ + { + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/assets/25", + "id": 25, + "name": "GitTfs-0.16.0.zip", + "label": "GitTfs-0.16.0.zip", + "content_type": "application/zip", + "state": "uploaded", + "size": 778026, + "download_count": 4, + "created_at": "2013-03-08T12:58:22Z", + "updated_at": "2013-07-16T10:12:00Z" + } + ] + }, + { + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/110", + "assets_url": "https://api.github.com/repos/git-tfs/git-tfs/releases/110/assets", + "upload_url": "https://uploads.github.com/repos/git-tfs/git-tfs/releases/110/assets{?name}", + "html_url": "https://github.com/git-tfs/git-tfs/releases/v0.16.1", + "id": 110, + "tag_name": "v0.16.1", + "target_commitish": "master", + "name": "0.16.1", + "body": "- Fixed `git tfs unshelve` (broken in 0.16.0) (#253).", + "draft": false, + "prerelease": false, + "created_at": "2012-12-06T16:14:15Z", + "published_at": "2013-02-19T23:16:11Z", + "assets": [ + { + "url": "https://api.github.com/repos/git-tfs/git-tfs/releases/assets/12", + "id": 12, + "name": "GitTfs-0.16.1.zip", + "label": "GitTfs-0.16.1.zip", + "content_type": "application/zip", + "state": "uploaded", + "size": 778060, + "download_count": 23, + "created_at": "2013-02-19T23:14:53Z", + "updated_at": "2013-07-16T10:11:33Z" + } + ] + } +] diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index 1a2d582e..d0fba100 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -92,6 +92,9 @@ + + + From 15a5cc9591ae0f93549cf8bcebbc7a4b2a063e07 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Wed, 2 Oct 2013 14:55:05 -0400 Subject: [PATCH 06/14] Front end for uploading a release asset. --- .../Clients/RepositoriesClientTests.cs | 32 +++++++++++++++++++ .../Helpers/StringExtensionsTests.cs | 12 +++++++ Octokit/Clients/RepositoriesClient.cs | 10 ++++++ Octokit/GitHubModels.cs | 22 +++++++++++++ Octokit/Helpers/StringExtensions.cs | 32 +++++++++++++++++++ Octokit/Http/ApiConnection.cs | 5 +++ Octokit/Http/IApiConnection.cs | 2 ++ Octokit/IRepositoriesClient.cs | 9 ++++++ 8 files changed, 124 insertions(+) diff --git a/Octokit.Tests/Clients/RepositoriesClientTests.cs b/Octokit.Tests/Clients/RepositoriesClientTests.cs index 87379473..ac18d560 100644 --- a/Octokit.Tests/Clients/RepositoriesClientTests.cs +++ b/Octokit.Tests/Clients/RepositoriesClientTests.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Text; using System.Threading.Tasks; using NSubstitute; @@ -189,5 +191,35 @@ namespace Octokit.Tests.Clients await AssertEx.Throws(async () => await repositoriesClient.CreateRelease("owner", "name", null)); } } + + public class TheUploadReleaseAssetMethod + { + [Fact] + public void UploadsToCorrectUrl() + { + var client = Substitute.For>(); + var repositoriesClient = new RepositoriesClient(client); + var release = new Release { UploadUrl = "https://uploads.test.dev/does/not/matter/releases/1/assets{?name}" }; + var rawData = Substitute.For(); + var upload = new ReleaseAssetUpload { FileName = "example.zip", ContentType = "application/zip", RawData = rawData }; + + repositoriesClient.UploadAsset(release, upload); + + client.Received().Upload(Arg.Is(u => u.ToString() == "https://uploads.test.dev/does/not/matter/releases/1/assets?name=example.zip"), + rawData, + Arg.Is>(headers => headers["Content-Type"] == "application/zip")); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var repositoriesClient = new RepositoriesClient(Substitute.For>()); + + var release = new Release { UploadUrl = "https://uploads.github.com/anything" }; + var uploadData = new ReleaseAssetUpload { FileName = "good", ContentType = "good/good", RawData = Stream.Null }; + await AssertEx.Throws(async () => await repositoriesClient.UploadAsset(null, uploadData)); + await AssertEx.Throws(async () => await repositoriesClient.UploadAsset(release, null)); + } + } } } diff --git a/Octokit.Tests/Helpers/StringExtensionsTests.cs b/Octokit.Tests/Helpers/StringExtensionsTests.cs index a35df2c6..0d363272 100644 --- a/Octokit.Tests/Helpers/StringExtensionsTests.cs +++ b/Octokit.Tests/Helpers/StringExtensionsTests.cs @@ -50,5 +50,17 @@ namespace Octokit.Tests.Helpers Assert.Throws(() => "".ToRubyCase()); } } + + public class TheExpandUriTemplateMethod + { + [Theory] + [InlineData("https://host.com/path?name=other", "https://host.com/path?name=other")] + [InlineData("https://host.com/path?name=example name.txt", "https://host.com/path{?name}")] + [InlineData("https://host.com/path", "https://host.com/path{?other}")] + public void ExpandsUriTemplates(string expected, string template) + { + Assert.Equal(expected, template.ExpandUriTemplate(new { name = "example name.txt" }).ToString()); + } + } } } diff --git a/Octokit/Clients/RepositoriesClient.cs b/Octokit/Clients/RepositoriesClient.cs index a8353832..5ea179b0 100644 --- a/Octokit/Clients/RepositoriesClient.cs +++ b/Octokit/Clients/RepositoriesClient.cs @@ -73,5 +73,15 @@ namespace Octokit.Clients var endpoint = "/repos/{0}/{1}/releases".FormatUri(owner, name); return await Client.Create(endpoint, data); } + + + public async Task UploadAsset(Release release, ReleaseAssetUpload data) + { + Ensure.ArgumentNotNull(release, "release"); + Ensure.ArgumentNotNull(data, "data"); + + var endpoint = release.UploadUrl.ExpandUriTemplate(new { name = data.FileName }); + return await Client.Upload(endpoint, data.RawData, new Dictionary { { "Content-Type", data.ContentType } }); + } } } diff --git a/Octokit/GitHubModels.cs b/Octokit/GitHubModels.cs index 29645349..9562494b 100644 --- a/Octokit/GitHubModels.cs +++ b/Octokit/GitHubModels.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Text; using System.Threading.Tasks; using Octokit.Http; @@ -489,6 +490,27 @@ namespace Octokit public bool Prerelease { get; set; } } + public class ReleaseAsset + { + public string Url { get; set; } + public int Id { get; set; } + public string Name { get; set; } + public string Label { get; set; } + public string State { get; set; } + public string ContentType { get; set; } + public int Size { get; set; } + public int DownloadCount { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset UpdatedAt { get; set; } + } + + public class ReleaseAssetUpload + { + public string FileName { get; set; } + public string ContentType { get; set; } + public Stream RawData { get; set; } + } + public class ApiError { public string Message { get; set; } diff --git a/Octokit/Helpers/StringExtensions.cs b/Octokit/Helpers/StringExtensions.cs index 7abaf279..e62d711f 100644 --- a/Octokit/Helpers/StringExtensions.cs +++ b/Octokit/Helpers/StringExtensions.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Reflection; +using System.Text.RegularExpressions; namespace Octokit { @@ -24,6 +26,36 @@ namespace Octokit return new Uri(string.Format(CultureInfo.InvariantCulture, pattern, args), UriKind.Relative); } + static Regex OptionalQueryStringRegex = new Regex("\\{\\?([^}]+)\\}"); + public static Uri ExpandUriTemplate(this string template, object values) + { + var optionalQueryStringMatch = OptionalQueryStringRegex.Match(template); + if(optionalQueryStringMatch.Success) + { + var expansion = ""; + var parameterName = optionalQueryStringMatch.Groups[1].Value; + var parameterProperty = values.GetType().GetProperty(parameterName); + if(parameterProperty != null) + { + expansion = "?" + parameterName + "=" + Uri.EscapeDataString("" + parameterProperty.GetValue(values, new object[0])); + } + template = OptionalQueryStringRegex.Replace(template, expansion); + } + return new Uri(template); + } + +#if NETFX_CORE + public static PropertyInfo GetProperty(this Type t, string propertyName) + { + return t.GetTypeInfo().GetDeclaredProperty(propertyName); + } +#endif + + public static string EscapeUri(this string s) + { + return Uri.EscapeUriString(s); + } + // :trollface: [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Ruby don't care. Ruby don't play that.")] diff --git a/Octokit/Http/ApiConnection.cs b/Octokit/Http/ApiConnection.cs index ff1f22dc..262f739e 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using Octokit.Clients; @@ -124,5 +125,9 @@ namespace Octokit.Http return new ReadOnlyPagedCollection(response, Connection); } + public Task Upload(Uri uri, Stream rawData, Dictionary headers) + { + throw new NotImplementedException(); + } } } diff --git a/Octokit/Http/IApiConnection.cs b/Octokit/Http/IApiConnection.cs index b4389ac9..5c264232 100644 --- a/Octokit/Http/IApiConnection.cs +++ b/Octokit/Http/IApiConnection.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Threading.Tasks; namespace Octokit.Http @@ -22,5 +23,6 @@ namespace Octokit.Http Task Create(Uri endpoint, object data); Task Update(Uri endpoint, object data); Task Delete(Uri endpoint); + Task Upload(Uri uri, Stream rawData, Dictionary headers); } } \ No newline at end of file diff --git a/Octokit/IRepositoriesClient.cs b/Octokit/IRepositoriesClient.cs index fd26ae56..ad3e133d 100644 --- a/Octokit/IRepositoriesClient.cs +++ b/Octokit/IRepositoriesClient.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Threading.Tasks; namespace Octokit @@ -73,5 +74,13 @@ namespace Octokit /// The data for the release. /// A new . Task CreateRelease(string owner, string name, ReleaseUpdate data); + + /// + /// Upload a for the specified release. + /// + /// The to attach the asset to. + /// The asset information. + /// A new . + Task UploadAsset(Release release, ReleaseAssetUpload data); } } From d0a5c5f9a67822db701b28cca9beee5021b23ad8 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Wed, 2 Oct 2013 15:17:59 -0400 Subject: [PATCH 07/14] Implement upload in ApiConnection. --- Octokit.Tests/Http/ApiConnectionTests.cs | 48 +++++++++++++++++++----- Octokit/Http/ApiConnection.cs | 14 ++++--- Octokit/Http/Connection.cs | 7 +++- Octokit/Http/IConnection.cs | 2 + 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/Octokit.Tests/Http/ApiConnectionTests.cs b/Octokit.Tests/Http/ApiConnectionTests.cs index 583ae635..5202f794 100644 --- a/Octokit.Tests/Http/ApiConnectionTests.cs +++ b/Octokit.Tests/Http/ApiConnectionTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using NSubstitute; using Octokit.Http; @@ -31,7 +32,7 @@ namespace Octokit.Tests.Http public async Task EnsuresArgumentNotNull() { var client = new ApiConnection(Substitute.For()); - AssertEx.Throws(async () => await client.Get(null)); + await AssertEx.Throws(async () => await client.Get(null)); } } @@ -56,7 +57,7 @@ namespace Octokit.Tests.Http public async Task EnsuresArgumentNotNull() { var connection = new ApiConnection(Substitute.For()); - AssertEx.Throws(async () => await connection.GetItem(null, null)); + await AssertEx.Throws(async () => await connection.GetItem(null, null)); } } @@ -81,7 +82,7 @@ namespace Octokit.Tests.Http public async Task EnsuresArgumentNotNull() { var client = new ApiConnection(Substitute.For()); - AssertEx.Throws(async () => await client.GetHtml(null)); + await AssertEx.Throws(async () => await client.GetHtml(null)); } } @@ -112,7 +113,7 @@ namespace Octokit.Tests.Http public async Task EnsuresArgumentNotNull() { var client = new ApiConnection(Substitute.For()); - AssertEx.Throws(async () => await client.GetAll(null)); + await AssertEx.Throws(async () => await client.GetAll(null)); } } @@ -140,8 +141,8 @@ namespace Octokit.Tests.Http { var connection = new ApiConnection(Substitute.For()); var patchUri = new Uri("/", UriKind.Relative); - AssertEx.Throws(async () => await connection.Update(null, new object())); - AssertEx.Throws(async () => await connection.Update(patchUri, null)); + await AssertEx.Throws(async () => await connection.Update(null, new object())); + await AssertEx.Throws(async () => await connection.Update(patchUri, null)); } } @@ -169,8 +170,8 @@ namespace Octokit.Tests.Http { var client = new ApiConnection(Substitute.For()); var postUri = new Uri("/", UriKind.Relative); - AssertEx.Throws(async () => await client.Create(null, new object())); - AssertEx.Throws(async () => await client.Create(postUri, null)); + await AssertEx.Throws(async () => await client.Create(null, new object())); + await AssertEx.Throws(async () => await client.Create(postUri, null)); } } @@ -195,7 +196,36 @@ namespace Octokit.Tests.Http public async Task EnsuresArgumentNotNull() { var connection = new ApiConnection(Substitute.For()); - AssertEx.Throws(async () => await connection.Delete(null)); + await AssertEx.Throws(async () => await connection.Delete(null)); + } + } + + public class TheUploadMethod + { + [Fact] + public async Task MakesUploadRequest() + { + var uploadUrl = new Uri("/anything", UriKind.Relative); + IResponse response = new ApiResponse { BodyAsObject = "the response" }; + var connection = Substitute.For(); + connection.PostRawAsync(Args.Uri, Arg.Any(), Arg.Any>()).Returns(Task.FromResult(response)); + var apiConnection = new ApiConnection(connection); + var rawData = new MemoryStream(); + var headers = new Dictionary { { "A", "B" } }; + + await apiConnection.Upload(uploadUrl, rawData, headers); + + connection.Received().PostRawAsync(uploadUrl, rawData, headers); + } + + [Fact] + public async Task EnsuresArgumentNotNull() + { + var connection = new ApiConnection(Substitute.For()); + await AssertEx.Throws(async () => await connection.Upload(null, Stream.Null, new Dictionary())); + await AssertEx.Throws(async () => await connection.Upload(new Uri("/ok", UriKind.Relative), null, new Dictionary())); + // This one is OK to be null. + await connection.Upload(new Uri("/ok", UriKind.Relative), Stream.Null, null); } } diff --git a/Octokit/Http/ApiConnection.cs b/Octokit/Http/ApiConnection.cs index 262f739e..b155241a 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -109,6 +109,15 @@ namespace Octokit.Http await Connection.DeleteAsync(endpoint); } + public async Task Upload(Uri uri, Stream rawData, Dictionary headers) + { + Ensure.ArgumentNotNull(uri, "uri"); + Ensure.ArgumentNotNull(rawData, "rawData"); + + var response = await Connection.PostRawAsync(uri, rawData, headers); + return response.BodyAsObject; + } + async Task> GetPage(Uri endpoint, IDictionary parameters) { Ensure.ArgumentNotNull(endpoint, "endpoint"); @@ -124,10 +133,5 @@ namespace Octokit.Http var response = await Connection.GetAsync>(endpoint, parameters); return new ReadOnlyPagedCollection(response, Connection); } - - public Task Upload(Uri uri, Stream rawData, Dictionary headers) - { - throw new NotImplementedException(); - } } } diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs index f2d535ba..ac16394f 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -178,7 +178,7 @@ namespace Octokit.Http { if (response.StatusCode == HttpStatusCode.Unauthorized) throw new AuthorizationException("You must be authenticated to call this method. Either supply a " + - "login/password or an oauth token."); + "login/password or an oauth token."); if (response.StatusCode == HttpStatusCode.Forbidden) { @@ -200,5 +200,10 @@ namespace Octokit.Http throw new ApiException(response.Body, response.StatusCode); } } + + public Task> PostRawAsync(Uri endpoint, System.IO.Stream body, IDictionary headers) + { + throw new NotImplementedException(); + } } } diff --git a/Octokit/Http/IConnection.cs b/Octokit/Http/IConnection.cs index f89840fa..47a0689c 100644 --- a/Octokit/Http/IConnection.cs +++ b/Octokit/Http/IConnection.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Threading.Tasks; namespace Octokit.Http @@ -11,6 +12,7 @@ namespace Octokit.Http Task> GetAsync(Uri endpoint, IDictionary parameters); Task> PatchAsync(Uri endpoint, object body); Task> PostAsync(Uri endpoint, object body); + Task> PostRawAsync(Uri endpoint, Stream body, IDictionary headers); Task> PutAsync(Uri endpoint, object body); [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] From 84b42b7074e7232254bb7a215a4a63edbdf9213d Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Wed, 2 Oct 2013 15:56:24 -0400 Subject: [PATCH 08/14] Implement PostRawAsync, for asset uploading. --- Octokit.Tests/Http/ConnectionTests.cs | 27 +++++++++++++++++++++++ Octokit/Http/Connection.cs | 31 ++++++++++++++++++++++----- Octokit/Http/HttpClientAdapter.cs | 5 +++++ 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/Octokit.Tests/Http/ConnectionTests.cs b/Octokit.Tests/Http/ConnectionTests.cs index 13b20e19..d1eb8511 100644 --- a/Octokit.Tests/Http/ConnectionTests.cs +++ b/Octokit.Tests/Http/ConnectionTests.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net; using System.Net.Http; @@ -233,6 +235,31 @@ namespace Octokit.Tests.Http } } + public class ThePostRawAsyncMethod + { + [Fact] + public async Task RunsConfiguredAppWithAppropriateEnv() + { + var httpClient = Substitute.For(); + IResponse response = new ApiResponse(); + httpClient.Send(Args.Request).Returns(Task.FromResult(response)); + var connection = new Connection(ExampleUri, + Substitute.For(), + httpClient, + Substitute.For()); + + var body = new MemoryStream(new byte[] { 48, 49, 50 }); + var headers = new Dictionary { { "Content-Type", "application/arbitrary" } }; + await connection.PostRawAsync(new Uri("https://other.host.com/path?query=val"), body, headers); + + httpClient.Received().Send(Arg.Is(req => + req.BaseAddress == ExampleUri && + req.Body == body && + req.Method == HttpMethod.Post && + req.Endpoint == new Uri("https://other.host.com/path?query=val"))); + } + } + public class TheDeleteAsyncMethod { [Fact] diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs index ac16394f..43814f7a 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -102,6 +102,32 @@ namespace Octokit.Http return await SendData(endpoint, HttpMethod.Post, body); } + public async Task> PostRawAsync(Uri endpoint, System.IO.Stream body, IDictionary headers) + { + Ensure.ArgumentNotNull(endpoint, "endpoint"); + Ensure.ArgumentNotNull(body, "body"); + + var request = new Request + { + Method = HttpMethod.Post, + BaseAddress = BaseAddress, + Endpoint = endpoint, + Body = body + }; + if (headers != null) + { + foreach (var header in headers) + { + request.Headers[header.Key] = header.Value; + } + } + authenticator.Apply(request); + var response = await httpClient.Send(request); + apiInfoParser.ParseApiHttpHeaders(response); + jsonPipeline.DeserializeResponse(response); + return response; + } + public async Task> PutAsync(Uri endpoint, object body) { return await SendData(endpoint, HttpMethod.Put, body); @@ -200,10 +226,5 @@ namespace Octokit.Http throw new ApiException(response.Body, response.StatusCode); } } - - public Task> PostRawAsync(Uri endpoint, System.IO.Stream body, IDictionary headers) - { - throw new NotImplementedException(); - } } } diff --git a/Octokit/Http/HttpClientAdapter.cs b/Octokit/Http/HttpClientAdapter.cs index b0a1461e..9cfbcd5d 100644 --- a/Octokit/Http/HttpClientAdapter.cs +++ b/Octokit/Http/HttpClientAdapter.cs @@ -58,6 +58,11 @@ namespace Octokit.Http { requestMessage.Content = new StringContent(body, Encoding.UTF8); } + var bodyStream = request.Body as System.IO.Stream; + if (bodyStream != null) + { + requestMessage.Content = new StreamContent(bodyStream); + } } catch (Exception) { From 2efd9aa23b5032441e75e5ffcb8c68055d4228ba Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Wed, 2 Oct 2013 15:56:58 -0400 Subject: [PATCH 09/14] Tell github.com that we really do want to use the new API. --- Octokit/Clients/RepositoriesClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Octokit/Clients/RepositoriesClient.cs b/Octokit/Clients/RepositoriesClient.cs index 5ea179b0..1808b3e4 100644 --- a/Octokit/Clients/RepositoriesClient.cs +++ b/Octokit/Clients/RepositoriesClient.cs @@ -81,7 +81,7 @@ namespace Octokit.Clients Ensure.ArgumentNotNull(data, "data"); var endpoint = release.UploadUrl.ExpandUriTemplate(new { name = data.FileName }); - return await Client.Upload(endpoint, data.RawData, new Dictionary { { "Content-Type", data.ContentType } }); + return await Client.Upload(endpoint, data.RawData, new Dictionary { { "Content-Type", data.ContentType }, { "Accept", "application/vnd.github.manifold-preview" } }); } } } From ede0267a967fbcd4bb597b7dad484ae858c18f71 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Wed, 2 Oct 2013 16:35:30 -0400 Subject: [PATCH 10/14] Back to ReleasesCLient. --- .../Octokit.Tests.Integration.csproj | 1 + .../ReleasesClientTests.cs | 26 +++++ .../RepositoriesClientTests.cs | 17 ---- Octokit.Tests/Clients/ReleasesClientTests.cs | 96 +++++++++++++++++++ .../Clients/RepositoriesClientTests.cs | 80 ---------------- Octokit.Tests/Octokit.Tests.csproj | 1 + Octokit/Clients/ReleasesClient.cs | 43 +++++++++ Octokit/Clients/RepositoriesClient.cs | 30 ------ Octokit/GitHubClient.cs | 2 + Octokit/IReleasesClient.cs | 33 +++++++ Octokit/IRepositoriesClient.cs | 25 ----- Octokit/Octokit.csproj | 2 + Octokit/OctokitRT.csproj | 2 + 13 files changed, 206 insertions(+), 152 deletions(-) create mode 100644 Octokit.Tests.Integration/ReleasesClientTests.cs create mode 100644 Octokit.Tests/Clients/ReleasesClientTests.cs create mode 100644 Octokit/Clients/ReleasesClient.cs create mode 100644 Octokit/IReleasesClient.cs diff --git a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj index 0d91a136..2e2ddae2 100644 --- a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj +++ b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj @@ -49,6 +49,7 @@ + diff --git a/Octokit.Tests.Integration/ReleasesClientTests.cs b/Octokit.Tests.Integration/ReleasesClientTests.cs new file mode 100644 index 00000000..8dcd7f91 --- /dev/null +++ b/Octokit.Tests.Integration/ReleasesClientTests.cs @@ -0,0 +1,26 @@ +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Octokit.Tests.Integration +{ + public class ReleasesClientTests + { + public class TheGetReleasesMethod + { + [IntegrationTest] + public async Task ReturnsReleases() + { + var github = new GitHubClient + { + Credentials = AutomationSettings.Current.GitHubCredentials + }; + + var releases = await github.Releases.GetAll("git-tfs", "git-tfs"); + + Assert.True(releases.Count > 5); + Assert.True(releases.Any(release => release.TagName == "v0.18.0")); + } + } + } +} diff --git a/Octokit.Tests.Integration/RepositoriesClientTests.cs b/Octokit.Tests.Integration/RepositoriesClientTests.cs index b599e9d0..92157975 100644 --- a/Octokit.Tests.Integration/RepositoriesClientTests.cs +++ b/Octokit.Tests.Integration/RepositoriesClientTests.cs @@ -90,22 +90,5 @@ namespace Octokit.Tests.Integration Assert.Contains("

WARNING: This is some haacky code.", readMeHtml); } } - - public class TheGetReleasesMethod - { - [IntegrationTest] - public async Task ReturnsReleases() - { - var github = new GitHubClient - { - Credentials = AutomationSettings.Current.GitHubCredentials - }; - - var releases = await github.Repository.GetReleases("git-tfs", "git-tfs"); - - Assert.True(releases.Count > 5); - Assert.True(releases.Any(release => release.TagName == "v0.18.0")); - } - } } } diff --git a/Octokit.Tests/Clients/ReleasesClientTests.cs b/Octokit.Tests/Clients/ReleasesClientTests.cs new file mode 100644 index 00000000..2ab3ffc1 --- /dev/null +++ b/Octokit.Tests/Clients/ReleasesClientTests.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using NSubstitute; +using Octokit.Clients; +using Octokit.Http; +using Octokit.Tests.Helpers; +using Xunit; + +namespace Octokit.Tests.Clients +{ + public class ReleasesClientTests + { + + public class TheGetReleasesMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var client = Substitute.For>(); + var releasesClient = new ReleasesClient(client); + + releasesClient.GetAll("fake", "repo"); + + client.Received().GetAll(Arg.Is(u => u.ToString() == "/repos/fake/repo/releases"), null); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var releasesClient = new ReleasesClient(Substitute.For>()); + + await AssertEx.Throws(async () => await releasesClient.GetAll(null, "name")); + await AssertEx.Throws(async () => await releasesClient.GetAll("owner", null)); + } + } + + public class TheCreateReleaseMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var client = Substitute.For>(); + var releasesClient = new ReleasesClient(client); + var data = new ReleaseUpdate("fake-tag"); + + releasesClient.CreateRelease("fake", "repo", data); + + client.Received().Create(Arg.Is(u => u.ToString() == "/repos/fake/repo/releases"), data); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var releasesClient = new ReleasesClient(Substitute.For>()); + var data = new ReleaseUpdate("fake-tag"); + + Assert.Throws(() => new ReleaseUpdate(null)); + await AssertEx.Throws(async () => await releasesClient.CreateRelease(null, "name", data)); + await AssertEx.Throws(async () => await releasesClient.CreateRelease("owner", null, data)); + await AssertEx.Throws(async () => await releasesClient.CreateRelease("owner", "name", null)); + } + } + + public class TheUploadReleaseAssetMethod + { + [Fact] + public void UploadsToCorrectUrl() + { + var client = Substitute.For>(); + var releasesClient = new ReleasesClient(client); + var release = new Release { UploadUrl = "https://uploads.test.dev/does/not/matter/releases/1/assets{?name}" }; + var rawData = Substitute.For(); + var upload = new ReleaseAssetUpload { FileName = "example.zip", ContentType = "application/zip", RawData = rawData }; + + releasesClient.UploadAsset(release, upload); + + client.Received().Upload(Arg.Is(u => u.ToString() == "https://uploads.test.dev/does/not/matter/releases/1/assets?name=example.zip"), + rawData, + Arg.Is>(headers => headers["Content-Type"] == "application/zip")); + } + + [Fact] + public async Task EnsuresArgumentsNotNull() + { + var releasesClient = new ReleasesClient(Substitute.For>()); + + var release = new Release { UploadUrl = "https://uploads.github.com/anything" }; + var uploadData = new ReleaseAssetUpload { FileName = "good", ContentType = "good/good", RawData = Stream.Null }; + await AssertEx.Throws(async () => await releasesClient.UploadAsset(null, uploadData)); + await AssertEx.Throws(async () => await releasesClient.UploadAsset(release, null)); + } + } + } +} diff --git a/Octokit.Tests/Clients/RepositoriesClientTests.cs b/Octokit.Tests/Clients/RepositoriesClientTests.cs index ac18d560..0504b6d5 100644 --- a/Octokit.Tests/Clients/RepositoriesClientTests.cs +++ b/Octokit.Tests/Clients/RepositoriesClientTests.cs @@ -141,85 +141,5 @@ namespace Octokit.Tests.Clients client.Received().GetHtml(Arg.Is(u => u.ToString() == "https://github.example.com/readme"), null); } } - - public class TheGetReleasesMethod - { - [Fact] - public void RequestsCorrectUrl() - { - var client = Substitute.For>(); - var repositoriesClient = new RepositoriesClient(client); - - repositoriesClient.GetReleases("fake", "repo"); - - client.Received().GetAll(Arg.Is(u => u.ToString() == "/repos/fake/repo/releases"), null); - } - - [Fact] - public async Task EnsuresNonNullArguments() - { - var repositoriesClient = new RepositoriesClient(Substitute.For>()); - - await AssertEx.Throws(async () => await repositoriesClient.GetReleases(null, "name")); - await AssertEx.Throws(async () => await repositoriesClient.GetReleases("owner", null)); - } - } - - public class TheCreateReleaseMethod - { - [Fact] - public void RequestsCorrectUrl() - { - var client = Substitute.For>(); - var repositoriesClient = new RepositoriesClient(client); - var data = new ReleaseUpdate("fake-tag"); - - repositoriesClient.CreateRelease("fake", "repo", data); - - client.Received().Create(Arg.Is(u => u.ToString() == "/repos/fake/repo/releases"), data); - } - - [Fact] - public async Task EnsuresArgumentsNotNull() - { - var repositoriesClient = new RepositoriesClient(Substitute.For>()); - var data = new ReleaseUpdate("fake-tag"); - - Assert.Throws(() => new ReleaseUpdate(null)); - await AssertEx.Throws(async () => await repositoriesClient.CreateRelease(null, "name", data)); - await AssertEx.Throws(async () => await repositoriesClient.CreateRelease("owner", null, data)); - await AssertEx.Throws(async () => await repositoriesClient.CreateRelease("owner", "name", null)); - } - } - - public class TheUploadReleaseAssetMethod - { - [Fact] - public void UploadsToCorrectUrl() - { - var client = Substitute.For>(); - var repositoriesClient = new RepositoriesClient(client); - var release = new Release { UploadUrl = "https://uploads.test.dev/does/not/matter/releases/1/assets{?name}" }; - var rawData = Substitute.For(); - var upload = new ReleaseAssetUpload { FileName = "example.zip", ContentType = "application/zip", RawData = rawData }; - - repositoriesClient.UploadAsset(release, upload); - - client.Received().Upload(Arg.Is(u => u.ToString() == "https://uploads.test.dev/does/not/matter/releases/1/assets?name=example.zip"), - rawData, - Arg.Is>(headers => headers["Content-Type"] == "application/zip")); - } - - [Fact] - public async Task EnsuresArgumentsNotNull() - { - var repositoriesClient = new RepositoriesClient(Substitute.For>()); - - var release = new Release { UploadUrl = "https://uploads.github.com/anything" }; - var uploadData = new ReleaseAssetUpload { FileName = "good", ContentType = "good/good", RawData = Stream.Null }; - await AssertEx.Throws(async () => await repositoriesClient.UploadAsset(null, uploadData)); - await AssertEx.Throws(async () => await repositoriesClient.UploadAsset(release, null)); - } - } } } diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index d0fba100..235256d6 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -51,6 +51,7 @@ + diff --git a/Octokit/Clients/ReleasesClient.cs b/Octokit/Clients/ReleasesClient.cs new file mode 100644 index 00000000..8c340f4e --- /dev/null +++ b/Octokit/Clients/ReleasesClient.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Octokit.Http; + +namespace Octokit.Clients +{ + public class ReleasesClient : ApiClient, IReleasesClient + { + public ReleasesClient(IApiConnection client) : base(client) + { + } + + public async Task> GetAll(string owner, string name) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "repository"); + + var endpoint = "/repos/{0}/{1}/releases".FormatUri(owner, name); + return await Client.GetAll(endpoint); + } + + + public async Task CreateRelease(string owner, string name, ReleaseUpdate data) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "repository"); + Ensure.ArgumentNotNull(data, "data"); + + var endpoint = "/repos/{0}/{1}/releases".FormatUri(owner, name); + return await Client.Create(endpoint, data); + } + + + public async Task UploadAsset(Release release, ReleaseAssetUpload data) + { + Ensure.ArgumentNotNull(release, "release"); + Ensure.ArgumentNotNull(data, "data"); + + var endpoint = release.UploadUrl.ExpandUriTemplate(new { name = data.FileName }); + return await Client.Upload(endpoint, data.RawData, new Dictionary { { "Content-Type", data.ContentType }, { "Accept", "application/vnd.github.manifold-preview" } }); + } + } +} diff --git a/Octokit/Clients/RepositoriesClient.cs b/Octokit/Clients/RepositoriesClient.cs index 1808b3e4..e09e0dbb 100644 --- a/Octokit/Clients/RepositoriesClient.cs +++ b/Octokit/Clients/RepositoriesClient.cs @@ -53,35 +53,5 @@ namespace Octokit.Clients var readmeInfo = await Client.GetItem(endpoint, null); return new Readme(readmeInfo, Client); } - - public async Task> GetReleases(string owner, string name) - { - Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); - Ensure.ArgumentNotNullOrEmptyString(name, "repository"); - - var endpoint = "/repos/{0}/{1}/releases".FormatUri(owner, name); - return await Client.GetAll(endpoint); - } - - - public async Task CreateRelease(string owner, string name, ReleaseUpdate data) - { - Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); - Ensure.ArgumentNotNullOrEmptyString(name, "repository"); - Ensure.ArgumentNotNull(data, "data"); - - var endpoint = "/repos/{0}/{1}/releases".FormatUri(owner, name); - return await Client.Create(endpoint, data); - } - - - public async Task UploadAsset(Release release, ReleaseAssetUpload data) - { - Ensure.ArgumentNotNull(release, "release"); - Ensure.ArgumentNotNull(data, "data"); - - var endpoint = release.UploadUrl.ExpandUriTemplate(new { name = data.FileName }); - return await Client.Upload(endpoint, data.RawData, new Dictionary { { "Content-Type", data.ContentType }, { "Accept", "application/vnd.github.manifold-preview" } }); - } } } diff --git a/Octokit/GitHubClient.cs b/Octokit/GitHubClient.cs index d11a6a9b..0399e50e 100644 --- a/Octokit/GitHubClient.cs +++ b/Octokit/GitHubClient.cs @@ -39,6 +39,7 @@ namespace Octokit AutoComplete = new AutoCompleteClient(connection); Organization = new OrganizationsClient(new ApiConnection(connection)); Repository = new RepositoriesClient(new ApiConnection(connection)); + Releases = new ReleasesClient(new ApiConnection(connection)); User = new UsersClient(new ApiConnection(connection)); SshKey = new SshKeysClient(new ApiConnection(connection)); } @@ -79,6 +80,7 @@ namespace Octokit public IAutoCompleteClient AutoComplete { get; private set; } public IOrganizationsClient Organization { get; private set; } public IRepositoriesClient Repository { get; private set; } + public IReleasesClient Releases { get; private set; } public ISshKeysClient SshKey { get; private set; } public IUsersClient User { get; private set; } } diff --git a/Octokit/IReleasesClient.cs b/Octokit/IReleasesClient.cs new file mode 100644 index 00000000..6be68490 --- /dev/null +++ b/Octokit/IReleasesClient.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Octokit +{ + public interface IReleasesClient + { + ///

+ /// Retrieves every for the specified repository. + /// + /// The owner of the repository. + /// The name of the repository. + /// A of . + Task> GetAll(string owner, string name); + + /// + /// Create a for the specified repository. + /// + /// The owner of the repository. + /// The name of the repository. + /// The data for the release. + /// A new . + Task CreateRelease(string owner, string name, ReleaseUpdate data); + + /// + /// Upload a for the specified release. + /// + /// The to attach the asset to. + /// The asset information. + /// A new . + Task UploadAsset(Release release, ReleaseAssetUpload data); + } +} diff --git a/Octokit/IRepositoriesClient.cs b/Octokit/IRepositoriesClient.cs index ad3e133d..41200131 100644 --- a/Octokit/IRepositoriesClient.cs +++ b/Octokit/IRepositoriesClient.cs @@ -57,30 +57,5 @@ namespace Octokit /// The name of the repository. /// Task GetReadme(string owner, string name); - - /// - /// Retrieves every for the specified repository. - /// - /// The owner of the repository. - /// The name of the repository. - /// A of . - Task> GetReleases(string owner, string name); - - /// - /// Create a for the specified repository. - /// - /// The owner of the repository. - /// The name of the repository. - /// The data for the release. - /// A new . - Task CreateRelease(string owner, string name, ReleaseUpdate data); - - /// - /// Upload a for the specified release. - /// - /// The to attach the asset to. - /// The asset information. - /// A new . - Task UploadAsset(Release release, ReleaseAssetUpload data); } } diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index dd4104b5..1672bee8 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -86,6 +86,7 @@ + @@ -106,6 +107,7 @@ + diff --git a/Octokit/OctokitRT.csproj b/Octokit/OctokitRT.csproj index 3e9254aa..1b1519f0 100644 --- a/Octokit/OctokitRT.csproj +++ b/Octokit/OctokitRT.csproj @@ -110,6 +110,7 @@ + @@ -153,6 +154,7 @@ + From 6092c4f4912ef1627fd51095878377f47315a53e Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Wed, 2 Oct 2013 17:24:54 -0400 Subject: [PATCH 11/14] Reduce noise. * Remove using statements that were the only change to a file. * Remove methods that were added and are no longer used. --- .../RepositoriesClientTests.cs | 1 - Octokit.Tests/Clients/ReleasesClientTests.cs | 4 +-- .../Clients/RepositoriesClientTests.cs | 2 -- Octokit/ApiExtensions.cs | 8 ------ Octokit/Clients/ApiPagination.cs | 13 ---------- Octokit/Helpers/StringExtensions.cs | 5 ---- Octokit/Http/ApiConnection.cs | 25 ------------------- Octokit/Http/IApiConnection.cs | 2 -- Octokit/IApiPagination.cs | 1 - 9 files changed, 2 insertions(+), 59 deletions(-) diff --git a/Octokit.Tests.Integration/RepositoriesClientTests.cs b/Octokit.Tests.Integration/RepositoriesClientTests.cs index 92157975..3b55a09c 100644 --- a/Octokit.Tests.Integration/RepositoriesClientTests.cs +++ b/Octokit.Tests.Integration/RepositoriesClientTests.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading.Tasks; using Xunit; diff --git a/Octokit.Tests/Clients/ReleasesClientTests.cs b/Octokit.Tests/Clients/ReleasesClientTests.cs index 2ab3ffc1..c7de133e 100644 --- a/Octokit.Tests/Clients/ReleasesClientTests.cs +++ b/Octokit.Tests/Clients/ReleasesClientTests.cs @@ -23,7 +23,7 @@ namespace Octokit.Tests.Clients releasesClient.GetAll("fake", "repo"); - client.Received().GetAll(Arg.Is(u => u.ToString() == "/repos/fake/repo/releases"), null); + client.Received().GetAll(Arg.Is(u => u.ToString() == "/repos/fake/repo/releases"), null); } [Fact] @@ -47,7 +47,7 @@ namespace Octokit.Tests.Clients releasesClient.CreateRelease("fake", "repo", data); - client.Received().Create(Arg.Is(u => u.ToString() == "/repos/fake/repo/releases"), data); + client.Received().Create(Arg.Is(u => u.ToString() == "/repos/fake/repo/releases"), data); } [Fact] diff --git a/Octokit.Tests/Clients/RepositoriesClientTests.cs b/Octokit.Tests/Clients/RepositoriesClientTests.cs index 0504b6d5..08a0ec5b 100644 --- a/Octokit.Tests/Clients/RepositoriesClientTests.cs +++ b/Octokit.Tests/Clients/RepositoriesClientTests.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.IO; using System.Text; using System.Threading.Tasks; using NSubstitute; diff --git a/Octokit/ApiExtensions.cs b/Octokit/ApiExtensions.cs index 58a882ef..d813a203 100644 --- a/Octokit/ApiExtensions.cs +++ b/Octokit/ApiExtensions.cs @@ -26,14 +26,6 @@ namespace Octokit return connection.GetAll(endpoint, null); } - public static Task> GetAll(this IApiConnection connection, Uri endpoint) - { - Ensure.ArgumentNotNull(connection, "connection"); - Ensure.ArgumentNotNull(endpoint, "endpoint"); - - return connection.GetAll(endpoint, null); - } - public static Task GetHtml(this IApiConnection connection, Uri endpoint) { Ensure.ArgumentNotNull(connection, "connection"); diff --git a/Octokit/Clients/ApiPagination.cs b/Octokit/Clients/ApiPagination.cs index ff5e0a8d..997d9a13 100644 --- a/Octokit/Clients/ApiPagination.cs +++ b/Octokit/Clients/ApiPagination.cs @@ -26,18 +26,5 @@ namespace Octokit.Clients } return new ReadOnlyCollection(allItems); } - - public async Task> GetAllPages(Func>> getFirstPage) - { - Ensure.ArgumentNotNull(getFirstPage, "getFirstPage"); - - var page = await getFirstPage(); - var allItems = new List(page); - while ((page = await page.GetNextPage()) != null) - { - allItems.AddRange(page); - } - return new ReadOnlyCollection(allItems); - } } } diff --git a/Octokit/Helpers/StringExtensions.cs b/Octokit/Helpers/StringExtensions.cs index e62d711f..a834271f 100644 --- a/Octokit/Helpers/StringExtensions.cs +++ b/Octokit/Helpers/StringExtensions.cs @@ -51,11 +51,6 @@ namespace Octokit } #endif - public static string EscapeUri(this string s) - { - return Uri.EscapeUriString(s); - } - // :trollface: [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Ruby don't care. Ruby don't play that.")] diff --git a/Octokit/Http/ApiConnection.cs b/Octokit/Http/ApiConnection.cs index b155241a..04a0cd57 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -55,13 +55,6 @@ namespace Octokit.Http return await pagination.GetAllPages(async () => await GetPage(endpoint, parameters)); } - public async Task> GetAll(Uri endpoint, IDictionary parameters) - { - Ensure.ArgumentNotNull(endpoint, "endpoint"); - - return await pagination.GetAllPages(async () => await GetPage(endpoint, parameters)); - } - public async Task Create(Uri endpoint, object data) { Ensure.ArgumentNotNull(endpoint, "endpoint"); @@ -72,16 +65,6 @@ namespace Octokit.Http return response.BodyAsObject; } - public async Task Create(Uri endpoint, object data) - { - Ensure.ArgumentNotNull(endpoint, "endpoint"); - Ensure.ArgumentNotNull(data, "data"); - - var response = await Connection.PostAsync(endpoint, data); - - return response.BodyAsObject; - } - public async Task Update(Uri endpoint, object data) { Ensure.ArgumentNotNull(endpoint, "endpoint"); @@ -125,13 +108,5 @@ namespace Octokit.Http var response = await Connection.GetAsync>(endpoint, parameters); return new ReadOnlyPagedCollection(response, Connection); } - - async Task> GetPage(Uri endpoint, IDictionary parameters) - { - Ensure.ArgumentNotNull(endpoint, "endpoint"); - - var response = await Connection.GetAsync>(endpoint, parameters); - return new ReadOnlyPagedCollection(response, Connection); - } } } diff --git a/Octokit/Http/IApiConnection.cs b/Octokit/Http/IApiConnection.cs index 5c264232..36e33bf7 100644 --- a/Octokit/Http/IApiConnection.cs +++ b/Octokit/Http/IApiConnection.cs @@ -18,9 +18,7 @@ namespace Octokit.Http Task GetItem(Uri endpoint, IDictionary parameters); Task GetHtml(Uri endpoint, IDictionary parameters); Task> GetAll(Uri endpoint, IDictionary parameters); - Task> GetAll(Uri endpoint, IDictionary parameters); Task Create(Uri endpoint, object data); - Task Create(Uri endpoint, object data); Task Update(Uri endpoint, object data); Task Delete(Uri endpoint); Task Upload(Uri uri, Stream rawData, Dictionary headers); diff --git a/Octokit/IApiPagination.cs b/Octokit/IApiPagination.cs index b7986b71..a6aae0e7 100644 --- a/Octokit/IApiPagination.cs +++ b/Octokit/IApiPagination.cs @@ -7,6 +7,5 @@ namespace Octokit public interface IApiPagination { Task> GetAllPages(Func>> getFirstPage); - Task> GetAllPages(Func>> getFirstPage); } } \ No newline at end of file From 10064de852f2ceaca69eb08069372f3f610bdde3 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Wed, 2 Oct 2013 21:08:55 -0400 Subject: [PATCH 12/14] Add Releases to IGitHubClient. --- Octokit/IGitHubClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Octokit/IGitHubClient.cs b/Octokit/IGitHubClient.cs index a497fbc0..e455e4b9 100644 --- a/Octokit/IGitHubClient.cs +++ b/Octokit/IGitHubClient.cs @@ -10,6 +10,7 @@ namespace Octokit IAutoCompleteClient AutoComplete { get; } IOrganizationsClient Organization { get; } IRepositoriesClient Repository { get; } + IReleasesClient Releases { get; } ISshKeysClient SshKey { get; } IUsersClient User { get; } } From e19765ece9bbaf93d2e1257ddb374a5afff049e9 Mon Sep 17 00:00:00 2001 From: Haacked Date: Thu, 3 Oct 2013 09:27:25 -0700 Subject: [PATCH 13/14] Do not overwrite existing Accept headers --- Octokit.Tests/Http/JsonHttpPipelineTests.cs | 15 ++++++++++++++- Octokit/Http/JsonHttpPipeline.cs | 6 +++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Octokit.Tests/Http/JsonHttpPipelineTests.cs b/Octokit.Tests/Http/JsonHttpPipelineTests.cs index 9f0f7b93..8b91a1ba 100644 --- a/Octokit.Tests/Http/JsonHttpPipelineTests.cs +++ b/Octokit.Tests/Http/JsonHttpPipelineTests.cs @@ -18,7 +18,7 @@ namespace Octokit.Tests.Http public class TheSerializeRequestMethod { [Fact] - public void SetsRequestHeader() + public void SetsRequestAcceptHeader() { var request = new Request(); var jsonPipeline = new JsonHttpPipeline(); @@ -29,6 +29,19 @@ namespace Octokit.Tests.Http Assert.Equal("application/vnd.github.v3+json; charset=utf-8", request.Headers["Accept"]); } + [Fact] + public void DoesNotChangeExistingAcceptsHeader() + { + var request = new Request(); + request.Headers.Add("Accept", "application/vnd.github.manifold-preview; charset=utf-8"); + var jsonPipeline = new JsonHttpPipeline(); + + jsonPipeline.SerializeRequest(request); + + Assert.Contains("Accept", request.Headers.Keys); + Assert.Equal("application/vnd.github.manifold-preview; charset=utf-8", request.Headers["Accept"]); + } + [Fact] public void LeavesStringBodyAlone() { diff --git a/Octokit/Http/JsonHttpPipeline.cs b/Octokit/Http/JsonHttpPipeline.cs index e42bd1b1..90d90295 100644 --- a/Octokit/Http/JsonHttpPipeline.cs +++ b/Octokit/Http/JsonHttpPipeline.cs @@ -25,7 +25,11 @@ namespace Octokit.Http { Ensure.ArgumentNotNull(request, "request"); - request.Headers["Accept"] = "application/vnd.github.v3+json; charset=utf-8"; + if (!request.Headers.ContainsKey("Accept")) + { + request.Headers["Accept"] = "application/vnd.github.v3+json; charset=utf-8"; + } + if (request.Endpoint != null && request.Endpoint.ToString().Contains("releases")) request.Headers["Accept"] = "application/vnd.github.manifold-preview; charset=utf-8"; From c979b342b6c9132e105b29c297e3d9d19ed1b9f0 Mon Sep 17 00:00:00 2001 From: Haacked Date: Thu, 3 Oct 2013 09:49:55 -0700 Subject: [PATCH 14/14] Upload should take explicit contenttype parameter Rather than accept an arbitrary dictionary, let's make it accept the values we know. We still need to tackle the arbitrary headers problem soon though. --- Octokit.Tests/Clients/ReleasesClientTests.cs | 2 +- Octokit.Tests/Helpers/Arg.cs | 5 +++++ Octokit.Tests/Http/ApiConnectionTests.cs | 13 ++++++------- Octokit/Clients/ReleasesClient.cs | 2 +- Octokit/Http/ApiConnection.cs | 9 +++++++-- Octokit/Http/Connection.cs | 15 ++++++--------- Octokit/Http/IApiConnection.cs | 2 +- 7 files changed, 27 insertions(+), 21 deletions(-) diff --git a/Octokit.Tests/Clients/ReleasesClientTests.cs b/Octokit.Tests/Clients/ReleasesClientTests.cs index c7de133e..a5af7d07 100644 --- a/Octokit.Tests/Clients/ReleasesClientTests.cs +++ b/Octokit.Tests/Clients/ReleasesClientTests.cs @@ -78,7 +78,7 @@ namespace Octokit.Tests.Clients client.Received().Upload(Arg.Is(u => u.ToString() == "https://uploads.test.dev/does/not/matter/releases/1/assets?name=example.zip"), rawData, - Arg.Is>(headers => headers["Content-Type"] == "application/zip")); + Arg.Is(contentType => contentType == "application/zip")); } [Fact] diff --git a/Octokit.Tests/Helpers/Arg.cs b/Octokit.Tests/Helpers/Arg.cs index e14868aa..5b967512 100644 --- a/Octokit.Tests/Helpers/Arg.cs +++ b/Octokit.Tests/Helpers/Arg.cs @@ -30,5 +30,10 @@ namespace Octokit.Tests { get { return Arg.Any(); } } + + public static string String + { + get { return Arg.Any(); } + } } } diff --git a/Octokit.Tests/Http/ApiConnectionTests.cs b/Octokit.Tests/Http/ApiConnectionTests.cs index 5202f794..d4521c0a 100644 --- a/Octokit.Tests/Http/ApiConnectionTests.cs +++ b/Octokit.Tests/Http/ApiConnectionTests.cs @@ -211,21 +211,20 @@ namespace Octokit.Tests.Http connection.PostRawAsync(Args.Uri, Arg.Any(), Arg.Any>()).Returns(Task.FromResult(response)); var apiConnection = new ApiConnection(connection); var rawData = new MemoryStream(); - var headers = new Dictionary { { "A", "B" } }; - await apiConnection.Upload(uploadUrl, rawData, headers); + await apiConnection.Upload(uploadUrl, rawData, "B"); - connection.Received().PostRawAsync(uploadUrl, rawData, headers); + connection.Received().PostRawAsync(uploadUrl, rawData, + Arg.Any>()); } [Fact] public async Task EnsuresArgumentNotNull() { var connection = new ApiConnection(Substitute.For()); - await AssertEx.Throws(async () => await connection.Upload(null, Stream.Null, new Dictionary())); - await AssertEx.Throws(async () => await connection.Upload(new Uri("/ok", UriKind.Relative), null, new Dictionary())); - // This one is OK to be null. - await connection.Upload(new Uri("/ok", UriKind.Relative), Stream.Null, null); + await AssertEx.Throws(async () => await connection.Upload(null, Stream.Null, "some-content-type")); + await AssertEx.Throws(async () => await connection.Upload(new Uri("/ok", UriKind.Relative), null, "some-content-type")); + await AssertEx.Throws(async () => await connection.Upload(new Uri("/ok", UriKind.Relative), null, null)); } } diff --git a/Octokit/Clients/ReleasesClient.cs b/Octokit/Clients/ReleasesClient.cs index 8c340f4e..7f8c5f01 100644 --- a/Octokit/Clients/ReleasesClient.cs +++ b/Octokit/Clients/ReleasesClient.cs @@ -37,7 +37,7 @@ namespace Octokit.Clients Ensure.ArgumentNotNull(data, "data"); var endpoint = release.UploadUrl.ExpandUriTemplate(new { name = data.FileName }); - return await Client.Upload(endpoint, data.RawData, new Dictionary { { "Content-Type", data.ContentType }, { "Accept", "application/vnd.github.manifold-preview" } }); + return await Client.Upload(endpoint, data.RawData, data.ContentType); } } } diff --git a/Octokit/Http/ApiConnection.cs b/Octokit/Http/ApiConnection.cs index 04a0cd57..2492b07c 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -92,12 +92,17 @@ namespace Octokit.Http await Connection.DeleteAsync(endpoint); } - public async Task Upload(Uri uri, Stream rawData, Dictionary headers) + public async Task Upload(Uri uri, Stream rawData, string contentType) { Ensure.ArgumentNotNull(uri, "uri"); Ensure.ArgumentNotNull(rawData, "rawData"); + Ensure.ArgumentNotNull(contentType, "contentType"); - var response = await Connection.PostRawAsync(uri, rawData, headers); + var response = await Connection.PostRawAsync(uri, rawData, new Dictionary + { + { "Content-Type", contentType }, + { "Accept", "application/vnd.github.manifold-preview" } + }); return response.BodyAsObject; } diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs index 43814f7a..9e2bcafd 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Net; using System.Net.Http; using System.Threading.Tasks; @@ -102,10 +103,11 @@ namespace Octokit.Http return await SendData(endpoint, HttpMethod.Post, body); } - public async Task> PostRawAsync(Uri endpoint, System.IO.Stream body, IDictionary headers) + public async Task> PostRawAsync(Uri endpoint, Stream body, IDictionary headers) { Ensure.ArgumentNotNull(endpoint, "endpoint"); Ensure.ArgumentNotNull(body, "body"); + Ensure.ArgumentNotNull(headers, "headers"); var request = new Request { @@ -114,16 +116,11 @@ namespace Octokit.Http Endpoint = endpoint, Body = body }; - if (headers != null) + foreach (var header in headers) { - foreach (var header in headers) - { - request.Headers[header.Key] = header.Value; - } + request.Headers[header.Key] = header.Value; } - authenticator.Apply(request); - var response = await httpClient.Send(request); - apiInfoParser.ParseApiHttpHeaders(response); + var response = await RunRequest(request); jsonPipeline.DeserializeResponse(response); return response; } diff --git a/Octokit/Http/IApiConnection.cs b/Octokit/Http/IApiConnection.cs index 36e33bf7..6dc81290 100644 --- a/Octokit/Http/IApiConnection.cs +++ b/Octokit/Http/IApiConnection.cs @@ -21,6 +21,6 @@ namespace Octokit.Http Task Create(Uri endpoint, object data); Task Update(Uri endpoint, object data); Task Delete(Uri endpoint); - Task Upload(Uri uri, Stream rawData, Dictionary headers); + Task Upload(Uri uri, Stream rawData, string contentType); } } \ No newline at end of file