Merge pull request #27 from octokit/refactorings

Some refactorings to support query string parameters
This commit is contained in:
Phil Haack
2013-09-23 10:17:59 -07:00
19 changed files with 229 additions and 55 deletions
@@ -1,6 +1,6 @@
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Xunit;
namespace Octokit.Tests.Integration
{
@@ -16,9 +16,9 @@ namespace Octokit.Tests.Integration
Credentials = AutomationSettings.Current.GitHubCredentials
};
var repository = await github.Repository.Get("ReactiveCocoa", "ReactiveCocoa");
Repository repository = await github.Repository.Get("haacked", "seegit");
repository.CloneUrl.Should().Be("https://github.com/ReactiveCocoa/ReactiveCocoa.git");
repository.CloneUrl.Should().Be("https://github.com/Haacked/SeeGit.git");
}
}
@@ -32,7 +32,7 @@ namespace Octokit.Tests.Integration
Credentials = AutomationSettings.Current.GitHubCredentials
};
var repositories = await github.Repository.GetAllForOrg("github");
IReadOnlyCollection<Repository> repositories = await github.Repository.GetAllForOrg("github");
repositories.Count.Should().BeGreaterThan(80);
}
@@ -49,9 +49,9 @@ namespace Octokit.Tests.Integration
};
// TODO: Change this to request github/Octokit.net once we make this OSS.
var readme = await github.Repository.GetReadme("haacked", "seegit");
Readme readme = await github.Repository.GetReadme("haacked", "seegit");
readme.Name.Should().Be("README.md");
var readMeHtml = await readme.GetHtmlContent();
string readMeHtml = await readme.GetHtmlContent();
readMeHtml.Should().Contain(@"<div id=""readme""");
readMeHtml.Should().Contain("<p><strong>WARNING: This is some haacky code.");
}
@@ -31,7 +31,7 @@ namespace Octokit.Tests.Clients
authEndpoint.GetAll();
client.Received().GetAll(Arg.Is<Uri>(u => u.ToString() == "/authorizations"));
client.Received().GetAll(Arg.Is<Uri>(u => u.ToString() == "/authorizations"), null);
}
}
@@ -45,7 +45,7 @@ namespace Octokit.Tests.Clients
authEndpoint.Get(1);
client.Received().Get(Arg.Is<Uri>(u => u.ToString() == "/authorizations/1"));
client.Received().Get(Arg.Is<Uri>(u => u.ToString() == "/authorizations/1"), null);
}
}
@@ -37,14 +37,14 @@ namespace Octokit.Tests.Clients
}
};
var connection = Substitute.For<IConnection>();
connection.GetAsync<Dictionary<string, string>>(Args.Uri).Returns(Task.FromResult(response));
connection.GetAsync<Dictionary<string, string>>(Args.Uri, null).Returns(Task.FromResult(response));
var autoComplete = new AutoCompleteClient(connection);
var emojis = await autoComplete.GetEmojis();
emojis.Count.Should().Be(2);
connection.Received()
.GetAsync<Dictionary<string, string>>(Arg.Is<Uri>(u => u.ToString() == "/emojis"));
.GetAsync<Dictionary<string, string>>(Arg.Is<Uri>(u => u.ToString() == "/emojis"), null);
}
}
}
@@ -33,7 +33,7 @@ namespace Octokit.Tests.Clients
orgsClient.Get("orgName");
client.Received().Get(Arg.Is<Uri>(u => u.ToString() == "/orgs/orgName"));
client.Received().Get(Arg.Is<Uri>(u => u.ToString() == "/orgs/orgName"), null);
}
[Fact]
@@ -55,7 +55,7 @@ namespace Octokit.Tests.Clients
orgs.GetAll("username");
client.Received().GetAll(Arg.Is<Uri>(u => u.ToString() == "/users/username/orgs"));
client.Received().GetAll(Arg.Is<Uri>(u => u.ToString() == "/users/username/orgs"), null);
}
[Fact]
@@ -77,7 +77,7 @@ namespace Octokit.Tests.Clients
orgs.GetAllForCurrent();
client.Received().GetAll(Arg.Is<Uri>(u => u.ToString() == "/user/orgs"));
client.Received().GetAll(Arg.Is<Uri>(u => u.ToString() == "/user/orgs"), null);
}
}
}
@@ -35,7 +35,7 @@ namespace Octokit.Tests.Clients
repositoriesClient.Get("fake", "repo");
client.Received().Get(Arg.Is<Uri>(u => u.ToString() == "/repos/fake/repo"));
client.Received().Get(Arg.Is<Uri>(u => u.ToString() == "/repos/fake/repo"), null);
}
[Fact]
@@ -59,7 +59,7 @@ namespace Octokit.Tests.Clients
repositoriesClient.GetAllForCurrent();
client.Received()
.GetAll(Arg.Is<Uri>(u => u.ToString() == "user/repos"));
.GetAll(Arg.Is<Uri>(u => u.ToString() == "user/repos"), null);
}
}
@@ -74,7 +74,7 @@ namespace Octokit.Tests.Clients
repositoriesClient.GetAllForUser("username");
client.Received()
.GetAll(Arg.Is<Uri>(u => u.ToString() == "/users/username/repos"));
.GetAll(Arg.Is<Uri>(u => u.ToString() == "/users/username/repos"), null);
}
[Fact]
@@ -97,7 +97,7 @@ namespace Octokit.Tests.Clients
repositoriesClient.GetAllForOrg("orgname");
client.Received()
.GetAll(Arg.Is<Uri>(u => u.ToString() == "/orgs/orgname/repos"));
.GetAll(Arg.Is<Uri>(u => u.ToString() == "/orgs/orgname/repos"), null);
}
[Fact]
@@ -124,18 +124,20 @@ namespace Octokit.Tests.Clients
HtmlUrl = "https://github.example.com/readme"
};
var client = Substitute.For<IApiConnection<Repository>>();
client.GetItem<ReadmeResponse>(Args.Uri).Returns(Task.FromResult(readmeInfo));
client.GetHtml(Args.Uri).Returns(Task.FromResult("<html>README</html>"));
client.GetItem<ReadmeResponse>(Args.Uri, null).Returns(Task.FromResult(readmeInfo));
client.GetHtml(Args.Uri, null).Returns(Task.FromResult("<html>README</html>"));
var reposEndpoint = new RepositoriesClient(client);
var readme = await reposEndpoint.GetReadme("fake", "repo");
readme.Name.Should().Be("README.md");
client.Received().GetItem<ReadmeResponse>(Arg.Is<Uri>(u => u.ToString() == "/repos/fake/repo/readme"));
client.DidNotReceive().GetHtml(Arg.Is<Uri>(u => u.ToString() == "https://github.example.com/readme"));
client.Received().GetItem<ReadmeResponse>(Arg.Is<Uri>(u => u.ToString() == "/repos/fake/repo/readme"),
null);
client.DidNotReceive().GetHtml(Arg.Is<Uri>(u => u.ToString() == "https://github.example.com/readme"),
null);
var htmlReadme = await readme.GetHtmlContent();
htmlReadme.Should().Be("<html>README</html>");
client.Received().GetHtml(Arg.Is<Uri>(u => u.ToString() == "https://github.example.com/readme"));
client.Received().GetHtml(Arg.Is<Uri>(u => u.ToString() == "https://github.example.com/readme"), null);
}
}
}
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using Xunit;
namespace Octokit.Tests.Helpers
{
public class UriExtensionsTests
{
public class TheApplyParametersMethod
{
[Fact]
public void AppendsParametersAsQueryString()
{
var uri = new Uri("https://example.com");
var uriWithParameters = uri.ApplyParameters(new Dictionary<string, string>
{
{"foo", "fooval"},
{"bar", "barval"}
});
Assert.Equal(new Uri("https://example.com?foo=fooval&bar=barval"), uriWithParameters);
}
[Fact]
public void OverwritesExistingParameters()
{
var uri = new Uri("https://example.com?crap=crapola");
var uriWithParameters = uri.ApplyParameters(new Dictionary<string, string>
{
{"foo", "fooval"},
{"bar", "barval"}
});
Assert.Equal(new Uri("https://example.com?foo=fooval&bar=barval"), uriWithParameters);
}
[Fact]
public void DoesNotChangeUrlWhenParametersIsNullOrEmpty()
{
var uri = new Uri("https://example.com");
var uriWithNullParameters = uri.ApplyParameters(null);
var uriWithEmptyParameters = uri.ApplyParameters(new Dictionary<string, string>());
Assert.Same(uri, uriWithNullParameters);
Assert.Equal(uri, uriWithEmptyParameters);
}
[Fact]
public void EnsuresUriNotNull()
{
Uri uri = null;
Assert.Throws<ArgumentNullException>(() => uri.ApplyParameters(new Dictionary<string, string>()));
}
}
}
}
+7 -7
View File
@@ -19,7 +19,7 @@ namespace Octokit.Tests.Http
var getUri = new Uri("/anything", UriKind.Relative);
IResponse<object> response = new ApiResponse<object> { BodyAsObject = new object() };
var connection = Substitute.For<IConnection>();
connection.GetAsync<object>(Args.Uri).Returns(Task.FromResult(response));
connection.GetAsync<object>(Args.Uri, null).Returns(Task.FromResult(response));
var apiConnection = new ApiConnection<object>(connection);
var data = await apiConnection.Get(getUri);
@@ -44,10 +44,10 @@ namespace Octokit.Tests.Http
var getUri = new Uri("/anything", UriKind.Relative);
IResponse<object> response = new ApiResponse<object> { BodyAsObject = new object() };
var connection = Substitute.For<IConnection>();
connection.GetAsync<object>(Args.Uri).Returns(Task.FromResult(response));
connection.GetAsync<object>(Args.Uri, null).Returns(Task.FromResult(response));
var apiConnection = new ApiConnection<object>(connection);
var data = await apiConnection.GetItem<object>(getUri);
var data = await apiConnection.GetItem<object>(getUri, null);
data.Should().BeSameAs(response.BodyAsObject);
connection.Received().GetAsync<object>(getUri);
@@ -57,7 +57,7 @@ namespace Octokit.Tests.Http
public async Task EnsuresArgumentNotNull()
{
var connection = new ApiConnection<object>(Substitute.For<IConnection>());
AssertEx.Throws<ArgumentNullException>(async () => await connection.GetItem<object>(null));
AssertEx.Throws<ArgumentNullException>(async () => await connection.GetItem<object>(null, null));
}
}
@@ -69,7 +69,7 @@ namespace Octokit.Tests.Http
var getUri = new Uri("/anything", UriKind.Relative);
IResponse<string> response = new ApiResponse<string> { Body = "<html />" };
var connection = Substitute.For<IConnection>();
connection.GetHtml(Args.Uri).Returns(Task.FromResult(response));
connection.GetHtml(Args.Uri, null).Returns(Task.FromResult(response));
var apiConnection = new ApiConnection<object>(connection);
var data = await apiConnection.GetHtml(getUri);
@@ -100,13 +100,13 @@ namespace Octokit.Tests.Http
BodyAsObject = new List<object> { new object(), new object() }
};
var connection = Substitute.For<IConnection>();
connection.GetAsync<List<object>>(Args.Uri).Returns(Task.FromResult(response));
connection.GetAsync<List<object>>(Args.Uri, null).Returns(Task.FromResult(response));
var apiConnection = new ApiConnection<object>(connection);
var data = await apiConnection.GetAll(getAllUri);
data.Count.Should().Be(2);
connection.Received().GetAsync<List<object>>(getAllUri);
connection.Received().GetAsync<List<object>>(getAllUri, null);
}
[Fact]
+1
View File
@@ -55,6 +55,7 @@
<ItemGroup>
<Compile Include="Authentication\CredentialsTests.cs" />
<Compile Include="Clients\SshKeysClientTests.cs" />
<Compile Include="Helpers\UriExtensionsTests.cs" />
<Compile Include="Http\ApiConnectionTests.cs" />
<Compile Include="Clients\AuthorizationsClientTests.cs" />
<Compile Include="Clients\AutoCompleteClientTests.cs" />
+64
View File
@@ -0,0 +1,64 @@
using System;
using System.Globalization;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Octokit.Http;
namespace Octokit
{
public static class ApiExtensions
{
public static Task<T> Get<T>(this IApiConnection<T> connection, Uri endpoint)
{
Ensure.ArgumentNotNull(connection, "connection");
Ensure.ArgumentNotNull(endpoint, "endpoint");
return connection.Get(endpoint, null);
}
public static Task<IReadOnlyCollection<T>> GetAll<T>(this IApiConnection<T> connection, Uri endpoint)
{
Ensure.ArgumentNotNull(connection, "connection");
Ensure.ArgumentNotNull(endpoint, "endpoint");
return connection.GetAll(endpoint, null);
}
public static Task<string> GetHtml<T>(this IApiConnection<T> connection, Uri endpoint)
{
Ensure.ArgumentNotNull(connection, "connection");
Ensure.ArgumentNotNull(endpoint, "endpoint");
return connection.GetHtml(endpoint, null);
}
public static IDictionary<string, string> ToDictionary(this object value)
{
Ensure.ArgumentNotNull(value, "value");
return value.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => !p.IsSpecialName && p.CanRead)
.ToDictionary(
prop => prop.Name,
prop => Convert.ToString(prop.GetValue(value), CultureInfo.InvariantCulture));
}
public static Task<IResponse<string>> GetHtml(this IConnection connection, Uri endpoint)
{
Ensure.ArgumentNotNull(connection, "connection");
Ensure.ArgumentNotNull(endpoint, "endpoint");
return connection.GetHtml(endpoint, null);
}
public static async Task<IResponse<T>> GetAsync<T>(this IConnection connection, Uri endpoint)
{
Ensure.ArgumentNotNull(connection, "connection");
Ensure.ArgumentNotNull(endpoint, "endpoint");
return await connection.GetAsync<T>(endpoint, null);
}
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ namespace Octokit.Clients
public async Task<IReadOnlyDictionary<string, Uri>> GetEmojis()
{
var endpoint = new Uri("/emojis", UriKind.Relative);
var response = await connection.GetAsync<Dictionary<string, string>>(endpoint);
var response = await connection.GetAsync<Dictionary<string, string>>(endpoint, null);
return new ReadOnlyDictionary<string, Uri>(
response.BodyAsObject.ToDictionary(kvp => kvp.Key, kvp => new Uri(kvp.Value)));
}
+1 -1
View File
@@ -50,7 +50,7 @@ namespace Octokit.Clients
Ensure.ArgumentNotNullOrEmptyString(name, "name");
var endpoint = "/repos/{0}/{1}/readme".FormatUri(owner, name);
var readmeInfo = await Client.GetItem<ReadmeResponse>(endpoint);
var readmeInfo = await Client.GetItem<ReadmeResponse>(endpoint, null);
return new Readme(readmeInfo, Client);
}
}
+22
View File
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Octokit
{
public static class UriExtensions
{
public static Uri ApplyParameters(this Uri uri, IDictionary<string, string> parameters)
{
Ensure.ArgumentNotNull(uri, "uri");
if (parameters == null) return uri;
var uriBuilder = new UriBuilder(uri)
{
Query = String.Join("&", parameters.Select(kvp => kvp.Key + "=" + kvp.Value))
};
return uriBuilder.Uri;
}
}
}
+20 -10
View File
@@ -24,34 +24,34 @@ namespace Octokit.Http
protected IConnection Connection { get; private set; }
public async Task<T> Get(Uri endpoint)
public async Task<T> Get(Uri endpoint, IDictionary<string, string> parameters)
{
Ensure.ArgumentNotNull(endpoint, "endpoint");
return await GetItem<T>(endpoint);
return await GetItem<T>(endpoint, parameters);
}
public async Task<TOther> GetItem<TOther>(Uri endpoint)
public async Task<TOther> GetItem<TOther>(Uri endpoint, IDictionary<string, string> parameters)
{
Ensure.ArgumentNotNull(endpoint, "endpoint");
var response = await Connection.GetAsync<TOther>(endpoint);
var response = await Connection.GetAsync<TOther>(endpoint, parameters);
return response.BodyAsObject;
}
public async Task<string> GetHtml(Uri endpoint)
public async Task<string> GetHtml(Uri endpoint, IDictionary<string, string> parameters)
{
Ensure.ArgumentNotNull(endpoint, "endpoint");
var response = await Connection.GetHtml(endpoint);
var response = await Connection.GetHtml(endpoint, parameters);
return response.Body;
}
public async Task<IReadOnlyCollection<T>> GetAll(Uri endpoint)
public async Task<IReadOnlyCollection<T>> GetAll(Uri endpoint, IDictionary<string, string> parameters)
{
Ensure.ArgumentNotNull(endpoint, "endpoint");
return await pagination.GetAllPages(async () => await GetPage(endpoint));
return await pagination.GetAllPages(async () => await GetPage(endpoint, parameters));
}
public async Task<T> Create(Uri endpoint, object data)
@@ -74,6 +74,16 @@ namespace Octokit.Http
return response.BodyAsObject;
}
public async Task<T> Put(Uri endpoint, object data)
{
Ensure.ArgumentNotNull(endpoint, "endpoint");
Ensure.ArgumentNotNull(data, "data");
var response = await Connection.PostAsync<T>(endpoint, data);
return response.BodyAsObject;
}
public async Task Delete(Uri endpoint)
{
Ensure.ArgumentNotNull(endpoint, "endpoint");
@@ -81,11 +91,11 @@ namespace Octokit.Http
await Connection.DeleteAsync<T>(endpoint);
}
async Task<IReadOnlyPagedCollection<T>> GetPage(Uri endpoint)
async Task<IReadOnlyPagedCollection<T>> GetPage(Uri endpoint, IDictionary<string, string> parameters)
{
Ensure.ArgumentNotNull(endpoint, "endpoint");
var response = await Connection.GetAsync<List<T>>(endpoint);
var response = await Connection.GetAsync<List<T>>(endpoint, parameters);
return new ReadOnlyPagedCollection<T>(response, Connection);
}
+16 -5
View File
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Threading.Tasks;
@@ -57,7 +58,7 @@ namespace Octokit.Http
apiInfoParser = new ApiInfoParser();
}
public async Task<IResponse<T>> GetAsync<T>(Uri endpoint)
public async Task<IResponse<T>> GetAsync<T>(Uri endpoint, IDictionary<string, string> parameters)
{
Ensure.ArgumentNotNull(endpoint, "endpoint");
@@ -65,11 +66,11 @@ namespace Octokit.Http
{
Method = HttpMethod.Get,
BaseAddress = BaseAddress,
Endpoint = endpoint
Endpoint = endpoint.ApplyParameters(parameters)
});
}
public async Task<IResponse<string>> GetHtml(Uri endpoint)
public async Task<IResponse<string>> GetHtml(Uri endpoint, IDictionary<string, string> parameters)
{
Ensure.ArgumentNotNull(endpoint, "endpoint");
@@ -77,7 +78,7 @@ namespace Octokit.Http
{
Method = HttpMethod.Get,
BaseAddress = BaseAddress,
Endpoint = endpoint
Endpoint = endpoint.ApplyParameters(parameters)
});
}
@@ -96,13 +97,23 @@ namespace Octokit.Http
}
public async Task<IResponse<T>> PostAsync<T>(Uri endpoint, object body)
{
return await SendData<T>(endpoint, HttpMethod.Post, body);
}
public async Task<IResponse<T>> PutAsync<T>(Uri endpoint, object body)
{
return await SendData<T>(endpoint, HttpMethod.Put, body);
}
async Task<IResponse<T>> SendData<T>(Uri endpoint, HttpMethod method, object body)
{
Ensure.ArgumentNotNull(endpoint, "endpoint");
Ensure.ArgumentNotNull(body, "body");
return await Run<T>(new Request
{
Method = HttpMethod.Post,
Method = method,
BaseAddress = BaseAddress,
Endpoint = endpoint,
Body = body
+4 -4
View File
@@ -13,10 +13,10 @@ namespace Octokit.Http
{
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get",
Justification = "It's fiiiine. It's fine. Trust us.")]
Task<T> Get(Uri endpoint);
Task<TOther> GetItem<TOther>(Uri endpoint);
Task<string> GetHtml(Uri endpoint);
Task<IReadOnlyCollection<T>> GetAll(Uri endpoint);
Task<T> Get(Uri endpoint, IDictionary<string, string> parameters);
Task<TOther> GetItem<TOther>(Uri endpoint, IDictionary<string, string> parameters);
Task<string> GetHtml(Uri endpoint, IDictionary<string, string> parameters);
Task<IReadOnlyCollection<T>> GetAll(Uri endpoint, IDictionary<string, string> parameters);
Task<T> Create(Uri endpoint, object data);
Task<T> Update(Uri endpoint, object data);
Task Delete(Uri endpoint);
+4 -3
View File
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
@@ -6,11 +7,11 @@ namespace Octokit.Http
{
public interface IConnection
{
Task<IResponse<string>> GetHtml(Uri endpoint);
Task<IResponse<T>> GetAsync<T>(Uri endpoint);
Task<IResponse<string>> GetHtml(Uri endpoint, IDictionary<string, string> parameters);
Task<IResponse<T>> GetAsync<T>(Uri endpoint, IDictionary<string, string> parameters);
Task<IResponse<T>> PatchAsync<T>(Uri endpoint, object body);
Task<IResponse<T>> PostAsync<T>(Uri endpoint, object body);
Task<IResponse<T>> PutAsync<T>(Uri endpoint, object body);
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
Task DeleteAsync<T>(Uri endpoint);
+1 -1
View File
@@ -24,7 +24,7 @@ namespace Octokit.Http
var nextPageUrl = info.GetNextPageUrl();
if (nextPageUrl == null) return null;
var response = await connection.GetAsync<List<T>>(nextPageUrl);
var response = await connection.GetAsync<List<T>>(nextPageUrl, null);
return new ReadOnlyPagedCollection<T>(response, connection);
}
}
+2
View File
@@ -39,6 +39,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup>
<Compile Include="ApiExtensions.cs" />
<Compile Include="Clients\ApiClient.cs" />
<Compile Include="Clients\AuthorizationsClient.cs" />
<Compile Include="Clients\ApiPagination.cs" />
@@ -48,6 +49,7 @@
<Compile Include="Authentication\Authenticator.cs" />
<Compile Include="Clients\SshKeysClient.cs" />
<Compile Include="Helpers\CollectionExtensions.cs" />
<Compile Include="Helpers\UriExtensions.cs" />
<Compile Include="Http\ApiConnection.cs" />
<Compile Include="Http\IApiConnection.cs" />
<Compile Include="Http\IHttpClient.cs" />
+2
View File
@@ -102,6 +102,7 @@
<!-- A reference to the entire .Net Framework and Windows SDK are automatically included -->
</ItemGroup>
<ItemGroup>
<Compile Include="ApiExtensions.cs" />
<Compile Include="Authentication\AnonymousAuthenticator.cs" />
<Compile Include="Authentication\Authenticator.cs" />
<Compile Include="Authentication\BasicAuthenticator.cs" />
@@ -116,6 +117,7 @@
<Compile Include="Clients\SshKeysClient.cs" />
<Compile Include="Clients\UsersClient.cs" />
<Compile Include="Helpers\CollectionExtensions.cs" />
<Compile Include="Helpers\UriExtensions.cs" />
<Compile Include="Http\ApiConnection.cs" />
<Compile Include="Http\ApiResponse.cs" />
<Compile Include="Http\Credentials.cs" />