Skip to content

Commit c4cb46e

Browse files
Merge pull request #131 from martincostello/HttpClient-Injection
HttpClient injection
2 parents 74c95b9 + 59fe63c commit c4cb46e

File tree

8 files changed

+511
-28
lines changed

8 files changed

+511
-28
lines changed
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
using System;
2+
using System.Net;
3+
using System.Net.Http;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Xunit;
7+
8+
namespace Octokit.GraphQL.Core.UnitTests
9+
{
10+
public static class ConnectionTests
11+
{
12+
private static readonly ICredentialStore CredentialStore = new MockCredentialStore();
13+
private static readonly ProductHeaderValue ProductInformation = new ProductHeaderValue("Octokit.GraphQL.Core.Tests", "1.0.0");
14+
15+
[Fact]
16+
public static void Default_GraphQL_Uri_Is_Correct()
17+
{
18+
Assert.Equal("https://api.github.com/graphql", Connection.GithubApiUri?.ToString());
19+
}
20+
21+
[Fact]
22+
public static void Connection_Constructor_Validates_Arguments_For_Null()
23+
{
24+
var productInformation = ProductInformation;
25+
var uri = new Uri("https://api.github.enterprise.local/graphql");
26+
var credentialStore = CredentialStore;
27+
var httpClient = new HttpClient();
28+
29+
Assert.Throws<ArgumentNullException>("productInformation", () => new Connection(null, uri, credentialStore, httpClient));
30+
Assert.Throws<ArgumentNullException>("uri", () => new Connection(productInformation, null, credentialStore, httpClient));
31+
Assert.Throws<ArgumentNullException>("credentialStore", () => new Connection(productInformation, uri, null, httpClient));
32+
Assert.Throws<ArgumentNullException>("httpClient", () => new Connection(productInformation, uri, credentialStore, null));
33+
}
34+
35+
[Fact]
36+
public static void Connection_Constructor_Throws_If_Uri_Is_Relative()
37+
{
38+
var productInformation = ProductInformation;
39+
var uri = new Uri("/graphql", UriKind.Relative);
40+
var credentialStore = CredentialStore;
41+
42+
var exception = Assert.Throws<ArgumentException>("uri", () => new Connection(productInformation, uri, credentialStore));
43+
Assert.StartsWith("The base address for the connection must be an absolute URI.", exception.Message);
44+
}
45+
46+
[Fact]
47+
public static void Connection_Constructors_With_No_Uri_Parameter_Use_GitHub_GraphQL_Uri()
48+
{
49+
var connection = new Connection(ProductInformation, "token");
50+
Assert.Equal(Connection.GithubApiUri, connection.Uri);
51+
52+
connection = new Connection(ProductInformation, CredentialStore);
53+
Assert.Equal(Connection.GithubApiUri, connection.Uri);
54+
55+
connection = new Connection(ProductInformation, CredentialStore, new HttpClient());
56+
Assert.Equal(Connection.GithubApiUri, connection.Uri);
57+
}
58+
59+
[Fact]
60+
public static async Task Run_Specifies_Cancellation_Token()
61+
{
62+
var query = "{}";
63+
var cancellationToken = new CancellationToken(true);
64+
65+
var httpClient = CreateFakeHttpClient(
66+
(request, token) =>
67+
{
68+
Assert.True(token.IsCancellationRequested);
69+
});
70+
71+
var connection = new Connection(ProductInformation, CredentialStore, httpClient);
72+
73+
await connection.Run(query, cancellationToken);
74+
}
75+
76+
[Theory]
77+
[InlineData("Accept", "application/vnd.github.antiope-preview+json")]
78+
[InlineData("Authorization", "bearer my-token")]
79+
[InlineData("User-Agent", "Octokit.GraphQL.Core.Tests/1.0.0")]
80+
public static async Task Run_Specifies_Http_Headers(string name, string expected)
81+
{
82+
var query = "{}";
83+
84+
var httpClient = CreateFakeHttpClient(
85+
(request, token) =>
86+
{
87+
Assert.Equal(expected, string.Concat(request.Headers.GetValues(name)));
88+
});
89+
90+
var connection = new Connection(ProductInformation, CredentialStore, httpClient);
91+
92+
await connection.Run(query);
93+
}
94+
95+
[Fact]
96+
public static void Run_Throws_If_Query_Is_Null()
97+
{
98+
var connection = new Connection(ProductInformation, CredentialStore);
99+
Assert.ThrowsAsync<ArgumentNullException>("query", () => connection.Run(null));
100+
}
101+
102+
[Fact]
103+
public static async Task Run_Throws_If_Http_Request_Does_Not_Return_An_Http_2xx_Response_Code()
104+
{
105+
var httpClient = CreateFakeHttpClient(statusCode: HttpStatusCode.BadRequest);
106+
var connection = new Connection(ProductInformation, CredentialStore);
107+
var query = "{}";
108+
109+
await Assert.ThrowsAsync<HttpRequestException>(() => connection.Run(query));
110+
}
111+
112+
[Fact]
113+
public static async Task Run_Uses_The_Specified_Uri()
114+
{
115+
var productInformation = ProductInformation;
116+
var uri = new Uri("https://api.github.enterprise.local/graphql");
117+
var credentialStore = CredentialStore;
118+
var query = "{}";
119+
120+
var httpClient = CreateFakeHttpClient(
121+
(request, token) =>
122+
{
123+
Assert.Equal(uri, request.RequestUri);
124+
});
125+
126+
var connection = new Connection(ProductInformation, uri, CredentialStore, httpClient);
127+
128+
await connection.Run(query);
129+
}
130+
131+
private static HttpClient CreateFakeHttpClient(Action<HttpRequestMessage, CancellationToken> inspector = null, HttpStatusCode statusCode = HttpStatusCode.OK)
132+
{
133+
var handler = new MockHttpMessageHandler(inspector, statusCode);
134+
return new HttpClient(handler);
135+
}
136+
137+
private sealed class MockHttpMessageHandler : HttpMessageHandler
138+
{
139+
private readonly Action<HttpRequestMessage, CancellationToken> _inspector;
140+
private readonly HttpStatusCode _statusCode;
141+
142+
public MockHttpMessageHandler(Action<HttpRequestMessage, CancellationToken> inspector, HttpStatusCode statusCode = HttpStatusCode.OK)
143+
{
144+
_inspector = inspector;
145+
_statusCode = statusCode;
146+
}
147+
148+
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
149+
{
150+
_inspector?.Invoke(request, cancellationToken);
151+
152+
var response = new HttpResponseMessage(_statusCode)
153+
{
154+
Content = new StringContent(string.Empty)
155+
};
156+
157+
return Task.FromResult(response);
158+
}
159+
}
160+
161+
private sealed class MockCredentialStore : ICredentialStore
162+
{
163+
public Task<string> GetCredentials(CancellationToken cancellationToken) => Task.FromResult("my-token");
164+
}
165+
}
166+
}

Octokit.GraphQL.Core.UnitTests/MockConnection.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Threading;
34
using System.Threading.Tasks;
45
using Newtonsoft.Json;
56
using Newtonsoft.Json.Linq;
@@ -23,7 +24,7 @@ public async Task<T> Run<T>(string query, Func<JObject, T> deserialize)
2324
return deserialize(JObject.Parse(result));
2425
}
2526

26-
public Task<string> Run(string query)
27+
public Task<string> Run(string query, CancellationToken cancellationToken = default)
2728
{
2829
var payload = JsonConvert.DeserializeObject<Payload>(query);
2930
return Task.FromResult(execute(payload.Query, payload.Variables));

0 commit comments

Comments
 (0)