From 15a5cc9591ae0f93549cf8bcebbc7a4b2a063e07 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Wed, 2 Oct 2013 14:55:05 -0400 Subject: [PATCH] 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); } }